From: s_kawamoto Date: Tue, 25 Oct 2011 13:12:55 +0000 (+0900) Subject: Add VC++ Project files for PuTTY DLL without exported functions. X-Git-Url: http://git.osdn.net/view?p=ffftp%2Fffftp.git;a=commitdiff_plain;h=3ffc348451c39c892fe62e4666808474a0e60dae Add VC++ Project files for PuTTY DLL without exported functions. --- diff --git a/FFFTP.sln b/FFFTP.sln index 1de1a58..6a7e1a6 100644 --- a/FFFTP.sln +++ b/FFFTP.sln @@ -4,6 +4,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FFFTP", "FFFTP.vcproj", "{5 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FFFTP_English", "FFFTP_English.vcproj", "{EE7CC1BD-92A9-46D5-8270-386BD7BEEA55}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PuTTY", "putty\PuTTY.vc90.vcproj", "{AF1981EB-379B-43B8-BE66-298194297B5C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -18,6 +20,10 @@ Global {EE7CC1BD-92A9-46D5-8270-386BD7BEEA55}.Debug|Win32.Build.0 = Debug|Win32 {EE7CC1BD-92A9-46D5-8270-386BD7BEEA55}.Release|Win32.ActiveCfg = Release|Win32 {EE7CC1BD-92A9-46D5-8270-386BD7BEEA55}.Release|Win32.Build.0 = Release|Win32 + {AF1981EB-379B-43B8-BE66-298194297B5C}.Debug|Win32.ActiveCfg = Debug|Win32 + {AF1981EB-379B-43B8-BE66-298194297B5C}.Debug|Win32.Build.0 = Debug|Win32 + {AF1981EB-379B-43B8-BE66-298194297B5C}.Release|Win32.ActiveCfg = Release|Win32 + {AF1981EB-379B-43B8-BE66-298194297B5C}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/FFFTP.vc90.sln b/FFFTP.vc90.sln index f73d409..34ca1c7 100644 --- a/FFFTP.vc90.sln +++ b/FFFTP.vc90.sln @@ -4,6 +4,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FFFTP", "FFFTP.vc90.vcproj" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FFFTP_English", "FFFTP_English.vc90.vcproj", "{EE7CC1BD-92A9-46D5-8270-386BD7BEEA55}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PuTTY", "putty\PuTTY.vc90.vcproj", "{AF1981EB-379B-43B8-BE66-298194297B5C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -18,6 +20,10 @@ Global {EE7CC1BD-92A9-46D5-8270-386BD7BEEA55}.Debug|Win32.Build.0 = Debug|Win32 {EE7CC1BD-92A9-46D5-8270-386BD7BEEA55}.Release|Win32.ActiveCfg = Release|Win32 {EE7CC1BD-92A9-46D5-8270-386BD7BEEA55}.Release|Win32.Build.0 = Release|Win32 + {AF1981EB-379B-43B8-BE66-298194297B5C}.Debug|Win32.ActiveCfg = Debug|Win32 + {AF1981EB-379B-43B8-BE66-298194297B5C}.Debug|Win32.Build.0 = Debug|Win32 + {AF1981EB-379B-43B8-BE66-298194297B5C}.Release|Win32.ActiveCfg = Release|Win32 + {AF1981EB-379B-43B8-BE66-298194297B5C}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/FFFTP.vc90.vcproj b/FFFTP.vc90.vcproj index 53eb2cb..a479903 100644 --- a/FFFTP.vc90.vcproj +++ b/FFFTP.vc90.vcproj @@ -311,11 +311,11 @@ > +#include "putty.h" + +/* + * This appname is not strictly in the right place, since Plink + * also uses this module. However, Plink doesn't currently use any + * of the dialog-box sorts of things that make use of appname, so + * it shouldn't do any harm here. I'm trying to avoid having to + * have tiny little source modules containing nothing but + * declarations of appname, for as long as I can... + */ +const char *const appname = "PuTTY"; + +#ifdef TELNET_DEFAULT +const int be_default_protocol = PROT_TELNET; +#else +const int be_default_protocol = PROT_SSH; +#endif + +Backend *backends[] = { + &ssh_backend, + &telnet_backend, + &rlogin_backend, + &raw_backend, + NULL +}; diff --git a/putty/BE_ALL_S.C b/putty/BE_ALL_S.C new file mode 100644 index 0000000..95891fb --- /dev/null +++ b/putty/BE_ALL_S.C @@ -0,0 +1,32 @@ +/* + * Linking module for PuTTY proper: list the available backends + * including ssh, plus the serial backend. + */ + +#include +#include "putty.h" + +/* + * This appname is not strictly in the right place, since Plink + * also uses this module. However, Plink doesn't currently use any + * of the dialog-box sorts of things that make use of appname, so + * it shouldn't do any harm here. I'm trying to avoid having to + * have tiny little source modules containing nothing but + * declarations of appname, for as long as I can... + */ +const char *const appname = "PuTTY"; + +#ifdef TELNET_DEFAULT +const int be_default_protocol = PROT_TELNET; +#else +const int be_default_protocol = PROT_SSH; +#endif + +Backend *backends[] = { + &ssh_backend, + &telnet_backend, + &rlogin_backend, + &raw_backend, + &serial_backend, + NULL +}; diff --git a/putty/BE_NONE.C b/putty/BE_NONE.C new file mode 100644 index 0000000..95ddbd5 --- /dev/null +++ b/putty/BE_NONE.C @@ -0,0 +1,11 @@ +/* + * Linking module for programs that do not support selection of backend + * (such as pscp or pterm). + */ + +#include +#include "putty.h" + +Backend *backends[] = { + NULL +}; diff --git a/putty/BE_NOSSH.C b/putty/BE_NOSSH.C new file mode 100644 index 0000000..e127cd9 --- /dev/null +++ b/putty/BE_NOSSH.C @@ -0,0 +1,33 @@ +/* + * Linking module for PuTTYtel: list the available backends not + * including ssh. + */ + +#include +#include "putty.h" + +const int be_default_protocol = PROT_TELNET; + +const char *const appname = "PuTTYtel"; + +Backend *backends[] = { + &telnet_backend, + &rlogin_backend, + &raw_backend, + NULL +}; + +/* + * Stub implementations of functions not used in non-ssh versions. + */ +void random_save_seed(void) +{ +} + +void random_destroy_seed(void) +{ +} + +void noise_ultralight(unsigned long data) +{ +} diff --git a/putty/BE_NOS_S.C b/putty/BE_NOS_S.C new file mode 100644 index 0000000..32eb88c --- /dev/null +++ b/putty/BE_NOS_S.C @@ -0,0 +1,34 @@ +/* + * Linking module for PuTTYtel: list the available backends not + * including ssh. + */ + +#include +#include "putty.h" + +const int be_default_protocol = PROT_TELNET; + +const char *const appname = "PuTTYtel"; + +Backend *backends[] = { + &telnet_backend, + &rlogin_backend, + &raw_backend, + &serial_backend, + NULL +}; + +/* + * Stub implementations of functions not used in non-ssh versions. + */ +void random_save_seed(void) +{ +} + +void random_destroy_seed(void) +{ +} + +void noise_ultralight(unsigned long data) +{ +} diff --git a/putty/BUILDSCR b/putty/BUILDSCR new file mode 100644 index 0000000..131a734 --- /dev/null +++ b/putty/BUILDSCR @@ -0,0 +1,118 @@ +# -*- sh -*- +# Build script to construct a full distribution directory of PuTTY. + +module putty + +# Set up the arguments for the main make command. +set Makever -DSVN_REV=$(revision) +ifneq "$(!numeric $(revision))" "yes" set Makever $(Makever) -DMODIFIED +ifneq "$(RELEASE)" "" set Makever $(Makever) -DRELEASE=$(RELEASE) +ifneq "$(date)" "" set Makever $(Makever) -DSNAPSHOT=$(date) +set Makeargs VER="$(Makever)" +ifneq "$(XFLAGS)" "" set Makeargs $(Makeargs) XFLAGS="$(XFLAGS)" +ifneq "$(MAKEARGS)" "" set Makeargs $(Makeargs) $(MAKEARGS) + +# Set up the version string for the docs build. +set Docmakeargs VERSION="PuTTY revision $(revision)" +ifneq "$(RELEASE)" "" set Docmakeargs VERSION="PuTTY release $(RELEASE)" +ifneq "$(date)" "" set Docmakeargs VERSION="PuTTY development snapshot $(date)" + +# Set up the version string for the Unix source archive. +set Unxver r$(revision) +ifneq "$(RELEASE)" "" set Unxver $(RELEASE) +ifneq "$(date)" "" set Unxver $(date) + +# Set up the various version strings for the installer. +set Iversion r$(revision) +set Iname PuTTY revision $(revision) +set Ivertext Revision $(revision) +set Irev $(revision) +set Ifilename putty-$(Iversion)-installer.exe +ifneq "$(RELEASE)" "" set Iversion $(RELEASE) +ifneq "$(RELEASE)" "" set Iname PuTTY version $(RELEASE) +ifneq "$(RELEASE)" "" set Ivertext Release $(RELEASE) +ifneq "$(RELEASE)" "" set Irev 0 +ifneq "$(RELEASE)" "" set Ifilename putty-$(RELEASE)-installer.exe +ifneq "$(date)" "" set Iversion $(date):r$(revision) +ifneq "$(date)" "" set Iname PuTTY development snapshot $(date):r$(revision) +ifneq "$(date)" "" set Ivertext Development snapshot $(date):r$(revision) +ifneq "$(date)" "" set Ifilename putty-$(date)-installer.exe + +# Set up the version string for the installer. +set Iversion r$(revision) +ifneq "$(RELEASE)" "" set Iversion $(RELEASE) +ifneq "$(date)" "" set Iversion $(date):r$(revision) + +in putty do ./mksrcarc.sh +in putty do ./mkunxarc.sh $(Unxver) +in putty do perl mkfiles.pl +in putty/doc do make $(Docmakeargs) putty.hlp +in putty/doc do make $(Docmakeargs) chm + +# Munge the installer script locally so that it reports the version +# we're really building. +in putty/windows do perl -i~ -pe 'BEGIN{$$a=shift@ARGV;}s/^(AppVerName=).*$$/$$1$$a/' '$(Iname)' putty.iss +in putty/windows do perl -i~ -pe 'BEGIN{$$a=shift@ARGV;}s/^(VersionInfoTextVersion=).*$$/$$1$$a/' '$(Ivertext)' putty.iss +in putty/windows do perl -i~ -pe 'BEGIN{$$a=shift@ARGV;}s/^(AppVersion=).*$$/$$1$$a/' '$(Iversion)' putty.iss +in putty/windows do perl -i~ -pe 'BEGIN{$$a=shift@ARGV;$$a=~s/M//;}s/^(VersionInfoVersion=\d+\.\d+\.)\d+(\.\d+)\r?$$/$$1$$a$$2/' '$(Irev)' putty.iss + +# Windowsify LICENCE, since it's going in the Windows installer. +in putty do perl -i~ -pe 'y/\015//d;s/$$/\015/' LICENCE + +delegate windows + # FIXME: Cygwin alternative? + in putty/windows do cmd /c vcvars32 \& nmake -f Makefile.vc $(Makeargs) + # Ignore exit code from hhc, in favour of seeing whether the .chm + # file was created. (Yuck; but hhc appears to return non-zero + # exit codes on whim.) + in putty/doc do hhc putty.hhp; test -f putty.chm + in putty/windows do iscc putty.iss + return putty/windows/*.exe + return putty/windows/*.map + return putty/doc/putty.chm + return putty/windows/Output/setup.exe +enddelegate +in putty/doc do make mostlyclean +in putty/doc do make $(Docmakeargs) +in putty/windows do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../doc/putty.chm ../doc/putty.hlp ../doc/putty.cnt +in putty/doc do zip puttydoc.zip *.html + +# Deliver the actual PuTTY release directory into a subdir `putty'. +deliver putty/windows/*.exe putty/x86/$@ +deliver putty/windows/putty.zip putty/x86/$@ +deliver putty/windows/Output/setup.exe putty/x86/$(Ifilename) +deliver putty/doc/puttydoc.zip putty/$@ +deliver putty/doc/putty.chm putty/$@ +deliver putty/doc/putty.hlp putty/$@ +deliver putty/doc/putty.cnt putty/$@ +deliver putty/doc/puttydoc.txt putty/$@ +deliver putty/doc/*.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/*.map maps-x86/$@ + +# Deliver sign.sh, so that whoever has just built PuTTY (the +# snapshot scripts or me, depending) can conveniently sign it with +# whatever key they want. +deliver putty/sign.sh $@ + +# Create files of cryptographic checksums, which will be signed along +# with the files they verify. We've provided MD5 checksums for a +# while, but now MD5 is looking iffy, we're expanding our selection. +# +# Creating these files is most easily done in the destination +# directory, where all the files we're delivering are already in their +# final relative layout. +in-dest putty do a=`\find * -type f -print`; md5sum $$a > md5sums && sha1sum $$a > sha1sums && sha256sum $$a > sha256sums && sha512sum $$a > sha512sums + +# And construct .htaccess files. One in the top-level directory, +# setting the MIME types for Windows help files and providing an +# appropriate link to the source archive: +in-dest putty do echo "AddType application/octet-stream .chm" >> .htaccess +in-dest putty do echo "AddType application/octet-stream .hlp" >> .htaccess +in-dest putty do echo "AddType application/octet-stream .cnt" >> .htaccess +in-dest putty do set -- putty*.tar.gz; for k in '' .DSA .RSA; do echo RedirectMatch temp '(.*/)'putty.tar.gz$$k\$$ '$$1'"$$1$$k" >> .htaccess; done +# And one in the x86 directory, providing a link for the installer. +in-dest putty/x86 do set -- putty*installer.exe; for k in '' .DSA .RSA; do echo RedirectMatch temp '(.*/)'putty-installer.exe$$k\$$ '$$1'"$$1$$k" >> .htaccess; done diff --git a/putty/CHARSET/CHARSET.H b/putty/CHARSET/CHARSET.H new file mode 100644 index 0000000..3f7eb34 --- /dev/null +++ b/putty/CHARSET/CHARSET.H @@ -0,0 +1,154 @@ +/* + * charset.h - header file for general character set conversion + * routines. + */ + +#ifndef charset_charset_h +#define charset_charset_h + +#include + +/* + * Enumeration that lists all the multibyte or single-byte + * character sets known to this library. + */ +typedef enum { + CS_NONE, /* used for reporting errors, etc */ + CS_ISO8859_1, + CS_ISO8859_1_X11, /* X font encoding with VT100 glyphs */ + CS_ISO8859_2, + CS_ISO8859_3, + CS_ISO8859_4, + CS_ISO8859_5, + CS_ISO8859_6, + CS_ISO8859_7, + CS_ISO8859_8, + CS_ISO8859_9, + CS_ISO8859_10, + CS_ISO8859_11, + CS_ISO8859_13, + CS_ISO8859_14, + CS_ISO8859_15, + CS_ISO8859_16, + CS_CP437, + CS_CP850, + CS_CP866, + CS_CP1250, + CS_CP1251, + CS_CP1252, + CS_CP1253, + CS_CP1254, + CS_CP1255, + CS_CP1256, + CS_CP1257, + CS_CP1258, + CS_KOI8_R, + CS_KOI8_U, + CS_MAC_ROMAN, + CS_MAC_TURKISH, + CS_MAC_CROATIAN, + CS_MAC_ICELAND, + CS_MAC_ROMANIAN, + CS_MAC_GREEK, + CS_MAC_CYRILLIC, + CS_MAC_THAI, + CS_MAC_CENTEURO, + CS_MAC_SYMBOL, + CS_MAC_DINGBATS, + CS_MAC_ROMAN_OLD, + CS_MAC_CROATIAN_OLD, + CS_MAC_ICELAND_OLD, + CS_MAC_ROMANIAN_OLD, + CS_MAC_GREEK_OLD, + CS_MAC_CYRILLIC_OLD, + CS_MAC_UKRAINE, + CS_MAC_VT100, + CS_MAC_VT100_OLD, + CS_VISCII, + CS_HP_ROMAN8, + CS_DEC_MCS, + CS_UTF8 +} charset_t; + +typedef struct { + unsigned long s0; +} charset_state; + +/* + * Routine to convert a MB/SB character set to Unicode. + * + * This routine accepts some number of bytes, updates a state + * variable, and outputs some number of Unicode characters. There + * are no guarantees. You can't even guarantee that at most one + * Unicode character will be output per byte you feed in; for + * example, suppose you're reading UTF-8, you've seen E1 80, and + * then you suddenly see FE. Now you need to output _two_ error + * characters - one for the incomplete sequence E1 80, and one for + * the completely invalid UTF-8 byte FE. + * + * Returns the number of wide characters output; will never output + * more than the size of the buffer (as specified on input). + * Advances the `input' pointer and decrements `inlen', to indicate + * how far along the input string it got. + * + * The sequence of `errlen' wide characters pointed to by `errstr' + * will be used to indicate a conversion error. If `errstr' is + * NULL, `errlen' will be ignored, and the library will choose + * something sensible to do on its own. For Unicode, this will be + * U+FFFD (REPLACEMENT CHARACTER). + */ + +int charset_to_unicode(char **input, int *inlen, wchar_t *output, int outlen, + int charset, charset_state *state, + const wchar_t *errstr, int errlen); + +/* + * Routine to convert Unicode to an MB/SB character set. + * + * This routine accepts some number of Unicode characters, updates + * a state variable, and outputs some number of bytes. + * + * Returns the number of bytes characters output; will never output + * more than the size of the buffer (as specified on input), and + * will never output a partial MB character. Advances the `input' + * pointer and decrements `inlen', to indicate how far along the + * input string it got. + * + * The sequence of `errlen' characters pointed to by `errstr' will + * be used to indicate a conversion error. If `errstr' is NULL, + * `errlen' will be ignored, and the library will choose something + * sensible to do on its own (which will vary depending on the + * output charset). + */ + +int charset_from_unicode(wchar_t **input, int *inlen, char *output, int outlen, + int charset, charset_state *state, + const char *errstr, int errlen); + +/* + * Convert X11 encoding names to and from our charset identifiers. + */ +const char *charset_to_xenc(int charset); +int charset_from_xenc(const char *name); + +/* + * Convert MIME encoding names to and from our charset identifiers. + */ +const char *charset_to_mimeenc(int charset); +int charset_from_mimeenc(const char *name); + +/* + * Convert our own encoding names to and from our charset + * identifiers. + */ +const char *charset_to_localenc(int charset); +int charset_from_localenc(const char *name); +int charset_localenc_nth(int n); + +/* + * Convert Mac OS script/region/font to our charset identifiers. + */ +int charset_from_macenc(int script, int region, int sysvers, + const char *fontname); + +#endif /* charset_charset_h */ diff --git a/putty/CHARSET/ENUM.C b/putty/CHARSET/ENUM.C new file mode 100644 index 0000000..4c559be --- /dev/null +++ b/putty/CHARSET/ENUM.C @@ -0,0 +1,19 @@ +/* + * enum.c - enumerate all charsets defined by the library. + * + * This file maintains a list of every other source file which + * contains ENUM_CHARSET definitions. It #includes each one with + * ENUM_CHARSETS defined, which causes those source files to do + * nothing at all except call the ENUM_CHARSET macro on each + * charset they define. + * + * This file in turn is included from various other places, with + * the ENUM_CHARSET macro defined to various different things. This + * allows us to have multiple implementations of the master charset + * lookup table (a static one and a dynamic one). + */ + +#define ENUM_CHARSETS +#include "sbcsdat.c" +#include "utf8.c" +#undef ENUM_CHARSETS diff --git a/putty/CHARSET/FROMUCS.C b/putty/CHARSET/FROMUCS.C new file mode 100644 index 0000000..ce69cd7 --- /dev/null +++ b/putty/CHARSET/FROMUCS.C @@ -0,0 +1,91 @@ +/* + * fromucs.c - convert Unicode to other character sets. + */ + +#include "charset.h" +#include "internal.h" + +struct charset_emit_param { + char *output; + int outlen; + const char *errstr; + int errlen; + int stopped; +}; + +static void charset_emit(void *ctx, long int output) +{ + struct charset_emit_param *param = (struct charset_emit_param *)ctx; + char outval; + char const *p; + int outlen; + + if (output == ERROR) { + p = param->errstr; + outlen = param->errlen; + } else { + outval = output; + p = &outval; + outlen = 1; + } + + if (param->outlen >= outlen) { + while (outlen > 0) { + *param->output++ = *p++; + param->outlen--; + outlen--; + } + } else { + param->stopped = 1; + } +} + +int charset_from_unicode(wchar_t **input, int *inlen, char *output, int outlen, + int charset, charset_state *state, + const char *errstr, int errlen) +{ + charset_spec const *spec = charset_find_spec(charset); + charset_state localstate; + struct charset_emit_param param; + + param.output = output; + param.outlen = outlen; + param.stopped = 0; + + /* + * charset_emit will expect a valid errstr. + */ + if (!errstr) { + /* *shrug* this is good enough, and consistent across all SBCS... */ + param.errstr = "."; + param.errlen = 1; + } + param.errstr = errstr; + param.errlen = errlen; + + if (!state) { + localstate.s0 = 0; + } else { + localstate = *state; /* structure copy */ + } + state = &localstate; + + while (*inlen > 0) { + int lenbefore = param.output - output; + spec->write(spec, **input, &localstate, charset_emit, ¶m); + if (param.stopped) { + /* + * The emit function has _tried_ to output some + * characters, but ran up against the end of the + * buffer. Leave immediately, and return what happened + * _before_ attempting to process this character. + */ + return lenbefore; + } + if (state) + *state = localstate; /* structure copy */ + (*input)++; + (*inlen)--; + } + return param.output - output; +} diff --git a/putty/CHARSET/INTERNAL.H b/putty/CHARSET/INTERNAL.H new file mode 100644 index 0000000..683b8a6 --- /dev/null +++ b/putty/CHARSET/INTERNAL.H @@ -0,0 +1,89 @@ +/* + * internal.h - internal header stuff for the charset library. + */ + +#ifndef charset_internal_h +#define charset_internal_h + +/* This invariably comes in handy */ +#define lenof(x) ( sizeof((x)) / sizeof(*(x)) ) + +/* This is an invalid Unicode value used to indicate an error. */ +#define ERROR 0xFFFFL /* Unicode value representing error */ + +typedef struct charset_spec charset_spec; +typedef struct sbcs_data sbcs_data; + +struct charset_spec { + int charset; /* numeric identifier */ + + /* + * A function to read the character set and output Unicode + * characters. The `emit' function expects to get Unicode chars + * passed to it; it should be sent ERROR for any encoding error + * on the input. + */ + void (*read)(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); + /* + * A function to read Unicode characters and output in this + * character set. The `emit' function expects to get byte + * values passed to it; it should be sent ERROR for any + * non-representable characters on the input. + */ + void (*write)(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); + void const *data; +}; + +/* + * This is the format of `data' used by the SBCS read and write + * functions; so it's the format used in all SBCS definitions. + */ +struct sbcs_data { + /* + * This is a simple mapping table converting each SBCS position + * to a Unicode code point. Some positions may contain ERROR, + * indicating that that byte value is not defined in the SBCS + * in question and its occurrence in input is an error. + */ + unsigned long sbcs2ucs[256]; + + /* + * This lookup table is used to convert Unicode back to the + * SBCS. It consists of the valid byte values in the SBCS, + * sorted in order of their Unicode translation. So given a + * Unicode value U, you can do a binary search on this table + * using the above table as a lookup: when testing the Xth + * position in this table, you branch according to whether + * sbcs2ucs[ucs2sbcs[X]] is less than, greater than, or equal + * to U. + * + * Note that since there may be fewer than 256 valid byte + * values in a particular SBCS, we must supply the length of + * this table as well as the contents. + */ + unsigned char ucs2sbcs[256]; + int nvalid; +}; + +/* + * Prototypes for internal library functions. + */ +charset_spec const *charset_find_spec(int charset); +void read_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); +void write_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); + +/* + * Placate compiler warning about unused parameters, of which we + * expect to have some in this library. + */ +#define UNUSEDARG(x) ( (x) = (x) ) + +#endif /* charset_internal_h */ diff --git a/putty/CHARSET/LOCALENC.C b/putty/CHARSET/LOCALENC.C new file mode 100644 index 0000000..9e51f72 --- /dev/null +++ b/putty/CHARSET/LOCALENC.C @@ -0,0 +1,125 @@ +/* + * local.c - translate our internal character set codes to and from + * our own set of plausibly legible character-set names. Also + * provides a canonical name for each encoding (useful for software + * announcing what character set it will be using), and a set of + * enumeration functions which return a list of supported + * encodings one by one. + * + * charset_from_localenc will attempt all other text translations + * as well as this table, to maximise the number of different ways + * you can select a supported charset. + */ + +#include +#include "charset.h" +#include "internal.h" + +static const struct { + const char *name; + int charset; + int return_in_enum; /* enumeration misses some charsets */ +} localencs[] = { + { "", CS_NONE, 0 }, + { "ISO-8859-1", CS_ISO8859_1, 1 }, + { "ISO-8859-1 with X11 line drawing", CS_ISO8859_1_X11, 0 }, + { "ISO-8859-2", CS_ISO8859_2, 1 }, + { "ISO-8859-3", CS_ISO8859_3, 1 }, + { "ISO-8859-4", CS_ISO8859_4, 1 }, + { "ISO-8859-5", CS_ISO8859_5, 1 }, + { "ISO-8859-6", CS_ISO8859_6, 1 }, + { "ISO-8859-7", CS_ISO8859_7, 1 }, + { "ISO-8859-8", CS_ISO8859_8, 1 }, + { "ISO-8859-9", CS_ISO8859_9, 1 }, + { "ISO-8859-10", CS_ISO8859_10, 1 }, + { "ISO-8859-11", CS_ISO8859_11, 1 }, + { "ISO-8859-13", CS_ISO8859_13, 1 }, + { "ISO-8859-14", CS_ISO8859_14, 1 }, + { "ISO-8859-15", CS_ISO8859_15, 1 }, + { "ISO-8859-16", CS_ISO8859_16, 1 }, + { "CP437", CS_CP437, 1 }, + { "CP850", CS_CP850, 1 }, + { "CP866", CS_CP866, 1 }, + { "CP1250", CS_CP1250, 1 }, + { "CP1251", CS_CP1251, 1 }, + { "CP1252", CS_CP1252, 1 }, + { "CP1253", CS_CP1253, 1 }, + { "CP1254", CS_CP1254, 1 }, + { "CP1255", CS_CP1255, 1 }, + { "CP1256", CS_CP1256, 1 }, + { "CP1257", CS_CP1257, 1 }, + { "CP1258", CS_CP1258, 1 }, + { "KOI8-R", CS_KOI8_R, 1 }, + { "KOI8-U", CS_KOI8_U, 1 }, + { "Mac Roman", CS_MAC_ROMAN, 1 }, + { "Mac Turkish", CS_MAC_TURKISH, 1 }, + { "Mac Croatian", CS_MAC_CROATIAN, 1 }, + { "Mac Iceland", CS_MAC_ICELAND, 1 }, + { "Mac Romanian", CS_MAC_ROMANIAN, 1 }, + { "Mac Greek", CS_MAC_GREEK, 1 }, + { "Mac Cyrillic", CS_MAC_CYRILLIC, 1 }, + { "Mac Thai", CS_MAC_THAI, 1 }, + { "Mac Centeuro", CS_MAC_CENTEURO, 1 }, + { "Mac Symbol", CS_MAC_SYMBOL, 1 }, + { "Mac Dingbats", CS_MAC_DINGBATS, 1 }, + { "Mac Roman (old)", CS_MAC_ROMAN_OLD, 0 }, + { "Mac Croatian (old)", CS_MAC_CROATIAN_OLD, 0 }, + { "Mac Iceland (old)", CS_MAC_ICELAND_OLD, 0 }, + { "Mac Romanian (old)", CS_MAC_ROMANIAN_OLD, 0 }, + { "Mac Greek (old)", CS_MAC_GREEK_OLD, 0 }, + { "Mac Cyrillic (old)", CS_MAC_CYRILLIC_OLD, 0 }, + { "Mac Ukraine", CS_MAC_UKRAINE, 1 }, + { "Mac VT100", CS_MAC_VT100, 1 }, + { "Mac VT100 (old)", CS_MAC_VT100_OLD, 0 }, + { "VISCII", CS_VISCII, 1 }, + { "HP ROMAN8", CS_HP_ROMAN8, 1 }, + { "DEC MCS", CS_DEC_MCS, 1 }, + { "UTF-8", CS_UTF8, 1 }, +}; + +const char *charset_to_localenc(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(localencs); i++) + if (charset == localencs[i].charset) + return localencs[i].name; + + return NULL; /* not found */ +} + +int charset_from_localenc(const char *name) +{ + int i; + + if ( (i = charset_from_mimeenc(name)) != CS_NONE) + return i; + if ( (i = charset_from_xenc(name)) != CS_NONE) + return i; + + for (i = 0; i < (int)lenof(localencs); i++) { + const char *p, *q; + p = name; + q = localencs[i].name; + while (*p || *q) { + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + break; + p++; q++; + } + if (!*p && !*q) + return localencs[i].charset; + } + + return CS_NONE; /* not found */ +} + +int charset_localenc_nth(int n) +{ + int i; + + for (i = 0; i < (int)lenof(localencs); i++) + if (localencs[i].return_in_enum && !n--) + return localencs[i].charset; + + return CS_NONE; /* end of list */ +} diff --git a/putty/CHARSET/MACENC.C b/putty/CHARSET/MACENC.C new file mode 100644 index 0000000..a6d9aab --- /dev/null +++ b/putty/CHARSET/MACENC.C @@ -0,0 +1,169 @@ +/* $Id: macenc.c 8037 2008-06-04 23:05:48Z simon $ */ +/* + * Copyright (c) 2003 Ben Harris + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/* + * macenc.c -- Convert a Mac OS script/region/font combination to our + * internal charset code. + */ + +#include + +#include "charset.h" +#include "internal.h" + +/* + * These are defined by Mac OS's , but we'd like to be + * independent of that. + */ + +#define smRoman 0 +#define smJapanese 1 +#define smTradChinese 2 +#define smKorean 3 +#define smArabic 4 +#define smHebrew 5 +#define smCyrillic 7 +#define smDevenagari 9 +#define smGurmukhi 10 +#define smGujurati 11 +#define smThai 21 +#define smSimpChinese 25 +#define smTibetan 26 +#define smEthiopic 28 +#define smCentralEuroRoman 29 + +#define verGreece 20 +#define verIceland 21 +#define verTurkey 24 +#define verYugoCroatian 25 +#define verRomania 39 +#define verFaroeIsl 47 +#define verIran 48 +#define verRussia 49 +#define verSlovenian 66 +#define verCroatia 68 +#define verBulgaria 72 +#define verScottishGaelic 75 +#define verManxGaelic 76 +#define verBreton 77 +#define verNunavut 78 +#define verWelsh 79 +#define verIrishGaelicScript 81 + +static const struct { + int script; + int region; + int sysvermin; + char const *fontname; + int charset; +} macencs[] = { + { smRoman, -1, 0x850, "VT100", CS_MAC_VT100 }, + { smRoman, -1, 0, "VT100", CS_MAC_VT100_OLD }, + /* + * From here on, this table is largely derived from + * , + * with _OLD version added based on the comments in individual + * mapping files. + */ + { smRoman, -1, 0, "Symbol", CS_MAC_SYMBOL }, + { smRoman, -1, 0, "Zapf Dingbats", CS_MAC_DINGBATS }, + { smRoman, verTurkey, 0, NULL, CS_MAC_TURKISH }, + { smRoman, verYugoCroatian, 0x850, NULL, CS_MAC_CROATIAN }, + { smRoman, verYugoCroatian, 0, NULL, CS_MAC_CROATIAN_OLD }, + { smRoman, verSlovenian, 0x850, NULL, CS_MAC_CROATIAN }, + { smRoman, verSlovenian, 0, NULL, CS_MAC_CROATIAN_OLD }, + { smRoman, verCroatia, 0x850, NULL, CS_MAC_CROATIAN }, + { smRoman, verCroatia, 0, NULL, CS_MAC_CROATIAN_OLD }, + { smRoman, verIceland, 0x850, NULL, CS_MAC_ICELAND }, + { smRoman, verIceland, 0, NULL, CS_MAC_ICELAND_OLD }, + { smRoman, verFaroeIsl, 0x850, NULL, CS_MAC_ICELAND }, + { smRoman, verFaroeIsl, 0, NULL, CS_MAC_ICELAND_OLD }, + { smRoman, verRomania, 0x850, NULL, CS_MAC_ROMANIAN }, + { smRoman, verRomania, 0, NULL, CS_MAC_ROMANIAN_OLD }, +#if 0 /* No mapping table on ftp.unicode.org */ + { smRoman, verIreland, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verIreland, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verScottishGaelic, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verScottishGaelic, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verManxGaelic, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verManxGaelic, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verBreton, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verBreton, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verWelsh, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verWelsh, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verIrishGaelicScript, 0x850, NULL, CS_MAC_GAELIC }, + { smRoman, verIrishGaelicScript, 0, NULL, CS_MAC_GAELIC_OLD }, +#endif + { smRoman, verGreece, 0x922, NULL, CS_MAC_GREEK }, + { smRoman, verGreece, 0, NULL, CS_MAC_GREEK_OLD }, + { smRoman, -1, 0x850, NULL, CS_MAC_ROMAN }, + { smRoman, -1, 0, NULL, CS_MAC_ROMAN_OLD }, +#if 0 /* Multi-byte encodings, not yet supported */ + { smJapanese, -1, 0, NULL, CS_MAC_JAPANESE }, + { smTradChinese, -1, 0, NULL, CS_MAC_CHINTRAD }, + { smKorean, -1, 0, NULL, CS_MAC_KOREAN }, +#endif +#if 0 /* Bidirectional encodings, not yet supported */ + { smArabic, verIran, 0, NULL, CS_MAC_FARSI }, + { smArabic, -1, 0, NULL, CS_MAC_ARABIC }, + { smHebrew, -1, 0, NULL, CS_MAC_HEBREW }, +#endif + { smCyrillic, -1, 0x900, NULL, CS_MAC_CYRILLIC }, + { smCyrillic, verRussia, 0, NULL, CS_MAC_CYRILLIC_OLD }, + { smCyrillic, verBulgaria, 0, NULL, CS_MAC_CYRILLIC_OLD }, + { smCyrillic, -1, 0, NULL, CS_MAC_UKRAINE }, +#if 0 /* Complex Indic scripts, not yet supported */ + { smDevanagari, -1, 0, NULL, CS_MAC_DEVENAGA }, + { smGurmukhi, -1, 0, NULL, CS_MAC_GURMUKHI }, + { smGujurati, -1, 0, NULL, CS_MAC_GUJURATI }, +#endif + { smThai, -1, 0, NULL, CS_MAC_THAI }, +#if 0 /* Multi-byte encoding, not yet supported */ + { smSimpChinese, -1, 0, NULL, CS_MAC_CHINSIMP }, +#endif +#if 0 /* No mapping table on ftp.unicode.org */ + { smTibetan, -1, 0, NULL, CS_MAC_TIBETAN }, + { smEthiopic, -1, 0, NULL, CS_MAC_ETHIOPIC }, + { smEthiopic, verNanavut, 0, NULL, CS_MAC_INUIT }, +#endif + { smCentralEuroRoman, -1, 0, NULL, CS_MAC_CENTEURO }, +}; + +int charset_from_macenc(int script, int region, int sysvers, + char const *fontname) +{ + int i; + + for (i = 0; i < (int)lenof(macencs); i++) + if ((macencs[i].script == script) && + (macencs[i].region < 0 || macencs[i].region == region) && + (macencs[i].sysvermin <= sysvers) && + (macencs[i].fontname == NULL || + (fontname != NULL && strcmp(macencs[i].fontname, fontname) == 0))) + return macencs[i].charset; + + return CS_NONE; +} diff --git a/putty/CHARSET/MIMEENC.C b/putty/CHARSET/MIMEENC.C new file mode 100644 index 0000000..27b860a --- /dev/null +++ b/putty/CHARSET/MIMEENC.C @@ -0,0 +1,214 @@ +/* + * mimeenc.c - translate our internal character set codes to and + * from MIME standard character-set names. + * + */ + +#include +#include "charset.h" +#include "internal.h" + +static const struct { + const char *name; + int charset; +} mimeencs[] = { + /* + * These names are taken from + * + * http://www.iana.org/assignments/character-sets + * + * Where multiple encoding names map to the same encoding id + * (such as the variety of aliases for ISO-8859-1), the first + * is considered canonical and will be returned when + * translating the id to a string. + */ + { "ISO-8859-1", CS_ISO8859_1 }, + { "iso-ir-100", CS_ISO8859_1 }, + { "ISO_8859-1", CS_ISO8859_1 }, + { "ISO_8859-1:1987", CS_ISO8859_1 }, + { "latin1", CS_ISO8859_1 }, + { "l1", CS_ISO8859_1 }, + { "IBM819", CS_ISO8859_1 }, + { "CP819", CS_ISO8859_1 }, + { "csISOLatin1", CS_ISO8859_1 }, + + { "ISO-8859-2", CS_ISO8859_2 }, + { "ISO_8859-2:1987", CS_ISO8859_2 }, + { "iso-ir-101", CS_ISO8859_2 }, + { "ISO_8859-2", CS_ISO8859_2 }, + { "latin2", CS_ISO8859_2 }, + { "l2", CS_ISO8859_2 }, + { "csISOLatin2", CS_ISO8859_2 }, + + { "ISO-8859-3", CS_ISO8859_3 }, + { "ISO_8859-3:1988", CS_ISO8859_3 }, + { "iso-ir-109", CS_ISO8859_3 }, + { "ISO_8859-3", CS_ISO8859_3 }, + { "latin3", CS_ISO8859_3 }, + { "l3", CS_ISO8859_3 }, + { "csISOLatin3", CS_ISO8859_3 }, + + { "ISO-8859-4", CS_ISO8859_4 }, + { "ISO_8859-4:1988", CS_ISO8859_4 }, + { "iso-ir-110", CS_ISO8859_4 }, + { "ISO_8859-4", CS_ISO8859_4 }, + { "latin4", CS_ISO8859_4 }, + { "l4", CS_ISO8859_4 }, + { "csISOLatin4", CS_ISO8859_4 }, + + { "ISO-8859-5", CS_ISO8859_5 }, + { "ISO_8859-5:1988", CS_ISO8859_5 }, + { "iso-ir-144", CS_ISO8859_5 }, + { "ISO_8859-5", CS_ISO8859_5 }, + { "cyrillic", CS_ISO8859_5 }, + { "csISOLatinCyrillic", CS_ISO8859_5 }, + + { "ISO-8859-6", CS_ISO8859_6 }, + { "ISO_8859-6:1987", CS_ISO8859_6 }, + { "iso-ir-127", CS_ISO8859_6 }, + { "ISO_8859-6", CS_ISO8859_6 }, + { "ECMA-114", CS_ISO8859_6 }, + { "ASMO-708", CS_ISO8859_6 }, + { "arabic", CS_ISO8859_6 }, + { "csISOLatinArabic", CS_ISO8859_6 }, + + { "ISO-8859-7", CS_ISO8859_7 }, + { "ISO_8859-7:1987", CS_ISO8859_7 }, + { "iso-ir-126", CS_ISO8859_7 }, + { "ISO_8859-7", CS_ISO8859_7 }, + { "ELOT_928", CS_ISO8859_7 }, + { "ECMA-118", CS_ISO8859_7 }, + { "greek", CS_ISO8859_7 }, + { "greek8", CS_ISO8859_7 }, + { "csISOLatinGreek", CS_ISO8859_7 }, + + { "ISO-8859-8", CS_ISO8859_8 }, + { "ISO_8859-8:1988", CS_ISO8859_8 }, + { "iso-ir-138", CS_ISO8859_8 }, + { "ISO_8859-8", CS_ISO8859_8 }, + { "hebrew", CS_ISO8859_8 }, + { "csISOLatinHebrew", CS_ISO8859_8 }, + + { "ISO-8859-9", CS_ISO8859_9 }, + { "ISO_8859-9:1989", CS_ISO8859_9 }, + { "iso-ir-148", CS_ISO8859_9 }, + { "ISO_8859-9", CS_ISO8859_9 }, + { "latin5", CS_ISO8859_9 }, + { "l5", CS_ISO8859_9 }, + { "csISOLatin5", CS_ISO8859_9 }, + + { "ISO-8859-10", CS_ISO8859_10 }, + { "iso-ir-157", CS_ISO8859_10 }, + { "l6", CS_ISO8859_10 }, + { "ISO_8859-10:1992", CS_ISO8859_10 }, + { "csISOLatin6", CS_ISO8859_10 }, + { "latin6", CS_ISO8859_10 }, + + { "ISO-8859-13", CS_ISO8859_13 }, + + { "ISO-8859-14", CS_ISO8859_14 }, + { "iso-ir-199", CS_ISO8859_14 }, + { "ISO_8859-14:1998", CS_ISO8859_14 }, + { "ISO_8859-14", CS_ISO8859_14 }, + { "latin8", CS_ISO8859_14 }, + { "iso-celtic", CS_ISO8859_14 }, + { "l8", CS_ISO8859_14 }, + + { "ISO-8859-15", CS_ISO8859_15 }, + { "ISO_8859-15", CS_ISO8859_15 }, + { "Latin-9", CS_ISO8859_15 }, + + { "ISO-8859-16", CS_ISO8859_16 }, + { "iso-ir-226", CS_ISO8859_16 }, + { "ISO_8859-16", CS_ISO8859_16 }, + { "ISO_8859-16:2001", CS_ISO8859_16 }, + { "latin10", CS_ISO8859_16 }, + { "l10", CS_ISO8859_16 }, + + { "IBM437", CS_CP437 }, + { "cp437", CS_CP437 }, + { "437", CS_CP437 }, + { "csPC8CodePage437", CS_CP437 }, + + { "IBM850", CS_CP850 }, + { "cp850", CS_CP850 }, + { "850", CS_CP850 }, + { "csPC850Multilingual", CS_CP850 }, + + { "IBM866", CS_CP866 }, + { "cp866", CS_CP866 }, + { "866", CS_CP866 }, + { "csIBM866", CS_CP866 }, + + { "windows-1250", CS_CP1250 }, + + { "windows-1251", CS_CP1251 }, + + { "windows-1252", CS_CP1252 }, + + { "windows-1253", CS_CP1253 }, + + { "windows-1254", CS_CP1254 }, + + { "windows-1255", CS_CP1255 }, + + { "windows-1256", CS_CP1256 }, + + { "windows-1257", CS_CP1257 }, + + { "windows-1258", CS_CP1258 }, + + { "KOI8-R", CS_KOI8_R }, + { "csKOI8R", CS_KOI8_R }, + + { "KOI8-U", CS_KOI8_U }, + + { "macintosh", CS_MAC_ROMAN_OLD }, + { "mac", CS_MAC_ROMAN_OLD }, + { "csMacintosh", CS_MAC_ROMAN_OLD }, + + { "VISCII", CS_VISCII }, + { "csVISCII", CS_VISCII }, + + { "hp-roman8", CS_HP_ROMAN8 }, + { "roman8", CS_HP_ROMAN8 }, + { "r8", CS_HP_ROMAN8 }, + { "csHPRoman8", CS_HP_ROMAN8 }, + + { "DEC-MCS", CS_DEC_MCS }, + { "dec", CS_DEC_MCS }, + { "csDECMCS", CS_DEC_MCS }, + + { "UTF-8", CS_UTF8 }, +}; + +const char *charset_to_mimeenc(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(mimeencs); i++) + if (charset == mimeencs[i].charset) + return mimeencs[i].name; + + return NULL; /* not found */ +} + +int charset_from_mimeenc(const char *name) +{ + int i; + + for (i = 0; i < (int)lenof(mimeencs); i++) { + const char *p, *q; + p = name; + q = mimeencs[i].name; + while (*p || *q) { + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + break; + p++; q++; + } + if (!*p && !*q) + return mimeencs[i].charset; + } + + return CS_NONE; /* not found */ +} diff --git a/putty/CHARSET/README b/putty/CHARSET/README new file mode 100644 index 0000000..2d08b36 --- /dev/null +++ b/putty/CHARSET/README @@ -0,0 +1,15 @@ +This subdirectory contains a general character-set conversion +library, used in the Unix port of PuTTY, and available for use in +other ports if it should happen to be useful. + +This is a variant of a library that's currently used in some other +programs such as Timber and Halibut. At some future date, we would +like to merge the two libraries, so that all programs use the same +libcharset. + +It is therefore a _strong_ design goal that this library should remain +perfectly general, and not tied to particulars of PuTTY. It must not +reference any code outside its own subdirectory; it should not have +PuTTY-specific helper routines added to it unless they can be +documented in a general manner which might make them useful in other +circumstances as well. diff --git a/putty/CHARSET/SBCS.C b/putty/CHARSET/SBCS.C new file mode 100644 index 0000000..f5ea523 --- /dev/null +++ b/putty/CHARSET/SBCS.C @@ -0,0 +1,53 @@ +/* + * sbcs.c - routines to handle single-byte character sets. + */ + +#include "charset.h" +#include "internal.h" + +/* + * The charset_spec for any single-byte character set should + * provide read_sbcs() as its read function, and its `data' field + * should be a wchar_t string constant containing the 256 entries + * of the translation table. + */ + +void read_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) +{ + const struct sbcs_data *sd = charset->data; + + UNUSEDARG(state); + + emit(emitctx, sd->sbcs2ucs[input_chr]); +} + +void write_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) +{ + const struct sbcs_data *sd = charset->data; + int i, j, k, c; + + UNUSEDARG(state); + + /* + * Binary-search in the ucs2sbcs table. + */ + i = -1; + j = sd->nvalid; + while (i+1 < j) { + k = (i+j)/2; + c = sd->ucs2sbcs[k]; + if (input_chr < sd->sbcs2ucs[c]) + j = k; + else if (input_chr > sd->sbcs2ucs[c]) + i = k; + else { + emit(emitctx, c); + return; + } + } + emit(emitctx, ERROR); +} diff --git a/putty/CHARSET/SBCS.DAT b/putty/CHARSET/SBCS.DAT new file mode 100644 index 0000000..2b919a4 --- /dev/null +++ b/putty/CHARSET/SBCS.DAT @@ -0,0 +1,1117 @@ + Data file defining single-byte character sets. + + All lines which begin with whitespace are considered comments. + + To generate an SBCS table from a unicode.org mapping table: + + gensbcs() { + wget -q -O - "$1" | tr '\r' '\n' | \ + perl -ne '/^(0x.*)\s+(0x.*)\s+/ and $a[hex $1]=sprintf "%04x", hex $2;' \ + -e 'BEGIN{for($i=0;$i<256;$i++){$a[$i]="XXXX";' \ + -e ' if ($i < 32 or $i == 127) {$a[$i]=sprintf "%04x", $i}}}' \ + -e 'END{for($i=0;$i<256;$i++){printf"%s%s",$a[$i],$i%16==15?"\n":" "}}' + } + + (A couple of noteworthy ickinesses here. For a start, any + undefined characters in the control-code regions (00-1F and 7F) + are assumed to be the Unicode code point corresponding to their + index, since the Mac Roman mapping table declines to define them + but realistically you don't want to be messing with that sort of + thing. Secondly, the Mac mapping tables are shipped with Mac line + endings, so note the `tr' to turn them into something legible to + Perl...) + + Here are the ISO-8859-x tables, generated by this piece of Bourne + shell: + + for i in 1 2 3 4 5 6 7 8 9 10 11 13 14 15 16; do + echo charset CS_ISO8859_$i + gensbcs http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-$i.TXT + echo + done + +charset CS_ISO8859_1 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af +00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf +00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf +00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df +00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef +00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff + +charset CS_ISO8859_2 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 0104 02d8 0141 00a4 013d 015a 00a7 00a8 0160 015e 0164 0179 00ad 017d 017b +00b0 0105 02db 0142 00b4 013e 015b 02c7 00b8 0161 015f 0165 017a 02dd 017e 017c +0154 00c1 00c2 0102 00c4 0139 0106 00c7 010c 00c9 0118 00cb 011a 00cd 00ce 010e +0110 0143 0147 00d3 00d4 0150 00d6 00d7 0158 016e 00da 0170 00dc 00dd 0162 00df +0155 00e1 00e2 0103 00e4 013a 0107 00e7 010d 00e9 0119 00eb 011b 00ed 00ee 010f +0111 0144 0148 00f3 00f4 0151 00f6 00f7 0159 016f 00fa 0171 00fc 00fd 0163 02d9 + +charset CS_ISO8859_3 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 0126 02d8 00a3 00a4 XXXX 0124 00a7 00a8 0130 015e 011e 0134 00ad XXXX 017b +00b0 0127 00b2 00b3 00b4 00b5 0125 00b7 00b8 0131 015f 011f 0135 00bd XXXX 017c +00c0 00c1 00c2 XXXX 00c4 010a 0108 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf +XXXX 00d1 00d2 00d3 00d4 0120 00d6 00d7 011c 00d9 00da 00db 00dc 016c 015c 00df +00e0 00e1 00e2 XXXX 00e4 010b 0109 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef +XXXX 00f1 00f2 00f3 00f4 0121 00f6 00f7 011d 00f9 00fa 00fb 00fc 016d 015d 02d9 + +charset CS_ISO8859_4 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 0104 0138 0156 00a4 0128 013b 00a7 00a8 0160 0112 0122 0166 00ad 017d 00af +00b0 0105 02db 0157 00b4 0129 013c 02c7 00b8 0161 0113 0123 0167 014a 017e 014b +0100 00c1 00c2 00c3 00c4 00c5 00c6 012e 010c 00c9 0118 00cb 0116 00cd 00ce 012a +0110 0145 014c 0136 00d4 00d5 00d6 00d7 00d8 0172 00da 00db 00dc 0168 016a 00df +0101 00e1 00e2 00e3 00e4 00e5 00e6 012f 010d 00e9 0119 00eb 0117 00ed 00ee 012b +0111 0146 014d 0137 00f4 00f5 00f6 00f7 00f8 0173 00fa 00fb 00fc 0169 016b 02d9 + +charset CS_ISO8859_5 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 0401 0402 0403 0404 0405 0406 0407 0408 0409 040a 040b 040c 00ad 040e 040f +0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f +0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f +0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f +0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 044f +2116 0451 0452 0453 0454 0455 0456 0457 0458 0459 045a 045b 045c 00a7 045e 045f + +charset CS_ISO8859_6 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 XXXX XXXX XXXX 00a4 XXXX XXXX XXXX XXXX XXXX XXXX XXXX 060c 00ad XXXX XXXX +XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX 061b XXXX XXXX XXXX 061f +XXXX 0621 0622 0623 0624 0625 0626 0627 0628 0629 062a 062b 062c 062d 062e 062f +0630 0631 0632 0633 0634 0635 0636 0637 0638 0639 063a XXXX XXXX XXXX XXXX XXXX +0640 0641 0642 0643 0644 0645 0646 0647 0648 0649 064a 064b 064c 064d 064e 064f +0650 0651 0652 XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX + +charset CS_ISO8859_7 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 2018 2019 00a3 XXXX XXXX 00a6 00a7 00a8 00a9 XXXX 00ab 00ac 00ad XXXX 2015 +00b0 00b1 00b2 00b3 0384 0385 0386 00b7 0388 0389 038a 00bb 038c 00bd 038e 038f +0390 0391 0392 0393 0394 0395 0396 0397 0398 0399 039a 039b 039c 039d 039e 039f +03a0 03a1 XXXX 03a3 03a4 03a5 03a6 03a7 03a8 03a9 03aa 03ab 03ac 03ad 03ae 03af +03b0 03b1 03b2 03b3 03b4 03b5 03b6 03b7 03b8 03b9 03ba 03bb 03bc 03bd 03be 03bf +03c0 03c1 03c2 03c3 03c4 03c5 03c6 03c7 03c8 03c9 03ca 03cb 03cc 03cd 03ce XXXX + +charset CS_ISO8859_8 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 XXXX 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00d7 00ab 00ac 00ad 00ae 00af +00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00f7 00bb 00bc 00bd 00be XXXX +XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX +XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX 2017 +05d0 05d1 05d2 05d3 05d4 05d5 05d6 05d7 05d8 05d9 05da 05db 05dc 05dd 05de 05df +05e0 05e1 05e2 05e3 05e4 05e5 05e6 05e7 05e8 05e9 05ea XXXX XXXX 200e 200f XXXX + +charset CS_ISO8859_9 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af +00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf +00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf +011e 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 0130 015e 00df +00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef +011f 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 0131 015f 00ff + +charset CS_ISO8859_10 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 0104 0112 0122 012a 0128 0136 00a7 013b 0110 0160 0166 017d 00ad 016a 014a +00b0 0105 0113 0123 012b 0129 0137 00b7 013c 0111 0161 0167 017e 2015 016b 014b +0100 00c1 00c2 00c3 00c4 00c5 00c6 012e 010c 00c9 0118 00cb 0116 00cd 00ce 00cf +00d0 0145 014c 00d3 00d4 00d5 00d6 0168 00d8 0172 00da 00db 00dc 00dd 00de 00df +0101 00e1 00e2 00e3 00e4 00e5 00e6 012f 010d 00e9 0119 00eb 0117 00ed 00ee 00ef +00f0 0146 014d 00f3 00f4 00f5 00f6 0169 00f8 0173 00fa 00fb 00fc 00fd 00fe 0138 + +charset CS_ISO8859_11 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 0e01 0e02 0e03 0e04 0e05 0e06 0e07 0e08 0e09 0e0a 0e0b 0e0c 0e0d 0e0e 0e0f +0e10 0e11 0e12 0e13 0e14 0e15 0e16 0e17 0e18 0e19 0e1a 0e1b 0e1c 0e1d 0e1e 0e1f +0e20 0e21 0e22 0e23 0e24 0e25 0e26 0e27 0e28 0e29 0e2a 0e2b 0e2c 0e2d 0e2e 0e2f +0e30 0e31 0e32 0e33 0e34 0e35 0e36 0e37 0e38 0e39 0e3a XXXX XXXX XXXX XXXX 0e3f +0e40 0e41 0e42 0e43 0e44 0e45 0e46 0e47 0e48 0e49 0e4a 0e4b 0e4c 0e4d 0e4e 0e4f +0e50 0e51 0e52 0e53 0e54 0e55 0e56 0e57 0e58 0e59 0e5a 0e5b XXXX XXXX XXXX XXXX + +charset CS_ISO8859_13 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 201d 00a2 00a3 00a4 201e 00a6 00a7 00d8 00a9 0156 00ab 00ac 00ad 00ae 00c6 +00b0 00b1 00b2 00b3 201c 00b5 00b6 00b7 00f8 00b9 0157 00bb 00bc 00bd 00be 00e6 +0104 012e 0100 0106 00c4 00c5 0118 0112 010c 00c9 0179 0116 0122 0136 012a 013b +0160 0143 0145 00d3 014c 00d5 00d6 00d7 0172 0141 015a 016a 00dc 017b 017d 00df +0105 012f 0101 0107 00e4 00e5 0119 0113 010d 00e9 017a 0117 0123 0137 012b 013c +0161 0144 0146 00f3 014d 00f5 00f6 00f7 0173 0142 015b 016b 00fc 017c 017e 2019 + +charset CS_ISO8859_14 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 1e02 1e03 00a3 010a 010b 1e0a 00a7 1e80 00a9 1e82 1e0b 1ef2 00ad 00ae 0178 +1e1e 1e1f 0120 0121 1e40 1e41 00b6 1e56 1e81 1e57 1e83 1e60 1ef3 1e84 1e85 1e61 +00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf +0174 00d1 00d2 00d3 00d4 00d5 00d6 1e6a 00d8 00d9 00da 00db 00dc 00dd 0176 00df +00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef +0175 00f1 00f2 00f3 00f4 00f5 00f6 1e6b 00f8 00f9 00fa 00fb 00fc 00fd 0177 00ff + +charset CS_ISO8859_15 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 00a1 00a2 00a3 20ac 00a5 0160 00a7 0161 00a9 00aa 00ab 00ac 00ad 00ae 00af +00b0 00b1 00b2 00b3 017d 00b5 00b6 00b7 017e 00b9 00ba 00bb 0152 0153 0178 00bf +00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf +00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df +00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef +00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff + +charset CS_ISO8859_16 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 0104 0105 0141 20ac 201e 0160 00a7 0161 00a9 0218 00ab 0179 00ad 017a 017b +00b0 00b1 010c 0142 017d 201d 00b6 00b7 017e 010d 0219 00bb 0152 0153 0178 017c +00c0 00c1 00c2 0102 00c4 0106 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf +0110 0143 00d2 00d3 00d4 0150 00d6 015a 0170 00d9 00da 00db 00dc 0118 021a 00df +00e0 00e1 00e2 0103 00e4 0107 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef +0111 0144 00f2 00f3 00f4 0151 00f6 015b 0171 00f9 00fa 00fb 00fc 0119 021b 00ff + + Some X fonts are encoded in a variant form of ISO8859-1: + everything above 0x20 (space) is as normal, but the first 32 + characters contain the VT100 line drawing glyphs as they would + appear from positions 0x5F to 0x7E inclusive. Here is the modified + ISO8859-1 code table. + + Since this table contains a few duplicated positions, we use the + `sortpriority' hint to indicate that things in the main part of + the code table (0x20-0xFF) should be generated preferentially when + converting _from_ Unicode. Hence, U+00b0 (for example) will yield + 0xb0 rather than 0x07. + +charset CS_ISO8859_1_X11 +sortpriority 00-1F -1 +0020 2666 2592 2409 240c 240d 240a 00b0 00b1 2424 240b 2518 2510 250c 2514 253c +23ba 23bb 2500 23bc 23bd 251c 2524 2534 252c 2502 2264 2265 03c0 2260 00a3 00b7 +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af +00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf +00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf +00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df +00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef +00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff + + Here are some PC (old DOS) code pages, generated by this piece of + Bourne shell: + + for i in 437 850 866; do + echo charset CS_CP$i + gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP$i.TXT + echo + done + +charset CS_CP437 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c7 00fc 00e9 00e2 00e4 00e0 00e5 00e7 00ea 00eb 00e8 00ef 00ee 00ec 00c4 00c5 +00c9 00e6 00c6 00f4 00f6 00f2 00fb 00f9 00ff 00d6 00dc 00a2 00a3 00a5 20a7 0192 +00e1 00ed 00f3 00fa 00f1 00d1 00aa 00ba 00bf 2310 00ac 00bd 00bc 00a1 00ab 00bb +2591 2592 2593 2502 2524 2561 2562 2556 2555 2563 2551 2557 255d 255c 255b 2510 +2514 2534 252c 251c 2500 253c 255e 255f 255a 2554 2569 2566 2560 2550 256c 2567 +2568 2564 2565 2559 2558 2552 2553 256b 256a 2518 250c 2588 2584 258c 2590 2580 +03b1 00df 0393 03c0 03a3 03c3 00b5 03c4 03a6 0398 03a9 03b4 221e 03c6 03b5 2229 +2261 00b1 2265 2264 2320 2321 00f7 2248 00b0 2219 00b7 221a 207f 00b2 25a0 00a0 + +charset CS_CP850 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c7 00fc 00e9 00e2 00e4 00e0 00e5 00e7 00ea 00eb 00e8 00ef 00ee 00ec 00c4 00c5 +00c9 00e6 00c6 00f4 00f6 00f2 00fb 00f9 00ff 00d6 00dc 00f8 00a3 00d8 00d7 0192 +00e1 00ed 00f3 00fa 00f1 00d1 00aa 00ba 00bf 00ae 00ac 00bd 00bc 00a1 00ab 00bb +2591 2592 2593 2502 2524 00c1 00c2 00c0 00a9 2563 2551 2557 255d 00a2 00a5 2510 +2514 2534 252c 251c 2500 253c 00e3 00c3 255a 2554 2569 2566 2560 2550 256c 00a4 +00f0 00d0 00ca 00cb 00c8 0131 00cd 00ce 00cf 2518 250c 2588 2584 00a6 00cc 2580 +00d3 00df 00d4 00d2 00f5 00d5 00b5 00fe 00de 00da 00db 00d9 00fd 00dd 00af 00b4 +00ad 00b1 2017 00be 00b6 00a7 00f7 00b8 00b0 00a8 00b7 00b9 00b3 00b2 25a0 00a0 + +charset CS_CP866 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f +0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f +0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f +2591 2592 2593 2502 2524 2561 2562 2556 2555 2563 2551 2557 255d 255c 255b 2510 +2514 2534 252c 251c 2500 253c 255e 255f 255a 2554 2569 2566 2560 2550 256c 2567 +2568 2564 2565 2559 2558 2552 2553 256b 256a 2518 250c 2588 2584 258c 2590 2580 +0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 044f +0401 0451 0404 0454 0407 0457 040e 045e 00b0 2219 00b7 221a 2116 00a4 25a0 00a0 + + Here are some Windows code pages, generated by this piece of + Bourne shell: + + for i in 1250 1251 1252 1253 1254 1255 1256 1257 1258; do + echo charset CS_CP$i + gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP$i.TXT + echo + done + +charset CS_CP1250 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +20ac XXXX 201a XXXX 201e 2026 2020 2021 XXXX 2030 0160 2039 015a 0164 017d 0179 +XXXX 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 0161 203a 015b 0165 017e 017a +00a0 02c7 02d8 0141 00a4 0104 00a6 00a7 00a8 00a9 015e 00ab 00ac 00ad 00ae 017b +00b0 00b1 02db 0142 00b4 00b5 00b6 00b7 00b8 0105 015f 00bb 013d 02dd 013e 017c +0154 00c1 00c2 0102 00c4 0139 0106 00c7 010c 00c9 0118 00cb 011a 00cd 00ce 010e +0110 0143 0147 00d3 00d4 0150 00d6 00d7 0158 016e 00da 0170 00dc 00dd 0162 00df +0155 00e1 00e2 0103 00e4 013a 0107 00e7 010d 00e9 0119 00eb 011b 00ed 00ee 010f +0111 0144 0148 00f3 00f4 0151 00f6 00f7 0159 016f 00fa 0171 00fc 00fd 0163 02d9 + +charset CS_CP1251 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0402 0403 201a 0453 201e 2026 2020 2021 20ac 2030 0409 2039 040a 040c 040b 040f +0452 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 0459 203a 045a 045c 045b 045f +00a0 040e 045e 0408 00a4 0490 00a6 00a7 0401 00a9 0404 00ab 00ac 00ad 00ae 0407 +00b0 00b1 0406 0456 0491 00b5 00b6 00b7 0451 2116 0454 00bb 0458 0405 0455 0457 +0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f +0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f +0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f +0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 044f + +charset CS_CP1252 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 0160 2039 0152 XXXX 017d XXXX +XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 0161 203a 0153 XXXX 017e 0178 +00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af +00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf +00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf +00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df +00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef +00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff + +charset CS_CP1253 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +20ac XXXX 201a 0192 201e 2026 2020 2021 XXXX 2030 XXXX 2039 XXXX XXXX XXXX XXXX +XXXX 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 XXXX 203a XXXX XXXX XXXX XXXX +00a0 0385 0386 00a3 00a4 00a5 00a6 00a7 00a8 00a9 XXXX 00ab 00ac 00ad 00ae 2015 +00b0 00b1 00b2 00b3 0384 00b5 00b6 00b7 0388 0389 038a 00bb 038c 00bd 038e 038f +0390 0391 0392 0393 0394 0395 0396 0397 0398 0399 039a 039b 039c 039d 039e 039f +03a0 03a1 XXXX 03a3 03a4 03a5 03a6 03a7 03a8 03a9 03aa 03ab 03ac 03ad 03ae 03af +03b0 03b1 03b2 03b3 03b4 03b5 03b6 03b7 03b8 03b9 03ba 03bb 03bc 03bd 03be 03bf +03c0 03c1 03c2 03c3 03c4 03c5 03c6 03c7 03c8 03c9 03ca 03cb 03cc 03cd 03ce XXXX + +charset CS_CP1254 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 0160 2039 0152 XXXX XXXX XXXX +XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 0161 203a 0153 XXXX XXXX 0178 +00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af +00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf +00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf +011e 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 0130 015e 00df +00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef +011f 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 0131 015f 00ff + +charset CS_CP1255 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 XXXX 2039 XXXX XXXX XXXX XXXX +XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 XXXX 203a XXXX XXXX XXXX XXXX +00a0 00a1 00a2 00a3 20aa 00a5 00a6 00a7 00a8 00a9 00d7 00ab 00ac 00ad 00ae 00af +00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00f7 00bb 00bc 00bd 00be 00bf +05b0 05b1 05b2 05b3 05b4 05b5 05b6 05b7 05b8 05b9 XXXX 05bb 05bc 05bd 05be 05bf +05c0 05c1 05c2 05c3 05f0 05f1 05f2 05f3 05f4 XXXX XXXX XXXX XXXX XXXX XXXX XXXX +05d0 05d1 05d2 05d3 05d4 05d5 05d6 05d7 05d8 05d9 05da 05db 05dc 05dd 05de 05df +05e0 05e1 05e2 05e3 05e4 05e5 05e6 05e7 05e8 05e9 05ea XXXX XXXX 200e 200f XXXX + +charset CS_CP1256 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +20ac 067e 201a 0192 201e 2026 2020 2021 02c6 2030 0679 2039 0152 0686 0698 0688 +06af 2018 2019 201c 201d 2022 2013 2014 06a9 2122 0691 203a 0153 200c 200d 06ba +00a0 060c 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 06be 00ab 00ac 00ad 00ae 00af +00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 061b 00bb 00bc 00bd 00be 061f +06c1 0621 0622 0623 0624 0625 0626 0627 0628 0629 062a 062b 062c 062d 062e 062f +0630 0631 0632 0633 0634 0635 0636 00d7 0637 0638 0639 063a 0640 0641 0642 0643 +00e0 0644 00e2 0645 0646 0647 0648 00e7 00e8 00e9 00ea 00eb 0649 064a 00ee 00ef +064b 064c 064d 064e 00f4 064f 0650 00f7 0651 00f9 0652 00fb 00fc 200e 200f 06d2 + +charset CS_CP1257 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +20ac XXXX 201a XXXX 201e 2026 2020 2021 XXXX 2030 XXXX 2039 XXXX 00a8 02c7 00b8 +XXXX 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 XXXX 203a XXXX 00af 02db XXXX +00a0 XXXX 00a2 00a3 00a4 XXXX 00a6 00a7 00d8 00a9 0156 00ab 00ac 00ad 00ae 00c6 +00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00f8 00b9 0157 00bb 00bc 00bd 00be 00e6 +0104 012e 0100 0106 00c4 00c5 0118 0112 010c 00c9 0179 0116 0122 0136 012a 013b +0160 0143 0145 00d3 014c 00d5 00d6 00d7 0172 0141 015a 016a 00dc 017b 017d 00df +0105 012f 0101 0107 00e4 00e5 0119 0113 010d 00e9 017a 0117 0123 0137 012b 013c +0161 0144 0146 00f3 014d 00f5 00f6 00f7 0173 0142 015b 016b 00fc 017c 017e 02d9 + +charset CS_CP1258 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 XXXX 2039 0152 XXXX XXXX XXXX +XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 XXXX 203a 0153 XXXX XXXX 0178 +00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af +00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf +00c0 00c1 00c2 0102 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 0300 00cd 00ce 00cf +0110 00d1 0309 00d3 00d4 01a0 00d6 00d7 00d8 00d9 00da 00db 00dc 01af 0303 00df +00e0 00e1 00e2 0103 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 0301 00ed 00ee 00ef +0111 00f1 0323 00f3 00f4 01a1 00f6 00f7 00f8 00f9 00fa 00fb 00fc 01b0 20ab 00ff + + KOI8-R, generated by this code: + + { echo charset CS_KOI8_R; + gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/MISC/KOI8-R.TXT; } + +charset CS_KOI8_R +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +2500 2502 250c 2510 2514 2518 251c 2524 252c 2534 253c 2580 2584 2588 258c 2590 +2591 2592 2593 2320 25a0 2219 221a 2248 2264 2265 00a0 2321 00b0 00b2 00b7 00f7 +2550 2551 2552 0451 2553 2554 2555 2556 2557 2558 2559 255a 255b 255c 255d 255e +255f 2560 2561 0401 2562 2563 2564 2565 2566 2567 2568 2569 256a 256b 256c 00a9 +044e 0430 0431 0446 0434 0435 0444 0433 0445 0438 0439 043a 043b 043c 043d 043e +043f 044f 0440 0441 0442 0443 0436 0432 044c 044b 0437 0448 044d 0449 0447 044a +042e 0410 0411 0426 0414 0415 0424 0413 0425 0418 0419 041a 041b 041c 041d 041e +041f 042f 0420 0421 0422 0423 0416 0412 042c 042b 0417 0428 042d 0429 0427 042a + + KOI8-U: I can't find an easily machine-processable mapping table + for this one, so I've created it by hand-editing the KOI8-R + mapping table in accordance with the list of differences specified + in RFC2319. Note that RFC2319 has an apparent error: position B4 + is listed as U+0404 in the main character set list, but as U+0403 + in Appendix A (differences from KOI8-R). Both agree that it should + be CYRILLIC CAPITAL LETTER UKRAINIAN IE, however, and the Unicode + character database says that therefore U+0404 is the correct value. + +charset CS_KOI8_U +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +2500 2502 250c 2510 2514 2518 251c 2524 252c 2534 253c 2580 2584 2588 258c 2590 +2591 2592 2593 2320 25a0 2219 221a 2248 2264 2265 00a0 2321 00b0 00b2 00b7 00f7 +2550 2551 2552 0451 0454 2554 0456 0457 2557 2558 2559 255a 255b 0491 255d 255e +255f 2560 2561 0401 0404 2563 0406 0407 2566 2567 2568 2569 256a 0490 256c 00a9 +044e 0430 0431 0446 0434 0435 0444 0433 0445 0438 0439 043a 043b 043c 043d 043e +043f 044f 0440 0441 0442 0443 0436 0432 044c 044b 0437 0448 044d 0449 0447 044a +042e 0410 0411 0426 0414 0415 0424 0413 0425 0418 0419 041a 041b 041c 041d 041e +041f 042f 0420 0421 0422 0423 0416 0412 042c 042b 0417 0428 042d 0429 0427 042a + + Various Mac character sets, generated by: + + for i in ROMAN TURKISH CROATIAN ICELAND ROMANIAN GREEK CYRILLIC THAI \ + CENTEURO SYMBOL DINGBATS; do + echo charset CS_MAC_$i + gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/$i.TXT | \ + sed s/f8a0/XXXX/ + echo + done + + The code point F8FF at position F0 in Mac OS Roman an interesting + one. In Unicode, it's the last of the Private Use section. The + mapping table states that it should be an Apple logo. I suppose we + should just leave it as it is; there's bound to be some software out + there that understands U+F8FF to be an Apple logo! + + Code point F8A0 at position F5 in Mac OS Turkish is actually just an + undefined character, so we make it properly undefined. + + Many of the positions 80-9F in Mac OS Thai are for presentation + forms of other characters. When converting from Unicode, we use + `sortpriority' to avoid them. + + Positions E2-E4 in Mac OS Symbol are for sans-serif variants of + other characters. Similarly, we avoid them. + +charset CS_MAC_ROMAN +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 +00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc +2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 +221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8 +00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 +2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 20ac 2039 203a fb01 fb02 +2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 +f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 + +charset CS_MAC_TURKISH +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 +00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc +2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 +221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8 +00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 +2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 011e 011f 0130 0131 015e 015f +2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 +f8ff 00d2 00da 00db 00d9 XXXX 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 + +charset CS_MAC_CROATIAN +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 +00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc +2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 0160 2122 00b4 00a8 2260 017d 00d8 +221e 00b1 2264 2265 2206 00b5 2202 2211 220f 0161 222b 00aa 00ba 03a9 017e 00f8 +00bf 00a1 00ac 221a 0192 2248 0106 00ab 010c 2026 00a0 00c0 00c3 00d5 0152 0153 +0110 2014 201c 201d 2018 2019 00f7 25ca f8ff 00a9 2044 20ac 2039 203a 00c6 00bb +2013 00b7 201a 201e 2030 00c2 0107 00c1 010d 00c8 00cd 00ce 00cf 00cc 00d3 00d4 +0111 00d2 00da 00db 00d9 0131 02c6 02dc 00af 03c0 00cb 02da 00b8 00ca 00e6 02c7 + +charset CS_MAC_ICELAND +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 +00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc +00dd 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 +221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8 +00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 +2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 20ac 00d0 00f0 00de 00fe +00fd 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 +f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 + +charset CS_MAC_ROMANIAN +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 +00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc +2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 0102 0218 +221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 0103 0219 +00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 +2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 20ac 2039 203a 021a 021b +2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 +f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 + +charset CS_MAC_GREEK +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 00b9 00b2 00c9 00b3 00d6 00dc 0385 00e0 00e2 00e4 0384 00a8 00e7 00e9 00e8 +00ea 00eb 00a3 2122 00ee 00ef 2022 00bd 2030 00f4 00f6 00a6 20ac 00f9 00fb 00fc +2020 0393 0394 0398 039b 039e 03a0 00df 00ae 00a9 03a3 03aa 00a7 2260 00b0 00b7 +0391 00b1 2264 2265 00a5 0392 0395 0396 0397 0399 039a 039c 03a6 03ab 03a8 03a9 +03ac 039d 00ac 039f 03a1 2248 03a4 00ab 00bb 2026 00a0 03a5 03a7 0386 0388 0153 +2013 2015 201c 201d 2018 2019 00f7 0389 038a 038c 038e 03ad 03ae 03af 03cc 038f +03cd 03b1 03b2 03c8 03b4 03b5 03c6 03b3 03b7 03b9 03be 03ba 03bb 03bc 03bd 03bf +03c0 03ce 03c1 03c3 03c4 03b8 03c9 03c2 03c7 03c5 03b6 03ca 03cb 0390 03b0 00ad + +charset CS_MAC_CYRILLIC +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f +0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f +2020 00b0 0490 00a3 00a7 2022 00b6 0406 00ae 00a9 2122 0402 0452 2260 0403 0453 +221e 00b1 2264 2265 0456 00b5 0491 0408 0404 0454 0407 0457 0409 0459 040a 045a +0458 0405 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 040b 045b 040c 045c 0455 +2013 2014 201c 201d 2018 2019 00f7 201e 040e 045e 040f 045f 2116 0401 0451 044f +0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f +0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 20ac + +charset CS_MAC_THAI +sortpriority 83-8C -1 +sortpriority 8F-8F -1 +sortpriority 92-9C -1 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00ab 00bb 2026 0e48 0e49 0e4a 0e4b 0e4c 0e48 0e49 0e4a 0e4b 0e4c 201c 201d 0e4d +XXXX 2022 0e31 0e47 0e34 0e35 0e36 0e37 0e48 0e49 0e4a 0e4b 0e4c 2018 2019 XXXX +00a0 0e01 0e02 0e03 0e04 0e05 0e06 0e07 0e08 0e09 0e0a 0e0b 0e0c 0e0d 0e0e 0e0f +0e10 0e11 0e12 0e13 0e14 0e15 0e16 0e17 0e18 0e19 0e1a 0e1b 0e1c 0e1d 0e1e 0e1f +0e20 0e21 0e22 0e23 0e24 0e25 0e26 0e27 0e28 0e29 0e2a 0e2b 0e2c 0e2d 0e2e 0e2f +0e30 0e31 0e32 0e33 0e34 0e35 0e36 0e37 0e38 0e39 0e3a 2060 200b 2013 2014 0e3f +0e40 0e41 0e42 0e43 0e44 0e45 0e46 0e47 0e48 0e49 0e4a 0e4b 0e4c 0e4d 2122 0e4f +0e50 0e51 0e52 0e53 0e54 0e55 0e56 0e57 0e58 0e59 00ae 00a9 XXXX XXXX XXXX XXXX + +charset CS_MAC_CENTEURO +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 0100 0101 00c9 0104 00d6 00dc 00e1 0105 010c 00e4 010d 0106 0107 00e9 0179 +017a 010e 00ed 010f 0112 0113 0116 00f3 0117 00f4 00f6 00f5 00fa 011a 011b 00fc +2020 00b0 0118 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 0119 00a8 2260 0123 012e +012f 012a 2264 2265 012b 0136 2202 2211 0142 013b 013c 013d 013e 0139 013a 0145 +0146 0143 00ac 221a 0144 0147 2206 00ab 00bb 2026 00a0 0148 0150 00d5 0151 014c +2013 2014 201c 201d 2018 2019 00f7 25ca 014d 0154 0155 0158 2039 203a 0159 0156 +0157 0160 201a 201e 0161 015a 015b 00c1 0164 0165 00cd 017d 017e 016a 00d3 00d4 +016b 016e 00da 016f 0170 0171 0172 0173 00dd 00fd 0137 017b 0141 017c 0122 02c7 + +charset CS_MAC_SYMBOL +sortpriority E2-E4 -1 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 2200 0023 2203 0025 0026 220d 0028 0029 2217 002b 002c 2212 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +2245 0391 0392 03a7 0394 0395 03a6 0393 0397 0399 03d1 039a 039b 039c 039d 039f +03a0 0398 03a1 03a3 03a4 03a5 03c2 03a9 039e 03a8 0396 005b 2234 005d 22a5 005f +f8e5 03b1 03b2 03c7 03b4 03b5 03c6 03b3 03b7 03b9 03d5 03ba 03bb 03bc 03bd 03bf +03c0 03b8 03c1 03c3 03c4 03c5 03d6 03c9 03be 03c8 03b6 007b 007c 007d 223c 007f +XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX +XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX +20ac 03d2 2032 2264 2044 221e 0192 2663 2666 2665 2660 2194 2190 2191 2192 2193 +00b0 00b1 2033 2265 00d7 221d 2202 2022 00f7 2260 2261 2248 2026 f8e6 23af 21b5 +2135 2111 211c 2118 2297 2295 2205 2229 222a 2283 2287 2284 2282 2286 2208 2209 +2220 2207 00ae 00a9 2122 220f 221a 22c5 00ac 2227 2228 21d4 21d0 21d1 21d2 21d3 +22c4 3008 00ae 00a9 2122 2211 239b 239c 239d 23a1 23a2 23a3 23a7 23a8 23a9 23aa +f8ff 3009 222b 2320 23ae 2321 239e 239f 23a0 23a4 23a5 23a6 23ab 23ac 23ad XXXX + +charset CS_MAC_DINGBATS +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 2701 2702 2703 2704 260e 2706 2707 2708 2709 261b 261e 270c 270d 270e 270f +2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 271a 271b 271c 271d 271e 271f +2720 2721 2722 2723 2724 2725 2726 2727 2605 2729 272a 272b 272c 272d 272e 272f +2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 273a 273b 273c 273d 273e 273f +2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 274a 274b 25cf 274d 25a0 274f +2750 2751 2752 25b2 25bc 25c6 2756 25d7 2758 2759 275a 275b 275c 275d 275e 007f +2768 2769 276a 276b 276c 276d 276e 276f 2770 2771 2772 2773 2774 2775 XXXX XXXX +XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX +XXXX 2761 2762 2763 2764 2765 2766 2767 2663 2666 2665 2660 2460 2461 2462 2463 +2464 2465 2466 2467 2468 2469 2776 2777 2778 2779 277a 277b 277c 277d 277e 277f +2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 278a 278b 278c 278d 278e 278f +2790 2791 2792 2793 2794 2192 2194 2195 2798 2799 279a 279b 279c 279d 279e 279f +27a0 27a1 27a2 27a3 27a4 27a5 27a6 27a7 27a8 27a9 27aa 27ab 27ac 27ad 27ae 27af +XXXX 27b1 27b2 27b3 27b4 27b5 27b6 27b7 27b8 27b9 27ba 27bb 27bc 27bd 27be XXXX + + Various Mac character sets have older (usually pre-Euro) variants + which are documented in the comments in their mapping tables. I've + manually applied these changes below. + + Mac OS Roman variants before Mac OS 8.5 (CURRENCY SIGN rather than + EURO SIGN): + +charset CS_MAC_ROMAN_OLD +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 +00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc +2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 +221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8 +00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 +2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 00a4 2039 203a fb01 fb02 +2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 +f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 + +charset CS_MAC_CROATIAN_OLD +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 +00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc +2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 0160 2122 00b4 00a8 2260 017d 00d8 +221e 00b1 2264 2265 2206 00b5 2202 2211 220f 0161 222b 00aa 00ba 03a9 017e 00f8 +00bf 00a1 00ac 221a 0192 2248 0106 00ab 010c 2026 00a0 00c0 00c3 00d5 0152 0153 +0110 2014 201c 201d 2018 2019 00f7 25ca f8ff 00a9 2044 00a4 2039 203a 00c6 00bb +2013 00b7 201a 201e 2030 00c2 0107 00c1 010d 00c8 00cd 00ce 00cf 00cc 00d3 00d4 +0111 00d2 00da 00db 00d9 0131 02c6 02dc 00af 03c0 00cb 02da 00b8 00ca 00e6 02c7 + +charset CS_MAC_ICELAND_OLD +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 +00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc +00dd 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 +221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8 +00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 +2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 00a4 00d0 00f0 00de 00fe +00fd 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 +f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 + +charset CS_MAC_ROMANIAN_OLD +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 +00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc +2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 0102 0218 +221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 0103 0219 +00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 +2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 00a4 2039 203a 021a 021b +2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 +f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 + + Mac OS Greek before Mac OS 9.2.2 (SOFT HYPHEN instead of EURO SIGN, + and undefined instead of SOFT HYPHEN). + +charset CS_MAC_GREEK_OLD +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +00c4 00b9 00b2 00c9 00b3 00d6 00dc 0385 00e0 00e2 00e4 0384 00a8 00e7 00e9 00e8 +00ea 00eb 00a3 2122 00ee 00ef 2022 00bd 2030 00f4 00f6 00a6 00ad 00f9 00fb 00fc +2020 0393 0394 0398 039b 039e 03a0 00df 00ae 00a9 03a3 03aa 00a7 2260 00b0 00b7 +0391 00b1 2264 2265 00a5 0392 0395 0396 0397 0399 039a 039c 03a6 03ab 03a8 03a9 +03ac 039d 00ac 039f 03a1 2248 03a4 00ab 00bb 2026 00a0 03a5 03a7 0386 0388 0153 +2013 2015 201c 201d 2018 2019 00f7 0389 038a 038c 038e 03ad 03ae 03af 03cc 038f +03cd 03b1 03b2 03c8 03b4 03b5 03c6 03b3 03b7 03b9 03be 03ba 03bb 03bc 03bd 03bf +03c0 03ce 03c1 03c3 03c4 03b8 03c9 03c2 03c7 03c5 03b6 03ca 03cb 0390 03b0 XXXX + + Mac OS Cyrillic before Mac OS 9.0 (CENT SIGN instead of CYRILLIC + CAPITAL LETTER GHE WITH UPTURN, PARTIAL DIFFERENTIAL instead of + CYRILLIC SMALL LETTER GHE WITH UPTURN, CURRENCY SIGN instead of EURO + SIGN): + +charset CS_MAC_CYRILLIC_OLD +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f +0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f +2020 00b0 00a2 00a3 00a7 2022 00b6 0406 00ae 00a9 2122 0402 0452 2260 0403 0453 +221e 00b1 2264 2265 0456 00b5 2022 0408 0404 0454 0407 0457 0409 0459 040a 045a +0458 0405 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 040b 045b 040c 045c 0455 +2013 2014 201c 201d 2018 2019 00f7 201e 040e 045e 040f 045f 2116 0401 0451 044f +0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f +0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 00a4 + + Mac OS Ukrainian (now Cyrillic) before Mac OS 9.0 (CURRENCY SIGN + instead of EURO SIGN): + +charset CS_MAC_UKRAINE +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f +0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f +2020 00b0 0490 00a3 00a7 2022 00b6 0406 00ae 00a9 2122 0402 0452 2260 0403 0453 +221e 00b1 2264 2265 0456 00b5 0491 0408 0404 0454 0407 0457 0409 0459 040a 045a +0458 0405 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 040b 045b 040c 045c 0455 +2013 2014 201c 201d 2018 2019 00f7 201e 040e 045e 040f 045f 2116 0401 0451 044f +0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f +0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 00a4 + + Mac OS VT100 character set, as used by the "VT100" font. Basically + Mac OS Roman hacked about to give it an almost-Latin1 repertoire and + most of the VT100 line-drawing set too. + + Point CA is the backward question-mark used for silo overflows. + + This table was derived by pasting the relevant part of 'utom' 140 + from the "Western Language Encodings" file shipped with TEC 1.5 and + then manually fixing up the scan line characters to use the Unicode + 3.2 HORIZONTAL SCAN LINE characters rather than UPPER ONE EIGHTH + BLOCK and LOWER ONE EIGHTH BLOCK with transcoding hints. + +charset CS_MAC_VT100 +2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 240a 240b 240c 240d 240e 240f +2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 241a 241b 241c 241d 241e 241f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 2421 +00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 +00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc +00dd 00b0 00a2 00a3 00a7 00b8 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 +00d7 00b1 2264 2265 00a5 00b5 00b9 00b2 00b3 03c0 00a6 00aa 00ba 2592 00e6 00f8 +00bf 00a1 00ac 00bd 0192 00bc 00be 00ab 00bb 2026 XXXX 00c0 00c3 00d5 0152 0153 +2013 2014 2518 2510 250c 2514 00f7 2022 00ff 0178 253c 20ac 00d0 00f0 00fe 00de +00fd 00b7 23ba 23bb 2500 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 +XXXX 00d2 00da 00db 00d9 23bc 23bd 251c 2524 2534 252c 2502 XXXX XXXX XXXX XXXX + + As with so many others, before Mac OS 8.5 this font had CURRENCY + SIGN rather than EURO SIGN. + +charset CS_MAC_VT100_OLD +2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 240a 240b 240c 240d 240e 240f +2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 241a 241b 241c 241d 241e 241f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 2421 +00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 +00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc +00dd 00b0 00a2 00a3 00a7 00b8 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 +00d7 00b1 2264 2265 00a5 00b5 00b9 00b2 00b3 03c0 00a6 00aa 00ba 2592 00e6 00f8 +00bf 00a1 00ac 00bd 0192 00bc 00be 00ab 00bb 2026 XXXX 00c0 00c3 00d5 0152 0153 +2013 2014 2518 2510 250c 2514 00f7 2022 00ff 0178 253c 00a4 00d0 00f0 00fe 00de +00fd 00b7 23ba 23bb 2500 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 +XXXX 00d2 00da 00db 00d9 23bc 23bd 251c 2524 2534 252c 2502 XXXX XXXX XXXX XXXX + + Roman Czyborra's web site (http://czyborra.com/) has a variety of + other useful mapping tables, in a slightly different format (and + gzipped). Here's a shell/Perl function to generate an SBCS table + from a Czyborra mapping table: + + gensbcs_c() { + wget -q -O - "$1" | gzip -d | \ + perl -ne '/^=(.*)\s+U\+(.*)\s+/ and $a[hex $1]=sprintf "%04x", hex $2;' \ + -e 'BEGIN{for($i=0;$i<256;$i++){$a[$i]="XXXX";' \ + -e 'if ($i < 32 or ($i >=127 and $i < 160)) {$a[$i]=sprintf "%04x", $i}}}' \ + -e 'END{for($i=0;$i<256;$i++){printf"%s%s",$a[$i],$i%16==15?"\n":" "}}' + } + + So here we have some character sets generated from Czyborra + mapping tables: VISCII, HP-Roman8, and the DEC Multinational + Character Set. + + { echo charset CS_VISCII; + gensbcs_c http://czyborra.com/charsets/viscii.txt.gz; echo; + echo charset CS_HP_ROMAN8; + gensbcs_c http://czyborra.com/charsets/hp-roman8.txt.gz; echo; + echo charset CS_DEC_MCS; + gensbcs_c http://czyborra.com/charsets/dec-mcs.txt.gz; echo; } + +charset CS_VISCII +0000 0001 1eb2 0003 0004 1eb4 1eaa 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 1ef6 0015 0016 0017 0018 1ef8 001a 001b 001c 001d 1ef4 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +1ea0 1eae 1eb0 1eb6 1ea4 1ea6 1ea8 1eac 1ebc 1eb8 1ebe 1ec0 1ec2 1ec4 1ec6 1ed0 +1ed2 1ed4 1ed6 1ed8 1ee2 1eda 1edc 1ede 1eca 1ece 1ecc 1ec8 1ee6 0168 1ee4 1ef2 +00d5 1eaf 1eb1 1eb7 1ea5 1ea7 1ea8 1ead 1ebd 1eb9 1ebf 1ec1 1ec3 1ec5 1ec7 1ed1 +1ed3 1ed5 1ed7 1ee0 01a0 1ed9 1edd 1edf 1ecb 1ef0 1ee8 1eea 1eec 01a1 1edb 01af +00c0 00c1 00c2 00c3 1ea2 0102 1eb3 1eb5 00c8 00c9 00ca 1eba 00cc 00cd 0128 1ef3 +0110 1ee9 00d2 00d3 00d4 1ea1 1ef7 1eeb 1eed 00d9 00da 1ef9 1ef5 00dd 1ee1 01b0 +00e0 00e1 00e2 00e3 1ea3 0103 1eef 1eab 00e8 00e9 00ea 1ebb 00ec 00ed 0129 1ec9 +0111 1ef1 00f2 00f3 00f4 00f5 1ecf 1ecd 1ee5 00f9 00fa 0169 1ee7 00fd 1ee3 1eee + +charset CS_HP_ROMAN8 +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +00a0 00c0 00c2 00c8 00ca 00cb 00ce 00cf 00b4 02cb 02c6 00a8 02dc 00d9 00db 20a4 +00af 00dd 00fd 00b0 00c7 00e7 00d1 00f1 00a1 00bf 00a4 00a3 00a5 00a7 0192 00a2 +00e2 00ea 00f4 00fb 00e1 00e9 00f3 00fa 00e0 00e8 00f2 00f9 00e4 00eb 00f6 00fc +00c5 00ee 00d8 00c6 00e5 00ed 00f8 00e6 00c4 00ec 00d6 00dc 00c9 00ef 00df 00d4 +00c1 00c3 00e3 00d0 00f0 00cd 00cc 00d3 00d2 00d5 00f5 0160 0161 00da 0178 00ff +00de 00fe 00b7 00b5 00b6 00be 2014 00bc 00bd 00aa 00ba 00ab 25a0 00bb 00b1 XXXX + +charset CS_DEC_MCS +0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f +0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f +0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f +0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f +0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f +0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f +0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f +0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f +0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f +0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f +XXXX 00a1 00a2 00a3 XXXX 00a5 XXXX 00a7 00a4 00a9 00aa 00ab XXXX XXXX XXXX XXXX +00b0 00b1 00b2 00b3 XXXX 00b5 00b6 00b7 XXXX 00b9 00ba 00bb 00bc 00bd XXXX 00bf +00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf +XXXX 00d1 00d2 00d3 00d4 00d5 00d6 0152 00d8 00d9 00da 00db 00dc 0178 XXXX 00df +00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef +XXXX 00f1 00f2 00f3 00f4 00f5 00f6 0153 00f8 00f9 00fa 00fb 00fc 00ff XXXX XXXX diff --git a/putty/CHARSET/SBCSDAT.C b/putty/CHARSET/SBCSDAT.C new file mode 100644 index 0000000..664bcd5 --- /dev/null +++ b/putty/CHARSET/SBCSDAT.C @@ -0,0 +1,4018 @@ +/* + * sbcsdat.c - data definitions for single-byte character sets. + * + * Generated by sbcsgen.pl from sbcs.dat. + * You should edit those files rather than editing this one. + */ + +#ifndef ENUM_CHARSETS + +#include "charset.h" +#include "internal.h" + +static const sbcs_data data_CS_ISO8859_1 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }, + 256 +}; +const charset_spec charset_CS_ISO8859_1 = { + CS_ISO8859_1, read_sbcs, write_sbcs, &data_CS_ISO8859_1 +}; + +static const sbcs_data data_CS_ISO8859_2 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x02d8, 0x0141, 0x00a4, 0x013d, 0x015a, 0x00a7, + 0x00a8, 0x0160, 0x015e, 0x0164, 0x0179, 0x00ad, 0x017d, 0x017b, + 0x00b0, 0x0105, 0x02db, 0x0142, 0x00b4, 0x013e, 0x015b, 0x02c7, + 0x00b8, 0x0161, 0x015f, 0x0165, 0x017a, 0x02dd, 0x017e, 0x017c, + 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, + 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, + 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, + 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, + 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, + 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa4, 0xa7, 0xa8, 0xad, 0xb0, 0xb4, 0xb8, + 0xc1, 0xc2, 0xc4, 0xc7, 0xc9, 0xcb, 0xcd, 0xce, + 0xd3, 0xd4, 0xd6, 0xd7, 0xda, 0xdc, 0xdd, 0xdf, + 0xe1, 0xe2, 0xe4, 0xe7, 0xe9, 0xeb, 0xed, 0xee, + 0xf3, 0xf4, 0xf6, 0xf7, 0xfa, 0xfc, 0xfd, 0xc3, + 0xe3, 0xa1, 0xb1, 0xc6, 0xe6, 0xc8, 0xe8, 0xcf, + 0xef, 0xd0, 0xf0, 0xca, 0xea, 0xcc, 0xec, 0xc5, + 0xe5, 0xa5, 0xb5, 0xa3, 0xb3, 0xd1, 0xf1, 0xd2, + 0xf2, 0xd5, 0xf5, 0xc0, 0xe0, 0xd8, 0xf8, 0xa6, + 0xb6, 0xaa, 0xba, 0xa9, 0xb9, 0xde, 0xfe, 0xab, + 0xbb, 0xd9, 0xf9, 0xdb, 0xfb, 0xac, 0xbc, 0xaf, + 0xbf, 0xae, 0xbe, 0xb7, 0xa2, 0xff, 0xb2, 0xbd + }, + 256 +}; +const charset_spec charset_CS_ISO8859_2 = { + CS_ISO8859_2, read_sbcs, write_sbcs, &data_CS_ISO8859_2 +}; + +static const sbcs_data data_CS_ISO8859_3 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0126, 0x02d8, 0x00a3, 0x00a4, ERROR , 0x0124, 0x00a7, + 0x00a8, 0x0130, 0x015e, 0x011e, 0x0134, 0x00ad, ERROR , 0x017b, + 0x00b0, 0x0127, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x0125, 0x00b7, + 0x00b8, 0x0131, 0x015f, 0x011f, 0x0135, 0x00bd, ERROR , 0x017c, + 0x00c0, 0x00c1, 0x00c2, ERROR , 0x00c4, 0x010a, 0x0108, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + ERROR , 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x0120, 0x00d6, 0x00d7, + 0x011c, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x016c, 0x015c, 0x00df, + 0x00e0, 0x00e1, 0x00e2, ERROR , 0x00e4, 0x010b, 0x0109, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + ERROR , 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x0121, 0x00f6, 0x00f7, + 0x011d, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x016d, 0x015d, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xb0, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb7, 0xb8, 0xbd, 0xc0, 0xc1, + 0xc2, 0xc4, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, + 0xcd, 0xce, 0xcf, 0xd1, 0xd2, 0xd3, 0xd4, 0xd6, + 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdf, 0xe0, 0xe1, + 0xe2, 0xe4, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, 0xf4, 0xf6, + 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xc6, 0xe6, 0xc5, + 0xe5, 0xd8, 0xf8, 0xab, 0xbb, 0xd5, 0xf5, 0xa6, + 0xb6, 0xa1, 0xb1, 0xa9, 0xb9, 0xac, 0xbc, 0xde, + 0xfe, 0xaa, 0xba, 0xdd, 0xfd, 0xaf, 0xbf, 0xa2, + 0xff + }, + 249 +}; +const charset_spec charset_CS_ISO8859_3 = { + CS_ISO8859_3, read_sbcs, write_sbcs, &data_CS_ISO8859_3 +}; + +static const sbcs_data data_CS_ISO8859_4 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x0138, 0x0156, 0x00a4, 0x0128, 0x013b, 0x00a7, + 0x00a8, 0x0160, 0x0112, 0x0122, 0x0166, 0x00ad, 0x017d, 0x00af, + 0x00b0, 0x0105, 0x02db, 0x0157, 0x00b4, 0x0129, 0x013c, 0x02c7, + 0x00b8, 0x0161, 0x0113, 0x0123, 0x0167, 0x014a, 0x017e, 0x014b, + 0x0100, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x012e, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x0116, 0x00cd, 0x00ce, 0x012a, + 0x0110, 0x0145, 0x014c, 0x0136, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x0172, 0x00da, 0x00db, 0x00dc, 0x0168, 0x016a, 0x00df, + 0x0101, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x012f, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x0117, 0x00ed, 0x00ee, 0x012b, + 0x0111, 0x0146, 0x014d, 0x0137, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x0173, 0x00fa, 0x00fb, 0x00fc, 0x0169, 0x016b, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa4, 0xa7, 0xa8, 0xad, 0xaf, 0xb0, 0xb4, + 0xb8, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc9, + 0xcb, 0xcd, 0xce, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xda, 0xdb, 0xdc, 0xdf, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe9, 0xeb, 0xed, 0xee, 0xf4, 0xf5, + 0xf6, 0xf7, 0xf8, 0xfa, 0xfb, 0xfc, 0xc0, 0xe0, + 0xa1, 0xb1, 0xc8, 0xe8, 0xd0, 0xf0, 0xaa, 0xba, + 0xcc, 0xec, 0xca, 0xea, 0xab, 0xbb, 0xa5, 0xb5, + 0xcf, 0xef, 0xc7, 0xe7, 0xd3, 0xf3, 0xa2, 0xa6, + 0xb6, 0xd1, 0xf1, 0xbd, 0xbf, 0xd2, 0xf2, 0xa3, + 0xb3, 0xa9, 0xb9, 0xac, 0xbc, 0xdd, 0xfd, 0xde, + 0xfe, 0xd9, 0xf9, 0xae, 0xbe, 0xb7, 0xff, 0xb2 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_4 = { + CS_ISO8859_4, read_sbcs, write_sbcs, &data_CS_ISO8859_4 +}; + +static const sbcs_data data_CS_ISO8859_5 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, + 0x0408, 0x0409, 0x040a, 0x040b, 0x040c, 0x00ad, 0x040e, 0x040f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, + 0x2116, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457, + 0x0458, 0x0459, 0x045a, 0x045b, 0x045c, 0x00a7, 0x045e, 0x045f + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xfd, 0xad, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xae, + 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, + 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, + 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, + 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, + 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, + 0xef, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfe, 0xff, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_5 = { + CS_ISO8859_5, read_sbcs, write_sbcs, &data_CS_ISO8859_5 +}; + +static const sbcs_data data_CS_ISO8859_6 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, ERROR , ERROR , ERROR , 0x00a4, ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , 0x060c, 0x00ad, ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , 0x061b, ERROR , ERROR , ERROR , 0x061f, + ERROR , 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062a, 0x062b, 0x062c, 0x062d, 0x062e, 0x062f, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, + 0x0638, 0x0639, 0x063a, ERROR , ERROR , ERROR , ERROR , ERROR , + 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, + 0x0648, 0x0649, 0x064a, 0x064b, 0x064c, 0x064d, 0x064e, 0x064f, + 0x0650, 0x0651, 0x0652, ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa4, 0xad, 0xac, 0xbb, 0xbf, 0xc1, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2 + }, + 211 +}; +const charset_spec charset_CS_ISO8859_6 = { + CS_ISO8859_6, read_sbcs, write_sbcs, &data_CS_ISO8859_6 +}; + +static const sbcs_data data_CS_ISO8859_7 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x2018, 0x2019, 0x00a3, ERROR , ERROR , 0x00a6, 0x00a7, + 0x00a8, 0x00a9, ERROR , 0x00ab, 0x00ac, 0x00ad, ERROR , 0x2015, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x0384, 0x0385, 0x0386, 0x00b7, + 0x0388, 0x0389, 0x038a, 0x00bb, 0x038c, 0x00bd, 0x038e, 0x038f, + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, + 0x03a0, 0x03a1, ERROR , 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, + 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03ae, 0x03af, + 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, + 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, + 0x03c8, 0x03c9, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03ce, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa3, 0xa6, 0xa7, 0xa8, 0xa9, 0xab, 0xac, + 0xad, 0xb0, 0xb1, 0xb2, 0xb3, 0xb7, 0xbb, 0xbd, + 0xb4, 0xb5, 0xb6, 0xb8, 0xb9, 0xba, 0xbc, 0xbe, + 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xaf, + 0xa1, 0xa2 + }, + 250 +}; +const charset_spec charset_CS_ISO8859_7 = { + CS_ISO8859_7, read_sbcs, write_sbcs, &data_CS_ISO8859_7 +}; + +static const sbcs_data data_CS_ISO8859_8 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, ERROR , 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00d7, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00f7, 0x00bb, 0x00bc, 0x00bd, 0x00be, ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , 0x2017, + 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0x05d7, + 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de, 0x05df, + 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6, 0x05e7, + 0x05e8, 0x05e9, 0x05ea, ERROR , ERROR , 0x200e, 0x200f, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xbb, 0xbc, 0xbd, 0xbe, 0xaa, 0xba, 0xe0, 0xe1, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfd, 0xfe, 0xdf + }, + 220 +}; +const charset_spec charset_CS_ISO8859_8 = { + CS_ISO8859_8, read_sbcs, write_sbcs, &data_CS_ISO8859_8 +}; + +static const sbcs_data data_CS_ISO8859_9 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x011e, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0130, 0x015e, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x011f, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x0131, 0x015f, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xdb, 0xdc, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xff, 0xd0, 0xf0, 0xdd, 0xfd, 0xde, 0xfe + }, + 256 +}; +const charset_spec charset_CS_ISO8859_9 = { + CS_ISO8859_9, read_sbcs, write_sbcs, &data_CS_ISO8859_9 +}; + +static const sbcs_data data_CS_ISO8859_10 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x0112, 0x0122, 0x012a, 0x0128, 0x0136, 0x00a7, + 0x013b, 0x0110, 0x0160, 0x0166, 0x017d, 0x00ad, 0x016a, 0x014a, + 0x00b0, 0x0105, 0x0113, 0x0123, 0x012b, 0x0129, 0x0137, 0x00b7, + 0x013c, 0x0111, 0x0161, 0x0167, 0x017e, 0x2015, 0x016b, 0x014b, + 0x0100, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x012e, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x0116, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x0145, 0x014c, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x0168, + 0x00d8, 0x0172, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x0101, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x012f, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x0117, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x0146, 0x014d, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x0169, + 0x00f8, 0x0173, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x0138 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa7, 0xad, 0xb0, 0xb7, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc9, 0xcb, 0xcd, 0xce, 0xcf, + 0xd0, 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xda, 0xdb, + 0xdc, 0xdd, 0xde, 0xdf, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe9, 0xeb, 0xed, 0xee, 0xef, 0xf0, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf8, 0xfa, 0xfb, 0xfc, + 0xfd, 0xfe, 0xc0, 0xe0, 0xa1, 0xb1, 0xc8, 0xe8, + 0xa9, 0xb9, 0xa2, 0xb2, 0xcc, 0xec, 0xca, 0xea, + 0xa3, 0xb3, 0xa5, 0xb5, 0xa4, 0xb4, 0xc7, 0xe7, + 0xa6, 0xb6, 0xff, 0xa8, 0xb8, 0xd1, 0xf1, 0xaf, + 0xbf, 0xd2, 0xf2, 0xaa, 0xba, 0xab, 0xbb, 0xd7, + 0xf7, 0xae, 0xbe, 0xd9, 0xf9, 0xac, 0xbc, 0xbd + }, + 256 +}; +const charset_spec charset_CS_ISO8859_10 = { + CS_ISO8859_10, read_sbcs, write_sbcs, &data_CS_ISO8859_10 +}; + +static const sbcs_data data_CS_ISO8859_11 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07, + 0x0e08, 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, + 0x0e10, 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17, + 0x0e18, 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, + 0x0e20, 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27, + 0x0e28, 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, + 0x0e30, 0x0e31, 0x0e32, 0x0e33, 0x0e34, 0x0e35, 0x0e36, 0x0e37, + 0x0e38, 0x0e39, 0x0e3a, ERROR , ERROR , ERROR , ERROR , 0x0e3f, + 0x0e40, 0x0e41, 0x0e42, 0x0e43, 0x0e44, 0x0e45, 0x0e46, 0x0e47, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x0e4d, 0x0e4e, 0x0e4f, + 0x0e50, 0x0e51, 0x0e52, 0x0e53, 0x0e54, 0x0e55, 0x0e56, 0x0e57, + 0x0e58, 0x0e59, 0x0e5a, 0x0e5b, ERROR , ERROR , ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, + 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb + }, + 248 +}; +const charset_spec charset_CS_ISO8859_11 = { + CS_ISO8859_11, read_sbcs, write_sbcs, &data_CS_ISO8859_11 +}; + +static const sbcs_data data_CS_ISO8859_13 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x201d, 0x00a2, 0x00a3, 0x00a4, 0x201e, 0x00a6, 0x00a7, + 0x00d8, 0x00a9, 0x0156, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00c6, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x201c, 0x00b5, 0x00b6, 0x00b7, + 0x00f8, 0x00b9, 0x0157, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00e6, + 0x0104, 0x012e, 0x0100, 0x0106, 0x00c4, 0x00c5, 0x0118, 0x0112, + 0x010c, 0x00c9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012a, 0x013b, + 0x0160, 0x0143, 0x0145, 0x00d3, 0x014c, 0x00d5, 0x00d6, 0x00d7, + 0x0172, 0x0141, 0x015a, 0x016a, 0x00dc, 0x017b, 0x017d, 0x00df, + 0x0105, 0x012f, 0x0101, 0x0107, 0x00e4, 0x00e5, 0x0119, 0x0113, + 0x010d, 0x00e9, 0x017a, 0x0117, 0x0123, 0x0137, 0x012b, 0x013c, + 0x0161, 0x0144, 0x0146, 0x00f3, 0x014d, 0x00f5, 0x00f6, 0x00f7, + 0x0173, 0x0142, 0x015b, 0x016b, 0x00fc, 0x017c, 0x017e, 0x2019 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa6, 0xa7, 0xa9, 0xab, + 0xac, 0xad, 0xae, 0xb0, 0xb1, 0xb2, 0xb3, 0xb5, + 0xb6, 0xb7, 0xb9, 0xbb, 0xbc, 0xbd, 0xbe, 0xc4, + 0xc5, 0xaf, 0xc9, 0xd3, 0xd5, 0xd6, 0xd7, 0xa8, + 0xdc, 0xdf, 0xe4, 0xe5, 0xbf, 0xe9, 0xf3, 0xf5, + 0xf6, 0xf7, 0xb8, 0xfc, 0xc2, 0xe2, 0xc0, 0xe0, + 0xc3, 0xe3, 0xc8, 0xe8, 0xc7, 0xe7, 0xcb, 0xeb, + 0xc6, 0xe6, 0xcc, 0xec, 0xce, 0xee, 0xc1, 0xe1, + 0xcd, 0xed, 0xcf, 0xef, 0xd9, 0xf9, 0xd1, 0xf1, + 0xd2, 0xf2, 0xd4, 0xf4, 0xaa, 0xba, 0xda, 0xfa, + 0xd0, 0xf0, 0xdb, 0xfb, 0xd8, 0xf8, 0xca, 0xea, + 0xdd, 0xfd, 0xde, 0xfe, 0xff, 0xb4, 0xa1, 0xa5 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_13 = { + CS_ISO8859_13, read_sbcs, write_sbcs, &data_CS_ISO8859_13 +}; + +static const sbcs_data data_CS_ISO8859_14 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x1e02, 0x1e03, 0x00a3, 0x010a, 0x010b, 0x1e0a, 0x00a7, + 0x1e80, 0x00a9, 0x1e82, 0x1e0b, 0x1ef2, 0x00ad, 0x00ae, 0x0178, + 0x1e1e, 0x1e1f, 0x0120, 0x0121, 0x1e40, 0x1e41, 0x00b6, 0x1e56, + 0x1e81, 0x1e57, 0x1e83, 0x1e60, 0x1ef3, 0x1e84, 0x1e85, 0x1e61, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x0174, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x1e6a, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x0176, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x0175, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x1e6b, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x0177, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa3, 0xa7, 0xa9, 0xad, 0xae, 0xb6, 0xc0, + 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd1, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xd9, 0xda, + 0xdb, 0xdc, 0xdd, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, + 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, + 0xff, 0xa4, 0xa5, 0xb2, 0xb3, 0xd0, 0xf0, 0xde, + 0xfe, 0xaf, 0xa1, 0xa2, 0xa6, 0xab, 0xb0, 0xb1, + 0xb4, 0xb5, 0xb7, 0xb9, 0xbb, 0xbf, 0xd7, 0xf7, + 0xa8, 0xb8, 0xaa, 0xba, 0xbd, 0xbe, 0xac, 0xbc + }, + 256 +}; +const charset_spec charset_CS_ISO8859_14 = { + CS_ISO8859_14, read_sbcs, write_sbcs, &data_CS_ISO8859_14 +}; + +static const sbcs_data data_CS_ISO8859_15 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x20ac, 0x00a5, 0x0160, 0x00a7, + 0x0161, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x017d, 0x00b5, 0x00b6, 0x00b7, + 0x017e, 0x00b9, 0x00ba, 0x00bb, 0x0152, 0x0153, 0x0178, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, + 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, + 0xb3, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0xbc, 0xbd, 0xa6, 0xa8, 0xbe, 0xb4, 0xb8, 0xa4 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_15 = { + CS_ISO8859_15, read_sbcs, write_sbcs, &data_CS_ISO8859_15 +}; + +static const sbcs_data data_CS_ISO8859_16 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x0105, 0x0141, 0x20ac, 0x201e, 0x0160, 0x00a7, + 0x0161, 0x00a9, 0x0218, 0x00ab, 0x0179, 0x00ad, 0x017a, 0x017b, + 0x00b0, 0x00b1, 0x010c, 0x0142, 0x017d, 0x201d, 0x00b6, 0x00b7, + 0x017e, 0x010d, 0x0219, 0x00bb, 0x0152, 0x0153, 0x0178, 0x017c, + 0x00c0, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0106, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x0110, 0x0143, 0x00d2, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x015a, + 0x0170, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0118, 0x021a, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x0107, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x0111, 0x0144, 0x00f2, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x015b, + 0x0171, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x0119, 0x021b, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa7, 0xa9, 0xab, 0xad, 0xb0, 0xb1, 0xb6, + 0xb7, 0xbb, 0xc0, 0xc1, 0xc2, 0xc4, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd2, 0xd3, 0xd4, 0xd6, 0xd9, 0xda, 0xdb, 0xdc, + 0xdf, 0xe0, 0xe1, 0xe2, 0xe4, 0xe6, 0xe7, 0xe8, + 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf2, + 0xf3, 0xf4, 0xf6, 0xf9, 0xfa, 0xfb, 0xfc, 0xff, + 0xc3, 0xe3, 0xa1, 0xa2, 0xc5, 0xe5, 0xb2, 0xb9, + 0xd0, 0xf0, 0xdd, 0xfd, 0xa3, 0xb3, 0xd1, 0xf1, + 0xd5, 0xf5, 0xbc, 0xbd, 0xd7, 0xf7, 0xa6, 0xa8, + 0xd8, 0xf8, 0xbe, 0xac, 0xae, 0xaf, 0xbf, 0xb4, + 0xb8, 0xaa, 0xba, 0xde, 0xfe, 0xb5, 0xa5, 0xa4 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_16 = { + CS_ISO8859_16, read_sbcs, write_sbcs, &data_CS_ISO8859_16 +}; + +static const sbcs_data data_CS_ISO8859_1_X11 = { + { + 0x0020, 0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, + 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, + 0x23ba, 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, + 0x252c, 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x1c, 0x1d, 0x1a, 0x1b, 0x10, 0x11, 0x13, 0x14, + 0x03, 0x06, 0x0a, 0x04, 0x05, 0x09, 0x12, 0x19, + 0x0d, 0x0c, 0x0e, 0x0b, 0x15, 0x16, 0x18, 0x17, + 0x0f, 0x02, 0x01 + }, + 251 +}; +const charset_spec charset_CS_ISO8859_1_X11 = { + CS_ISO8859_1_X11, read_sbcs, write_sbcs, &data_CS_ISO8859_1_X11 +}; + +static const sbcs_data data_CS_CP437 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, + 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, + 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, + 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, + 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, + 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, + 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, + 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, + 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, + 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, + 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, + 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xff, 0xad, 0x9b, 0x9c, 0x9d, 0xa6, 0xae, 0xaa, + 0xf8, 0xf1, 0xfd, 0xe6, 0xfa, 0xa7, 0xaf, 0xac, + 0xab, 0xa8, 0x8e, 0x8f, 0x92, 0x80, 0x90, 0xa5, + 0x99, 0x9a, 0xe1, 0x85, 0xa0, 0x83, 0x84, 0x86, + 0x91, 0x87, 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, + 0x8c, 0x8b, 0xa4, 0x95, 0xa2, 0x93, 0x94, 0xf6, + 0x97, 0xa3, 0x96, 0x81, 0x98, 0x9f, 0xe2, 0xe9, + 0xe4, 0xe8, 0xea, 0xe0, 0xeb, 0xee, 0xe3, 0xe5, + 0xe7, 0xed, 0xfc, 0x9e, 0xf9, 0xfb, 0xec, 0xef, + 0xf7, 0xf0, 0xf3, 0xf2, 0xa9, 0xf4, 0xf5, 0xc4, + 0xb3, 0xda, 0xbf, 0xc0, 0xd9, 0xc3, 0xb4, 0xc2, + 0xc1, 0xc5, 0xcd, 0xba, 0xd5, 0xd6, 0xc9, 0xb8, + 0xb7, 0xbb, 0xd4, 0xd3, 0xc8, 0xbe, 0xbd, 0xbc, + 0xc6, 0xc7, 0xcc, 0xb5, 0xb6, 0xb9, 0xd1, 0xd2, + 0xcb, 0xcf, 0xd0, 0xca, 0xd8, 0xd7, 0xce, 0xdf, + 0xdc, 0xdb, 0xdd, 0xde, 0xb0, 0xb1, 0xb2, 0xfe + }, + 256 +}; +const charset_spec charset_CS_CP437 = { + CS_CP437, read_sbcs, write_sbcs, &data_CS_CP437 +}; + +static const sbcs_data data_CS_CP850 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, + 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, + 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, + 0x00ff, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x00d7, 0x0192, + 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, + 0x00bf, 0x00ae, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, 0x00c2, 0x00c0, + 0x00a9, 0x2563, 0x2551, 0x2557, 0x255d, 0x00a2, 0x00a5, 0x2510, + 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x00e3, 0x00c3, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4, + 0x00f0, 0x00d0, 0x00ca, 0x00cb, 0x00c8, 0x0131, 0x00cd, 0x00ce, + 0x00cf, 0x2518, 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580, + 0x00d3, 0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, 0x00b5, 0x00fe, + 0x00de, 0x00da, 0x00db, 0x00d9, 0x00fd, 0x00dd, 0x00af, 0x00b4, + 0x00ad, 0x00b1, 0x2017, 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, + 0x00b0, 0x00a8, 0x00b7, 0x00b9, 0x00b3, 0x00b2, 0x25a0, 0x00a0 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xff, 0xad, 0xbd, 0x9c, 0xcf, 0xbe, 0xdd, 0xf5, + 0xf9, 0xb8, 0xa6, 0xae, 0xaa, 0xf0, 0xa9, 0xee, + 0xf8, 0xf1, 0xfd, 0xfc, 0xef, 0xe6, 0xf4, 0xfa, + 0xf7, 0xfb, 0xa7, 0xaf, 0xac, 0xab, 0xf3, 0xa8, + 0xb7, 0xb5, 0xb6, 0xc7, 0x8e, 0x8f, 0x92, 0x80, + 0xd4, 0x90, 0xd2, 0xd3, 0xde, 0xd6, 0xd7, 0xd8, + 0xd1, 0xa5, 0xe3, 0xe0, 0xe2, 0xe5, 0x99, 0x9e, + 0x9d, 0xeb, 0xe9, 0xea, 0x9a, 0xed, 0xe8, 0xe1, + 0x85, 0xa0, 0x83, 0xc6, 0x84, 0x86, 0x91, 0x87, + 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b, + 0xd0, 0xa4, 0x95, 0xa2, 0x93, 0xe4, 0x94, 0xf6, + 0x9b, 0x97, 0xa3, 0x96, 0x81, 0xec, 0xe7, 0x98, + 0xd5, 0x9f, 0xf2, 0xc4, 0xb3, 0xda, 0xbf, 0xc0, + 0xd9, 0xc3, 0xb4, 0xc2, 0xc1, 0xc5, 0xcd, 0xba, + 0xc9, 0xbb, 0xc8, 0xbc, 0xcc, 0xb9, 0xcb, 0xca, + 0xce, 0xdf, 0xdc, 0xdb, 0xb0, 0xb1, 0xb2, 0xfe + }, + 256 +}; +const charset_spec charset_CS_CP850 = { + CS_CP850, read_sbcs, write_sbcs, &data_CS_CP850 +}; + +static const sbcs_data data_CS_CP866 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, + 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, + 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, + 0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040e, 0x045e, + 0x00b0, 0x2219, 0x00b7, 0x221a, 0x2116, 0x00a4, 0x25a0, 0x00a0 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xff, 0xfd, 0xf8, 0xfa, 0xf0, 0xf2, 0xf4, 0xf6, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf1, 0xf3, 0xf5, 0xf7, 0xfc, 0xf9, 0xfb, 0xc4, + 0xb3, 0xda, 0xbf, 0xc0, 0xd9, 0xc3, 0xb4, 0xc2, + 0xc1, 0xc5, 0xcd, 0xba, 0xd5, 0xd6, 0xc9, 0xb8, + 0xb7, 0xbb, 0xd4, 0xd3, 0xc8, 0xbe, 0xbd, 0xbc, + 0xc6, 0xc7, 0xcc, 0xb5, 0xb6, 0xb9, 0xd1, 0xd2, + 0xcb, 0xcf, 0xd0, 0xca, 0xd8, 0xd7, 0xce, 0xdf, + 0xdc, 0xdb, 0xdd, 0xde, 0xb0, 0xb1, 0xb2, 0xfe + }, + 256 +}; +const charset_spec charset_CS_CP866 = { + CS_CP866, read_sbcs, write_sbcs, &data_CS_CP866 +}; + +static const sbcs_data data_CS_CP1250 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, ERROR , 0x201e, 0x2026, 0x2020, 0x2021, + ERROR , 0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179, + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a, + 0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b, + 0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c, + 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, + 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, + 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, + 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, + 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, + 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa4, 0xa6, 0xa7, 0xa8, 0xa9, 0xab, 0xac, + 0xad, 0xae, 0xb0, 0xb1, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xbb, 0xc1, 0xc2, 0xc4, 0xc7, 0xc9, 0xcb, + 0xcd, 0xce, 0xd3, 0xd4, 0xd6, 0xd7, 0xda, 0xdc, + 0xdd, 0xdf, 0xe1, 0xe2, 0xe4, 0xe7, 0xe9, 0xeb, + 0xed, 0xee, 0xf3, 0xf4, 0xf6, 0xf7, 0xfa, 0xfc, + 0xfd, 0xc3, 0xe3, 0xa5, 0xb9, 0xc6, 0xe6, 0xc8, + 0xe8, 0xcf, 0xef, 0xd0, 0xf0, 0xca, 0xea, 0xcc, + 0xec, 0xc5, 0xe5, 0xbc, 0xbe, 0xa3, 0xb3, 0xd1, + 0xf1, 0xd2, 0xf2, 0xd5, 0xf5, 0xc0, 0xe0, 0xd8, + 0xf8, 0x8c, 0x9c, 0xaa, 0xba, 0x8a, 0x9a, 0xde, + 0xfe, 0x8d, 0x9d, 0xd9, 0xf9, 0xdb, 0xfb, 0x8f, + 0x9f, 0xaf, 0xbf, 0x8e, 0x9e, 0xa1, 0xa2, 0xff, + 0xb2, 0xbd, 0x96, 0x97, 0x91, 0x92, 0x82, 0x93, + 0x94, 0x84, 0x86, 0x87, 0x95, 0x85, 0x89, 0x8b, + 0x9b, 0x80, 0x99 + }, + 251 +}; +const charset_spec charset_CS_CP1250 = { + CS_CP1250, read_sbcs, write_sbcs, &data_CS_CP1250 +}; + +static const sbcs_data data_CS_CP1251 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0402, 0x0403, 0x201a, 0x0453, 0x201e, 0x2026, 0x2020, 0x2021, + 0x20ac, 0x2030, 0x0409, 0x2039, 0x040a, 0x040c, 0x040b, 0x040f, + 0x0452, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, 0x0459, 0x203a, 0x045a, 0x045c, 0x045b, 0x045f, + 0x00a0, 0x040e, 0x045e, 0x0408, 0x00a4, 0x0490, 0x00a6, 0x00a7, + 0x0401, 0x00a9, 0x0404, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0407, + 0x00b0, 0x00b1, 0x0406, 0x0456, 0x0491, 0x00b5, 0x00b6, 0x00b7, + 0x0451, 0x2116, 0x0454, 0x00bb, 0x0458, 0x0405, 0x0455, 0x0457, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa4, 0xa6, 0xa7, 0xa9, 0xab, 0xac, 0xad, + 0xae, 0xb0, 0xb1, 0xb5, 0xb6, 0xb7, 0xbb, 0xa8, + 0x80, 0x81, 0xaa, 0xbd, 0xb2, 0xaf, 0xa3, 0x8a, + 0x8c, 0x8e, 0x8d, 0xa1, 0x8f, 0xc0, 0xc1, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xb8, 0x90, 0x83, + 0xba, 0xbe, 0xb3, 0xbf, 0xbc, 0x9a, 0x9c, 0x9e, + 0x9d, 0xa2, 0x9f, 0xa5, 0xb4, 0x96, 0x97, 0x91, + 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, 0x87, 0x95, + 0x85, 0x89, 0x8b, 0x9b, 0x88, 0xb9, 0x99 + }, + 255 +}; +const charset_spec charset_CS_CP1251 = { + CS_CP1251, read_sbcs, write_sbcs, &data_CS_CP1251 +}; + +static const sbcs_data data_CS_CP1252 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, ERROR , 0x017d, ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, ERROR , 0x017e, 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x8c, 0x9c, 0x8a, 0x9a, 0x9f, 0x8e, 0x9e, 0x83, + 0x88, 0x98, 0x96, 0x97, 0x91, 0x92, 0x82, 0x93, + 0x94, 0x84, 0x86, 0x87, 0x95, 0x85, 0x89, 0x8b, + 0x9b, 0x80, 0x99 + }, + 251 +}; +const charset_spec charset_CS_CP1252 = { + CS_CP1252, read_sbcs, write_sbcs, &data_CS_CP1252 +}; + +static const sbcs_data data_CS_CP1253 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + ERROR , 0x2030, ERROR , 0x2039, ERROR , ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, ERROR , 0x203a, ERROR , ERROR , ERROR , ERROR , + 0x00a0, 0x0385, 0x0386, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, ERROR , 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x2015, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x0384, 0x00b5, 0x00b6, 0x00b7, + 0x0388, 0x0389, 0x038a, 0x00bb, 0x038c, 0x00bd, 0x038e, 0x038f, + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, + 0x03a0, 0x03a1, ERROR , 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, + 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03ae, 0x03af, + 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, + 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, + 0x03c8, 0x03c9, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03ce, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xab, 0xac, 0xad, 0xae, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb5, 0xb6, 0xb7, 0xbb, 0xbd, 0x83, 0xb4, 0xa1, + 0xa2, 0xb8, 0xb9, 0xba, 0xbc, 0xbe, 0xbf, 0xc0, + 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, + 0xd1, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0x96, 0x97, 0xaf, + 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, 0x87, + 0x95, 0x85, 0x89, 0x8b, 0x9b, 0x80, 0x99 + }, + 239 +}; +const charset_spec charset_CS_CP1253 = { + CS_CP1253, read_sbcs, write_sbcs, &data_CS_CP1253 +}; + +static const sbcs_data data_CS_CP1254 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, ERROR , ERROR , 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x011e, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0130, 0x015e, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x011f, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x0131, 0x015f, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xdb, 0xdc, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xff, 0xd0, 0xf0, 0xdd, 0xfd, 0x8c, 0x9c, + 0xde, 0xfe, 0x8a, 0x9a, 0x9f, 0x83, 0x88, 0x98, + 0x96, 0x97, 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, + 0x86, 0x87, 0x95, 0x85, 0x89, 0x8b, 0x9b, 0x80, + 0x99 + }, + 249 +}; +const charset_spec charset_CS_CP1254 = { + CS_CP1254, read_sbcs, write_sbcs, &data_CS_CP1254 +}; + +static const sbcs_data data_CS_CP1255 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, ERROR , 0x2039, ERROR , ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, ERROR , 0x203a, ERROR , ERROR , ERROR , ERROR , + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x20aa, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00d7, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00f7, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x05b0, 0x05b1, 0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7, + 0x05b8, 0x05b9, ERROR , 0x05bb, 0x05bc, 0x05bd, 0x05be, 0x05bf, + 0x05c0, 0x05c1, 0x05c2, 0x05c3, 0x05f0, 0x05f1, 0x05f2, 0x05f3, + 0x05f4, ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0x05d7, + 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de, 0x05df, + 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6, 0x05e7, + 0x05e8, 0x05e9, 0x05ea, ERROR , ERROR , 0x200e, 0x200f, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xaa, 0xba, 0x83, + 0x88, 0x98, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xfd, 0xfe, 0x96, + 0x97, 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, + 0x87, 0x95, 0x85, 0x89, 0x8b, 0x9b, 0xa4, 0x80, + 0x99 + }, + 233 +}; +const charset_spec charset_CS_CP1255 = { + CS_CP1255, read_sbcs, write_sbcs, &data_CS_CP1255 +}; + +static const sbcs_data data_CS_CP1256 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x067e, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0679, 0x2039, 0x0152, 0x0686, 0x0698, 0x0688, + 0x06af, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x06a9, 0x2122, 0x0691, 0x203a, 0x0153, 0x200c, 0x200d, 0x06ba, + 0x00a0, 0x060c, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x06be, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x061b, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x061f, + 0x06c1, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062a, 0x062b, 0x062c, 0x062d, 0x062e, 0x062f, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x00d7, + 0x0637, 0x0638, 0x0639, 0x063a, 0x0640, 0x0641, 0x0642, 0x0643, + 0x00e0, 0x0644, 0x00e2, 0x0645, 0x0646, 0x0647, 0x0648, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x0649, 0x064a, 0x00ee, 0x00ef, + 0x064b, 0x064c, 0x064d, 0x064e, 0x00f4, 0x064f, 0x0650, 0x00f7, + 0x0651, 0x00f9, 0x0652, 0x00fb, 0x00fc, 0x200e, 0x200f, 0x06d2 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xbb, 0xbc, 0xbd, 0xbe, 0xd7, 0xe0, 0xe2, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xee, 0xef, 0xf4, 0xf7, + 0xf9, 0xfb, 0xfc, 0x8c, 0x9c, 0x83, 0x88, 0xa1, + 0xba, 0xbf, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe1, 0xe3, 0xe4, 0xe5, 0xe6, 0xec, 0xed, 0xf0, + 0xf1, 0xf2, 0xf3, 0xf5, 0xf6, 0xf8, 0xfa, 0x8a, + 0x81, 0x8d, 0x8f, 0x9a, 0x8e, 0x98, 0x90, 0x9f, + 0xaa, 0xc0, 0xff, 0x9d, 0x9e, 0xfd, 0xfe, 0x96, + 0x97, 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, + 0x87, 0x95, 0x85, 0x89, 0x8b, 0x9b, 0x80, 0x99 + }, + 256 +}; +const charset_spec charset_CS_CP1256 = { + CS_CP1256, read_sbcs, write_sbcs, &data_CS_CP1256 +}; + +static const sbcs_data data_CS_CP1257 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, ERROR , 0x201e, 0x2026, 0x2020, 0x2021, + ERROR , 0x2030, ERROR , 0x2039, ERROR , 0x00a8, 0x02c7, 0x00b8, + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, ERROR , 0x203a, ERROR , 0x00af, 0x02db, ERROR , + 0x00a0, ERROR , 0x00a2, 0x00a3, 0x00a4, ERROR , 0x00a6, 0x00a7, + 0x00d8, 0x00a9, 0x0156, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00c6, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00f8, 0x00b9, 0x0157, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00e6, + 0x0104, 0x012e, 0x0100, 0x0106, 0x00c4, 0x00c5, 0x0118, 0x0112, + 0x010c, 0x00c9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012a, 0x013b, + 0x0160, 0x0143, 0x0145, 0x00d3, 0x014c, 0x00d5, 0x00d6, 0x00d7, + 0x0172, 0x0141, 0x015a, 0x016a, 0x00dc, 0x017b, 0x017d, 0x00df, + 0x0105, 0x012f, 0x0101, 0x0107, 0x00e4, 0x00e5, 0x0119, 0x0113, + 0x010d, 0x00e9, 0x017a, 0x0117, 0x0123, 0x0137, 0x012b, 0x013c, + 0x0161, 0x0144, 0x0146, 0x00f3, 0x014d, 0x00f5, 0x00f6, 0x00f7, + 0x0173, 0x0142, 0x015b, 0x016b, 0x00fc, 0x017c, 0x017e, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa6, 0xa7, 0x8d, 0xa9, + 0xab, 0xac, 0xad, 0xae, 0x9d, 0xb0, 0xb1, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0x8f, 0xb9, 0xbb, + 0xbc, 0xbd, 0xbe, 0xc4, 0xc5, 0xaf, 0xc9, 0xd3, + 0xd5, 0xd6, 0xd7, 0xa8, 0xdc, 0xdf, 0xe4, 0xe5, + 0xbf, 0xe9, 0xf3, 0xf5, 0xf6, 0xf7, 0xb8, 0xfc, + 0xc2, 0xe2, 0xc0, 0xe0, 0xc3, 0xe3, 0xc8, 0xe8, + 0xc7, 0xe7, 0xcb, 0xeb, 0xc6, 0xe6, 0xcc, 0xec, + 0xce, 0xee, 0xc1, 0xe1, 0xcd, 0xed, 0xcf, 0xef, + 0xd9, 0xf9, 0xd1, 0xf1, 0xd2, 0xf2, 0xd4, 0xf4, + 0xaa, 0xba, 0xda, 0xfa, 0xd0, 0xf0, 0xdb, 0xfb, + 0xd8, 0xf8, 0xca, 0xea, 0xdd, 0xfd, 0xde, 0xfe, + 0x8e, 0xff, 0x9e, 0x96, 0x97, 0x91, 0x92, 0x82, + 0x93, 0x94, 0x84, 0x86, 0x87, 0x95, 0x85, 0x89, + 0x8b, 0x9b, 0x80, 0x99 + }, + 244 +}; +const charset_spec charset_CS_CP1257 = { + CS_CP1257, read_sbcs, write_sbcs, &data_CS_CP1257 +}; + +static const sbcs_data data_CS_CP1258 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, ERROR , 0x2039, 0x0152, ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, ERROR , 0x203a, 0x0153, ERROR , ERROR , 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x0300, 0x00cd, 0x00ce, 0x00cf, + 0x0110, 0x00d1, 0x0309, 0x00d3, 0x00d4, 0x01a0, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x01af, 0x0303, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x0301, 0x00ed, 0x00ee, 0x00ef, + 0x0111, 0x00f1, 0x0323, 0x00f3, 0x00f4, 0x01a1, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x01b0, 0x20ab, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf, 0xd1, 0xd3, + 0xd4, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, + 0xdf, 0xe0, 0xe1, 0xe2, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xed, 0xee, 0xef, 0xf1, + 0xf3, 0xf4, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xff, 0xc3, 0xe3, 0xd0, 0xf0, 0x8c, 0x9c, + 0x9f, 0x83, 0xd5, 0xf5, 0xdd, 0xfd, 0x88, 0x98, + 0xcc, 0xec, 0xde, 0xd2, 0xf2, 0x96, 0x97, 0x91, + 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, 0x87, 0x95, + 0x85, 0x89, 0x8b, 0x9b, 0xfe, 0x80, 0x99 + }, + 247 +}; +const charset_spec charset_CS_CP1258 = { + CS_CP1258, read_sbcs, write_sbcs, &data_CS_CP1258 +}; + +static const sbcs_data data_CS_KOI8_R = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x2500, 0x2502, 0x250c, 0x2510, 0x2514, 0x2518, 0x251c, 0x2524, + 0x252c, 0x2534, 0x253c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590, + 0x2591, 0x2592, 0x2593, 0x2320, 0x25a0, 0x2219, 0x221a, 0x2248, + 0x2264, 0x2265, 0x00a0, 0x2321, 0x00b0, 0x00b2, 0x00b7, 0x00f7, + 0x2550, 0x2551, 0x2552, 0x0451, 0x2553, 0x2554, 0x2555, 0x2556, + 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x255c, 0x255d, 0x255e, + 0x255f, 0x2560, 0x2561, 0x0401, 0x2562, 0x2563, 0x2564, 0x2565, + 0x2566, 0x2567, 0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x00a9, + 0x044e, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, + 0x0445, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, + 0x043f, 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, + 0x044c, 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, + 0x042e, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, + 0x0425, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, + 0x041f, 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, + 0x042c, 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x9a, 0xbf, 0x9c, 0x9d, 0x9e, 0x9f, 0xb3, 0xe1, + 0xe2, 0xf7, 0xe7, 0xe4, 0xe5, 0xf6, 0xfa, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf2, + 0xf3, 0xf4, 0xf5, 0xe6, 0xe8, 0xe3, 0xfe, 0xfb, + 0xfd, 0xff, 0xf9, 0xf8, 0xfc, 0xe0, 0xf1, 0xc1, + 0xc2, 0xd7, 0xc7, 0xc4, 0xc5, 0xd6, 0xda, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd2, + 0xd3, 0xd4, 0xd5, 0xc6, 0xc8, 0xc3, 0xde, 0xdb, + 0xdd, 0xdf, 0xd9, 0xd8, 0xdc, 0xc0, 0xd1, 0xa3, + 0x95, 0x96, 0x97, 0x98, 0x99, 0x93, 0x9b, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0xa0, 0xa1, 0xa2, 0xa4, 0xa5, 0xa6, + 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, + 0xaf, 0xb0, 0xb1, 0xb2, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0x8b, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x94 + }, + 256 +}; +const charset_spec charset_CS_KOI8_R = { + CS_KOI8_R, read_sbcs, write_sbcs, &data_CS_KOI8_R +}; + +static const sbcs_data data_CS_KOI8_U = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x2500, 0x2502, 0x250c, 0x2510, 0x2514, 0x2518, 0x251c, 0x2524, + 0x252c, 0x2534, 0x253c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590, + 0x2591, 0x2592, 0x2593, 0x2320, 0x25a0, 0x2219, 0x221a, 0x2248, + 0x2264, 0x2265, 0x00a0, 0x2321, 0x00b0, 0x00b2, 0x00b7, 0x00f7, + 0x2550, 0x2551, 0x2552, 0x0451, 0x0454, 0x2554, 0x0456, 0x0457, + 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x0491, 0x255d, 0x255e, + 0x255f, 0x2560, 0x2561, 0x0401, 0x0404, 0x2563, 0x0406, 0x0407, + 0x2566, 0x2567, 0x2568, 0x2569, 0x256a, 0x0490, 0x256c, 0x00a9, + 0x044e, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, + 0x0445, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, + 0x043f, 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, + 0x044c, 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, + 0x042e, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, + 0x0425, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, + 0x041f, 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, + 0x042c, 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x9a, 0xbf, 0x9c, 0x9d, 0x9e, 0x9f, 0xb3, 0xb4, + 0xb6, 0xb7, 0xe1, 0xe2, 0xf7, 0xe7, 0xe4, 0xe5, + 0xf6, 0xfa, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, + 0xef, 0xf0, 0xf2, 0xf3, 0xf4, 0xf5, 0xe6, 0xe8, + 0xe3, 0xfe, 0xfb, 0xfd, 0xff, 0xf9, 0xf8, 0xfc, + 0xe0, 0xf1, 0xc1, 0xc2, 0xd7, 0xc7, 0xc4, 0xc5, + 0xd6, 0xda, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd2, 0xd3, 0xd4, 0xd5, 0xc6, 0xc8, + 0xc3, 0xde, 0xdb, 0xdd, 0xdf, 0xd9, 0xd8, 0xdc, + 0xc0, 0xd1, 0xa3, 0xa4, 0xa6, 0xa7, 0xbd, 0xad, + 0x95, 0x96, 0x97, 0x98, 0x99, 0x93, 0x9b, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0xa0, 0xa1, 0xa2, 0xa5, 0xa8, 0xa9, + 0xaa, 0xab, 0xac, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, + 0xb5, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbe, 0x8b, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x94 + }, + 256 +}; +const charset_spec charset_CS_KOI8_U = { + CS_KOI8_U, read_sbcs, write_sbcs, &data_CS_KOI8_U +}; + +static const sbcs_data data_CS_MAC_ROMAN = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x20ac, 0x2039, 0x203a, 0xfb01, 0xfb02, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, + 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, + 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xd8, + 0xf5, 0xce, 0xcf, 0xd9, 0xc4, 0xf6, 0xff, 0xf9, + 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, + 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, 0xa0, + 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, 0xda, 0xdb, + 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, 0xb0, 0xba, + 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0, 0xde, 0xdf + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMAN = { + CS_MAC_ROMAN, read_sbcs, write_sbcs, &data_CS_MAC_ROMAN +}; + +static const sbcs_data data_CS_MAC_TURKISH = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x011e, 0x011f, 0x0130, 0x0131, 0x015e, 0x015f, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, ERROR , 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, + 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, + 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xd8, + 0xda, 0xdb, 0xdc, 0xdd, 0xce, 0xcf, 0xde, 0xdf, + 0xd9, 0xc4, 0xf6, 0xff, 0xf9, 0xfa, 0xfb, 0xfe, + 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, 0xd1, 0xd4, 0xd5, + 0xe2, 0xd2, 0xd3, 0xe3, 0xa0, 0xe0, 0xa5, 0xc9, + 0xe4, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, 0xb0, + 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 255 +}; +const charset_spec charset_CS_MAC_TURKISH = { + CS_MAC_TURKISH, read_sbcs, write_sbcs, &data_CS_MAC_TURKISH +}; + +static const sbcs_data data_CS_MAC_CROATIAN = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x0160, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x017d, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x2206, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x0161, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x017e, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x0106, 0x00ab, + 0x010c, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x0110, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0xf8ff, 0x00a9, 0x2044, 0x20ac, 0x2039, 0x203a, 0x00c6, 0x00bb, + 0x2013, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x0107, 0x00c1, + 0x010d, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0x0111, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x03c0, 0x00cb, 0x02da, 0x00b8, 0x00ca, 0x00e6, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xa4, 0xac, 0xd9, 0xbb, + 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, 0xb5, + 0xa6, 0xe1, 0xfc, 0xbc, 0xdf, 0xc0, 0xcb, 0xe7, + 0xe5, 0xcc, 0x80, 0x81, 0xde, 0x82, 0xe9, 0x83, + 0xfd, 0xfa, 0xed, 0xea, 0xeb, 0xec, 0x84, 0xf1, + 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, 0xf3, + 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, + 0xfe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, + 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, + 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xc6, 0xe6, + 0xc8, 0xe8, 0xd0, 0xf0, 0xf5, 0xce, 0xcf, 0xa9, + 0xb9, 0xae, 0xbe, 0xc4, 0xf6, 0xff, 0xfb, 0xf7, + 0xbd, 0xf9, 0xe0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, + 0xd3, 0xe3, 0xa0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, + 0xda, 0xdb, 0xaa, 0xb6, 0xb4, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xd8 + }, + 256 +}; +const charset_spec charset_CS_MAC_CROATIAN = { + CS_MAC_CROATIAN, read_sbcs, write_sbcs, &data_CS_MAC_CROATIAN +}; + +static const sbcs_data data_CS_MAC_ICELAND = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x20ac, 0x00d0, 0x00f0, 0x00de, 0x00fe, + 0x00fd, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0xdc, + 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, + 0xf2, 0xf3, 0x86, 0xa0, 0xde, 0xa7, 0x88, 0x87, + 0x89, 0x8b, 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, + 0x90, 0x91, 0x93, 0x92, 0x94, 0x95, 0xdd, 0x96, + 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, + 0x9c, 0x9e, 0x9f, 0xe0, 0xdf, 0xd8, 0xf5, 0xce, + 0xcf, 0xd9, 0xc4, 0xf6, 0xff, 0xf9, 0xfa, 0xfb, + 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, 0xd1, 0xd4, + 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, 0xa5, 0xc9, 0xe4, + 0xda, 0xdb, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ICELAND = { + CS_MAC_ICELAND, read_sbcs, write_sbcs, &data_CS_MAC_ICELAND +}; + +static const sbcs_data data_CS_MAC_ROMANIAN = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x0102, 0x0218, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x0103, 0x0219, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x20ac, 0x2039, 0x203a, 0x021a, 0x021b, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0x82, 0xe9, 0x83, + 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, 0xf1, + 0xee, 0xef, 0xcd, 0x85, 0xf4, 0xf2, 0xf3, 0x86, + 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, 0x8d, + 0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, 0x95, + 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, 0x9d, + 0x9c, 0x9e, 0x9f, 0xd8, 0xae, 0xbe, 0xf5, 0xce, + 0xcf, 0xd9, 0xc4, 0xaf, 0xbf, 0xde, 0xdf, 0xf6, + 0xff, 0xf9, 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, + 0xb9, 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, + 0xe3, 0xa0, 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, + 0xda, 0xdb, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMANIAN = { + CS_MAC_ROMANIAN, read_sbcs, write_sbcs, &data_CS_MAC_ROMANIAN +}; + +static const sbcs_data data_CS_MAC_GREEK = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00b9, 0x00b2, 0x00c9, 0x00b3, 0x00d6, 0x00dc, 0x0385, + 0x00e0, 0x00e2, 0x00e4, 0x0384, 0x00a8, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00a3, 0x2122, 0x00ee, 0x00ef, 0x2022, 0x00bd, + 0x2030, 0x00f4, 0x00f6, 0x00a6, 0x20ac, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x0393, 0x0394, 0x0398, 0x039b, 0x039e, 0x03a0, 0x00df, + 0x00ae, 0x00a9, 0x03a3, 0x03aa, 0x00a7, 0x2260, 0x00b0, 0x00b7, + 0x0391, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x0392, 0x0395, 0x0396, + 0x0397, 0x0399, 0x039a, 0x039c, 0x03a6, 0x03ab, 0x03a8, 0x03a9, + 0x03ac, 0x039d, 0x00ac, 0x039f, 0x03a1, 0x2248, 0x03a4, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x03a5, 0x03a7, 0x0386, 0x0388, 0x0153, + 0x2013, 0x2015, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x0389, + 0x038a, 0x038c, 0x038e, 0x03ad, 0x03ae, 0x03af, 0x03cc, 0x038f, + 0x03cd, 0x03b1, 0x03b2, 0x03c8, 0x03b4, 0x03b5, 0x03c6, 0x03b3, + 0x03b7, 0x03b9, 0x03be, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03bf, + 0x03c0, 0x03ce, 0x03c1, 0x03c3, 0x03c4, 0x03b8, 0x03c9, 0x03c2, + 0x03c7, 0x03c5, 0x03b6, 0x03ca, 0x03cb, 0x0390, 0x03b0, 0x00ad + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0x92, 0xb4, 0x9b, 0xac, 0x8c, 0xa9, 0xc7, + 0xc2, 0xff, 0xa8, 0xae, 0xb1, 0x82, 0x84, 0xaf, + 0x81, 0xc8, 0x97, 0x80, 0x83, 0x85, 0x86, 0xa7, + 0x88, 0x89, 0x8a, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x94, 0x95, 0x99, 0x9a, 0xd6, 0x9d, 0x9e, 0x9f, + 0xcf, 0x8b, 0x87, 0xcd, 0xce, 0xd7, 0xd8, 0xd9, + 0xda, 0xdf, 0xfd, 0xb0, 0xb5, 0xa1, 0xa2, 0xb6, + 0xb7, 0xb8, 0xa3, 0xb9, 0xba, 0xa4, 0xbb, 0xc1, + 0xa5, 0xc3, 0xa6, 0xc4, 0xaa, 0xc6, 0xcb, 0xbc, + 0xcc, 0xbe, 0xbf, 0xab, 0xbd, 0xc0, 0xdb, 0xdc, + 0xdd, 0xfe, 0xe1, 0xe2, 0xe7, 0xe4, 0xe5, 0xfa, + 0xe8, 0xf5, 0xe9, 0xeb, 0xec, 0xed, 0xee, 0xea, + 0xef, 0xf0, 0xf2, 0xf7, 0xf3, 0xf4, 0xf9, 0xe6, + 0xf8, 0xe3, 0xf6, 0xfb, 0xfc, 0xde, 0xe0, 0xf1, + 0xd0, 0xd1, 0xd4, 0xd5, 0xd2, 0xd3, 0xa0, 0x96, + 0xc9, 0x98, 0x9c, 0x93, 0xc5, 0xad, 0xb2, 0xb3 + }, + 256 +}; +const charset_spec charset_CS_MAC_GREEK = { + CS_MAC_GREEK, read_sbcs, write_sbcs, &data_CS_MAC_GREEK +}; + +static const sbcs_data data_CS_MAC_CYRILLIC = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x2020, 0x00b0, 0x0490, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x0406, + 0x00ae, 0x00a9, 0x2122, 0x0402, 0x0452, 0x2260, 0x0403, 0x0453, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x0456, 0x00b5, 0x0491, 0x0408, + 0x0404, 0x0454, 0x0407, 0x0457, 0x0409, 0x0459, 0x040a, 0x045a, + 0x0458, 0x0405, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x040b, 0x045b, 0x040c, 0x045c, 0x0455, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x201e, + 0x040e, 0x045e, 0x040f, 0x045f, 0x2116, 0x0401, 0x0451, 0x044f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x20ac + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa3, 0xa4, 0xa9, 0xc7, 0xc2, 0xa8, 0xa1, + 0xb1, 0xb5, 0xa6, 0xc8, 0xd6, 0xc4, 0xdd, 0xab, + 0xae, 0xb8, 0xc1, 0xa7, 0xba, 0xb7, 0xbc, 0xbe, + 0xcb, 0xcd, 0xd8, 0xda, 0x80, 0x81, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, + 0x9c, 0x9d, 0x9e, 0x9f, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, + 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe, 0xdf, 0xde, 0xac, 0xaf, 0xb9, + 0xcf, 0xb4, 0xbb, 0xc0, 0xbd, 0xbf, 0xcc, 0xce, + 0xd9, 0xdb, 0xa2, 0xb6, 0xd0, 0xd1, 0xd4, 0xd5, + 0xd2, 0xd3, 0xd7, 0xa0, 0xa5, 0xc9, 0xff, 0xdc, + 0xaa, 0xc6, 0xc3, 0xb0, 0xc5, 0xad, 0xb2, 0xb3 + }, + 256 +}; +const charset_spec charset_CS_MAC_CYRILLIC = { + CS_MAC_CYRILLIC, read_sbcs, write_sbcs, &data_CS_MAC_CYRILLIC +}; + +static const sbcs_data data_CS_MAC_THAI = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00ab, 0x00bb, 0x2026, 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x201c, 0x201d, 0x0e4d, + ERROR , 0x2022, 0x0e31, 0x0e47, 0x0e34, 0x0e35, 0x0e36, 0x0e37, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x2018, 0x2019, ERROR , + 0x00a0, 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07, + 0x0e08, 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, + 0x0e10, 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17, + 0x0e18, 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, + 0x0e20, 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27, + 0x0e28, 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, + 0x0e30, 0x0e31, 0x0e32, 0x0e33, 0x0e34, 0x0e35, 0x0e36, 0x0e37, + 0x0e38, 0x0e39, 0x0e3a, 0x2060, 0x200b, 0x2013, 0x2014, 0x0e3f, + 0x0e40, 0x0e41, 0x0e42, 0x0e43, 0x0e44, 0x0e45, 0x0e46, 0x0e47, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x0e4d, 0x2122, 0x0e4f, + 0x0e50, 0x0e51, 0x0e52, 0x0e53, 0x0e54, 0x0e55, 0x0e56, 0x0e57, + 0x0e58, 0x0e59, 0x00ae, 0x00a9, ERROR , ERROR , ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xfb, 0x80, 0xfa, 0x81, 0xa1, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, + 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, + 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xef, 0xf0, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xdc, 0xdd, 0xde, 0x9d, 0x9e, 0x8d, 0x8e, + 0x91, 0x82, 0xdb, 0xee + }, + 228 +}; +const charset_spec charset_CS_MAC_THAI = { + CS_MAC_THAI, read_sbcs, write_sbcs, &data_CS_MAC_THAI +}; + +static const sbcs_data data_CS_MAC_CENTEURO = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x0100, 0x0101, 0x00c9, 0x0104, 0x00d6, 0x00dc, 0x00e1, + 0x0105, 0x010c, 0x00e4, 0x010d, 0x0106, 0x0107, 0x00e9, 0x0179, + 0x017a, 0x010e, 0x00ed, 0x010f, 0x0112, 0x0113, 0x0116, 0x00f3, + 0x0117, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x011a, 0x011b, 0x00fc, + 0x2020, 0x00b0, 0x0118, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x0119, 0x00a8, 0x2260, 0x0123, 0x012e, + 0x012f, 0x012a, 0x2264, 0x2265, 0x012b, 0x0136, 0x2202, 0x2211, + 0x0142, 0x013b, 0x013c, 0x013d, 0x013e, 0x0139, 0x013a, 0x0145, + 0x0146, 0x0143, 0x00ac, 0x221a, 0x0144, 0x0147, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x0148, 0x0150, 0x00d5, 0x0151, 0x014c, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x014d, 0x0154, 0x0155, 0x0158, 0x2039, 0x203a, 0x0159, 0x0156, + 0x0157, 0x0160, 0x201a, 0x201e, 0x0161, 0x015a, 0x015b, 0x00c1, + 0x0164, 0x0165, 0x00cd, 0x017d, 0x017e, 0x016a, 0x00d3, 0x00d4, + 0x016b, 0x016e, 0x00da, 0x016f, 0x0170, 0x0171, 0x0172, 0x0173, + 0x00dd, 0x00fd, 0x0137, 0x017b, 0x0141, 0x017c, 0x0122, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa3, 0xa4, 0xac, 0xa9, 0xc7, 0xc2, 0xa8, + 0xa1, 0xa6, 0xc8, 0xe7, 0x80, 0x83, 0xea, 0xee, + 0xef, 0xcd, 0x85, 0xf2, 0x86, 0xf8, 0xa7, 0x87, + 0x8a, 0x8e, 0x92, 0x97, 0x99, 0x9b, 0x9a, 0xd6, + 0x9c, 0x9f, 0xf9, 0x81, 0x82, 0x84, 0x88, 0x8c, + 0x8d, 0x89, 0x8b, 0x91, 0x93, 0x94, 0x95, 0x96, + 0x98, 0xa2, 0xab, 0x9d, 0x9e, 0xfe, 0xae, 0xb1, + 0xb4, 0xaf, 0xb0, 0xb5, 0xfa, 0xbd, 0xbe, 0xb9, + 0xba, 0xbb, 0xbc, 0xfc, 0xb8, 0xc1, 0xc4, 0xbf, + 0xc0, 0xc5, 0xcb, 0xcf, 0xd8, 0xcc, 0xce, 0xd9, + 0xda, 0xdf, 0xe0, 0xdb, 0xde, 0xe5, 0xe6, 0xe1, + 0xe4, 0xe8, 0xe9, 0xed, 0xf0, 0xf1, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0x8f, 0x90, 0xfb, 0xfd, 0xeb, + 0xec, 0xff, 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, + 0xd3, 0xe3, 0xa0, 0xa5, 0xc9, 0xdc, 0xdd, 0xaa, + 0xb6, 0xc6, 0xb7, 0xc3, 0xad, 0xb2, 0xb3, 0xd7 + }, + 256 +}; +const charset_spec charset_CS_MAC_CENTEURO = { + CS_MAC_CENTEURO, read_sbcs, write_sbcs, &data_CS_MAC_CENTEURO +}; + +static const sbcs_data data_CS_MAC_SYMBOL = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x2200, 0x0023, 0x2203, 0x0025, 0x0026, 0x220d, + 0x0028, 0x0029, 0x2217, 0x002b, 0x002c, 0x2212, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x2245, 0x0391, 0x0392, 0x03a7, 0x0394, 0x0395, 0x03a6, 0x0393, + 0x0397, 0x0399, 0x03d1, 0x039a, 0x039b, 0x039c, 0x039d, 0x039f, + 0x03a0, 0x0398, 0x03a1, 0x03a3, 0x03a4, 0x03a5, 0x03c2, 0x03a9, + 0x039e, 0x03a8, 0x0396, 0x005b, 0x2234, 0x005d, 0x22a5, 0x005f, + 0xf8e5, 0x03b1, 0x03b2, 0x03c7, 0x03b4, 0x03b5, 0x03c6, 0x03b3, + 0x03b7, 0x03b9, 0x03d5, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03bf, + 0x03c0, 0x03b8, 0x03c1, 0x03c3, 0x03c4, 0x03c5, 0x03d6, 0x03c9, + 0x03be, 0x03c8, 0x03b6, 0x007b, 0x007c, 0x007d, 0x223c, 0x007f, + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + 0x20ac, 0x03d2, 0x2032, 0x2264, 0x2044, 0x221e, 0x0192, 0x2663, + 0x2666, 0x2665, 0x2660, 0x2194, 0x2190, 0x2191, 0x2192, 0x2193, + 0x00b0, 0x00b1, 0x2033, 0x2265, 0x00d7, 0x221d, 0x2202, 0x2022, + 0x00f7, 0x2260, 0x2261, 0x2248, 0x2026, 0xf8e6, 0x23af, 0x21b5, + 0x2135, 0x2111, 0x211c, 0x2118, 0x2297, 0x2295, 0x2205, 0x2229, + 0x222a, 0x2283, 0x2287, 0x2284, 0x2282, 0x2286, 0x2208, 0x2209, + 0x2220, 0x2207, 0x00ae, 0x00a9, 0x2122, 0x220f, 0x221a, 0x22c5, + 0x00ac, 0x2227, 0x2228, 0x21d4, 0x21d0, 0x21d1, 0x21d2, 0x21d3, + 0x22c4, 0x3008, 0x00ae, 0x00a9, 0x2122, 0x2211, 0x239b, 0x239c, + 0x239d, 0x23a1, 0x23a2, 0x23a3, 0x23a7, 0x23a8, 0x23a9, 0x23aa, + 0xf8ff, 0x3009, 0x222b, 0x2320, 0x23ae, 0x2321, 0x239e, 0x239f, + 0x23a0, 0x23a4, 0x23a5, 0x23a6, 0x23ab, 0x23ac, 0x23ad, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x23, 0x25, 0x26, 0x28, 0x29, 0x2b, + 0x2c, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, + 0x3d, 0x3e, 0x3f, 0x5b, 0x5d, 0x5f, 0x7b, 0x7c, + 0x7d, 0x7f, 0xd3, 0xd8, 0xd2, 0xb0, 0xb1, 0xb4, + 0xb8, 0xa6, 0x41, 0x42, 0x47, 0x44, 0x45, 0x5a, + 0x48, 0x51, 0x49, 0x4b, 0x4c, 0x4d, 0x4e, 0x58, + 0x4f, 0x50, 0x52, 0x53, 0x54, 0x55, 0x46, 0x43, + 0x59, 0x57, 0x61, 0x62, 0x67, 0x64, 0x65, 0x7a, + 0x68, 0x71, 0x69, 0x6b, 0x6c, 0x6d, 0x6e, 0x78, + 0x6f, 0x70, 0x72, 0x56, 0x73, 0x74, 0x75, 0x66, + 0x63, 0x79, 0x77, 0x4a, 0xa1, 0x6a, 0x76, 0xb7, + 0xbc, 0xa2, 0xb2, 0xa4, 0xa0, 0xc1, 0xc3, 0xc2, + 0xd4, 0xc0, 0xac, 0xad, 0xae, 0xaf, 0xab, 0xbf, + 0xdc, 0xdd, 0xde, 0xdf, 0xdb, 0x22, 0xb6, 0x24, + 0xc6, 0xd1, 0xce, 0xcf, 0x27, 0xd5, 0xe5, 0x2d, + 0x2a, 0xd6, 0xb5, 0xa5, 0xd0, 0xd9, 0xda, 0xc7, + 0xc8, 0xf2, 0x5c, 0x7e, 0x40, 0xbb, 0xb9, 0xba, + 0xa3, 0xb3, 0xcc, 0xc9, 0xcb, 0xcd, 0xca, 0xc5, + 0xc4, 0x5e, 0xe0, 0xd7, 0xf3, 0xf5, 0xe6, 0xe7, + 0xe8, 0xf6, 0xf7, 0xf8, 0xe9, 0xea, 0xeb, 0xf9, + 0xfa, 0xfb, 0xec, 0xed, 0xee, 0xef, 0xfc, 0xfd, + 0xfe, 0xf4, 0xbe, 0xaa, 0xa7, 0xa9, 0xa8, 0xe1, + 0xf1, 0x60, 0xbd, 0xf0 + }, + 220 +}; +const charset_spec charset_CS_MAC_SYMBOL = { + CS_MAC_SYMBOL, read_sbcs, write_sbcs, &data_CS_MAC_SYMBOL +}; + +static const sbcs_data data_CS_MAC_DINGBATS = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x2701, 0x2702, 0x2703, 0x2704, 0x260e, 0x2706, 0x2707, + 0x2708, 0x2709, 0x261b, 0x261e, 0x270c, 0x270d, 0x270e, 0x270f, + 0x2710, 0x2711, 0x2712, 0x2713, 0x2714, 0x2715, 0x2716, 0x2717, + 0x2718, 0x2719, 0x271a, 0x271b, 0x271c, 0x271d, 0x271e, 0x271f, + 0x2720, 0x2721, 0x2722, 0x2723, 0x2724, 0x2725, 0x2726, 0x2727, + 0x2605, 0x2729, 0x272a, 0x272b, 0x272c, 0x272d, 0x272e, 0x272f, + 0x2730, 0x2731, 0x2732, 0x2733, 0x2734, 0x2735, 0x2736, 0x2737, + 0x2738, 0x2739, 0x273a, 0x273b, 0x273c, 0x273d, 0x273e, 0x273f, + 0x2740, 0x2741, 0x2742, 0x2743, 0x2744, 0x2745, 0x2746, 0x2747, + 0x2748, 0x2749, 0x274a, 0x274b, 0x25cf, 0x274d, 0x25a0, 0x274f, + 0x2750, 0x2751, 0x2752, 0x25b2, 0x25bc, 0x25c6, 0x2756, 0x25d7, + 0x2758, 0x2759, 0x275a, 0x275b, 0x275c, 0x275d, 0x275e, 0x007f, + 0x2768, 0x2769, 0x276a, 0x276b, 0x276c, 0x276d, 0x276e, 0x276f, + 0x2770, 0x2771, 0x2772, 0x2773, 0x2774, 0x2775, ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , 0x2761, 0x2762, 0x2763, 0x2764, 0x2765, 0x2766, 0x2767, + 0x2663, 0x2666, 0x2665, 0x2660, 0x2460, 0x2461, 0x2462, 0x2463, + 0x2464, 0x2465, 0x2466, 0x2467, 0x2468, 0x2469, 0x2776, 0x2777, + 0x2778, 0x2779, 0x277a, 0x277b, 0x277c, 0x277d, 0x277e, 0x277f, + 0x2780, 0x2781, 0x2782, 0x2783, 0x2784, 0x2785, 0x2786, 0x2787, + 0x2788, 0x2789, 0x278a, 0x278b, 0x278c, 0x278d, 0x278e, 0x278f, + 0x2790, 0x2791, 0x2792, 0x2793, 0x2794, 0x2192, 0x2194, 0x2195, + 0x2798, 0x2799, 0x279a, 0x279b, 0x279c, 0x279d, 0x279e, 0x279f, + 0x27a0, 0x27a1, 0x27a2, 0x27a3, 0x27a4, 0x27a5, 0x27a6, 0x27a7, + 0x27a8, 0x27a9, 0x27aa, 0x27ab, 0x27ac, 0x27ad, 0x27ae, 0x27af, + ERROR , 0x27b1, 0x27b2, 0x27b3, 0x27b4, 0x27b5, 0x27b6, 0x27b7, + 0x27b8, 0x27b9, 0x27ba, 0x27bb, 0x27bc, 0x27bd, 0x27be, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x7f, 0xd5, 0xd6, 0xd7, 0xac, 0xad, 0xae, + 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0x6e, + 0x73, 0x74, 0x75, 0x6c, 0x77, 0x48, 0x25, 0x2a, + 0x2b, 0xab, 0xa8, 0xaa, 0xa9, 0x21, 0x22, 0x23, + 0x24, 0x26, 0x27, 0x28, 0x29, 0x2c, 0x2d, 0x2e, + 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6d, 0x6f, 0x70, 0x71, + 0x72, 0x76, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, + 0x7e, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd8, 0xd9, 0xda, + 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe + }, + 235 +}; +const charset_spec charset_CS_MAC_DINGBATS = { + CS_MAC_DINGBATS, read_sbcs, write_sbcs, &data_CS_MAC_DINGBATS +}; + +static const sbcs_data data_CS_MAC_ROMAN_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x00a4, 0x2039, 0x203a, 0xfb01, 0xfb02, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xa4, 0xac, + 0xa9, 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, + 0xab, 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, + 0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, + 0xe9, 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, + 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, + 0xf2, 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, + 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x93, 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, + 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, + 0xd8, 0xf5, 0xce, 0xcf, 0xd9, 0xc4, 0xf6, 0xff, + 0xf9, 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, + 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, + 0xa0, 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, 0xda, + 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, 0xb0, 0xba, + 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0, 0xde, 0xdf + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMAN_OLD = { + CS_MAC_ROMAN_OLD, read_sbcs, write_sbcs, &data_CS_MAC_ROMAN_OLD +}; + +static const sbcs_data data_CS_MAC_CROATIAN_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x0160, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x017d, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x2206, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x0161, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x017e, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x0106, 0x00ab, + 0x010c, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x0110, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0xf8ff, 0x00a9, 0x2044, 0x00a4, 0x2039, 0x203a, 0x00c6, 0x00bb, + 0x2013, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x0107, 0x00c1, + 0x010d, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0x0111, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x03c0, 0x00cb, 0x02da, 0x00b8, 0x00ca, 0x00e6, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xa4, 0xac, 0xd9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xdf, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xde, 0x82, 0xe9, + 0x83, 0xfd, 0xfa, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, + 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xfe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, + 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xc6, + 0xe6, 0xc8, 0xe8, 0xd0, 0xf0, 0xf5, 0xce, 0xcf, + 0xa9, 0xb9, 0xae, 0xbe, 0xc4, 0xf6, 0xff, 0xfb, + 0xf7, 0xbd, 0xf9, 0xe0, 0xd1, 0xd4, 0xd5, 0xe2, + 0xd2, 0xd3, 0xe3, 0xa0, 0xa5, 0xc9, 0xe4, 0xdc, + 0xdd, 0xda, 0xaa, 0xb6, 0xb4, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xd8 + }, + 256 +}; +const charset_spec charset_CS_MAC_CROATIAN_OLD = { + CS_MAC_CROATIAN_OLD, read_sbcs, write_sbcs, &data_CS_MAC_CROATIAN_OLD +}; + +static const sbcs_data data_CS_MAC_ICELAND_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x00a4, 0x00d0, 0x00f0, 0x00de, 0x00fe, + 0x00fd, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xa4, 0xac, + 0xa9, 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, + 0xab, 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, + 0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, + 0xe9, 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, + 0xdc, 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, + 0xf4, 0xf2, 0xf3, 0x86, 0xa0, 0xde, 0xa7, 0x88, + 0x87, 0x89, 0x8b, 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, + 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, 0x95, 0xdd, + 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, 0xbf, + 0x9d, 0x9c, 0x9e, 0x9f, 0xe0, 0xdf, 0xd8, 0xf5, + 0xce, 0xcf, 0xd9, 0xc4, 0xf6, 0xff, 0xf9, 0xfa, + 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, 0xd1, + 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, 0xa5, 0xc9, + 0xe4, 0xda, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ICELAND_OLD = { + CS_MAC_ICELAND_OLD, read_sbcs, write_sbcs, &data_CS_MAC_ICELAND_OLD +}; + +static const sbcs_data data_CS_MAC_ROMANIAN_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x0102, 0x0218, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x0103, 0x0219, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x00a4, 0x2039, 0x203a, 0x021a, 0x021b, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xa4, 0xac, + 0xa9, 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, + 0xab, 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, + 0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xf4, 0xf2, 0xf3, + 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, + 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, + 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, + 0x9d, 0x9c, 0x9e, 0x9f, 0xd8, 0xae, 0xbe, 0xf5, + 0xce, 0xcf, 0xd9, 0xc4, 0xaf, 0xbf, 0xde, 0xdf, + 0xf6, 0xff, 0xf9, 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, + 0xbd, 0xb9, 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, + 0xd3, 0xe3, 0xa0, 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, + 0xdd, 0xda, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMANIAN_OLD = { + CS_MAC_ROMANIAN_OLD, read_sbcs, write_sbcs, &data_CS_MAC_ROMANIAN_OLD +}; + +static const sbcs_data data_CS_MAC_GREEK_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00b9, 0x00b2, 0x00c9, 0x00b3, 0x00d6, 0x00dc, 0x0385, + 0x00e0, 0x00e2, 0x00e4, 0x0384, 0x00a8, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00a3, 0x2122, 0x00ee, 0x00ef, 0x2022, 0x00bd, + 0x2030, 0x00f4, 0x00f6, 0x00a6, 0x00ad, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x0393, 0x0394, 0x0398, 0x039b, 0x039e, 0x03a0, 0x00df, + 0x00ae, 0x00a9, 0x03a3, 0x03aa, 0x00a7, 0x2260, 0x00b0, 0x00b7, + 0x0391, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x0392, 0x0395, 0x0396, + 0x0397, 0x0399, 0x039a, 0x039c, 0x03a6, 0x03ab, 0x03a8, 0x03a9, + 0x03ac, 0x039d, 0x00ac, 0x039f, 0x03a1, 0x2248, 0x03a4, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x03a5, 0x03a7, 0x0386, 0x0388, 0x0153, + 0x2013, 0x2015, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x0389, + 0x038a, 0x038c, 0x038e, 0x03ad, 0x03ae, 0x03af, 0x03cc, 0x038f, + 0x03cd, 0x03b1, 0x03b2, 0x03c8, 0x03b4, 0x03b5, 0x03c6, 0x03b3, + 0x03b7, 0x03b9, 0x03be, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03bf, + 0x03c0, 0x03ce, 0x03c1, 0x03c3, 0x03c4, 0x03b8, 0x03c9, 0x03c2, + 0x03c7, 0x03c5, 0x03b6, 0x03ca, 0x03cb, 0x0390, 0x03b0, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0x92, 0xb4, 0x9b, 0xac, 0x8c, 0xa9, 0xc7, + 0xc2, 0x9c, 0xa8, 0xae, 0xb1, 0x82, 0x84, 0xaf, + 0x81, 0xc8, 0x97, 0x80, 0x83, 0x85, 0x86, 0xa7, + 0x88, 0x89, 0x8a, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x94, 0x95, 0x99, 0x9a, 0xd6, 0x9d, 0x9e, 0x9f, + 0xcf, 0x8b, 0x87, 0xcd, 0xce, 0xd7, 0xd8, 0xd9, + 0xda, 0xdf, 0xfd, 0xb0, 0xb5, 0xa1, 0xa2, 0xb6, + 0xb7, 0xb8, 0xa3, 0xb9, 0xba, 0xa4, 0xbb, 0xc1, + 0xa5, 0xc3, 0xa6, 0xc4, 0xaa, 0xc6, 0xcb, 0xbc, + 0xcc, 0xbe, 0xbf, 0xab, 0xbd, 0xc0, 0xdb, 0xdc, + 0xdd, 0xfe, 0xe1, 0xe2, 0xe7, 0xe4, 0xe5, 0xfa, + 0xe8, 0xf5, 0xe9, 0xeb, 0xec, 0xed, 0xee, 0xea, + 0xef, 0xf0, 0xf2, 0xf7, 0xf3, 0xf4, 0xf9, 0xe6, + 0xf8, 0xe3, 0xf6, 0xfb, 0xfc, 0xde, 0xe0, 0xf1, + 0xd0, 0xd1, 0xd4, 0xd5, 0xd2, 0xd3, 0xa0, 0x96, + 0xc9, 0x98, 0x93, 0xc5, 0xad, 0xb2, 0xb3 + }, + 255 +}; +const charset_spec charset_CS_MAC_GREEK_OLD = { + CS_MAC_GREEK_OLD, read_sbcs, write_sbcs, &data_CS_MAC_GREEK_OLD +}; + +static const sbcs_data data_CS_MAC_CYRILLIC_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x0406, + 0x00ae, 0x00a9, 0x2122, 0x0402, 0x0452, 0x2260, 0x0403, 0x0453, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x0456, 0x00b5, 0x2022, 0x0408, + 0x0404, 0x0454, 0x0407, 0x0457, 0x0409, 0x0459, 0x040a, 0x045a, + 0x0458, 0x0405, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x040b, 0x045b, 0x040c, 0x045c, 0x0455, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x201e, + 0x040e, 0x045e, 0x040f, 0x045f, 0x2116, 0x0401, 0x0451, 0x044f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x00a4 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa2, 0xa3, 0xff, 0xa4, 0xa9, 0xc7, 0xc2, + 0xa8, 0xa1, 0xb1, 0xb5, 0xa6, 0xc8, 0xd6, 0xc4, + 0xdd, 0xab, 0xae, 0xb8, 0xc1, 0xa7, 0xba, 0xb7, + 0xbc, 0xbe, 0xcb, 0xcd, 0xd8, 0xda, 0x80, 0x81, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xe0, 0xe1, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xdf, 0xde, 0xac, + 0xaf, 0xb9, 0xcf, 0xb4, 0xbb, 0xc0, 0xbd, 0xbf, + 0xcc, 0xce, 0xd9, 0xdb, 0xd0, 0xd1, 0xd4, 0xd5, + 0xd2, 0xd3, 0xd7, 0xa0, 0xa5, 0xc9, 0xdc, 0xaa, + 0xc6, 0xc3, 0xb0, 0xc5, 0xad, 0xb2, 0xb3 + }, + 255 +}; +const charset_spec charset_CS_MAC_CYRILLIC_OLD = { + CS_MAC_CYRILLIC_OLD, read_sbcs, write_sbcs, &data_CS_MAC_CYRILLIC_OLD +}; + +static const sbcs_data data_CS_MAC_UKRAINE = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x2020, 0x00b0, 0x0490, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x0406, + 0x00ae, 0x00a9, 0x2122, 0x0402, 0x0452, 0x2260, 0x0403, 0x0453, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x0456, 0x00b5, 0x0491, 0x0408, + 0x0404, 0x0454, 0x0407, 0x0457, 0x0409, 0x0459, 0x040a, 0x045a, + 0x0458, 0x0405, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x040b, 0x045b, 0x040c, 0x045c, 0x0455, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x201e, + 0x040e, 0x045e, 0x040f, 0x045f, 0x2116, 0x0401, 0x0451, 0x044f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x00a4 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa3, 0xff, 0xa4, 0xa9, 0xc7, 0xc2, 0xa8, + 0xa1, 0xb1, 0xb5, 0xa6, 0xc8, 0xd6, 0xc4, 0xdd, + 0xab, 0xae, 0xb8, 0xc1, 0xa7, 0xba, 0xb7, 0xbc, + 0xbe, 0xcb, 0xcd, 0xd8, 0xda, 0x80, 0x81, 0x82, + 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, + 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0xfb, 0xfc, 0xfd, 0xfe, 0xdf, 0xde, 0xac, 0xaf, + 0xb9, 0xcf, 0xb4, 0xbb, 0xc0, 0xbd, 0xbf, 0xcc, + 0xce, 0xd9, 0xdb, 0xa2, 0xb6, 0xd0, 0xd1, 0xd4, + 0xd5, 0xd2, 0xd3, 0xd7, 0xa0, 0xa5, 0xc9, 0xdc, + 0xaa, 0xc6, 0xc3, 0xb0, 0xc5, 0xad, 0xb2, 0xb3 + }, + 256 +}; +const charset_spec charset_CS_MAC_UKRAINE = { + CS_MAC_UKRAINE, read_sbcs, write_sbcs, &data_CS_MAC_UKRAINE +}; + +static const sbcs_data data_CS_MAC_VT100 = { + { + 0x2400, 0x2401, 0x2402, 0x2403, 0x2404, 0x2405, 0x2406, 0x2407, + 0x2408, 0x2409, 0x240a, 0x240b, 0x240c, 0x240d, 0x240e, 0x240f, + 0x2410, 0x2411, 0x2412, 0x2413, 0x2414, 0x2415, 0x2416, 0x2417, + 0x2418, 0x2419, 0x241a, 0x241b, 0x241c, 0x241d, 0x241e, 0x241f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2421, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x00b8, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x00d7, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x00b9, 0x00b2, + 0x00b3, 0x03c0, 0x00a6, 0x00aa, 0x00ba, 0x2592, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x00bd, 0x0192, 0x00bc, 0x00be, 0x00ab, + 0x00bb, 0x2026, ERROR , 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x2518, 0x2510, 0x250c, 0x2514, 0x00f7, 0x2022, + 0x00ff, 0x0178, 0x253c, 0x20ac, 0x00d0, 0x00f0, 0x00fe, 0x00de, + 0x00fd, 0x00b7, 0x23ba, 0x23bb, 0x2500, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + ERROR , 0x00d2, 0x00da, 0x00db, 0x00d9, 0x23bc, 0x23bd, 0x251c, + 0x2524, 0x2534, 0x252c, 0x2502, ERROR , ERROR , ERROR , ERROR + }, + { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0xc1, + 0xa2, 0xa3, 0xb4, 0xba, 0xa4, 0xac, 0xa9, 0xbb, + 0xc7, 0xc2, 0xa8, 0xa1, 0xb1, 0xb7, 0xb8, 0xab, + 0xb5, 0xa6, 0xe1, 0xa5, 0xb6, 0xbc, 0xc8, 0xc5, + 0xc3, 0xc6, 0xc0, 0xcb, 0xe7, 0xe5, 0xcc, 0x80, + 0x81, 0xae, 0x82, 0xe9, 0x83, 0xe6, 0xe8, 0xed, + 0xea, 0xeb, 0xec, 0xdc, 0x84, 0xf1, 0xee, 0xef, + 0xcd, 0x85, 0xb0, 0xaf, 0xf4, 0xf2, 0xf3, 0x86, + 0xa0, 0xdf, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0xdd, 0x96, 0x98, 0x97, 0x99, + 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, + 0xe0, 0xde, 0xd8, 0xce, 0xcf, 0xd9, 0xc4, 0xb9, + 0xd0, 0xd1, 0xd7, 0xc9, 0xdb, 0xaa, 0xad, 0xb2, + 0xb3, 0xe2, 0xe3, 0xf5, 0xf6, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x7f, 0xe4, 0xfb, + 0xd4, 0xd3, 0xd5, 0xd2, 0xf7, 0xf8, 0xfa, 0xf9, + 0xda, 0xbd + }, + 250 +}; +const charset_spec charset_CS_MAC_VT100 = { + CS_MAC_VT100, read_sbcs, write_sbcs, &data_CS_MAC_VT100 +}; + +static const sbcs_data data_CS_MAC_VT100_OLD = { + { + 0x2400, 0x2401, 0x2402, 0x2403, 0x2404, 0x2405, 0x2406, 0x2407, + 0x2408, 0x2409, 0x240a, 0x240b, 0x240c, 0x240d, 0x240e, 0x240f, + 0x2410, 0x2411, 0x2412, 0x2413, 0x2414, 0x2415, 0x2416, 0x2417, + 0x2418, 0x2419, 0x241a, 0x241b, 0x241c, 0x241d, 0x241e, 0x241f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2421, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x00b8, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x00d7, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x00b9, 0x00b2, + 0x00b3, 0x03c0, 0x00a6, 0x00aa, 0x00ba, 0x2592, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x00bd, 0x0192, 0x00bc, 0x00be, 0x00ab, + 0x00bb, 0x2026, ERROR , 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x2518, 0x2510, 0x250c, 0x2514, 0x00f7, 0x2022, + 0x00ff, 0x0178, 0x253c, 0x00a4, 0x00d0, 0x00f0, 0x00fe, 0x00de, + 0x00fd, 0x00b7, 0x23ba, 0x23bb, 0x2500, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + ERROR , 0x00d2, 0x00da, 0x00db, 0x00d9, 0x23bc, 0x23bd, 0x251c, + 0x2524, 0x2534, 0x252c, 0x2502, ERROR , ERROR , ERROR , ERROR + }, + { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0xc1, + 0xa2, 0xa3, 0xdb, 0xb4, 0xba, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xa1, 0xb1, 0xb7, 0xb8, + 0xab, 0xb5, 0xa6, 0xe1, 0xa5, 0xb6, 0xbc, 0xc8, + 0xc5, 0xc3, 0xc6, 0xc0, 0xcb, 0xe7, 0xe5, 0xcc, + 0x80, 0x81, 0xae, 0x82, 0xe9, 0x83, 0xe6, 0xe8, + 0xed, 0xea, 0xeb, 0xec, 0xdc, 0x84, 0xf1, 0xee, + 0xef, 0xcd, 0x85, 0xb0, 0xaf, 0xf4, 0xf2, 0xf3, + 0x86, 0xa0, 0xdf, 0xa7, 0x88, 0x87, 0x89, 0x8b, + 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x93, 0x92, 0x94, 0x95, 0xdd, 0x96, 0x98, 0x97, + 0x99, 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, + 0x9f, 0xe0, 0xde, 0xd8, 0xce, 0xcf, 0xd9, 0xc4, + 0xb9, 0xd0, 0xd1, 0xd7, 0xc9, 0xaa, 0xad, 0xb2, + 0xb3, 0xe2, 0xe3, 0xf5, 0xf6, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x7f, 0xe4, 0xfb, + 0xd4, 0xd3, 0xd5, 0xd2, 0xf7, 0xf8, 0xfa, 0xf9, + 0xda, 0xbd + }, + 250 +}; +const charset_spec charset_CS_MAC_VT100_OLD = { + CS_MAC_VT100_OLD, read_sbcs, write_sbcs, &data_CS_MAC_VT100_OLD +}; + +static const sbcs_data data_CS_VISCII = { + { + 0x0000, 0x0001, 0x1eb2, 0x0003, 0x0004, 0x1eb4, 0x1eaa, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x1ef6, 0x0015, 0x0016, 0x0017, + 0x0018, 0x1ef8, 0x001a, 0x001b, 0x001c, 0x001d, 0x1ef4, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x1ea0, 0x1eae, 0x1eb0, 0x1eb6, 0x1ea4, 0x1ea6, 0x1ea8, 0x1eac, + 0x1ebc, 0x1eb8, 0x1ebe, 0x1ec0, 0x1ec2, 0x1ec4, 0x1ec6, 0x1ed0, + 0x1ed2, 0x1ed4, 0x1ed6, 0x1ed8, 0x1ee2, 0x1eda, 0x1edc, 0x1ede, + 0x1eca, 0x1ece, 0x1ecc, 0x1ec8, 0x1ee6, 0x0168, 0x1ee4, 0x1ef2, + 0x00d5, 0x1eaf, 0x1eb1, 0x1eb7, 0x1ea5, 0x1ea7, 0x1ea8, 0x1ead, + 0x1ebd, 0x1eb9, 0x1ebf, 0x1ec1, 0x1ec3, 0x1ec5, 0x1ec7, 0x1ed1, + 0x1ed3, 0x1ed5, 0x1ed7, 0x1ee0, 0x01a0, 0x1ed9, 0x1edd, 0x1edf, + 0x1ecb, 0x1ef0, 0x1ee8, 0x1eea, 0x1eec, 0x01a1, 0x1edb, 0x01af, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x1ea2, 0x0102, 0x1eb3, 0x1eb5, + 0x00c8, 0x00c9, 0x00ca, 0x1eba, 0x00cc, 0x00cd, 0x0128, 0x1ef3, + 0x0110, 0x1ee9, 0x00d2, 0x00d3, 0x00d4, 0x1ea1, 0x1ef7, 0x1eeb, + 0x1eed, 0x00d9, 0x00da, 0x1ef9, 0x1ef5, 0x00dd, 0x1ee1, 0x01b0, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x1ea3, 0x0103, 0x1eef, 0x1eab, + 0x00e8, 0x00e9, 0x00ea, 0x1ebb, 0x00ec, 0x00ed, 0x0129, 0x1ec9, + 0x0111, 0x1ef1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x1ecf, 0x1ecd, + 0x1ee5, 0x00f9, 0x00fa, 0x0169, 0x1ee7, 0x00fd, 0x1ee3, 0x1eee + }, + { + 0x00, 0x01, 0x03, 0x04, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x1c, + 0x1d, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, + 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, + 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, + 0x7e, 0x7f, 0xc0, 0xc1, 0xc2, 0xc3, 0xc8, 0xc9, + 0xca, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xa0, 0xd9, + 0xda, 0xdd, 0xe0, 0xe1, 0xe2, 0xe3, 0xe8, 0xe9, + 0xea, 0xec, 0xed, 0xf2, 0xf3, 0xf4, 0xf5, 0xf9, + 0xfa, 0xfd, 0xc5, 0xe5, 0xd0, 0xf0, 0xce, 0xee, + 0x9d, 0xfb, 0xb4, 0xbd, 0xbf, 0xdf, 0x80, 0xd5, + 0xc4, 0xe4, 0x84, 0xa4, 0x85, 0xa5, 0x86, 0x06, + 0xe7, 0x87, 0xa7, 0x81, 0xa1, 0x82, 0xa2, 0x02, + 0xc6, 0x05, 0xc7, 0x83, 0xa3, 0x89, 0xa9, 0xcb, + 0xeb, 0x88, 0xa8, 0x8a, 0xaa, 0x8b, 0xab, 0x8c, + 0xac, 0x8d, 0xad, 0x8e, 0xae, 0x9b, 0xef, 0x98, + 0xb8, 0x9a, 0xf7, 0x99, 0xf6, 0x8f, 0xaf, 0x90, + 0xb0, 0x91, 0xb1, 0x92, 0xb2, 0x93, 0xb5, 0x95, + 0xbe, 0x96, 0xb6, 0x97, 0xb7, 0xb3, 0xde, 0x94, + 0xfe, 0x9e, 0xf8, 0x9c, 0xfc, 0xba, 0xd1, 0xbb, + 0xd7, 0xbc, 0xd8, 0xff, 0xe6, 0xb9, 0xf1, 0x9f, + 0xcf, 0x1e, 0xdc, 0x14, 0xd6, 0x19, 0xdb + }, + 255 +}; +const charset_spec charset_CS_VISCII = { + CS_VISCII, read_sbcs, write_sbcs, &data_CS_VISCII +}; + +static const sbcs_data data_CS_HP_ROMAN8 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00c0, 0x00c2, 0x00c8, 0x00ca, 0x00cb, 0x00ce, 0x00cf, + 0x00b4, 0x02cb, 0x02c6, 0x00a8, 0x02dc, 0x00d9, 0x00db, 0x20a4, + 0x00af, 0x00dd, 0x00fd, 0x00b0, 0x00c7, 0x00e7, 0x00d1, 0x00f1, + 0x00a1, 0x00bf, 0x00a4, 0x00a3, 0x00a5, 0x00a7, 0x0192, 0x00a2, + 0x00e2, 0x00ea, 0x00f4, 0x00fb, 0x00e1, 0x00e9, 0x00f3, 0x00fa, + 0x00e0, 0x00e8, 0x00f2, 0x00f9, 0x00e4, 0x00eb, 0x00f6, 0x00fc, + 0x00c5, 0x00ee, 0x00d8, 0x00c6, 0x00e5, 0x00ed, 0x00f8, 0x00e6, + 0x00c4, 0x00ec, 0x00d6, 0x00dc, 0x00c9, 0x00ef, 0x00df, 0x00d4, + 0x00c1, 0x00c3, 0x00e3, 0x00d0, 0x00f0, 0x00cd, 0x00cc, 0x00d3, + 0x00d2, 0x00d5, 0x00f5, 0x0160, 0x0161, 0x00da, 0x0178, 0x00ff, + 0x00de, 0x00fe, 0x00b7, 0x00b5, 0x00b6, 0x00be, 0x2014, 0x00bc, + 0x00bd, 0x00aa, 0x00ba, 0x00ab, 0x25a0, 0x00bb, 0x00b1, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xb8, 0xbf, 0xbb, 0xba, 0xbc, 0xbd, 0xab, + 0xf9, 0xfb, 0xb0, 0xb3, 0xfe, 0xa8, 0xf3, 0xf4, + 0xf2, 0xfa, 0xfd, 0xf7, 0xf8, 0xf5, 0xb9, 0xa1, + 0xe0, 0xa2, 0xe1, 0xd8, 0xd0, 0xd3, 0xb4, 0xa3, + 0xdc, 0xa4, 0xa5, 0xe6, 0xe5, 0xa6, 0xa7, 0xe3, + 0xb6, 0xe8, 0xe7, 0xdf, 0xe9, 0xda, 0xd2, 0xad, + 0xed, 0xae, 0xdb, 0xb1, 0xf0, 0xde, 0xc8, 0xc4, + 0xc0, 0xe2, 0xcc, 0xd4, 0xd7, 0xb5, 0xc9, 0xc5, + 0xc1, 0xcd, 0xd9, 0xd5, 0xd1, 0xdd, 0xe4, 0xb7, + 0xca, 0xc6, 0xc2, 0xea, 0xce, 0xd6, 0xcb, 0xc7, + 0xc3, 0xcf, 0xb2, 0xf1, 0xef, 0xeb, 0xec, 0xee, + 0xbe, 0xaa, 0xa9, 0xac, 0xf6, 0xaf, 0xfc + }, + 255 +}; +const charset_spec charset_CS_HP_ROMAN8 = { + CS_HP_ROMAN8, read_sbcs, write_sbcs, &data_CS_HP_ROMAN8 +}; + +static const sbcs_data data_CS_DEC_MCS = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + ERROR , 0x00a1, 0x00a2, 0x00a3, ERROR , 0x00a5, ERROR , 0x00a7, + 0x00a4, 0x00a9, 0x00aa, 0x00ab, ERROR , ERROR , ERROR , ERROR , + 0x00b0, 0x00b1, 0x00b2, 0x00b3, ERROR , 0x00b5, 0x00b6, 0x00b7, + ERROR , 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, ERROR , 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + ERROR , 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x0152, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0178, ERROR , 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + ERROR , 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x0153, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00ff, ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa1, 0xa2, 0xa3, 0xa8, 0xa5, 0xa7, 0xa9, 0xaa, + 0xab, 0xb0, 0xb1, 0xb2, 0xb3, 0xb5, 0xb6, 0xb7, + 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbf, 0xc0, 0xc1, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd1, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xd9, 0xda, 0xdb, + 0xdc, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, + 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, + 0xee, 0xef, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xd7, 0xf7, + 0xdd + }, + 241 +}; +const charset_spec charset_CS_DEC_MCS = { + CS_DEC_MCS, read_sbcs, write_sbcs, &data_CS_DEC_MCS +}; + +#else /* ENUM_CHARSETS */ + +ENUM_CHARSET(CS_ISO8859_1) +ENUM_CHARSET(CS_ISO8859_2) +ENUM_CHARSET(CS_ISO8859_3) +ENUM_CHARSET(CS_ISO8859_4) +ENUM_CHARSET(CS_ISO8859_5) +ENUM_CHARSET(CS_ISO8859_6) +ENUM_CHARSET(CS_ISO8859_7) +ENUM_CHARSET(CS_ISO8859_8) +ENUM_CHARSET(CS_ISO8859_9) +ENUM_CHARSET(CS_ISO8859_10) +ENUM_CHARSET(CS_ISO8859_11) +ENUM_CHARSET(CS_ISO8859_13) +ENUM_CHARSET(CS_ISO8859_14) +ENUM_CHARSET(CS_ISO8859_15) +ENUM_CHARSET(CS_ISO8859_16) +ENUM_CHARSET(CS_ISO8859_1_X11) +ENUM_CHARSET(CS_CP437) +ENUM_CHARSET(CS_CP850) +ENUM_CHARSET(CS_CP866) +ENUM_CHARSET(CS_CP1250) +ENUM_CHARSET(CS_CP1251) +ENUM_CHARSET(CS_CP1252) +ENUM_CHARSET(CS_CP1253) +ENUM_CHARSET(CS_CP1254) +ENUM_CHARSET(CS_CP1255) +ENUM_CHARSET(CS_CP1256) +ENUM_CHARSET(CS_CP1257) +ENUM_CHARSET(CS_CP1258) +ENUM_CHARSET(CS_KOI8_R) +ENUM_CHARSET(CS_KOI8_U) +ENUM_CHARSET(CS_MAC_ROMAN) +ENUM_CHARSET(CS_MAC_TURKISH) +ENUM_CHARSET(CS_MAC_CROATIAN) +ENUM_CHARSET(CS_MAC_ICELAND) +ENUM_CHARSET(CS_MAC_ROMANIAN) +ENUM_CHARSET(CS_MAC_GREEK) +ENUM_CHARSET(CS_MAC_CYRILLIC) +ENUM_CHARSET(CS_MAC_THAI) +ENUM_CHARSET(CS_MAC_CENTEURO) +ENUM_CHARSET(CS_MAC_SYMBOL) +ENUM_CHARSET(CS_MAC_DINGBATS) +ENUM_CHARSET(CS_MAC_ROMAN_OLD) +ENUM_CHARSET(CS_MAC_CROATIAN_OLD) +ENUM_CHARSET(CS_MAC_ICELAND_OLD) +ENUM_CHARSET(CS_MAC_ROMANIAN_OLD) +ENUM_CHARSET(CS_MAC_GREEK_OLD) +ENUM_CHARSET(CS_MAC_CYRILLIC_OLD) +ENUM_CHARSET(CS_MAC_UKRAINE) +ENUM_CHARSET(CS_MAC_VT100) +ENUM_CHARSET(CS_MAC_VT100_OLD) +ENUM_CHARSET(CS_VISCII) +ENUM_CHARSET(CS_HP_ROMAN8) +ENUM_CHARSET(CS_DEC_MCS) + +#endif /* ENUM_CHARSETS */ diff --git a/putty/CHARSET/SBCSGEN.PL b/putty/CHARSET/SBCSGEN.PL new file mode 100644 index 0000000..335c8e0 --- /dev/null +++ b/putty/CHARSET/SBCSGEN.PL @@ -0,0 +1,110 @@ +#!/usr/bin/env perl -w + +# This script generates sbcsdat.c (the data for all the SBCSes) from its +# source form sbcs.dat. + +$infile = "sbcs.dat"; +$outfile = "sbcsdat.c"; + +open FOO, $infile; +open BAR, ">$outfile"; +select BAR; + +print "/*\n"; +print " * sbcsdat.c - data definitions for single-byte character sets.\n"; +print " *\n"; +print " * Generated by sbcsgen.pl from sbcs.dat.\n"; +print " * You should edit those files rather than editing this one.\n"; +print " */\n"; +print "\n"; +print "#ifndef ENUM_CHARSETS\n"; +print "\n"; +print "#include \"charset.h\"\n"; +print "#include \"internal.h\"\n"; +print "\n"; + +my $charsetname = undef; +my @vals = (); + +my @charsetnames = (); +my @sortpriority = (); + +while () { + chomp; + if (/^charset (.*)$/) { + $charsetname = $1; + @vals = (); + @sortpriority = map { 0 } 0..255; + } elsif (/^sortpriority ([^-]*)-([^-]*) (.*)$/) { + for ($i = hex $1; $i <= hex $2; $i++) { + $sortpriority[$i] += $3; + } + } elsif (/^[0-9a-fA-FX]/) { + push @vals, map { $_ eq "XXXX" ? -1 : hex $_ } split / +/, $_; + if (scalar @vals > 256) { + die "$infile:$.: charset $charsetname has more than 256 values\n"; + } elsif (scalar @vals == 256) { + &outcharset($charsetname, \@vals, \@sortpriority); + push @charsetnames, $charsetname; + $charsetname = undef; + @vals = (); + @sortpriority = map { 0 } 0..255; + } + } +} + +print "#else /* ENUM_CHARSETS */\n"; +print "\n"; + +foreach $i (@charsetnames) { + print "ENUM_CHARSET($i)\n"; +} + +print "\n"; +print "#endif /* ENUM_CHARSETS */\n"; + +sub outcharset($$$) { + my ($name, $vals, $sortpriority) = @_; + my ($prefix, $i, @sorted); + + print "static const sbcs_data data_$name = {\n"; + print " {\n"; + $prefix = " "; + @sorted = (); + for ($i = 0; $i < 256; $i++) { + if ($vals->[$i] < 0) { + printf "%sERROR ", $prefix; + } else { + printf "%s0x%04x", $prefix, $vals->[$i]; + die "ooh? $i\n" unless defined $sortpriority->[$i]; + push @sorted, [$i, $vals->[$i], 0+$sortpriority->[$i]]; + } + if ($i % 8 == 7) { + $prefix = ",\n "; + } else { + $prefix = ", "; + } + } + print "\n },\n {\n"; + @sorted = sort { ($a->[1] == $b->[1] ? + $b->[2] <=> $a->[2] : + $a->[1] <=> $b->[1]) || + $a->[0] <=> $b->[0] } @sorted; + $prefix = " "; + $uval = -1; + for ($i = $j = 0; $i < scalar @sorted; $i++) { + next if ($uval == $sorted[$i]->[1]); # low-priority alternative + $uval = $sorted[$i]->[1]; + printf "%s0x%02x", $prefix, $sorted[$i]->[0]; + if ($j % 8 == 7) { + $prefix = ",\n "; + } else { + $prefix = ", "; + } + $j++; + } + printf "\n },\n %d\n", $j; + print "};\n"; + print "const charset_spec charset_$name = {\n" . + " $name, read_sbcs, write_sbcs, &data_$name\n};\n\n"; +} diff --git a/putty/CHARSET/SLOOKUP.C b/putty/CHARSET/SLOOKUP.C new file mode 100644 index 0000000..921a174 --- /dev/null +++ b/putty/CHARSET/SLOOKUP.C @@ -0,0 +1,29 @@ +/* + * slookup.c - static lookup of character sets. + */ + +#include "charset.h" +#include "internal.h" + +#define ENUM_CHARSET(x) extern charset_spec const charset_##x; +#include "enum.c" +#undef ENUM_CHARSET + +static charset_spec const *const cs_table[] = { + +#define ENUM_CHARSET(x) &charset_##x, +#include "enum.c" +#undef ENUM_CHARSET + +}; + +charset_spec const *charset_find_spec(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(cs_table); i++) + if (cs_table[i]->charset == charset) + return cs_table[i]; + + return NULL; +} diff --git a/putty/CHARSET/TOUCS.C b/putty/CHARSET/TOUCS.C new file mode 100644 index 0000000..658eb73 --- /dev/null +++ b/putty/CHARSET/TOUCS.C @@ -0,0 +1,89 @@ +/* + * toucs.c - convert charsets to Unicode. + */ + +#include "charset.h" +#include "internal.h" + +struct unicode_emit_param { + wchar_t *output; + int outlen; + const wchar_t *errstr; + int errlen; + int stopped; +}; + +static void unicode_emit(void *ctx, long int output) +{ + struct unicode_emit_param *param = (struct unicode_emit_param *)ctx; + wchar_t outval; + wchar_t const *p; + int outlen; + + if (output == ERROR) { + if (param->errstr) { + p = param->errstr; + outlen = param->errlen; + } else { + outval = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */ + p = &outval; + outlen = 1; + } + } else { + outval = output; + p = &outval; + outlen = 1; + } + + if (param->outlen >= outlen) { + while (outlen > 0) { + *param->output++ = *p++; + param->outlen--; + outlen--; + } + } else { + param->stopped = 1; + } +} + +int charset_to_unicode(char **input, int *inlen, wchar_t *output, int outlen, + int charset, charset_state *state, + const wchar_t *errstr, int errlen) +{ + charset_spec const *spec = charset_find_spec(charset); + charset_state localstate; + struct unicode_emit_param param; + + param.output = output; + param.outlen = outlen; + param.errstr = errstr; + param.errlen = errlen; + param.stopped = 0; + + if (!state) { + localstate.s0 = 0; + } else { + localstate = *state; /* structure copy */ + } + + while (*inlen > 0) { + int lenbefore = param.output - output; + spec->read(spec, (unsigned char)**input, &localstate, + unicode_emit, ¶m); + if (param.stopped) { + /* + * The emit function has _tried_ to output some + * characters, but ran up against the end of the + * buffer. Leave immediately, and return what happened + * _before_ attempting to process this character. + */ + return lenbefore; + } + if (state) + *state = localstate; /* structure copy */ + (*input)++; + (*inlen)--; + } + + return param.output - output; +} diff --git a/putty/CHARSET/UTF8.C b/putty/CHARSET/UTF8.C new file mode 100644 index 0000000..489ffa2 --- /dev/null +++ b/putty/CHARSET/UTF8.C @@ -0,0 +1,882 @@ +/* + * utf8.c - routines to handle UTF-8. + */ + +#ifndef ENUM_CHARSETS + +#include "charset.h" +#include "internal.h" + +void read_utf8(charset_spec const *, long int, charset_state *, + void (*)(void *, long int), void *); +void write_utf8(charset_spec const *, long int, + charset_state *, void (*)(void *, long int), void *); + +/* + * UTF-8 has no associated data, so `charset' may be ignored. + */ + +void read_utf8(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) +{ + UNUSEDARG(charset); + + /* + * For reading UTF-8, the `state' word contains: + * + * - in bits 29-31, the number of bytes expected to be in the + * current multibyte character (which we can tell instantly + * from the first byte, of course). + * + * - in bits 26-28, the number of bytes _seen so far_ in the + * current multibyte character. + * + * - in the remainder of the word, the current value of the + * character, which is shifted upwards by 6 bits to + * accommodate each new byte. + * + * As required, the state is zero when we are not in the middle + * of a multibyte character at all. + * + * For example, when reading E9 8D 8B, starting at state=0: + * + * - after E9, the state is 0x64000009 + * - after 8D, the state is 0x6800024d + * - after 8B, the state conceptually becomes 0x6c00934b, at + * which point we notice we've got as many characters as we + * were expecting, output U+934B, and reset the state to + * zero. + * + * Note that the maximum number of bits we might need to store + * in the character value field is 25 (U+7FFFFFFF contains 31 + * bits, but we will never actually store its full value + * because when we receive the last 6 bits in the final + * continuation byte we will output it and revert the state to + * zero). Hence the character value field never collides with + * the byte counts. + */ + + if (input_chr < 0x80) { + /* + * Single-byte character. If the state is nonzero before + * coming here, output an error for an incomplete sequence. + * Then output the character. + */ + if (state->s0 != 0) { + emit(emitctx, ERROR); + state->s0 = 0; + } + emit(emitctx, input_chr); + } else if (input_chr == 0xFE || input_chr == 0xFF) { + /* + * FE and FF bytes should _never_ occur in UTF-8. They are + * automatic errors; if the state was nonzero to start + * with, output a further error for an incomplete sequence. + */ + if (state->s0 != 0) { + emit(emitctx, ERROR); + state->s0 = 0; + } + emit(emitctx, ERROR); + } else if (input_chr >= 0x80 && input_chr < 0xC0) { + /* + * Continuation byte. Output an error for an unexpected + * continuation byte, if the state is zero. + */ + if (state->s0 == 0) { + emit(emitctx, ERROR); + } else { + unsigned long charval; + unsigned long topstuff; + int bytes; + + /* + * Otherwise, accumulate more of the character value. + */ + charval = state->s0 & 0x03ffffffL; + charval = (charval << 6) | (input_chr & 0x3F); + + /* + * Check the byte counts; if we have not reached the + * end of the character, update the state and return. + */ + topstuff = state->s0 & 0xfc000000L; + topstuff += 0x04000000L; /* add one to the byte count */ + if (((topstuff << 3) ^ topstuff) & 0xe0000000L) { + state->s0 = topstuff | charval; + return; + } + + /* + * Now we know we've reached the end of the character. + * `charval' is the Unicode value. We should check for + * various invalid things, and then either output + * charval or an error. In all cases we reset the state + * to zero. + */ + bytes = topstuff >> 29; + state->s0 = 0; + + if (charval >= 0xD800 && charval < 0xE000) { + /* + * Surrogates (0xD800-0xDFFF) may never be encoded + * in UTF-8. A surrogate pair in Unicode should + * have been encoded as a single UTF-8 character + * occupying more than three bytes. + */ + emit(emitctx, ERROR); + } else if (charval == 0xFFFE || charval == 0xFFFF) { + /* + * U+FFFE and U+FFFF are invalid Unicode characters + * and may never be encoded in UTF-8. (This is one + * reason why U+FFFF is our way of signalling an + * error to our `emit' function :-) + */ + emit(emitctx, ERROR); + } else if ((charval <= 0x7FL /* && bytes > 1 */) || + (charval <= 0x7FFL && bytes > 2) || + (charval <= 0xFFFFL && bytes > 3) || + (charval <= 0x1FFFFFL && bytes > 4) || + (charval <= 0x3FFFFFFL && bytes > 5)) { + /* + * Overlong sequences are not to be tolerated, + * under any circumstances. + */ + emit(emitctx, ERROR); + } else { + /* + * Oh, all right. We'll let this one off. + */ + emit(emitctx, charval); + } + } + + } else { + /* + * Lead byte. First output an error for an incomplete + * sequence, if the state is nonzero. + */ + if (state->s0 != 0) + emit(emitctx, ERROR); + + /* + * Now deal with the lead byte: work out the number of + * bytes we expect to see in this character, and extract + * the initial bits of it too. + */ + if (input_chr >= 0xC0 && input_chr < 0xE0) { + state->s0 = 0x44000000L | (input_chr & 0x1F); + } else if (input_chr >= 0xE0 && input_chr < 0xF0) { + state->s0 = 0x64000000L | (input_chr & 0x0F); + } else if (input_chr >= 0xF0 && input_chr < 0xF8) { + state->s0 = 0x84000000L | (input_chr & 0x07); + } else if (input_chr >= 0xF8 && input_chr < 0xFC) { + state->s0 = 0xa4000000L | (input_chr & 0x03); + } else if (input_chr >= 0xFC && input_chr < 0xFE) { + state->s0 = 0xc4000000L | (input_chr & 0x01); + } + } +} + +/* + * UTF-8 is a stateless multi-byte encoding (in the sense that just + * after any character has been completed, the state is always the + * same); hence when writing it, there is no need to use the + * charset_state. + */ + +void write_utf8(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) +{ + UNUSEDARG(charset); + UNUSEDARG(state); + + /* + * Refuse to output any illegal code points. + */ + if (input_chr == 0xFFFE || input_chr == 0xFFFF || + (input_chr >= 0xD800 && input_chr < 0xE000)) { + emit(emitctx, ERROR); + } else if (input_chr < 0x80) { /* one-byte character */ + emit(emitctx, input_chr); + } else if (input_chr < 0x800) { /* two-byte character */ + emit(emitctx, 0xC0 | (0x1F & (input_chr >> 6))); + emit(emitctx, 0x80 | (0x3F & (input_chr ))); + } else if (input_chr < 0x10000) { /* three-byte character */ + emit(emitctx, 0xE0 | (0x0F & (input_chr >> 12))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 6))); + emit(emitctx, 0x80 | (0x3F & (input_chr ))); + } else if (input_chr < 0x200000) { /* four-byte character */ + emit(emitctx, 0xF0 | (0x07 & (input_chr >> 18))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 12))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 6))); + emit(emitctx, 0x80 | (0x3F & (input_chr ))); + } else if (input_chr < 0x4000000) {/* five-byte character */ + emit(emitctx, 0xF8 | (0x03 & (input_chr >> 24))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 18))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 12))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 6))); + emit(emitctx, 0x80 | (0x3F & (input_chr ))); + } else { /* six-byte character */ + emit(emitctx, 0xFC | (0x01 & (input_chr >> 30))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 24))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 18))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 12))); + emit(emitctx, 0x80 | (0x3F & (input_chr >> 6))); + emit(emitctx, 0x80 | (0x3F & (input_chr ))); + } +} + +#ifdef TESTMODE + +#include +#include + +int total_errs = 0; + +void utf8_emit(void *ctx, long output) +{ + wchar_t **p = (wchar_t **)ctx; + *(*p)++ = output; +} + +void utf8_read_test(int line, char *input, int inlen, ...) +{ + va_list ap; + wchar_t *p, str[512]; + int i; + charset_state state; + unsigned long l; + + state.s0 = 0; + p = str; + + for (i = 0; i < inlen; i++) + read_utf8(NULL, input[i] & 0xFF, &state, utf8_emit, &p); + + va_start(ap, inlen); + l = 0; + for (i = 0; i < p - str; i++) { + l = va_arg(ap, long int); + if (l == -1) { + printf("%d: correct string shorter than output\n", line); + total_errs++; + break; + } + if (l != str[i]) { + printf("%d: char %d came out as %08x, should be %08x\n", + line, i, str[i], l); + total_errs++; + } + } + if (l != -1) { + l = va_arg(ap, long int); + if (l != -1) { + printf("%d: correct string longer than output\n", line); + total_errs++; + } + } + va_end(ap); +} + +void utf8_write_test(int line, const long *input, int inlen, ...) +{ + va_list ap; + wchar_t *p, str[512]; + int i; + charset_state state; + unsigned long l; + + state.s0 = 0; + p = str; + + for (i = 0; i < inlen; i++) + write_utf8(NULL, input[i], &state, utf8_emit, &p); + + va_start(ap, inlen); + l = 0; + for (i = 0; i < p - str; i++) { + l = va_arg(ap, long int); + if (l == -1) { + printf("%d: correct string shorter than output\n", line); + total_errs++; + break; + } + if (l != str[i]) { + printf("%d: char %d came out as %08x, should be %08x\n", + line, i, str[i], l); + total_errs++; + } + } + if (l != -1) { + l = va_arg(ap, long int); + if (l != -1) { + printf("%d: correct string longer than output\n", line); + total_errs++; + } + } + va_end(ap); +} + +/* Macro to concoct the first three parameters of utf8_read_test. */ +#define TESTSTR(x) __LINE__, x, lenof(x) + +int main(void) +{ + printf("read tests beginning\n"); + utf8_read_test(TESTSTR("\xCE\xBA\xE1\xBD\xB9\xCF\x83\xCE\xBC\xCE\xB5"), + 0x000003BA, /* GREEK SMALL LETTER KAPPA */ + 0x00001F79, /* GREEK SMALL LETTER OMICRON WITH OXIA */ + 0x000003C3, /* GREEK SMALL LETTER SIGMA */ + 0x000003BC, /* GREEK SMALL LETTER MU */ + 0x000003B5, /* GREEK SMALL LETTER EPSILON */ + 0, -1); + utf8_read_test(TESTSTR("\x00"), + 0x00000000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xC2\x80"), + 0x00000080, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\xA0\x80"), + 0x00000800, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x90\x80\x80"), + 0x00010000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x88\x80\x80\x80"), + 0x00200000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x84\x80\x80\x80\x80"), + 0x04000000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\x7F"), + 0x0000007F, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xDF\xBF"), + 0x000007FF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF\xBD"), + 0x0000FFFD, /* REPLACEMENT CHARACTER */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF\xBF"), + ERROR, /* (invalid char) */ + 0, -1); + utf8_read_test(TESTSTR("\xF7\xBF\xBF\xBF"), + 0x001FFFFF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xFB\xBF\xBF\xBF\xBF"), + 0x03FFFFFF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xFD\xBF\xBF\xBF\xBF\xBF"), + 0x7FFFFFFF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xED\x9F\xBF"), + 0x0000D7FF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xEE\x80\x80"), + 0x0000E000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF\xBD"), + 0x0000FFFD, /* REPLACEMENT CHARACTER */ + 0, -1); + utf8_read_test(TESTSTR("\xF4\x8F\xBF\xBF"), + 0x0010FFFF, /* */ + 0, -1); + utf8_read_test(TESTSTR("\xF4\x90\x80\x80"), + 0x00110000, /* */ + 0, -1); + utf8_read_test(TESTSTR("\x80"), + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\xBF"), + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF\x80"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF\x80\xBF"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF\x80\xBF\x80"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF\x80\xBF\x80\xBF"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\xBF\x80\xBF\x80\xBF\x80"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF"), + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + ERROR, /* (unexpected continuation byte) */ + 0, -1); + utf8_read_test(TESTSTR("\xC0\x20\xC1\x20\xC2\x20\xC3\x20\xC4\x20\xC5\x20\xC6\x20\xC7\x20"), + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\x20\xE1\x20\xE2\x20\xE3\x20\xE4\x20\xE5\x20\xE6\x20\xE7\x20\xE8\x20\xE9\x20\xEA\x20\xEB\x20\xEC\x20\xED\x20\xEE\x20\xEF\x20"), + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x20\xF1\x20\xF2\x20\xF3\x20\xF4\x20\xF5\x20\xF6\x20\xF7\x20"), + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x20\xF9\x20\xFA\x20\xFB\x20"), + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x20\xFD\x20"), + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + ERROR, /* (incomplete sequence) */ + 0x00000020, /* SPACE */ + 0, -1); + utf8_read_test(TESTSTR("\xC0"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\x80"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x80\x80"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x80\x80\x80"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x80\x80\x80\x80"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xDF"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xF7\xBF\xBF"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xFB\xBF\xBF\xBF"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xFD\xBF\xBF\xBF\xBF"), + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xC0\xE0\x80\xF0\x80\x80\xF8\x80\x80\x80\xFC\x80\x80\x80\x80\xDF\xEF\xBF\xF7\xBF\xBF\xFB\xBF\xBF\xBF\xFD\xBF\xBF\xBF\xBF"), + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + ERROR, /* (incomplete sequence) */ + 0, -1); + utf8_read_test(TESTSTR("\xFE"), + ERROR, /* (invalid UTF-8 byte) */ + 0, -1); + utf8_read_test(TESTSTR("\xFF"), + ERROR, /* (invalid UTF-8 byte) */ + 0, -1); + utf8_read_test(TESTSTR("\xFE\xFE\xFF\xFF"), + ERROR, /* (invalid UTF-8 byte) */ + ERROR, /* (invalid UTF-8 byte) */ + ERROR, /* (invalid UTF-8 byte) */ + ERROR, /* (invalid UTF-8 byte) */ + 0, -1); + utf8_read_test(TESTSTR("\xC0\xAF"), + ERROR, /* SOLIDUS (overlong form of 2F) */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\x80\xAF"), + ERROR, /* SOLIDUS (overlong form of 2F) */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x80\x80\xAF"), + ERROR, /* SOLIDUS (overlong form of 2F) */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x80\x80\x80\xAF"), + ERROR, /* SOLIDUS (overlong form of 2F) */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x80\x80\x80\x80\xAF"), + ERROR, /* SOLIDUS (overlong form of 2F) */ + 0, -1); + utf8_read_test(TESTSTR("\xC1\xBF"), + ERROR, /* (overlong form of 7F) */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\x9F\xBF"), + ERROR, /* (overlong form of DF BF) */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x8F\xBF\xBF"), + ERROR, /* (overlong form of EF BF BF) (invalid char) */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x87\xBF\xBF\xBF"), + ERROR, /* (overlong form of F7 BF BF BF) */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x83\xBF\xBF\xBF\xBF"), + ERROR, /* (overlong form of FB BF BF BF BF) */ + 0, -1); + utf8_read_test(TESTSTR("\xC0\x80"), + ERROR, /* (overlong form of 00) */ + 0, -1); + utf8_read_test(TESTSTR("\xE0\x80\x80"), + ERROR, /* (overlong form of 00) */ + 0, -1); + utf8_read_test(TESTSTR("\xF0\x80\x80\x80"), + ERROR, /* (overlong form of 00) */ + 0, -1); + utf8_read_test(TESTSTR("\xF8\x80\x80\x80\x80"), + ERROR, /* (overlong form of 00) */ + 0, -1); + utf8_read_test(TESTSTR("\xFC\x80\x80\x80\x80\x80"), + ERROR, /* (overlong form of 00) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xA0\x80"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAD\xBF"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAE\x80"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAF\xBF"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xB0\x80"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xBE\x80"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xBF\xBF"), + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xA0\x80\xED\xB0\x80"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xA0\x80\xED\xBF\xBF"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAD\xBF\xED\xB0\x80"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAD\xBF\xED\xBF\xBF"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAE\x80\xED\xB0\x80"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAE\x80\xED\xBF\xBF"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAF\xBF\xED\xB0\x80"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xED\xAF\xBF\xED\xBF\xBF"), + ERROR, /* (surrogate) */ + ERROR, /* (surrogate) */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF\xBE"), + ERROR, /* (invalid char) */ + 0, -1); + utf8_read_test(TESTSTR("\xEF\xBF\xBF"), + ERROR, /* (invalid char) */ + 0, -1); + printf("read tests completed\n"); + printf("write tests beginning\n"); + { + const static long str[] = + {0x03BAL, 0x1F79L, 0x03C3L, 0x03BCL, 0x03B5L, 0}; + utf8_write_test(TESTSTR(str), + 0xCE, 0xBA, + 0xE1, 0xBD, 0xB9, + 0xCF, 0x83, + 0xCE, 0xBC, + 0xCE, 0xB5, + 0, -1); + } + { + const static long str[] = {0x0000L, 0}; + utf8_write_test(TESTSTR(str), + 0x00, + 0, -1); + } + { + const static long str[] = {0x0080L, 0}; + utf8_write_test(TESTSTR(str), + 0xC2, 0x80, + 0, -1); + } + { + const static long str[] = {0x0800L, 0}; + utf8_write_test(TESTSTR(str), + 0xE0, 0xA0, 0x80, + 0, -1); + } + { + const static long str[] = {0x00010000L, 0}; + utf8_write_test(TESTSTR(str), + 0xF0, 0x90, 0x80, 0x80, + 0, -1); + } + { + const static long str[] = {0x00200000L, 0}; + utf8_write_test(TESTSTR(str), + 0xF8, 0x88, 0x80, 0x80, 0x80, + 0, -1); + } + { + const static long str[] = {0x04000000L, 0}; + utf8_write_test(TESTSTR(str), + 0xFC, 0x84, 0x80, 0x80, 0x80, 0x80, + 0, -1); + } + { + const static long str[] = {0x007FL, 0}; + utf8_write_test(TESTSTR(str), + 0x7F, + 0, -1); + } + { + const static long str[] = {0x07FFL, 0}; + utf8_write_test(TESTSTR(str), + 0xDF, 0xBF, + 0, -1); + } + { + const static long str[] = {0xFFFDL, 0}; + utf8_write_test(TESTSTR(str), + 0xEF, 0xBF, 0xBD, + 0, -1); + } + { + const static long str[] = {0xFFFFL, 0}; + utf8_write_test(TESTSTR(str), + ERROR, + 0, -1); + } + { + const static long str[] = {0x001FFFFFL, 0}; + utf8_write_test(TESTSTR(str), + 0xF7, 0xBF, 0xBF, 0xBF, + 0, -1); + } + { + const static long str[] = {0x03FFFFFFL, 0}; + utf8_write_test(TESTSTR(str), + 0xFB, 0xBF, 0xBF, 0xBF, 0xBF, + 0, -1); + } + { + const static long str[] = {0x7FFFFFFFL, 0}; + utf8_write_test(TESTSTR(str), + 0xFD, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, + 0, -1); + } + { + const static long str[] = {0xD7FFL, 0}; + utf8_write_test(TESTSTR(str), + 0xED, 0x9F, 0xBF, + 0, -1); + } + { + const static long str[] = {0xD800L, 0}; + utf8_write_test(TESTSTR(str), + ERROR, + 0, -1); + } + { + const static long str[] = {0xD800L, 0xDC00L, 0}; + utf8_write_test(TESTSTR(str), + ERROR, + ERROR, + 0, -1); + } + { + const static long str[] = {0xDFFFL, 0}; + utf8_write_test(TESTSTR(str), + ERROR, + 0, -1); + } + { + const static long str[] = {0xE000L, 0}; + utf8_write_test(TESTSTR(str), + 0xEE, 0x80, 0x80, + 0, -1); + } + printf("write tests completed\n"); + + printf("total: %d errors\n", total_errs); + return (total_errs != 0); +} +#endif /* TESTMODE */ + +const charset_spec charset_CS_UTF8 = { + CS_UTF8, read_utf8, write_utf8, NULL +}; + +#else /* ENUM_CHARSETS */ + +ENUM_CHARSET(CS_UTF8) + +#endif /* ENUM_CHARSETS */ diff --git a/putty/CHARSET/XENC.C b/putty/CHARSET/XENC.C new file mode 100644 index 0000000..2832f31 --- /dev/null +++ b/putty/CHARSET/XENC.C @@ -0,0 +1,93 @@ +/* + * xenc.c - translate our internal character set codes to and from + * X11 character encoding names. + * + */ + +#include +#include "charset.h" +#include "internal.h" + +static const struct { + const char *name; + int charset; +} xencs[] = { + /* + * Officially registered encoding names. This list is derived + * from the font encodings section of + * + * http://ftp.x.org/pub/DOCS/registry + * + * Where multiple encoding names map to the same encoding id + * (such as iso8859-15 and fcd8859-15), the first is considered + * canonical and will be returned when translating the id to a + * string. + */ + { "iso8859-1", CS_ISO8859_1 }, + { "iso8859-2", CS_ISO8859_2 }, + { "iso8859-3", CS_ISO8859_3 }, + { "iso8859-4", CS_ISO8859_4 }, + { "iso8859-5", CS_ISO8859_5 }, + { "iso8859-6", CS_ISO8859_6 }, + { "iso8859-7", CS_ISO8859_7 }, + { "iso8859-8", CS_ISO8859_8 }, + { "iso8859-9", CS_ISO8859_9 }, + { "iso8859-10", CS_ISO8859_10 }, + { "iso8859-13", CS_ISO8859_13 }, + { "iso8859-14", CS_ISO8859_14 }, + { "iso8859-15", CS_ISO8859_15 }, + { "fcd8859-15", CS_ISO8859_15 }, + { "hp-roman8", CS_HP_ROMAN8 }, + { "koi8-r", CS_KOI8_R }, + /* + * Unofficial encoding names found in the wild. + */ + { "iso8859-16", CS_ISO8859_16 }, + { "koi8-u", CS_KOI8_U }, + { "ibm-cp437", CS_CP437 }, + { "ibm-cp850", CS_CP850 }, + { "ibm-cp866", CS_CP866 }, + { "microsoft-cp1250", CS_CP1250 }, + { "microsoft-cp1251", CS_CP1251 }, + { "microsoft-cp1252", CS_CP1252 }, + { "microsoft-cp1253", CS_CP1253 }, + { "microsoft-cp1254", CS_CP1254 }, + { "microsoft-cp1255", CS_CP1255 }, + { "microsoft-cp1256", CS_CP1256 }, + { "microsoft-cp1257", CS_CP1257 }, + { "microsoft-cp1258", CS_CP1258 }, + { "mac-roman", CS_MAC_ROMAN }, + { "viscii1.1-1", CS_VISCII }, + { "viscii1-1", CS_VISCII }, +}; + +const char *charset_to_xenc(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(xencs); i++) + if (charset == xencs[i].charset) + return xencs[i].name; + + return NULL; /* not found */ +} + +int charset_from_xenc(const char *name) +{ + int i; + + for (i = 0; i < (int)lenof(xencs); i++) { + const char *p, *q; + p = name; + q = xencs[i].name; + while (*p || *q) { + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + break; + p++; q++; + } + if (!*p && !*q) + return xencs[i].charset; + } + + return CS_NONE; /* not found */ +} diff --git a/putty/CMDGEN.C b/putty/CMDGEN.C new file mode 100644 index 0000000..5d9efcf --- /dev/null +++ b/putty/CMDGEN.C @@ -0,0 +1,1598 @@ +/* + * cmdgen.c - command-line form of PuTTYgen + */ + +#define PUTTY_DO_GLOBALS + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" + +#ifdef TEST_CMDGEN +/* + * This section overrides some definitions below for test purposes. + * When compiled with -DTEST_CMDGEN: + * + * - Calls to get_random_data() are replaced with the diagnostic + * function below (I #define the name so that I can still link + * with the original set of modules without symbol clash), in + * order to avoid depleting the test system's /dev/random + * unnecessarily. + * + * - Calls to console_get_userpass_input() are replaced with the + * diagnostic function below, so that I can run tests in an + * automated manner and provide their interactive passphrase + * inputs. + * + * - main() is renamed to cmdgen_main(); at the bottom of the file + * I define another main() which calls the former repeatedly to + * run tests. + */ +#define get_random_data get_random_data_diagnostic +char *get_random_data(int len) +{ + char *buf = snewn(len, char); + memset(buf, 'x', len); + return buf; +} +#define console_get_userpass_input console_get_userpass_input_diagnostic +int nprompts, promptsgot; +const char *prompts[3]; +int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + size_t i; + int ret = 1; + for (i = 0; i < p->n_prompts; i++) { + if (promptsgot < nprompts) { + assert(strlen(prompts[promptsgot]) < p->prompts[i]->result_len); + strcpy(p->prompts[i]->result, prompts[promptsgot++]); + } else { + promptsgot++; /* track number of requests anyway */ + ret = 0; + } + } + return ret; +} +#define main cmdgen_main +#endif + +struct progress { + int phase, current; +}; + +static void progress_update(void *param, int action, int phase, int iprogress) +{ + struct progress *p = (struct progress *)param; + if (action != PROGFN_PROGRESS) + return; + if (phase > p->phase) { + if (p->phase >= 0) + fputc('\n', stderr); + p->phase = phase; + if (iprogress >= 0) + p->current = iprogress - 1; + else + p->current = iprogress; + } + while (p->current < iprogress) { + fputc('+', stdout); + p->current++; + } + fflush(stdout); +} + +static void no_progress(void *param, int action, int phase, int iprogress) +{ +} + +void modalfatalbox(char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + cleanup_exit(1); +} + +/* + * Stubs to let everything else link sensibly. + */ +void log_eventlog(void *handle, const char *event) +{ +} +char *x_get_default(const char *key) +{ + return NULL; +} +void sk_cleanup(void) +{ +} + +void showversion(void) +{ + char *verstr = dupstr(ver); + verstr[0] = tolower((unsigned char)verstr[0]); + printf("PuTTYgen %s\n", verstr); + sfree(verstr); +} + +void usage(int standalone) +{ + fprintf(stderr, + "Usage: puttygen ( keyfile | -t type [ -b bits ] )\n" + " [ -C comment ] [ -P ] [ -q ]\n" + " [ -o output-keyfile ] [ -O type | -l | -L" + " | -p ]\n"); + if (standalone) + fprintf(stderr, + "Use \"puttygen --help\" for more detail.\n"); +} + +void help(void) +{ + /* + * Help message is an extended version of the usage message. So + * start with that, plus a version heading. + */ + showversion(); + usage(FALSE); + fprintf(stderr, + " -t specify key type when generating (rsa, dsa, rsa1)\n" + " -b specify number of bits when generating key\n" + " -C change or specify key comment\n" + " -P change key passphrase\n" + " -q quiet: do not display progress bar\n" + " -O specify output type:\n" + " private output PuTTY private key format\n" + " private-openssh export OpenSSH private key\n" + " private-sshcom export ssh.com private key\n" + " public standard / ssh.com public key\n" + " public-openssh OpenSSH public key\n" + " fingerprint output the key fingerprint\n" + " -o specify output file\n" + " -l equivalent to `-O fingerprint'\n" + " -L equivalent to `-O public-openssh'\n" + " -p equivalent to `-O public'\n" + ); +} + +static int save_ssh2_pubkey(char *filename, char *comment, + void *v_pub_blob, int pub_len) +{ + unsigned char *pub_blob = (unsigned char *)v_pub_blob; + char *p; + int i, column; + FILE *fp; + + if (filename) { + fp = fopen(filename, "wb"); + if (!fp) + return 0; + } else + fp = stdout; + + fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n"); + + if (comment) { + fprintf(fp, "Comment: \""); + for (p = comment; *p; p++) { + if (*p == '\\' || *p == '\"') + fputc('\\', fp); + fputc(*p, fp); + } + fprintf(fp, "\"\n"); + } + + i = 0; + column = 0; + while (i < pub_len) { + char buf[5]; + int n = (pub_len - i < 3 ? pub_len - i : 3); + base64_encode_atom(pub_blob + i, n, buf); + i += n; + buf[4] = '\0'; + fputs(buf, fp); + if (++column >= 16) { + fputc('\n', fp); + column = 0; + } + } + if (column > 0) + fputc('\n', fp); + + fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n"); + if (filename) + fclose(fp); + return 1; +} + +static int move(char *from, char *to) +{ + int ret; + + ret = rename(from, to); + if (ret) { + /* + * This OS may require us to remove the original file first. + */ + remove(to); + ret = rename(from, to); + } + if (ret) { + perror("puttygen: cannot move new file on to old one"); + return FALSE; + } + return TRUE; +} + +static char *blobfp(char *alg, int bits, unsigned char *blob, int bloblen) +{ + char buffer[128]; + unsigned char digest[16]; + struct MD5Context md5c; + int i; + + MD5Init(&md5c); + MD5Update(&md5c, blob, bloblen); + MD5Final(digest, &md5c); + + sprintf(buffer, "%s ", alg); + if (bits > 0) + sprintf(buffer + strlen(buffer), "%d ", bits); + for (i = 0; i < 16; i++) + sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "", + digest[i]); + + return dupstr(buffer); +} + +int main(int argc, char **argv) +{ + char *infile = NULL; + Filename infilename; + enum { NOKEYGEN, RSA1, RSA2, DSA } keytype = NOKEYGEN; + char *outfile = NULL, *outfiletmp = NULL; + Filename outfilename; + enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH, SSHCOM } outtype = PRIVATE; + int bits = 1024; + char *comment = NULL, *origcomment = NULL; + int change_passphrase = FALSE; + int errs = FALSE, nogo = FALSE; + int intype = SSH_KEYTYPE_UNOPENABLE; + int sshver = 0; + struct ssh2_userkey *ssh2key = NULL; + struct RSAKey *ssh1key = NULL; + unsigned char *ssh2blob = NULL; + char *ssh2alg = NULL; + const struct ssh_signkey *ssh2algf = NULL; + int ssh2bloblen; + char *passphrase = NULL; + int load_encrypted; + progfn_t progressfn = is_interactive() ? progress_update : no_progress; + + /* ------------------------------------------------------------------ + * Parse the command line to figure out what we've been asked to do. + */ + + /* + * If run with no arguments at all, print the usage message and + * return success. + */ + if (argc <= 1) { + usage(TRUE); + return 0; + } + + /* + * Parse command line arguments. + */ + while (--argc) { + char *p = *++argv; + if (*p == '-') { + /* + * An option. + */ + while (p && *++p) { + char c = *p; + switch (c) { + case '-': + /* + * Long option. + */ + { + char *opt, *val; + opt = p++; /* opt will have _one_ leading - */ + while (*p && *p != '=') + p++; /* find end of option */ + if (*p == '=') { + *p++ = '\0'; + val = p; + } else + val = NULL; + + if (!strcmp(opt, "-help")) { + if (val) { + errs = TRUE; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + help(); + nogo = TRUE; + } + } else if (!strcmp(opt, "-version")) { + if (val) { + errs = TRUE; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + showversion(); + nogo = TRUE; + } + } else if (!strcmp(opt, "-pgpfp")) { + if (val) { + errs = TRUE; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + /* support --pgpfp for consistency */ + pgp_fingerprints(); + nogo = TRUE; + } + } + /* + * For long options requiring an argument, add + * code along the lines of + * + * else if (!strcmp(opt, "-output")) { + * if (!val) { + * errs = TRUE; + * fprintf(stderr, "puttygen: option `-%s'" + * " expects an argument\n", opt); + * } else + * ofile = val; + * } + */ + else { + errs = TRUE; + fprintf(stderr, + "puttygen: no such option `-%s'\n", opt); + } + } + p = NULL; + break; + case 'h': + case 'V': + case 'P': + case 'l': + case 'L': + case 'p': + case 'q': + /* + * Option requiring no parameter. + */ + switch (c) { + case 'h': + help(); + nogo = TRUE; + break; + case 'V': + showversion(); + nogo = TRUE; + break; + case 'P': + change_passphrase = TRUE; + break; + case 'l': + outtype = FP; + break; + case 'L': + outtype = PUBLICO; + break; + case 'p': + outtype = PUBLIC; + break; + case 'q': + progressfn = no_progress; + break; + } + break; + case 't': + case 'b': + case 'C': + case 'O': + case 'o': + /* + * Option requiring parameter. + */ + p++; + if (!*p && argc > 1) + --argc, p = *++argv; + else if (!*p) { + fprintf(stderr, "puttygen: option `-%c' expects a" + " parameter\n", c); + errs = TRUE; + } + /* + * Now c is the option and p is the parameter. + */ + switch (c) { + case 't': + if (!strcmp(p, "rsa") || !strcmp(p, "rsa2")) + keytype = RSA2, sshver = 2; + else if (!strcmp(p, "rsa1")) + keytype = RSA1, sshver = 1; + else if (!strcmp(p, "dsa") || !strcmp(p, "dss")) + keytype = DSA, sshver = 2; + else { + fprintf(stderr, + "puttygen: unknown key type `%s'\n", p); + errs = TRUE; + } + break; + case 'b': + bits = atoi(p); + break; + case 'C': + comment = p; + break; + case 'O': + if (!strcmp(p, "public")) + outtype = PUBLIC; + else if (!strcmp(p, "public-openssh")) + outtype = PUBLICO; + else if (!strcmp(p, "private")) + outtype = PRIVATE; + else if (!strcmp(p, "fingerprint")) + outtype = FP; + else if (!strcmp(p, "private-openssh")) + outtype = OPENSSH, sshver = 2; + else if (!strcmp(p, "private-sshcom")) + outtype = SSHCOM, sshver = 2; + else { + fprintf(stderr, + "puttygen: unknown output type `%s'\n", p); + errs = TRUE; + } + break; + case 'o': + outfile = p; + break; + } + p = NULL; /* prevent continued processing */ + break; + default: + /* + * Unrecognised option. + */ + errs = TRUE; + fprintf(stderr, "puttygen: no such option `-%c'\n", c); + break; + } + } + } else { + /* + * A non-option argument. + */ + if (!infile) + infile = p; + else { + errs = TRUE; + fprintf(stderr, "puttygen: cannot handle more than one" + " input file\n"); + } + } + } + + if (errs) + return 1; + + if (nogo) + return 0; + + /* + * If run with at least one argument _but_ not the required + * ones, print the usage message and return failure. + */ + if (!infile && keytype == NOKEYGEN) { + usage(TRUE); + return 1; + } + + /* ------------------------------------------------------------------ + * Figure out further details of exactly what we're going to do. + */ + + /* + * Bomb out if we've been asked to both load and generate a + * key. + */ + if (keytype != NOKEYGEN && infile) { + fprintf(stderr, "puttygen: cannot both load and generate a key\n"); + return 1; + } + + /* + * We must save the private part when generating a new key. + */ + if (keytype != NOKEYGEN && + (outtype != PRIVATE && outtype != OPENSSH && outtype != SSHCOM)) { + fprintf(stderr, "puttygen: this would generate a new key but " + "discard the private part\n"); + return 1; + } + + /* + * Analyse the type of the input file, in case this affects our + * course of action. + */ + if (infile) { + infilename = filename_from_str(infile); + + intype = key_type(&infilename); + + switch (intype) { + /* + * It would be nice here to be able to load _public_ + * key files, in any of a number of forms, and (a) + * convert them to other public key types, (b) print + * out their fingerprints. Or, I suppose, for real + * orthogonality, (c) change their comment! + * + * In fact this opens some interesting possibilities. + * Suppose ssh2_userkey_loadpub() were able to load + * public key files as well as extracting the public + * key from private ones. And suppose I did the thing + * I've been wanting to do, where specifying a + * particular private key file for authentication + * causes any _other_ key in the agent to be discarded. + * Then, if you had an agent forwarded to the machine + * you were running Unix PuTTY or Plink on, and you + * needed to specify which of the keys in the agent it + * should use, you could do that by supplying a + * _public_ key file, thus not needing to trust even + * your encrypted private key file to the network. Ooh! + */ + + case SSH_KEYTYPE_UNOPENABLE: + case SSH_KEYTYPE_UNKNOWN: + fprintf(stderr, "puttygen: unable to load file `%s': %s\n", + infile, key_type_to_str(intype)); + return 1; + + case SSH_KEYTYPE_SSH1: + if (sshver == 2) { + fprintf(stderr, "puttygen: conversion from SSH-1 to SSH-2 keys" + " not supported\n"); + return 1; + } + sshver = 1; + break; + + case SSH_KEYTYPE_SSH2: + case SSH_KEYTYPE_OPENSSH: + case SSH_KEYTYPE_SSHCOM: + if (sshver == 1) { + fprintf(stderr, "puttygen: conversion from SSH-2 to SSH-1 keys" + " not supported\n"); + return 1; + } + sshver = 2; + break; + } + } + + /* + * Determine the default output file, if none is provided. + * + * This will usually be equal to stdout, except that if the + * input and output file formats are the same then the default + * output is to overwrite the input. + * + * Also in this code, we bomb out if the input and output file + * formats are the same and no other action is performed. + */ + if ((intype == SSH_KEYTYPE_SSH1 && outtype == PRIVATE) || + (intype == SSH_KEYTYPE_SSH2 && outtype == PRIVATE) || + (intype == SSH_KEYTYPE_OPENSSH && outtype == OPENSSH) || + (intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) { + if (!outfile) { + outfile = infile; + outfiletmp = dupcat(outfile, ".tmp", NULL); + } + + if (!change_passphrase && !comment) { + fprintf(stderr, "puttygen: this command would perform no useful" + " action\n"); + return 1; + } + } else { + if (!outfile) { + /* + * Bomb out rather than automatically choosing to write + * a private key file to stdout. + */ + if (outtype==PRIVATE || outtype==OPENSSH || outtype==SSHCOM) { + fprintf(stderr, "puttygen: need to specify an output file\n"); + return 1; + } + } + } + + /* + * Figure out whether we need to load the encrypted part of the + * key. This will be the case if either (a) we need to write + * out a private key format, or (b) the entire input key file + * is encrypted. + */ + if (outtype == PRIVATE || outtype == OPENSSH || outtype == SSHCOM || + intype == SSH_KEYTYPE_OPENSSH || intype == SSH_KEYTYPE_SSHCOM) + load_encrypted = TRUE; + else + load_encrypted = FALSE; + + /* ------------------------------------------------------------------ + * Now we're ready to actually do some stuff. + */ + + /* + * Either load or generate a key. + */ + if (keytype != NOKEYGEN) { + char *entropy; + char default_comment[80]; + struct tm tm; + struct progress prog; + + prog.phase = -1; + prog.current = -1; + + tm = ltime(); + if (keytype == DSA) + strftime(default_comment, 30, "dsa-key-%Y%m%d", &tm); + else + strftime(default_comment, 30, "rsa-key-%Y%m%d", &tm); + + random_ref(); + entropy = get_random_data(bits / 8); + if (!entropy) { + fprintf(stderr, "puttygen: failed to collect entropy, " + "could not generate key\n"); + return 1; + } + random_add_heavynoise(entropy, bits / 8); + memset(entropy, 0, bits/8); + sfree(entropy); + + if (keytype == DSA) { + struct dss_key *dsskey = snew(struct dss_key); + dsa_generate(dsskey, bits, progressfn, &prog); + ssh2key = snew(struct ssh2_userkey); + ssh2key->data = dsskey; + ssh2key->alg = &ssh_dss; + ssh1key = NULL; + } else { + struct RSAKey *rsakey = snew(struct RSAKey); + rsa_generate(rsakey, bits, progressfn, &prog); + rsakey->comment = NULL; + if (keytype == RSA1) { + ssh1key = rsakey; + } else { + ssh2key = snew(struct ssh2_userkey); + ssh2key->data = rsakey; + ssh2key->alg = &ssh_rsa; + } + } + progressfn(&prog, PROGFN_PROGRESS, INT_MAX, -1); + + if (ssh2key) + ssh2key->comment = dupstr(default_comment); + if (ssh1key) + ssh1key->comment = dupstr(default_comment); + + } else { + const char *error = NULL; + int encrypted; + + assert(infile != NULL); + + /* + * Find out whether the input key is encrypted. + */ + if (intype == SSH_KEYTYPE_SSH1) + encrypted = rsakey_encrypted(&infilename, &origcomment); + else if (intype == SSH_KEYTYPE_SSH2) + encrypted = ssh2_userkey_encrypted(&infilename, &origcomment); + else + encrypted = import_encrypted(&infilename, intype, &origcomment); + + /* + * If so, ask for a passphrase. + */ + if (encrypted && load_encrypted) { + prompts_t *p = new_prompts(NULL); + int ret; + p->to_server = FALSE; + p->name = dupstr("SSH key passphrase"); + add_prompt(p, dupstr("Enter passphrase to load key: "), FALSE, 512); + ret = console_get_userpass_input(p, NULL, 0); + assert(ret >= 0); + if (!ret) { + free_prompts(p); + perror("puttygen: unable to read passphrase"); + return 1; + } else { + passphrase = dupstr(p->prompts[0]->result); + free_prompts(p); + } + } else { + passphrase = NULL; + } + + switch (intype) { + int ret; + + case SSH_KEYTYPE_SSH1: + ssh1key = snew(struct RSAKey); + if (!load_encrypted) { + void *vblob; + unsigned char *blob; + int n, l, bloblen; + + ret = rsakey_pubblob(&infilename, &vblob, &bloblen, + &origcomment, &error); + blob = (unsigned char *)vblob; + + n = 4; /* skip modulus bits */ + + l = ssh1_read_bignum(blob + n, bloblen - n, + &ssh1key->exponent); + if (l < 0) { + error = "SSH-1 public key blob was too short"; + } else { + n += l; + l = ssh1_read_bignum(blob + n, bloblen - n, + &ssh1key->modulus); + if (l < 0) { + error = "SSH-1 public key blob was too short"; + } else + n += l; + } + ssh1key->comment = dupstr(origcomment); + ssh1key->private_exponent = NULL; + } else { + ret = loadrsakey(&infilename, ssh1key, passphrase, &error); + } + if (ret > 0) + error = NULL; + else if (!error) + error = "unknown error"; + break; + + case SSH_KEYTYPE_SSH2: + if (!load_encrypted) { + ssh2blob = ssh2_userkey_loadpub(&infilename, &ssh2alg, + &ssh2bloblen, NULL, &error); + ssh2algf = find_pubkey_alg(ssh2alg); + if (ssh2algf) + bits = ssh2algf->pubkey_bits(ssh2blob, ssh2bloblen); + else + bits = -1; + } else { + ssh2key = ssh2_load_userkey(&infilename, passphrase, &error); + } + if ((ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) || ssh2blob) + error = NULL; + else if (!error) { + if (ssh2key == SSH2_WRONG_PASSPHRASE) + error = "wrong passphrase"; + else + error = "unknown error"; + } + break; + + case SSH_KEYTYPE_OPENSSH: + case SSH_KEYTYPE_SSHCOM: + ssh2key = import_ssh2(&infilename, intype, passphrase, &error); + if (ssh2key) { + if (ssh2key != SSH2_WRONG_PASSPHRASE) + error = NULL; + else + error = "wrong passphrase"; + } else if (!error) + error = "unknown error"; + break; + + default: + assert(0); + } + + if (error) { + fprintf(stderr, "puttygen: error loading `%s': %s\n", + infile, error); + return 1; + } + } + + /* + * Change the comment if asked to. + */ + if (comment) { + if (sshver == 1) { + assert(ssh1key); + sfree(ssh1key->comment); + ssh1key->comment = dupstr(comment); + } else { + assert(ssh2key); + sfree(ssh2key->comment); + ssh2key->comment = dupstr(comment); + } + } + + /* + * Prompt for a new passphrase if we have been asked to, or if + * we have just generated a key. + */ + if (change_passphrase || keytype != NOKEYGEN) { + prompts_t *p = new_prompts(NULL); + int ret; + + p->to_server = FALSE; + p->name = dupstr("New SSH key passphrase"); + add_prompt(p, dupstr("Enter passphrase to save key: "), FALSE, 512); + add_prompt(p, dupstr("Re-enter passphrase to verify: "), FALSE, 512); + ret = console_get_userpass_input(p, NULL, 0); + assert(ret >= 0); + if (!ret) { + free_prompts(p); + perror("puttygen: unable to read new passphrase"); + return 1; + } else { + if (strcmp(p->prompts[0]->result, p->prompts[1]->result)) { + free_prompts(p); + fprintf(stderr, "puttygen: passphrases do not match\n"); + return 1; + } + if (passphrase) { + memset(passphrase, 0, strlen(passphrase)); + sfree(passphrase); + } + passphrase = dupstr(p->prompts[0]->result); + free_prompts(p); + if (!*passphrase) { + sfree(passphrase); + passphrase = NULL; + } + } + } + + /* + * Write output. + * + * (In the case where outfile and outfiletmp are both NULL, + * there is no semantic reason to initialise outfilename at + * all; but we have to write _something_ to it or some compiler + * will probably complain that it might be used uninitialised.) + */ + if (outfiletmp) + outfilename = filename_from_str(outfiletmp); + else + outfilename = filename_from_str(outfile ? outfile : ""); + + switch (outtype) { + int ret; + + case PRIVATE: + if (sshver == 1) { + assert(ssh1key); + ret = saversakey(&outfilename, ssh1key, passphrase); + if (!ret) { + fprintf(stderr, "puttygen: unable to save SSH-1 private key\n"); + return 1; + } + } else { + assert(ssh2key); + ret = ssh2_save_userkey(&outfilename, ssh2key, passphrase); + if (!ret) { + fprintf(stderr, "puttygen: unable to save SSH-2 private key\n"); + return 1; + } + } + if (outfiletmp) { + if (!move(outfiletmp, outfile)) + return 1; /* rename failed */ + } + break; + + case PUBLIC: + case PUBLICO: + if (sshver == 1) { + FILE *fp; + char *dec1, *dec2; + + assert(ssh1key); + + if (outfile) + fp = f_open(outfilename, "w", FALSE); + else + fp = stdout; + dec1 = bignum_decimal(ssh1key->exponent); + dec2 = bignum_decimal(ssh1key->modulus); + fprintf(fp, "%d %s %s %s\n", bignum_bitcount(ssh1key->modulus), + dec1, dec2, ssh1key->comment); + sfree(dec1); + sfree(dec2); + if (outfile) + fclose(fp); + } else if (outtype == PUBLIC) { + if (!ssh2blob) { + assert(ssh2key); + ssh2blob = ssh2key->alg->public_blob(ssh2key->data, + &ssh2bloblen); + } + save_ssh2_pubkey(outfile, ssh2key ? ssh2key->comment : origcomment, + ssh2blob, ssh2bloblen); + } else if (outtype == PUBLICO) { + char *buffer, *p; + int i; + FILE *fp; + + if (!ssh2blob) { + assert(ssh2key); + ssh2blob = ssh2key->alg->public_blob(ssh2key->data, + &ssh2bloblen); + } + if (!ssh2alg) { + assert(ssh2key); + ssh2alg = ssh2key->alg->name; + } + if (ssh2key) + comment = ssh2key->comment; + else + comment = origcomment; + + buffer = snewn(strlen(ssh2alg) + + 4 * ((ssh2bloblen+2) / 3) + + strlen(comment) + 3, char); + strcpy(buffer, ssh2alg); + p = buffer + strlen(buffer); + *p++ = ' '; + i = 0; + while (i < ssh2bloblen) { + int n = (ssh2bloblen - i < 3 ? ssh2bloblen - i : 3); + base64_encode_atom(ssh2blob + i, n, p); + i += n; + p += 4; + } + if (*comment) { + *p++ = ' '; + strcpy(p, comment); + } else + *p++ = '\0'; + + if (outfile) + fp = f_open(outfilename, "w", FALSE); + else + fp = stdout; + fprintf(fp, "%s\n", buffer); + if (outfile) + fclose(fp); + + sfree(buffer); + } + break; + + case FP: + { + FILE *fp; + char *fingerprint; + + if (sshver == 1) { + assert(ssh1key); + fingerprint = snewn(128, char); + rsa_fingerprint(fingerprint, 128, ssh1key); + } else { + if (ssh2key) { + fingerprint = ssh2key->alg->fingerprint(ssh2key->data); + } else { + assert(ssh2blob); + fingerprint = blobfp(ssh2alg, bits, ssh2blob, ssh2bloblen); + } + } + + if (outfile) + fp = f_open(outfilename, "w", FALSE); + else + fp = stdout; + fprintf(fp, "%s\n", fingerprint); + if (outfile) + fclose(fp); + + sfree(fingerprint); + } + break; + + case OPENSSH: + case SSHCOM: + assert(sshver == 2); + assert(ssh2key); + ret = export_ssh2(&outfilename, outtype, ssh2key, passphrase); + if (!ret) { + fprintf(stderr, "puttygen: unable to export key\n"); + return 1; + } + if (outfiletmp) { + if (!move(outfiletmp, outfile)) + return 1; /* rename failed */ + } + break; + } + + if (passphrase) { + memset(passphrase, 0, strlen(passphrase)); + sfree(passphrase); + } + + if (ssh1key) + freersakey(ssh1key); + if (ssh2key) { + ssh2key->alg->freekey(ssh2key->data); + sfree(ssh2key); + } + + return 0; +} + +#ifdef TEST_CMDGEN + +#undef main + +#include + +int passes, fails; + +void setup_passphrases(char *first, ...) +{ + va_list ap; + char *next; + + nprompts = 0; + if (first) { + prompts[nprompts++] = first; + va_start(ap, first); + while ((next = va_arg(ap, char *)) != NULL) { + assert(nprompts < lenof(prompts)); + prompts[nprompts++] = next; + } + va_end(ap); + } +} + +void test(int retval, ...) +{ + va_list ap; + int i, argc, ret; + char **argv; + + argc = 0; + va_start(ap, retval); + while (va_arg(ap, char *) != NULL) + argc++; + va_end(ap); + + argv = snewn(argc+1, char *); + va_start(ap, retval); + for (i = 0; i <= argc; i++) + argv[i] = va_arg(ap, char *); + va_end(ap); + + promptsgot = 0; + ret = cmdgen_main(argc, argv); + + if (ret != retval) { + printf("FAILED retval (exp %d got %d):", retval, ret); + for (i = 0; i < argc; i++) + printf(" %s", argv[i]); + printf("\n"); + fails++; + } else if (promptsgot != nprompts) { + printf("FAILED nprompts (exp %d got %d):", nprompts, promptsgot); + for (i = 0; i < argc; i++) + printf(" %s", argv[i]); + printf("\n"); + fails++; + } else { + passes++; + } +} + +void filecmp(char *file1, char *file2, char *fmt, ...) +{ + /* + * Ideally I should do file comparison myself, to maximise the + * portability of this test suite once this application begins + * running on non-Unix platforms. For the moment, though, + * calling Unix diff is perfectly adequate. + */ + char *buf; + int ret; + + buf = dupprintf("diff -q '%s' '%s'", file1, file2); + ret = system(buf); + sfree(buf); + + if (ret) { + va_list ap; + + printf("FAILED diff (ret=%d): ", ret); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + printf("\n"); + + fails++; + } else + passes++; +} + +char *cleanup_fp(char *s) +{ + char *p; + + if (!strncmp(s, "ssh-", 4)) { + s += strcspn(s, " \n\t"); + s += strspn(s, " \n\t"); + } + + p = s; + s += strcspn(s, " \n\t"); + s += strspn(s, " \n\t"); + s += strcspn(s, " \n\t"); + + return dupprintf("%.*s", s - p, p); +} + +char *get_fp(char *filename) +{ + FILE *fp; + char buf[256], *ret; + + fp = fopen(filename, "r"); + if (!fp) + return NULL; + ret = fgets(buf, sizeof(buf), fp); + fclose(fp); + if (!ret) + return NULL; + return cleanup_fp(buf); +} + +void check_fp(char *filename, char *fp, char *fmt, ...) +{ + char *newfp; + + if (!fp) + return; + + newfp = get_fp(filename); + + if (!strcmp(fp, newfp)) { + passes++; + } else { + va_list ap; + + printf("FAILED check_fp ['%s' != '%s']: ", newfp, fp); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + printf("\n"); + + fails++; + } + + sfree(newfp); +} + +int main(int argc, char **argv) +{ + int i; + static char *const keytypes[] = { "rsa1", "dsa", "rsa" }; + + /* + * Even when this thing is compiled for automatic test mode, + * it's helpful to be able to invoke it with command-line + * options for _manual_ tests. + */ + if (argc > 1) + return cmdgen_main(argc, argv); + + passes = fails = 0; + + for (i = 0; i < lenof(keytypes); i++) { + char filename[128], osfilename[128], scfilename[128]; + char pubfilename[128], tmpfilename1[128], tmpfilename2[128]; + char *fp; + + sprintf(filename, "test-%s.ppk", keytypes[i]); + sprintf(pubfilename, "test-%s.pub", keytypes[i]); + sprintf(osfilename, "test-%s.os", keytypes[i]); + sprintf(scfilename, "test-%s.sc", keytypes[i]); + sprintf(tmpfilename1, "test-%s.tmp1", keytypes[i]); + sprintf(tmpfilename2, "test-%s.tmp2", keytypes[i]); + + /* + * Create an encrypted key. + */ + setup_passphrases("sponge", "sponge", NULL); + test(0, "puttygen", "-t", keytypes[i], "-o", filename, NULL); + + /* + * List the public key in OpenSSH format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-L", filename, "-o", pubfilename, NULL); + { + char cmdbuf[256]; + fp = NULL; + sprintf(cmdbuf, "ssh-keygen -l -f '%s' > '%s'", + pubfilename, tmpfilename1); + if (system(cmdbuf) || + (fp = get_fp(tmpfilename1)) == NULL) { + printf("UNABLE to test fingerprint matching against OpenSSH"); + } + } + + /* + * List the public key in IETF/ssh.com format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-p", filename, NULL); + + /* + * List the fingerprint of the key. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-l", filename, "-o", tmpfilename1, NULL); + if (!fp) { + /* + * If we can't test fingerprints against OpenSSH, we + * can at the very least test equality of all the + * fingerprints we generate of this key throughout + * testing. + */ + fp = get_fp(tmpfilename1); + } else { + check_fp(tmpfilename1, fp, "%s initial fp", keytypes[i]); + } + + /* + * Change the comment of the key; this _does_ require a + * passphrase owing to the tamperproofing. + * + * NOTE: In SSH-1, this only requires a passphrase because + * of inadequacies of the loading and saving mechanisms. In + * _principle_, it should be perfectly possible to modify + * the comment on an SSH-1 key without requiring a + * passphrase; the only reason I can't do it is because my + * loading and saving mechanisms don't include a method of + * loading all the key data without also trying to decrypt + * the private section. + * + * I don't consider this to be a problem worth solving, + * because (a) to fix it would probably end up bloating + * PuTTY proper, and (b) SSH-1 is on the way out anyway so + * it shouldn't be highly significant. If it seriously + * bothers anyone then perhaps I _might_ be persuadable. + */ + setup_passphrases("sponge", NULL); + test(0, "puttygen", "-C", "new-comment", filename, NULL); + + /* + * Change the passphrase to nothing. + */ + setup_passphrases("sponge", "", "", NULL); + test(0, "puttygen", "-P", filename, NULL); + + /* + * Change the comment of the key again; this time we expect no + * passphrase to be required. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-C", "new-comment-2", filename, NULL); + + /* + * Export the private key into OpenSSH format; no passphrase + * should be required since the key is currently unencrypted. + * For RSA1 keys, this should give an error. + */ + setup_passphrases(NULL); + test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the OpenSSH-formatted key. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s openssh clear fp", keytypes[i]); + + /* + * List the public half of the OpenSSH-formatted key in + * OpenSSH format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-L", osfilename, NULL); + + /* + * List the public half of the OpenSSH-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-p", osfilename, NULL); + } + + /* + * Export the private key into ssh.com format; no passphrase + * should be required since the key is currently unencrypted. + * For RSA1 keys, this should give an error. + */ + setup_passphrases(NULL); + test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the ssh.com-formatted key. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s ssh.com clear fp", keytypes[i]); + + /* + * List the public half of the ssh.com-formatted key in + * OpenSSH format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-L", scfilename, NULL); + + /* + * List the public half of the ssh.com-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-p", scfilename, NULL); + } + + if (i) { + /* + * Convert from OpenSSH into ssh.com. + */ + setup_passphrases(NULL); + test(0, "puttygen", osfilename, "-o", tmpfilename1, + "-O", "private-sshcom", NULL); + + /* + * Convert from ssh.com back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases(NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->o->s->p clear %s", keytypes[i]); + + /* + * Convert from ssh.com to OpenSSH. + */ + setup_passphrases(NULL); + test(0, "puttygen", scfilename, "-o", tmpfilename1, + "-O", "private-openssh", NULL); + + /* + * Convert from OpenSSH back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases(NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->s->o->p clear %s", keytypes[i]); + + /* + * Finally, do a round-trip conversion between PuTTY + * and ssh.com without involving OpenSSH, to test that + * the key comment is preserved in that case. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1, + filename, NULL); + setup_passphrases(NULL); + test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL); + filecmp(filename, tmpfilename2, + "p->s->p clear %s", keytypes[i]); + } + + /* + * Check that mismatched passphrases cause an error. + */ + setup_passphrases("sponge2", "sponge3", NULL); + test(1, "puttygen", "-P", filename, NULL); + + /* + * Put a passphrase back on. + */ + setup_passphrases("sponge2", "sponge2", NULL); + test(0, "puttygen", "-P", filename, NULL); + + /* + * Export the private key into OpenSSH format, this time + * while encrypted. For RSA1 keys, this should give an + * error. + */ + if (i == 0) + setup_passphrases(NULL); /* error, hence no passphrase read */ + else + setup_passphrases("sponge2", NULL); + test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the OpenSSH-formatted key. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s openssh encrypted fp", keytypes[i]); + + /* + * List the public half of the OpenSSH-formatted key in + * OpenSSH format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-L", osfilename, NULL); + + /* + * List the public half of the OpenSSH-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-p", osfilename, NULL); + } + + /* + * Export the private key into ssh.com format, this time + * while encrypted. For RSA1 keys, this should give an + * error. + */ + if (i == 0) + setup_passphrases(NULL); /* error, hence no passphrase read */ + else + setup_passphrases("sponge2", NULL); + test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the ssh.com-formatted key. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s ssh.com encrypted fp", keytypes[i]); + + /* + * List the public half of the ssh.com-formatted key in + * OpenSSH format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-L", scfilename, NULL); + + /* + * List the public half of the ssh.com-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-p", scfilename, NULL); + } + + if (i) { + /* + * Convert from OpenSSH into ssh.com. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", osfilename, "-o", tmpfilename1, + "-O", "private-sshcom", NULL); + + /* + * Convert from ssh.com back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->o->s->p encrypted %s", keytypes[i]); + + /* + * Convert from ssh.com to OpenSSH. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", scfilename, "-o", tmpfilename1, + "-O", "private-openssh", NULL); + + /* + * Convert from OpenSSH back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->s->o->p encrypted %s", keytypes[i]); + + /* + * Finally, do a round-trip conversion between PuTTY + * and ssh.com without involving OpenSSH, to test that + * the key comment is preserved in that case. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1, + filename, NULL); + setup_passphrases("sponge2", NULL); + test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL); + filecmp(filename, tmpfilename2, + "p->s->p encrypted %s", keytypes[i]); + } + + /* + * Load with the wrong passphrase. + */ + setup_passphrases("sponge8", NULL); + test(1, "puttygen", "-C", "spurious-new-comment", filename, NULL); + + /* + * Load a totally bogus file. + */ + setup_passphrases(NULL); + test(1, "puttygen", "-C", "spurious-new-comment", pubfilename, NULL); + } + printf("%d passes, %d fails\n", passes, fails); + return 0; +} + +#endif diff --git a/putty/CMDLINE.C b/putty/CMDLINE.C new file mode 100644 index 0000000..aa376a0 --- /dev/null +++ b/putty/CMDLINE.C @@ -0,0 +1,566 @@ +/* + * cmdline.c - command-line parsing shared between many of the + * PuTTY applications + */ + +#include +#include +#include +#include "putty.h" + +/* + * Some command-line parameters need to be saved up until after + * we've loaded the saved session which will form the basis of our + * eventual running configuration. For this we use the macro + * SAVEABLE, which notices if the `need_save' parameter is set and + * saves the parameter and value on a list. + * + * We also assign priorities to saved parameters, just to slightly + * ameliorate silly ordering problems. For example, if you specify + * a saved session to load, it will be loaded _before_ all your + * local modifications such as -L are evaluated; and if you specify + * a protocol and a port, the protocol is set up first so that the + * port can override its choice of port number. + * + * (In fact -load is not saved at all, since in at least Plink the + * processing of further command-line options depends on whether or + * not the loaded session contained a hostname. So it must be + * executed immediately.) + */ + +#define NPRIORITIES 2 + +struct cmdline_saved_param { + char *p, *value; +}; +struct cmdline_saved_param_set { + struct cmdline_saved_param *params; + int nsaved, savesize; +}; + +/* + * C guarantees this structure will be initialised to all zero at + * program start, which is exactly what we want. + */ +static struct cmdline_saved_param_set saves[NPRIORITIES]; + +static void cmdline_save_param(char *p, char *value, int pri) +{ + if (saves[pri].nsaved >= saves[pri].savesize) { + saves[pri].savesize = saves[pri].nsaved + 32; + saves[pri].params = sresize(saves[pri].params, saves[pri].savesize, + struct cmdline_saved_param); + } + saves[pri].params[saves[pri].nsaved].p = p; + saves[pri].params[saves[pri].nsaved].value = value; + saves[pri].nsaved++; +} + +static char *cmdline_password = NULL; + +void cmdline_cleanup(void) +{ + int pri; + + if (cmdline_password) { + memset(cmdline_password, 0, strlen(cmdline_password)); + sfree(cmdline_password); + cmdline_password = NULL; + } + + for (pri = 0; pri < NPRIORITIES; pri++) { + sfree(saves[pri].params); + saves[pri].params = NULL; + saves[pri].savesize = 0; + saves[pri].nsaved = 0; + } +} + +#define SAVEABLE(pri) do { \ + if (need_save) { cmdline_save_param(p, value, pri); return ret; } \ +} while (0) + +/* + * Similar interface to get_userpass_input(), except that here a -1 + * return means that we aren't capable of processing the prompt and + * someone else should do it. + */ +int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen) { + + static int tried_once = 0; + + /* + * We only handle prompts which don't echo (which we assume to be + * passwords), and (currently) we only cope with a password prompt + * that comes in a prompt-set on its own. + */ + if (!cmdline_password || in || p->n_prompts != 1 || p->prompts[0]->echo) { + return -1; + } + + /* + * If we've tried once, return utter failure (no more passwords left + * to try). + */ + if (tried_once) + return 0; + + strncpy(p->prompts[0]->result, cmdline_password, + p->prompts[0]->result_len); + p->prompts[0]->result[p->prompts[0]->result_len-1] = '\0'; + memset(cmdline_password, 0, strlen(cmdline_password)); + sfree(cmdline_password); + cmdline_password = NULL; + tried_once = 1; + return 1; + +} + +/* + * Here we have a flags word which describes the capabilities of + * the particular tool on whose behalf we're running. We will + * refuse certain command-line options if a particular tool + * inherently can't do anything sensible. For example, the file + * transfer tools (psftp, pscp) can't do a great deal with protocol + * selections (ever tried running scp over telnet?) or with port + * forwarding (even if it wasn't a hideously bad idea, they don't + * have the select() infrastructure to make them work). + */ +int cmdline_tooltype = 0; + +static int cmdline_check_unavailable(int flag, char *p) +{ + if (cmdline_tooltype & flag) { + cmdline_error("option \"%s\" not available in this tool", p); + return 1; + } + return 0; +} + +#define UNAVAILABLE_IN(flag) do { \ + if (cmdline_check_unavailable(flag, p)) return ret; \ +} while (0) + +/* + * Process a standard command-line parameter. `p' is the parameter + * in question; `value' is the subsequent element of argv, which + * may or may not be required as an operand to the parameter. + * If `need_save' is 1, arguments which need to be saved as + * described at this top of this file are, for later execution; + * if 0, they are processed normally. (-1 is a special value used + * by pterm to count arguments for a preliminary pass through the + * argument list; it causes immediate return with an appropriate + * value with no action taken.) + * Return value is 2 if both arguments were used; 1 if only p was + * used; 0 if the parameter wasn't one we recognised; -2 if it + * should have been 2 but value was NULL. + */ + +#define RETURN(x) do { \ + if ((x) == 2 && !value) return -2; \ + ret = x; \ + if (need_save < 0) return x; \ +} while (0) + +int cmdline_process_param(char *p, char *value, int need_save, Config *cfg) +{ + int ret = 0; + + if (!strcmp(p, "-load")) { + RETURN(2); + /* This parameter must be processed immediately rather than being + * saved. */ + do_defaults(value, cfg); + loaded_session = TRUE; + cmdline_session_name = dupstr(value); + return 2; + } + if (!strcmp(p, "-ssh")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + default_protocol = cfg->protocol = PROT_SSH; + default_port = cfg->port = 22; + return 1; + } + if (!strcmp(p, "-telnet")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + default_protocol = cfg->protocol = PROT_TELNET; + default_port = cfg->port = 23; + return 1; + } + if (!strcmp(p, "-rlogin")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + default_protocol = cfg->protocol = PROT_RLOGIN; + default_port = cfg->port = 513; + return 1; + } + if (!strcmp(p, "-raw")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + default_protocol = cfg->protocol = PROT_RAW; + } + if (!strcmp(p, "-serial")) { + RETURN(1); + /* Serial is not NONNETWORK in an odd sense of the word */ + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + default_protocol = cfg->protocol = PROT_SERIAL; + /* The host parameter will already be loaded into cfg->host, so copy it across */ + strncpy(cfg->serline, cfg->host, sizeof(cfg->serline) - 1); + cfg->serline[sizeof(cfg->serline) - 1] = '\0'; + } + if (!strcmp(p, "-v")) { + RETURN(1); + flags |= FLAG_VERBOSE; + } + if (!strcmp(p, "-l")) { + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + strncpy(cfg->username, value, sizeof(cfg->username)); + cfg->username[sizeof(cfg->username) - 1] = '\0'; + } + if (!strcmp(p, "-loghost")) { + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + strncpy(cfg->loghost, value, sizeof(cfg->loghost)); + cfg->loghost[sizeof(cfg->loghost) - 1] = '\0'; + } + if ((!strcmp(p, "-L") || !strcmp(p, "-R") || !strcmp(p, "-D"))) { + char *fwd, *ptr, *q, *qq; + int dynamic, i=0; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + dynamic = !strcmp(p, "-D"); + fwd = value; + ptr = cfg->portfwd; + /* if existing forwards, find end of list */ + while (*ptr) { + while (*ptr) + ptr++; + ptr++; + } + i = ptr - cfg->portfwd; + ptr[0] = p[1]; /* insert a 'L', 'R' or 'D' at the start */ + ptr++; + if (1 + strlen(fwd) + 2 > sizeof(cfg->portfwd) - i) { + cmdline_error("out of space for port forwardings"); + return ret; + } + strncpy(ptr, fwd, sizeof(cfg->portfwd) - i - 2); + if (!dynamic) { + /* + * We expect _at least_ two colons in this string. The + * possible formats are `sourceport:desthost:destport', + * or `sourceip:sourceport:desthost:destport' if you're + * specifying a particular loopback address. We need to + * replace the one between source and dest with a \t; + * this means we must find the second-to-last colon in + * the string. + */ + q = qq = strchr(ptr, ':'); + while (qq) { + char *qqq = strchr(qq+1, ':'); + if (qqq) + q = qq; + qq = qqq; + } + if (q) *q = '\t'; /* replace second-last colon with \t */ + } + cfg->portfwd[sizeof(cfg->portfwd) - 1] = '\0'; + cfg->portfwd[sizeof(cfg->portfwd) - 2] = '\0'; + ptr[strlen(ptr)+1] = '\000'; /* append 2nd '\000' */ + } + if ((!strcmp(p, "-nc"))) { + char *host, *portp; + + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + + host = portp = value; + while (*portp && *portp != ':') + portp++; + if (*portp) { + unsigned len = portp - host; + if (len >= sizeof(cfg->ssh_nc_host)) + len = sizeof(cfg->ssh_nc_host) - 1; + memcpy(cfg->ssh_nc_host, value, len); + cfg->ssh_nc_host[len] = '\0'; + cfg->ssh_nc_port = atoi(portp+1); + } else { + cmdline_error("-nc expects argument of form 'host:port'"); + return ret; + } + } + if (!strcmp(p, "-m")) { + char *filename, *command; + int cmdlen, cmdsize; + FILE *fp; + int c, d; + + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + + filename = value; + + cmdlen = cmdsize = 0; + command = NULL; + fp = fopen(filename, "r"); + if (!fp) { + cmdline_error("unable to open command " + "file \"%s\"", filename); + return ret; + } + do { + c = fgetc(fp); + d = c; + if (c == EOF) + d = 0; + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = sresize(command, cmdsize, char); + } + command[cmdlen++] = d; + } while (c != EOF); + cfg->remote_cmd_ptr = command; + cfg->remote_cmd_ptr2 = NULL; + cfg->nopty = TRUE; /* command => no terminal */ + fclose(fp); + } + if (!strcmp(p, "-P")) { + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(1); /* lower priority than -ssh,-telnet */ + cfg->port = atoi(value); + } + if (!strcmp(p, "-pw")) { + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(1); + /* We delay evaluating this until after the protocol is decided, + * so that we can warn if it's of no use with the selected protocol */ + if (cfg->protocol != PROT_SSH) + cmdline_error("the -pw option can only be used with the " + "SSH protocol"); + else { + cmdline_password = dupstr(value); + /* Assuming that `value' is directly from argv, make a good faith + * attempt to trample it, to stop it showing up in `ps' output + * on Unix-like systems. Not guaranteed, of course. */ + memset(value, 0, strlen(value)); + } + } + + if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") || + !strcmp(p, "-pageant")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + cfg->tryagent = TRUE; + } + if (!strcmp(p, "-noagent") || !strcmp(p, "-nopagent") || + !strcmp(p, "-nopageant")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + cfg->tryagent = FALSE; + } + + if (!strcmp(p, "-A")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + cfg->agentfwd = 1; + } + if (!strcmp(p, "-a")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + cfg->agentfwd = 0; + } + + if (!strcmp(p, "-X")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + cfg->x11_forward = 1; + } + if (!strcmp(p, "-x")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + cfg->x11_forward = 0; + } + + if (!strcmp(p, "-t")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(1); /* lower priority than -m */ + cfg->nopty = 0; + } + if (!strcmp(p, "-T")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(1); + cfg->nopty = 1; + } + + if (!strcmp(p, "-N")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + cfg->ssh_no_shell = 1; + } + + if (!strcmp(p, "-C")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + cfg->compression = 1; + } + + if (!strcmp(p, "-1")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + cfg->sshprot = 0; /* ssh protocol 1 only */ + } + if (!strcmp(p, "-2")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + cfg->sshprot = 3; /* ssh protocol 2 only */ + } + + if (!strcmp(p, "-i")) { + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + cfg->keyfile = filename_from_str(value); + } + + if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) { + RETURN(1); + SAVEABLE(1); + cfg->addressfamily = ADDRTYPE_IPV4; + } + if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) { + RETURN(1); + SAVEABLE(1); + cfg->addressfamily = ADDRTYPE_IPV6; + } + if (!strcmp(p, "-sercfg")) { + char* nextitem; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(1); + if (cfg->protocol != PROT_SERIAL) + cmdline_error("the -sercfg option can only be used with the " + "serial protocol"); + /* Value[0] contains one or more , separated values, like 19200,8,n,1,X */ + nextitem = value; + while (nextitem[0] != '\0') { + int length, skip; + char *end = strchr(nextitem, ','); + if (!end) { + length = strlen(nextitem); + skip = 0; + } else { + length = end - nextitem; + nextitem[length] = '\0'; + skip = 1; + } + if (length == 1) { + switch (*nextitem) { + case '1': + cfg->serstopbits = 2; + break; + case '2': + cfg->serstopbits = 4; + break; + + case '5': + cfg->serdatabits = 5; + break; + case '6': + cfg->serdatabits = 6; + break; + case '7': + cfg->serdatabits = 7; + break; + case '8': + cfg->serdatabits = 8; + break; + case '9': + cfg->serdatabits = 9; + break; + + case 'n': + cfg->serparity = SER_PAR_NONE; + break; + case 'o': + cfg->serparity = SER_PAR_ODD; + break; + case 'e': + cfg->serparity = SER_PAR_EVEN; + break; + case 'm': + cfg->serparity = SER_PAR_MARK; + break; + case 's': + cfg->serparity = SER_PAR_SPACE; + break; + + case 'N': + cfg->serflow = SER_FLOW_NONE; + break; + case 'X': + cfg->serflow = SER_FLOW_XONXOFF; + break; + case 'R': + cfg->serflow = SER_FLOW_RTSCTS; + break; + case 'D': + cfg->serflow = SER_FLOW_DSRDTR; + break; + + default: + cmdline_error("Unrecognised suboption \"-sercfg %c\"", + *nextitem); + } + } else if (length == 3 && !strncmp(nextitem,"1.5",3)) { + /* Messy special case */ + cfg->serstopbits = 3; + } else { + int serspeed = atoi(nextitem); + if (serspeed != 0) { + cfg->serspeed = serspeed; + } else { + cmdline_error("Unrecognised suboption \"-sercfg %s\"", + nextitem); + } + } + nextitem += length + skip; + } + } + return ret; /* unrecognised */ +} + +void cmdline_run_saved(Config *cfg) +{ + int pri, i; + for (pri = 0; pri < NPRIORITIES; pri++) + for (i = 0; i < saves[pri].nsaved; i++) + cmdline_process_param(saves[pri].params[i].p, + saves[pri].params[i].value, 0, cfg); +} diff --git a/putty/CONFIG.C b/putty/CONFIG.C new file mode 100644 index 0000000..38e47b6 --- /dev/null +++ b/putty/CONFIG.C @@ -0,0 +1,2388 @@ +/* + * config.c - the platform-independent parts of the PuTTY + * configuration box. + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +#define PRINTER_DISABLED_STRING "None (printing disabled)" + +#define HOST_BOX_TITLE "Host Name (or IP address)" +#define PORT_BOX_TITLE "Port" + +static void config_host_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + + /* + * This function works just like the standard edit box handler, + * only it has to choose the control's label and text from two + * different places depending on the protocol. + */ + if (event == EVENT_REFRESH) { + if (cfg->protocol == PROT_SERIAL) { + /* + * This label text is carefully chosen to contain an n, + * since that's the shortcut for the host name control. + */ + dlg_label_change(ctrl, dlg, "Serial line"); + dlg_editbox_set(ctrl, dlg, cfg->serline); + } else { + dlg_label_change(ctrl, dlg, HOST_BOX_TITLE); + dlg_editbox_set(ctrl, dlg, cfg->host); + } + } else if (event == EVENT_VALCHANGE) { + if (cfg->protocol == PROT_SERIAL) + dlg_editbox_get(ctrl, dlg, cfg->serline, lenof(cfg->serline)); + else + dlg_editbox_get(ctrl, dlg, cfg->host, lenof(cfg->host)); + } +} + +static void config_port_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + char buf[80]; + + /* + * This function works similarly to the standard edit box handler, + * only it has to choose the control's label and text from two + * different places depending on the protocol. + */ + if (event == EVENT_REFRESH) { + if (cfg->protocol == PROT_SERIAL) { + /* + * This label text is carefully chosen to contain a p, + * since that's the shortcut for the port control. + */ + dlg_label_change(ctrl, dlg, "Speed"); + sprintf(buf, "%d", cfg->serspeed); + } else { + dlg_label_change(ctrl, dlg, PORT_BOX_TITLE); + if (cfg->port != 0) + sprintf(buf, "%d", cfg->port); + else + /* Display an (invalid) port of 0 as blank */ + buf[0] = '\0'; + } + dlg_editbox_set(ctrl, dlg, buf); + } else if (event == EVENT_VALCHANGE) { + dlg_editbox_get(ctrl, dlg, buf, lenof(buf)); + if (cfg->protocol == PROT_SERIAL) + cfg->serspeed = atoi(buf); + else + cfg->port = atoi(buf); + } +} + +struct hostport { + union control *host, *port; +}; + +/* + * We export this function so that platform-specific config + * routines can use it to conveniently identify the protocol radio + * buttons in order to add to them. + */ +void config_protocolbuttons_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int button; + Config *cfg = (Config *)data; + struct hostport *hp = (struct hostport *)ctrl->radio.context.p; + + /* + * This function works just like the standard radio-button + * handler, except that it also has to change the setting of + * the port box, and refresh both host and port boxes when. We + * expect the context parameter to point at a hostport + * structure giving the `union control's for both. + */ + if (event == EVENT_REFRESH) { + for (button = 0; button < ctrl->radio.nbuttons; button++) + if (cfg->protocol == ctrl->radio.buttondata[button].i) + break; + /* We expected that `break' to happen, in all circumstances. */ + assert(button < ctrl->radio.nbuttons); + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + int oldproto = cfg->protocol; + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + cfg->protocol = ctrl->radio.buttondata[button].i; + if (oldproto != cfg->protocol) { + Backend *ob = backend_from_proto(oldproto); + Backend *nb = backend_from_proto(cfg->protocol); + assert(ob); + assert(nb); + /* Iff the user hasn't changed the port from the old protocol's + * default, update it with the new protocol's default. + * (This includes a "default" of 0, implying that there is no + * sensible default for that protocol; in this case it's + * displayed as a blank.) + * This helps with the common case of tabbing through the + * controls in order and setting a non-default port before + * getting to the protocol; we want that non-default port + * to be preserved. */ + if (cfg->port == ob->default_port) + cfg->port = nb->default_port; + } + dlg_refresh(hp->host, dlg); + dlg_refresh(hp->port, dlg); + } +} + +static void loggingbuttons_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int button; + Config *cfg = (Config *)data; + /* This function works just like the standard radio-button handler, + * but it has to fall back to "no logging" in situations where the + * configured logging type isn't applicable. + */ + if (event == EVENT_REFRESH) { + for (button = 0; button < ctrl->radio.nbuttons; button++) + if (cfg->logtype == ctrl->radio.buttondata[button].i) + break; + + /* We fell off the end, so we lack the configured logging type */ + if (button == ctrl->radio.nbuttons) { + button=0; + cfg->logtype=LGTYP_NONE; + } + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + cfg->logtype = ctrl->radio.buttondata[button].i; + } +} + +static void numeric_keypad_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int button; + Config *cfg = (Config *)data; + /* + * This function works much like the standard radio button + * handler, but it has to handle two fields in Config. + */ + if (event == EVENT_REFRESH) { + if (cfg->nethack_keypad) + button = 2; + else if (cfg->app_keypad) + button = 1; + else + button = 0; + assert(button < ctrl->radio.nbuttons); + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + if (button == 2) { + cfg->app_keypad = FALSE; + cfg->nethack_keypad = TRUE; + } else { + cfg->app_keypad = (button != 0); + cfg->nethack_keypad = FALSE; + } + } +} + +static void cipherlist_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + if (event == EVENT_REFRESH) { + int i; + + static const struct { char *s; int c; } ciphers[] = { + { "3DES", CIPHER_3DES }, + { "Blowfish", CIPHER_BLOWFISH }, + { "DES", CIPHER_DES }, + { "AES (SSH-2 only)", CIPHER_AES }, + { "Arcfour (SSH-2 only)", CIPHER_ARCFOUR }, + { "-- warn below here --", CIPHER_WARN } + }; + + /* Set up the "selected ciphers" box. */ + /* (cipherlist assumed to contain all ciphers) */ + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < CIPHER_MAX; i++) { + int c = cfg->ssh_cipherlist[i]; + int j; + char *cstr = NULL; + for (j = 0; j < (sizeof ciphers) / (sizeof ciphers[0]); j++) { + if (ciphers[j].c == c) { + cstr = ciphers[j].s; + break; + } + } + dlg_listbox_addwithid(ctrl, dlg, cstr, c); + } + dlg_update_done(ctrl, dlg); + + } else if (event == EVENT_VALCHANGE) { + int i; + + /* Update array to match the list box. */ + for (i=0; i < CIPHER_MAX; i++) + cfg->ssh_cipherlist[i] = dlg_listbox_getid(ctrl, dlg, i); + + } +} + +#ifndef NO_GSSAPI +static void gsslist_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + if (event == EVENT_REFRESH) { + int i; + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < ngsslibs; i++) { + int id = cfg->ssh_gsslist[i]; + assert(id >= 0 && id < ngsslibs); + dlg_listbox_addwithid(ctrl, dlg, gsslibnames[id], id); + } + dlg_update_done(ctrl, dlg); + + } else if (event == EVENT_VALCHANGE) { + int i; + + /* Update array to match the list box. */ + for (i=0; i < ngsslibs; i++) + cfg->ssh_gsslist[i] = dlg_listbox_getid(ctrl, dlg, i); + } +} +#endif + +static void kexlist_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + if (event == EVENT_REFRESH) { + int i; + + static const struct { char *s; int k; } kexes[] = { + { "Diffie-Hellman group 1", KEX_DHGROUP1 }, + { "Diffie-Hellman group 14", KEX_DHGROUP14 }, + { "Diffie-Hellman group exchange", KEX_DHGEX }, + { "RSA-based key exchange", KEX_RSA }, + { "-- warn below here --", KEX_WARN } + }; + + /* Set up the "kex preference" box. */ + /* (kexlist assumed to contain all algorithms) */ + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < KEX_MAX; i++) { + int k = cfg->ssh_kexlist[i]; + int j; + char *kstr = NULL; + for (j = 0; j < (sizeof kexes) / (sizeof kexes[0]); j++) { + if (kexes[j].k == k) { + kstr = kexes[j].s; + break; + } + } + dlg_listbox_addwithid(ctrl, dlg, kstr, k); + } + dlg_update_done(ctrl, dlg); + + } else if (event == EVENT_VALCHANGE) { + int i; + + /* Update array to match the list box. */ + for (i=0; i < KEX_MAX; i++) + cfg->ssh_kexlist[i] = dlg_listbox_getid(ctrl, dlg, i); + + } +} + +static void printerbox_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + if (event == EVENT_REFRESH) { + int nprinters, i; + printer_enum *pe; + + dlg_update_start(ctrl, dlg); + /* + * Some backends may wish to disable the drop-down list on + * this edit box. Be prepared for this. + */ + if (ctrl->editbox.has_list) { + dlg_listbox_clear(ctrl, dlg); + dlg_listbox_add(ctrl, dlg, PRINTER_DISABLED_STRING); + pe = printer_start_enum(&nprinters); + for (i = 0; i < nprinters; i++) + dlg_listbox_add(ctrl, dlg, printer_get_name(pe, i)); + printer_finish_enum(pe); + } + dlg_editbox_set(ctrl, dlg, + (*cfg->printer ? cfg->printer : + PRINTER_DISABLED_STRING)); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_VALCHANGE) { + dlg_editbox_get(ctrl, dlg, cfg->printer, sizeof(cfg->printer)); + if (!strcmp(cfg->printer, PRINTER_DISABLED_STRING)) + *cfg->printer = '\0'; + } +} + +static void codepage_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + if (event == EVENT_REFRESH) { + int i; + const char *cp, *thiscp; + dlg_update_start(ctrl, dlg); + thiscp = cp_name(decode_codepage(cfg->line_codepage)); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; (cp = cp_enumerate(i)) != NULL; i++) + dlg_listbox_add(ctrl, dlg, cp); + dlg_editbox_set(ctrl, dlg, thiscp); + strcpy(cfg->line_codepage, thiscp); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_VALCHANGE) { + dlg_editbox_get(ctrl, dlg, cfg->line_codepage, + sizeof(cfg->line_codepage)); + strcpy(cfg->line_codepage, + cp_name(decode_codepage(cfg->line_codepage))); + } +} + +static void sshbug_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + dlg_listbox_addwithid(ctrl, dlg, "Auto", AUTO); + dlg_listbox_addwithid(ctrl, dlg, "Off", FORCE_OFF); + dlg_listbox_addwithid(ctrl, dlg, "On", FORCE_ON); + switch (*(int *)ATOFFSET(data, ctrl->listbox.context.i)) { + case AUTO: dlg_listbox_select(ctrl, dlg, 0); break; + case FORCE_OFF: dlg_listbox_select(ctrl, dlg, 1); break; + case FORCE_ON: dlg_listbox_select(ctrl, dlg, 2); break; + } + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = AUTO; + else + i = dlg_listbox_getid(ctrl, dlg, i); + *(int *)ATOFFSET(data, ctrl->listbox.context.i) = i; + } +} + +#define SAVEDSESSION_LEN 2048 + +struct sessionsaver_data { + union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton; + union control *okbutton, *cancelbutton; + struct sesslist sesslist; + int midsession; +}; + +/* + * Helper function to load the session selected in the list box, if + * any, as this is done in more than one place below. Returns 0 for + * failure. + */ +static int load_selected_session(struct sessionsaver_data *ssd, + char *savedsession, + void *dlg, Config *cfg, int *maybe_launch) +{ + int i = dlg_listbox_index(ssd->listbox, dlg); + int isdef; + if (i < 0) { + dlg_beep(dlg); + return 0; + } + isdef = !strcmp(ssd->sesslist.sessions[i], "Default Settings"); + load_settings(ssd->sesslist.sessions[i], cfg); + if (!isdef) { + strncpy(savedsession, ssd->sesslist.sessions[i], + SAVEDSESSION_LEN); + savedsession[SAVEDSESSION_LEN-1] = '\0'; + if (maybe_launch) + *maybe_launch = TRUE; + } else { + savedsession[0] = '\0'; + if (maybe_launch) + *maybe_launch = FALSE; + } + dlg_refresh(NULL, dlg); + /* Restore the selection, which might have been clobbered by + * changing the value of the edit box. */ + dlg_listbox_select(ssd->listbox, dlg, i); + return 1; +} + +static void sessionsaver_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + struct sessionsaver_data *ssd = + (struct sessionsaver_data *)ctrl->generic.context.p; + char *savedsession; + + /* + * The first time we're called in a new dialog, we must + * allocate space to store the current contents of the saved + * session edit box (since it must persist even when we switch + * panels, but is not part of the Config). + */ + if (!ssd->editbox) { + savedsession = NULL; + } else if (!dlg_get_privdata(ssd->editbox, dlg)) { + savedsession = (char *) + dlg_alloc_privdata(ssd->editbox, dlg, SAVEDSESSION_LEN); + savedsession[0] = '\0'; + } else { + savedsession = dlg_get_privdata(ssd->editbox, dlg); + } + + if (event == EVENT_REFRESH) { + if (ctrl == ssd->editbox) { + dlg_editbox_set(ctrl, dlg, savedsession); + } else if (ctrl == ssd->listbox) { + int i; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < ssd->sesslist.nsessions; i++) + dlg_listbox_add(ctrl, dlg, ssd->sesslist.sessions[i]); + dlg_update_done(ctrl, dlg); + } + } else if (event == EVENT_VALCHANGE) { + int top, bottom, halfway, i; + if (ctrl == ssd->editbox) { + dlg_editbox_get(ctrl, dlg, savedsession, + SAVEDSESSION_LEN); + top = ssd->sesslist.nsessions; + bottom = -1; + while (top-bottom > 1) { + halfway = (top+bottom)/2; + i = strcmp(savedsession, ssd->sesslist.sessions[halfway]); + if (i <= 0 ) { + top = halfway; + } else { + bottom = halfway; + } + } + if (top == ssd->sesslist.nsessions) { + top -= 1; + } + dlg_listbox_select(ssd->listbox, dlg, top); + } + } else if (event == EVENT_ACTION) { + int mbl = FALSE; + if (!ssd->midsession && + (ctrl == ssd->listbox || + (ssd->loadbutton && ctrl == ssd->loadbutton))) { + /* + * The user has double-clicked a session, or hit Load. + * We must load the selected session, and then + * terminate the configuration dialog _if_ there was a + * double-click on the list box _and_ that session + * contains a hostname. + */ + if (load_selected_session(ssd, savedsession, dlg, cfg, &mbl) && + (mbl && ctrl == ssd->listbox && cfg_launchable(cfg))) { + dlg_end(dlg, 1); /* it's all over, and succeeded */ + } + } else if (ctrl == ssd->savebutton) { + int isdef = !strcmp(savedsession, "Default Settings"); + if (!savedsession[0]) { + int i = dlg_listbox_index(ssd->listbox, dlg); + if (i < 0) { + dlg_beep(dlg); + return; + } + isdef = !strcmp(ssd->sesslist.sessions[i], "Default Settings"); + if (!isdef) { + strncpy(savedsession, ssd->sesslist.sessions[i], + SAVEDSESSION_LEN); + savedsession[SAVEDSESSION_LEN-1] = '\0'; + } else { + savedsession[0] = '\0'; + } + } + { + char *errmsg = save_settings(savedsession, cfg); + if (errmsg) { + dlg_error_msg(dlg, errmsg); + sfree(errmsg); + } + } + get_sesslist(&ssd->sesslist, FALSE); + get_sesslist(&ssd->sesslist, TRUE); + dlg_refresh(ssd->editbox, dlg); + dlg_refresh(ssd->listbox, dlg); + } else if (!ssd->midsession && + ssd->delbutton && ctrl == ssd->delbutton) { + int i = dlg_listbox_index(ssd->listbox, dlg); + if (i <= 0) { + dlg_beep(dlg); + } else { + del_settings(ssd->sesslist.sessions[i]); + get_sesslist(&ssd->sesslist, FALSE); + get_sesslist(&ssd->sesslist, TRUE); + dlg_refresh(ssd->listbox, dlg); + } + } else if (ctrl == ssd->okbutton) { + if (ssd->midsession) { + /* In a mid-session Change Settings, Apply is always OK. */ + dlg_end(dlg, 1); + return; + } + /* + * Annoying special case. If the `Open' button is + * pressed while no host name is currently set, _and_ + * the session list previously had the focus, _and_ + * there was a session selected in that which had a + * valid host name in it, then load it and go. + */ + if (dlg_last_focused(ctrl, dlg) == ssd->listbox && + !cfg_launchable(cfg)) { + Config cfg2; + int mbl = FALSE; + if (!load_selected_session(ssd, savedsession, dlg, + &cfg2, &mbl)) { + dlg_beep(dlg); + return; + } + /* If at this point we have a valid session, go! */ + if (mbl && cfg_launchable(&cfg2)) { + *cfg = cfg2; /* structure copy */ + cfg->remote_cmd_ptr = NULL; + dlg_end(dlg, 1); + } else + dlg_beep(dlg); + return; + } + + /* + * Otherwise, do the normal thing: if we have a valid + * session, get going. + */ + if (cfg_launchable(cfg)) { + dlg_end(dlg, 1); + } else + dlg_beep(dlg); + } else if (ctrl == ssd->cancelbutton) { + dlg_end(dlg, 0); + } + } +} + +struct charclass_data { + union control *listbox, *editbox, *button; +}; + +static void charclass_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + struct charclass_data *ccd = + (struct charclass_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == ccd->listbox) { + int i; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < 128; i++) { + char str[100]; + sprintf(str, "%d\t(0x%02X)\t%c\t%d", i, i, + (i >= 0x21 && i != 0x7F) ? i : ' ', cfg->wordness[i]); + dlg_listbox_add(ctrl, dlg, str); + } + dlg_update_done(ctrl, dlg); + } + } else if (event == EVENT_ACTION) { + if (ctrl == ccd->button) { + char str[100]; + int i, n; + dlg_editbox_get(ccd->editbox, dlg, str, sizeof(str)); + n = atoi(str); + for (i = 0; i < 128; i++) { + if (dlg_listbox_issel(ccd->listbox, dlg, i)) + cfg->wordness[i] = n; + } + dlg_refresh(ccd->listbox, dlg); + } + } +} + +struct colour_data { + union control *listbox, *redit, *gedit, *bedit, *button; +}; + +static const char *const colours[] = { + "Default Foreground", "Default Bold Foreground", + "Default Background", "Default Bold Background", + "Cursor Text", "Cursor Colour", + "ANSI Black", "ANSI Black Bold", + "ANSI Red", "ANSI Red Bold", + "ANSI Green", "ANSI Green Bold", + "ANSI Yellow", "ANSI Yellow Bold", + "ANSI Blue", "ANSI Blue Bold", + "ANSI Magenta", "ANSI Magenta Bold", + "ANSI Cyan", "ANSI Cyan Bold", + "ANSI White", "ANSI White Bold" +}; + +static void colour_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + struct colour_data *cd = + (struct colour_data *)ctrl->generic.context.p; + int update = FALSE, clear = FALSE, r, g, b; + + if (event == EVENT_REFRESH) { + if (ctrl == cd->listbox) { + int i; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < lenof(colours); i++) + dlg_listbox_add(ctrl, dlg, colours[i]); + dlg_update_done(ctrl, dlg); + clear = TRUE; + update = TRUE; + } + } else if (event == EVENT_SELCHANGE) { + if (ctrl == cd->listbox) { + /* The user has selected a colour. Update the RGB text. */ + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) { + clear = TRUE; + } else { + clear = FALSE; + r = cfg->colours[i][0]; + g = cfg->colours[i][1]; + b = cfg->colours[i][2]; + } + update = TRUE; + } + } else if (event == EVENT_VALCHANGE) { + if (ctrl == cd->redit || ctrl == cd->gedit || ctrl == cd->bedit) { + /* The user has changed the colour using the edit boxes. */ + char buf[80]; + int i, cval; + + dlg_editbox_get(ctrl, dlg, buf, lenof(buf)); + cval = atoi(buf); + if (cval > 255) cval = 255; + if (cval < 0) cval = 0; + + i = dlg_listbox_index(cd->listbox, dlg); + if (i >= 0) { + if (ctrl == cd->redit) + cfg->colours[i][0] = cval; + else if (ctrl == cd->gedit) + cfg->colours[i][1] = cval; + else if (ctrl == cd->bedit) + cfg->colours[i][2] = cval; + } + } + } else if (event == EVENT_ACTION) { + if (ctrl == cd->button) { + int i = dlg_listbox_index(cd->listbox, dlg); + if (i < 0) { + dlg_beep(dlg); + return; + } + /* + * Start a colour selector, which will send us an + * EVENT_CALLBACK when it's finished and allow us to + * pick up the results. + */ + dlg_coloursel_start(ctrl, dlg, + cfg->colours[i][0], + cfg->colours[i][1], + cfg->colours[i][2]); + } + } else if (event == EVENT_CALLBACK) { + if (ctrl == cd->button) { + int i = dlg_listbox_index(cd->listbox, dlg); + /* + * Collect the results of the colour selector. Will + * return nonzero on success, or zero if the colour + * selector did nothing (user hit Cancel, for example). + */ + if (dlg_coloursel_results(ctrl, dlg, &r, &g, &b)) { + cfg->colours[i][0] = r; + cfg->colours[i][1] = g; + cfg->colours[i][2] = b; + clear = FALSE; + update = TRUE; + } + } + } + + if (update) { + if (clear) { + dlg_editbox_set(cd->redit, dlg, ""); + dlg_editbox_set(cd->gedit, dlg, ""); + dlg_editbox_set(cd->bedit, dlg, ""); + } else { + char buf[40]; + sprintf(buf, "%d", r); dlg_editbox_set(cd->redit, dlg, buf); + sprintf(buf, "%d", g); dlg_editbox_set(cd->gedit, dlg, buf); + sprintf(buf, "%d", b); dlg_editbox_set(cd->bedit, dlg, buf); + } + } +} + +struct ttymodes_data { + union control *modelist, *valradio, *valbox; + union control *addbutton, *rembutton, *listbox; +}; + +static void ttymodes_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + struct ttymodes_data *td = + (struct ttymodes_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == td->listbox) { + char *p = cfg->ttymodes; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + while (*p) { + int tabpos = strchr(p, '\t') - p; + char *disp = dupprintf("%.*s\t%s", tabpos, p, + (p[tabpos+1] == 'A') ? "(auto)" : + p+tabpos+2); + dlg_listbox_add(ctrl, dlg, disp); + p += strlen(p) + 1; + sfree(disp); + } + dlg_update_done(ctrl, dlg); + } else if (ctrl == td->modelist) { + int i; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; ttymodes[i]; i++) + dlg_listbox_add(ctrl, dlg, ttymodes[i]); + dlg_listbox_select(ctrl, dlg, 0); /* *shrug* */ + dlg_update_done(ctrl, dlg); + } else if (ctrl == td->valradio) { + dlg_radiobutton_set(ctrl, dlg, 0); + } + } else if (event == EVENT_ACTION) { + if (ctrl == td->addbutton) { + int ind = dlg_listbox_index(td->modelist, dlg); + if (ind >= 0) { + char type = dlg_radiobutton_get(td->valradio, dlg) ? 'V' : 'A'; + int slen, left; + char *p, str[lenof(cfg->ttymodes)]; + /* Construct new entry */ + memset(str, 0, lenof(str)); + strncpy(str, ttymodes[ind], lenof(str)-3); + slen = strlen(str); + str[slen] = '\t'; + str[slen+1] = type; + slen += 2; + if (type == 'V') { + dlg_editbox_get(td->valbox, dlg, str+slen, lenof(str)-slen); + } + /* Find end of list, deleting any existing instance */ + p = cfg->ttymodes; + left = lenof(cfg->ttymodes); + while (*p) { + int t = strchr(p, '\t') - p; + if (t == strlen(ttymodes[ind]) && + strncmp(p, ttymodes[ind], t) == 0) { + memmove(p, p+strlen(p)+1, left - (strlen(p)+1)); + continue; + } + left -= strlen(p) + 1; + p += strlen(p) + 1; + } + /* Append new entry */ + memset(p, 0, left); + strncpy(p, str, left - 2); + dlg_refresh(td->listbox, dlg); + } else + dlg_beep(dlg); + } else if (ctrl == td->rembutton) { + char *p = cfg->ttymodes; + int i = 0, len = lenof(cfg->ttymodes); + while (*p) { + int multisel = dlg_listbox_index(td->listbox, dlg) < 0; + if (dlg_listbox_issel(td->listbox, dlg, i)) { + if (!multisel) { + /* Populate controls with entry we're about to + * delete, for ease of editing. + * (If multiple entries were selected, don't + * touch the controls.) */ + char *val = strchr(p, '\t'); + if (val) { + int ind = 0; + val++; + while (ttymodes[ind]) { + if (strlen(ttymodes[ind]) == val-p-1 && + !strncmp(ttymodes[ind], p, val-p-1)) + break; + ind++; + } + dlg_listbox_select(td->modelist, dlg, ind); + dlg_radiobutton_set(td->valradio, dlg, + (*val == 'V')); + dlg_editbox_set(td->valbox, dlg, val+1); + } + } + memmove(p, p+strlen(p)+1, len - (strlen(p)+1)); + i++; + continue; + } + len -= strlen(p) + 1; + p += strlen(p) + 1; + i++; + } + memset(p, 0, lenof(cfg->ttymodes) - len); + dlg_refresh(td->listbox, dlg); + } + } +} + +struct environ_data { + union control *varbox, *valbox, *addbutton, *rembutton, *listbox; +}; + +static void environ_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + struct environ_data *ed = + (struct environ_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == ed->listbox) { + char *p = cfg->environmt; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + while (*p) { + dlg_listbox_add(ctrl, dlg, p); + p += strlen(p) + 1; + } + dlg_update_done(ctrl, dlg); + } + } else if (event == EVENT_ACTION) { + if (ctrl == ed->addbutton) { + char str[sizeof(cfg->environmt)]; + char *p; + dlg_editbox_get(ed->varbox, dlg, str, sizeof(str)-1); + if (!*str) { + dlg_beep(dlg); + return; + } + p = str + strlen(str); + *p++ = '\t'; + dlg_editbox_get(ed->valbox, dlg, p, sizeof(str)-1 - (p - str)); + if (!*p) { + dlg_beep(dlg); + return; + } + p = cfg->environmt; + while (*p) { + while (*p) + p++; + p++; + } + if ((p - cfg->environmt) + strlen(str) + 2 < + sizeof(cfg->environmt)) { + strcpy(p, str); + p[strlen(str) + 1] = '\0'; + dlg_listbox_add(ed->listbox, dlg, str); + dlg_editbox_set(ed->varbox, dlg, ""); + dlg_editbox_set(ed->valbox, dlg, ""); + } else { + dlg_error_msg(dlg, "Environment too big"); + } + } else if (ctrl == ed->rembutton) { + int i = dlg_listbox_index(ed->listbox, dlg); + if (i < 0) { + dlg_beep(dlg); + } else { + char *p, *q, *str; + + dlg_listbox_del(ed->listbox, dlg, i); + p = cfg->environmt; + while (i > 0) { + if (!*p) + goto disaster; + while (*p) + p++; + p++; + i--; + } + q = p; + if (!*p) + goto disaster; + /* Populate controls with the entry we're about to delete + * for ease of editing */ + str = p; + p = strchr(p, '\t'); + if (!p) + goto disaster; + *p = '\0'; + dlg_editbox_set(ed->varbox, dlg, str); + p++; + str = p; + dlg_editbox_set(ed->valbox, dlg, str); + p = strchr(p, '\0'); + if (!p) + goto disaster; + p++; + while (*p) { + while (*p) + *q++ = *p++; + *q++ = *p++; + } + *q = '\0'; + disaster:; + } + } + } +} + +struct portfwd_data { + union control *addbutton, *rembutton, *listbox; + union control *sourcebox, *destbox, *direction; +#ifndef NO_IPV6 + union control *addressfamily; +#endif +}; + +static void portfwd_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + Config *cfg = (Config *)data; + struct portfwd_data *pfd = + (struct portfwd_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == pfd->listbox) { + char *p = cfg->portfwd; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + while (*p) { + dlg_listbox_add(ctrl, dlg, p); + p += strlen(p) + 1; + } + dlg_update_done(ctrl, dlg); + } else if (ctrl == pfd->direction) { + /* + * Default is Local. + */ + dlg_radiobutton_set(ctrl, dlg, 0); +#ifndef NO_IPV6 + } else if (ctrl == pfd->addressfamily) { + dlg_radiobutton_set(ctrl, dlg, 0); +#endif + } + } else if (event == EVENT_ACTION) { + if (ctrl == pfd->addbutton) { + char str[sizeof(cfg->portfwd)]; + char *p; + int i, type; + int whichbutton; + + i = 0; +#ifndef NO_IPV6 + whichbutton = dlg_radiobutton_get(pfd->addressfamily, dlg); + if (whichbutton == 1) + str[i++] = '4'; + else if (whichbutton == 2) + str[i++] = '6'; +#endif + + whichbutton = dlg_radiobutton_get(pfd->direction, dlg); + if (whichbutton == 0) + type = 'L'; + else if (whichbutton == 1) + type = 'R'; + else + type = 'D'; + str[i++] = type; + + dlg_editbox_get(pfd->sourcebox, dlg, str+i, sizeof(str) - i); + if (!str[i]) { + dlg_error_msg(dlg, "You need to specify a source port number"); + return; + } + p = str + strlen(str); + if (type != 'D') { + *p++ = '\t'; + dlg_editbox_get(pfd->destbox, dlg, p, + sizeof(str) - (p - str)); + if (!*p || !strchr(p, ':')) { + dlg_error_msg(dlg, + "You need to specify a destination address\n" + "in the form \"host.name:port\""); + return; + } + } else + *p = '\0'; + p = cfg->portfwd; + while (*p) { + if (strcmp(p,str) == 0) { + dlg_error_msg(dlg, "Specified forwarding already exists"); + break; + } + while (*p) + p++; + p++; + } + if (!*p) { + if ((p - cfg->portfwd) + strlen(str) + 2 <= + sizeof(cfg->portfwd)) { + strcpy(p, str); + p[strlen(str) + 1] = '\0'; + dlg_listbox_add(pfd->listbox, dlg, str); + dlg_editbox_set(pfd->sourcebox, dlg, ""); + dlg_editbox_set(pfd->destbox, dlg, ""); + } else { + dlg_error_msg(dlg, "Too many forwardings"); + } + } + } else if (ctrl == pfd->rembutton) { + int i = dlg_listbox_index(pfd->listbox, dlg); + if (i < 0) + dlg_beep(dlg); + else { + char *p, *q, *src, *dst; + char dir; + + dlg_listbox_del(pfd->listbox, dlg, i); + p = cfg->portfwd; + while (i > 0) { + if (!*p) + goto disaster2; + while (*p) + p++; + p++; + i--; + } + q = p; + if (!*p) + goto disaster2; + /* Populate the controls with the entry we're about to + * delete, for ease of editing. */ + { + static const char *const afs = "A46"; + char *afp = strchr(afs, *p); +#ifndef NO_IPV6 + int idx = afp ? afp-afs : 0; +#endif + if (afp) + p++; +#ifndef NO_IPV6 + dlg_radiobutton_set(pfd->addressfamily, dlg, idx); +#endif + } + { + static const char *const dirs = "LRD"; + dir = *p; + dlg_radiobutton_set(pfd->direction, dlg, + strchr(dirs, dir) - dirs); + } + p++; + if (dir != 'D') { + src = p; + p = strchr(p, '\t'); + if (!p) + goto disaster2; + *p = '\0'; + p++; + dst = p; + } else { + src = p; + dst = ""; + } + p = strchr(p, '\0'); + if (!p) + goto disaster2; + dlg_editbox_set(pfd->sourcebox, dlg, src); + dlg_editbox_set(pfd->destbox, dlg, dst); + p++; + while (*p) { + while (*p) + *q++ = *p++; + *q++ = *p++; + } + *q = '\0'; + disaster2:; + } + } + } +} + +void setup_config_box(struct controlbox *b, int midsession, + int protocol, int protcfginfo) +{ + struct controlset *s; + struct sessionsaver_data *ssd; + struct charclass_data *ccd; + struct colour_data *cd; + struct ttymodes_data *td; + struct environ_data *ed; + struct portfwd_data *pfd; + union control *c; + char *str; + + ssd = (struct sessionsaver_data *) + ctrl_alloc(b, sizeof(struct sessionsaver_data)); + memset(ssd, 0, sizeof(*ssd)); + ssd->midsession = midsession; + + /* + * The standard panel that appears at the bottom of all panels: + * Open, Cancel, Apply etc. + */ + s = ctrl_getset(b, "", "", ""); + ctrl_columns(s, 5, 20, 20, 20, 20, 20); + ssd->okbutton = ctrl_pushbutton(s, + (midsession ? "Apply" : "Open"), + (char)(midsession ? 'a' : 'o'), + HELPCTX(no_help), + sessionsaver_handler, P(ssd)); + ssd->okbutton->button.isdefault = TRUE; + ssd->okbutton->generic.column = 3; + ssd->cancelbutton = ctrl_pushbutton(s, "Cancel", 'c', HELPCTX(no_help), + sessionsaver_handler, P(ssd)); + ssd->cancelbutton->button.iscancel = TRUE; + ssd->cancelbutton->generic.column = 4; + /* We carefully don't close the 5-column part, so that platform- + * specific add-ons can put extra buttons alongside Open and Cancel. */ + + /* + * The Session panel. + */ + str = dupprintf("Basic options for your %s session", appname); + ctrl_settitle(b, "Session", str); + sfree(str); + + if (!midsession) { + struct hostport *hp = (struct hostport *) + ctrl_alloc(b, sizeof(struct hostport)); + + s = ctrl_getset(b, "Session", "hostport", + "Specify the destination you want to connect to"); + ctrl_columns(s, 2, 75, 25); + c = ctrl_editbox(s, HOST_BOX_TITLE, 'n', 100, + HELPCTX(session_hostname), + config_host_handler, I(0), I(0)); + c->generic.column = 0; + hp->host = c; + c = ctrl_editbox(s, PORT_BOX_TITLE, 'p', 100, + HELPCTX(session_hostname), + config_port_handler, I(0), I(0)); + c->generic.column = 1; + hp->port = c; + ctrl_columns(s, 1, 100); + + if (!backend_from_proto(PROT_SSH)) { + ctrl_radiobuttons(s, "Connection type:", NO_SHORTCUT, 3, + HELPCTX(session_hostname), + config_protocolbuttons_handler, P(hp), + "Raw", 'w', I(PROT_RAW), + "Telnet", 't', I(PROT_TELNET), + "Rlogin", 'i', I(PROT_RLOGIN), + NULL); + } else { + ctrl_radiobuttons(s, "Connection type:", NO_SHORTCUT, 4, + HELPCTX(session_hostname), + config_protocolbuttons_handler, P(hp), + "Raw", 'w', I(PROT_RAW), + "Telnet", 't', I(PROT_TELNET), + "Rlogin", 'i', I(PROT_RLOGIN), + "SSH", 's', I(PROT_SSH), + NULL); + } + } + + /* + * The Load/Save panel is available even in mid-session. + */ + s = ctrl_getset(b, "Session", "savedsessions", + midsession ? "Save the current session settings" : + "Load, save or delete a stored session"); + ctrl_columns(s, 2, 75, 25); + get_sesslist(&ssd->sesslist, TRUE); + ssd->editbox = ctrl_editbox(s, "Saved Sessions", 'e', 100, + HELPCTX(session_saved), + sessionsaver_handler, P(ssd), P(NULL)); + ssd->editbox->generic.column = 0; + /* Reset columns so that the buttons are alongside the list, rather + * than alongside that edit box. */ + ctrl_columns(s, 1, 100); + ctrl_columns(s, 2, 75, 25); + ssd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->listbox->generic.column = 0; + ssd->listbox->listbox.height = 7; + if (!midsession) { + ssd->loadbutton = ctrl_pushbutton(s, "Load", 'l', + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->loadbutton->generic.column = 1; + } else { + /* We can't offer the Load button mid-session, as it would allow the + * user to load and subsequently save settings they can't see. (And + * also change otherwise immutable settings underfoot; that probably + * shouldn't be a problem, but.) */ + ssd->loadbutton = NULL; + } + /* "Save" button is permitted mid-session. */ + ssd->savebutton = ctrl_pushbutton(s, "Save", 'v', + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->savebutton->generic.column = 1; + if (!midsession) { + ssd->delbutton = ctrl_pushbutton(s, "Delete", 'd', + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->delbutton->generic.column = 1; + } else { + /* Disable the Delete button mid-session too, for UI consistency. */ + ssd->delbutton = NULL; + } + ctrl_columns(s, 1, 100); + + s = ctrl_getset(b, "Session", "otheropts", NULL); + c = ctrl_radiobuttons(s, "Close window on exit:", 'x', 4, + HELPCTX(session_coe), + dlg_stdradiobutton_handler, + I(offsetof(Config, close_on_exit)), + "Always", I(FORCE_ON), + "Never", I(FORCE_OFF), + "Only on clean exit", I(AUTO), NULL); + + /* + * The Session/Logging panel. + */ + ctrl_settitle(b, "Session/Logging", "Options controlling session logging"); + + s = ctrl_getset(b, "Session/Logging", "main", NULL); + /* + * The logging buttons change depending on whether SSH packet + * logging can sensibly be available. + */ + { + char *sshlogname, *sshrawlogname; + if ((midsession && protocol == PROT_SSH) || + (!midsession && backend_from_proto(PROT_SSH))) { + sshlogname = "SSH packets"; + sshrawlogname = "SSH packets and raw data"; + } else { + sshlogname = NULL; /* this will disable both buttons */ + sshrawlogname = NULL; /* this will just placate optimisers */ + } + ctrl_radiobuttons(s, "Session logging:", NO_SHORTCUT, 2, + HELPCTX(logging_main), + loggingbuttons_handler, + I(offsetof(Config, logtype)), + "None", 't', I(LGTYP_NONE), + "Printable output", 'p', I(LGTYP_ASCII), + "All session output", 'l', I(LGTYP_DEBUG), + sshlogname, 's', I(LGTYP_PACKETS), + sshrawlogname, 'r', I(LGTYP_SSHRAW), + NULL); + } + ctrl_filesel(s, "Log file name:", 'f', + NULL, TRUE, "Select session log file name", + HELPCTX(logging_filename), + dlg_stdfilesel_handler, I(offsetof(Config, logfilename))); + ctrl_text(s, "(Log file name can contain &Y, &M, &D for date," + " &T for time, and &H for host name)", + HELPCTX(logging_filename)); + ctrl_radiobuttons(s, "What to do if the log file already exists:", 'e', 1, + HELPCTX(logging_exists), + dlg_stdradiobutton_handler, I(offsetof(Config,logxfovr)), + "Always overwrite it", I(LGXF_OVR), + "Always append to the end of it", I(LGXF_APN), + "Ask the user every time", I(LGXF_ASK), NULL); + ctrl_checkbox(s, "Flush log file frequently", 'u', + HELPCTX(logging_flush), + dlg_stdcheckbox_handler, I(offsetof(Config,logflush))); + + if ((midsession && protocol == PROT_SSH) || + (!midsession && backend_from_proto(PROT_SSH))) { + s = ctrl_getset(b, "Session/Logging", "ssh", + "Options specific to SSH packet logging"); + ctrl_checkbox(s, "Omit known password fields", 'k', + HELPCTX(logging_ssh_omit_password), + dlg_stdcheckbox_handler, I(offsetof(Config,logomitpass))); + ctrl_checkbox(s, "Omit session data", 'd', + HELPCTX(logging_ssh_omit_data), + dlg_stdcheckbox_handler, I(offsetof(Config,logomitdata))); + } + + /* + * The Terminal panel. + */ + ctrl_settitle(b, "Terminal", "Options controlling the terminal emulation"); + + s = ctrl_getset(b, "Terminal", "general", "Set various terminal options"); + ctrl_checkbox(s, "Auto wrap mode initially on", 'w', + HELPCTX(terminal_autowrap), + dlg_stdcheckbox_handler, I(offsetof(Config,wrap_mode))); + ctrl_checkbox(s, "DEC Origin Mode initially on", 'd', + HELPCTX(terminal_decom), + dlg_stdcheckbox_handler, I(offsetof(Config,dec_om))); + ctrl_checkbox(s, "Implicit CR in every LF", 'r', + HELPCTX(terminal_lfhascr), + dlg_stdcheckbox_handler, I(offsetof(Config,lfhascr))); + ctrl_checkbox(s, "Implicit LF in every CR", 'f', + HELPCTX(terminal_crhaslf), + dlg_stdcheckbox_handler, I(offsetof(Config,crhaslf))); + ctrl_checkbox(s, "Use background colour to erase screen", 'e', + HELPCTX(terminal_bce), + dlg_stdcheckbox_handler, I(offsetof(Config,bce))); + ctrl_checkbox(s, "Enable blinking text", 'n', + HELPCTX(terminal_blink), + dlg_stdcheckbox_handler, I(offsetof(Config,blinktext))); + ctrl_editbox(s, "Answerback to ^E:", 's', 100, + HELPCTX(terminal_answerback), + dlg_stdeditbox_handler, I(offsetof(Config,answerback)), + I(sizeof(((Config *)0)->answerback))); + + s = ctrl_getset(b, "Terminal", "ldisc", "Line discipline options"); + ctrl_radiobuttons(s, "Local echo:", 'l', 3, + HELPCTX(terminal_localecho), + dlg_stdradiobutton_handler,I(offsetof(Config,localecho)), + "Auto", I(AUTO), + "Force on", I(FORCE_ON), + "Force off", I(FORCE_OFF), NULL); + ctrl_radiobuttons(s, "Local line editing:", 't', 3, + HELPCTX(terminal_localedit), + dlg_stdradiobutton_handler,I(offsetof(Config,localedit)), + "Auto", I(AUTO), + "Force on", I(FORCE_ON), + "Force off", I(FORCE_OFF), NULL); + + s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing"); + ctrl_combobox(s, "Printer to send ANSI printer output to:", 'p', 100, + HELPCTX(terminal_printing), + printerbox_handler, P(NULL), P(NULL)); + + /* + * The Terminal/Keyboard panel. + */ + ctrl_settitle(b, "Terminal/Keyboard", + "Options controlling the effects of keys"); + + s = ctrl_getset(b, "Terminal/Keyboard", "mappings", + "Change the sequences sent by:"); + ctrl_radiobuttons(s, "The Backspace key", 'b', 2, + HELPCTX(keyboard_backspace), + dlg_stdradiobutton_handler, + I(offsetof(Config, bksp_is_delete)), + "Control-H", I(0), "Control-? (127)", I(1), NULL); + ctrl_radiobuttons(s, "The Home and End keys", 'e', 2, + HELPCTX(keyboard_homeend), + dlg_stdradiobutton_handler, + I(offsetof(Config, rxvt_homeend)), + "Standard", I(0), "rxvt", I(1), NULL); + ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 3, + HELPCTX(keyboard_funkeys), + dlg_stdradiobutton_handler, + I(offsetof(Config, funky_type)), + "ESC[n~", I(0), "Linux", I(1), "Xterm R6", I(2), + "VT400", I(3), "VT100+", I(4), "SCO", I(5), NULL); + + s = ctrl_getset(b, "Terminal/Keyboard", "appkeypad", + "Application keypad settings:"); + ctrl_radiobuttons(s, "Initial state of cursor keys:", 'r', 3, + HELPCTX(keyboard_appcursor), + dlg_stdradiobutton_handler, + I(offsetof(Config, app_cursor)), + "Normal", I(0), "Application", I(1), NULL); + ctrl_radiobuttons(s, "Initial state of numeric keypad:", 'n', 3, + HELPCTX(keyboard_appkeypad), + numeric_keypad_handler, P(NULL), + "Normal", I(0), "Application", I(1), "NetHack", I(2), + NULL); + + /* + * The Terminal/Bell panel. + */ + ctrl_settitle(b, "Terminal/Bell", + "Options controlling the terminal bell"); + + s = ctrl_getset(b, "Terminal/Bell", "style", "Set the style of bell"); + ctrl_radiobuttons(s, "Action to happen when a bell occurs:", 'b', 1, + HELPCTX(bell_style), + dlg_stdradiobutton_handler, I(offsetof(Config, beep)), + "None (bell disabled)", I(BELL_DISABLED), + "Make default system alert sound", I(BELL_DEFAULT), + "Visual bell (flash window)", I(BELL_VISUAL), NULL); + + s = ctrl_getset(b, "Terminal/Bell", "overload", + "Control the bell overload behaviour"); + ctrl_checkbox(s, "Bell is temporarily disabled when over-used", 'd', + HELPCTX(bell_overload), + dlg_stdcheckbox_handler, I(offsetof(Config,bellovl))); + ctrl_editbox(s, "Over-use means this many bells...", 'm', 20, + HELPCTX(bell_overload), + dlg_stdeditbox_handler, I(offsetof(Config,bellovl_n)), I(-1)); + ctrl_editbox(s, "... in this many seconds", 't', 20, + HELPCTX(bell_overload), + dlg_stdeditbox_handler, I(offsetof(Config,bellovl_t)), + I(-TICKSPERSEC)); + ctrl_text(s, "The bell is re-enabled after a few seconds of silence.", + HELPCTX(bell_overload)); + ctrl_editbox(s, "Seconds of silence required", 's', 20, + HELPCTX(bell_overload), + dlg_stdeditbox_handler, I(offsetof(Config,bellovl_s)), + I(-TICKSPERSEC)); + + /* + * The Terminal/Features panel. + */ + ctrl_settitle(b, "Terminal/Features", + "Enabling and disabling advanced terminal features"); + + s = ctrl_getset(b, "Terminal/Features", "main", NULL); + ctrl_checkbox(s, "Disable application cursor keys mode", 'u', + HELPCTX(features_application), + dlg_stdcheckbox_handler, I(offsetof(Config,no_applic_c))); + ctrl_checkbox(s, "Disable application keypad mode", 'k', + HELPCTX(features_application), + dlg_stdcheckbox_handler, I(offsetof(Config,no_applic_k))); + ctrl_checkbox(s, "Disable xterm-style mouse reporting", 'x', + HELPCTX(features_mouse), + dlg_stdcheckbox_handler, I(offsetof(Config,no_mouse_rep))); + ctrl_checkbox(s, "Disable remote-controlled terminal resizing", 's', + HELPCTX(features_resize), + dlg_stdcheckbox_handler, + I(offsetof(Config,no_remote_resize))); + ctrl_checkbox(s, "Disable switching to alternate terminal screen", 'w', + HELPCTX(features_altscreen), + dlg_stdcheckbox_handler, I(offsetof(Config,no_alt_screen))); + ctrl_checkbox(s, "Disable remote-controlled window title changing", 't', + HELPCTX(features_retitle), + dlg_stdcheckbox_handler, + I(offsetof(Config,no_remote_wintitle))); + ctrl_radiobuttons(s, "Response to remote title query (SECURITY):", 'q', 3, + HELPCTX(features_qtitle), + dlg_stdradiobutton_handler, + I(offsetof(Config,remote_qtitle_action)), + "None", I(TITLE_NONE), + "Empty string", I(TITLE_EMPTY), + "Window title", I(TITLE_REAL), NULL); + ctrl_checkbox(s, "Disable destructive backspace on server sending ^?",'b', + HELPCTX(features_dbackspace), + dlg_stdcheckbox_handler, I(offsetof(Config,no_dbackspace))); + ctrl_checkbox(s, "Disable remote-controlled character set configuration", + 'r', HELPCTX(features_charset), dlg_stdcheckbox_handler, + I(offsetof(Config,no_remote_charset))); + ctrl_checkbox(s, "Disable Arabic text shaping", + 'l', HELPCTX(features_arabicshaping), dlg_stdcheckbox_handler, + I(offsetof(Config, arabicshaping))); + ctrl_checkbox(s, "Disable bidirectional text display", + 'd', HELPCTX(features_bidi), dlg_stdcheckbox_handler, + I(offsetof(Config, bidi))); + + /* + * The Window panel. + */ + str = dupprintf("Options controlling %s's window", appname); + ctrl_settitle(b, "Window", str); + sfree(str); + + s = ctrl_getset(b, "Window", "size", "Set the size of the window"); + ctrl_columns(s, 2, 50, 50); + c = ctrl_editbox(s, "Columns", 'm', 100, + HELPCTX(window_size), + dlg_stdeditbox_handler, I(offsetof(Config,width)), I(-1)); + c->generic.column = 0; + c = ctrl_editbox(s, "Rows", 'r', 100, + HELPCTX(window_size), + dlg_stdeditbox_handler, I(offsetof(Config,height)),I(-1)); + c->generic.column = 1; + ctrl_columns(s, 1, 100); + + s = ctrl_getset(b, "Window", "scrollback", + "Control the scrollback in the window"); + ctrl_editbox(s, "Lines of scrollback", 's', 50, + HELPCTX(window_scrollback), + dlg_stdeditbox_handler, I(offsetof(Config,savelines)), I(-1)); + ctrl_checkbox(s, "Display scrollbar", 'd', + HELPCTX(window_scrollback), + dlg_stdcheckbox_handler, I(offsetof(Config,scrollbar))); + ctrl_checkbox(s, "Reset scrollback on keypress", 'k', + HELPCTX(window_scrollback), + dlg_stdcheckbox_handler, I(offsetof(Config,scroll_on_key))); + ctrl_checkbox(s, "Reset scrollback on display activity", 'p', + HELPCTX(window_scrollback), + dlg_stdcheckbox_handler, I(offsetof(Config,scroll_on_disp))); + ctrl_checkbox(s, "Push erased text into scrollback", 'e', + HELPCTX(window_erased), + dlg_stdcheckbox_handler, + I(offsetof(Config,erase_to_scrollback))); + + /* + * The Window/Appearance panel. + */ + str = dupprintf("Configure the appearance of %s's window", appname); + ctrl_settitle(b, "Window/Appearance", str); + sfree(str); + + s = ctrl_getset(b, "Window/Appearance", "cursor", + "Adjust the use of the cursor"); + ctrl_radiobuttons(s, "Cursor appearance:", NO_SHORTCUT, 3, + HELPCTX(appearance_cursor), + dlg_stdradiobutton_handler, + I(offsetof(Config, cursor_type)), + "Block", 'l', I(0), + "Underline", 'u', I(1), + "Vertical line", 'v', I(2), NULL); + ctrl_checkbox(s, "Cursor blinks", 'b', + HELPCTX(appearance_cursor), + dlg_stdcheckbox_handler, I(offsetof(Config,blink_cur))); + + s = ctrl_getset(b, "Window/Appearance", "font", + "Font settings"); + ctrl_fontsel(s, "Font used in the terminal window", 'n', + HELPCTX(appearance_font), + dlg_stdfontsel_handler, I(offsetof(Config, font))); + + s = ctrl_getset(b, "Window/Appearance", "mouse", + "Adjust the use of the mouse pointer"); + ctrl_checkbox(s, "Hide mouse pointer when typing in window", 'p', + HELPCTX(appearance_hidemouse), + dlg_stdcheckbox_handler, I(offsetof(Config,hide_mouseptr))); + + s = ctrl_getset(b, "Window/Appearance", "border", + "Adjust the window border"); + ctrl_editbox(s, "Gap between text and window edge:", 'e', 20, + HELPCTX(appearance_border), + dlg_stdeditbox_handler, + I(offsetof(Config,window_border)), I(-1)); + + /* + * The Window/Behaviour panel. + */ + str = dupprintf("Configure the behaviour of %s's window", appname); + ctrl_settitle(b, "Window/Behaviour", str); + sfree(str); + + s = ctrl_getset(b, "Window/Behaviour", "title", + "Adjust the behaviour of the window title"); + ctrl_editbox(s, "Window title:", 't', 100, + HELPCTX(appearance_title), + dlg_stdeditbox_handler, I(offsetof(Config,wintitle)), + I(sizeof(((Config *)0)->wintitle))); + ctrl_checkbox(s, "Separate window and icon titles", 'i', + HELPCTX(appearance_title), + dlg_stdcheckbox_handler, + I(CHECKBOX_INVERT | offsetof(Config,win_name_always))); + + s = ctrl_getset(b, "Window/Behaviour", "main", NULL); + ctrl_checkbox(s, "Warn before closing window", 'w', + HELPCTX(behaviour_closewarn), + dlg_stdcheckbox_handler, I(offsetof(Config,warn_on_close))); + + /* + * The Window/Translation panel. + */ + ctrl_settitle(b, "Window/Translation", + "Options controlling character set translation"); + + s = ctrl_getset(b, "Window/Translation", "trans", + "Character set translation"); + ctrl_combobox(s, "Remote character set:", + 'r', 100, HELPCTX(translation_codepage), + codepage_handler, P(NULL), P(NULL)); + + s = ctrl_getset(b, "Window/Translation", "tweaks", NULL); + ctrl_checkbox(s, "Treat CJK ambiguous characters as wide", 'w', + HELPCTX(translation_cjk_ambig_wide), + dlg_stdcheckbox_handler, I(offsetof(Config,cjk_ambig_wide))); + + str = dupprintf("Adjust how %s handles line drawing characters", appname); + s = ctrl_getset(b, "Window/Translation", "linedraw", str); + sfree(str); + ctrl_radiobuttons(s, "Handling of line drawing characters:", NO_SHORTCUT,1, + HELPCTX(translation_linedraw), + dlg_stdradiobutton_handler, + I(offsetof(Config, vtmode)), + "Use Unicode line drawing code points",'u',I(VT_UNICODE), + "Poor man's line drawing (+, - and |)",'p',I(VT_POORMAN), + NULL); + ctrl_checkbox(s, "Copy and paste line drawing characters as lqqqk",'d', + HELPCTX(selection_linedraw), + dlg_stdcheckbox_handler, I(offsetof(Config,rawcnp))); + + /* + * The Window/Selection panel. + */ + ctrl_settitle(b, "Window/Selection", "Options controlling copy and paste"); + + s = ctrl_getset(b, "Window/Selection", "mouse", + "Control use of mouse"); + ctrl_checkbox(s, "Shift overrides application's use of mouse", 'p', + HELPCTX(selection_shiftdrag), + dlg_stdcheckbox_handler, I(offsetof(Config,mouse_override))); + ctrl_radiobuttons(s, + "Default selection mode (Alt+drag does the other one):", + NO_SHORTCUT, 2, + HELPCTX(selection_rect), + dlg_stdradiobutton_handler, + I(offsetof(Config, rect_select)), + "Normal", 'n', I(0), + "Rectangular block", 'r', I(1), NULL); + + s = ctrl_getset(b, "Window/Selection", "charclass", + "Control the select-one-word-at-a-time mode"); + ccd = (struct charclass_data *) + ctrl_alloc(b, sizeof(struct charclass_data)); + ccd->listbox = ctrl_listbox(s, "Character classes:", 'e', + HELPCTX(selection_charclasses), + charclass_handler, P(ccd)); + ccd->listbox->listbox.multisel = 1; + ccd->listbox->listbox.ncols = 4; + ccd->listbox->listbox.percentages = snewn(4, int); + ccd->listbox->listbox.percentages[0] = 15; + ccd->listbox->listbox.percentages[1] = 25; + ccd->listbox->listbox.percentages[2] = 20; + ccd->listbox->listbox.percentages[3] = 40; + ctrl_columns(s, 2, 67, 33); + ccd->editbox = ctrl_editbox(s, "Set to class", 't', 50, + HELPCTX(selection_charclasses), + charclass_handler, P(ccd), P(NULL)); + ccd->editbox->generic.column = 0; + ccd->button = ctrl_pushbutton(s, "Set", 's', + HELPCTX(selection_charclasses), + charclass_handler, P(ccd)); + ccd->button->generic.column = 1; + ctrl_columns(s, 1, 100); + + /* + * The Window/Colours panel. + */ + ctrl_settitle(b, "Window/Colours", "Options controlling use of colours"); + + s = ctrl_getset(b, "Window/Colours", "general", + "General options for colour usage"); + ctrl_checkbox(s, "Allow terminal to specify ANSI colours", 'i', + HELPCTX(colours_ansi), + dlg_stdcheckbox_handler, I(offsetof(Config,ansi_colour))); + ctrl_checkbox(s, "Allow terminal to use xterm 256-colour mode", '2', + HELPCTX(colours_xterm256), dlg_stdcheckbox_handler, + I(offsetof(Config,xterm_256_colour))); + ctrl_checkbox(s, "Bolded text is a different colour", 'b', + HELPCTX(colours_bold), + dlg_stdcheckbox_handler, I(offsetof(Config,bold_colour))); + + str = dupprintf("Adjust the precise colours %s displays", appname); + s = ctrl_getset(b, "Window/Colours", "adjust", str); + sfree(str); + ctrl_text(s, "Select a colour from the list, and then click the" + " Modify button to change its appearance.", + HELPCTX(colours_config)); + ctrl_columns(s, 2, 67, 33); + cd = (struct colour_data *)ctrl_alloc(b, sizeof(struct colour_data)); + cd->listbox = ctrl_listbox(s, "Select a colour to adjust:", 'u', + HELPCTX(colours_config), colour_handler, P(cd)); + cd->listbox->generic.column = 0; + cd->listbox->listbox.height = 7; + c = ctrl_text(s, "RGB value:", HELPCTX(colours_config)); + c->generic.column = 1; + cd->redit = ctrl_editbox(s, "Red", 'r', 50, HELPCTX(colours_config), + colour_handler, P(cd), P(NULL)); + cd->redit->generic.column = 1; + cd->gedit = ctrl_editbox(s, "Green", 'n', 50, HELPCTX(colours_config), + colour_handler, P(cd), P(NULL)); + cd->gedit->generic.column = 1; + cd->bedit = ctrl_editbox(s, "Blue", 'e', 50, HELPCTX(colours_config), + colour_handler, P(cd), P(NULL)); + cd->bedit->generic.column = 1; + cd->button = ctrl_pushbutton(s, "Modify", 'm', HELPCTX(colours_config), + colour_handler, P(cd)); + cd->button->generic.column = 1; + ctrl_columns(s, 1, 100); + + /* + * The Connection panel. This doesn't show up if we're in a + * non-network utility such as pterm. We tell this by being + * passed a protocol < 0. + */ + if (protocol >= 0) { + ctrl_settitle(b, "Connection", "Options controlling the connection"); + + s = ctrl_getset(b, "Connection", "keepalive", + "Sending of null packets to keep session active"); + ctrl_editbox(s, "Seconds between keepalives (0 to turn off)", 'k', 20, + HELPCTX(connection_keepalive), + dlg_stdeditbox_handler, I(offsetof(Config,ping_interval)), + I(-1)); + + if (!midsession) { + s = ctrl_getset(b, "Connection", "tcp", + "Low-level TCP connection options"); + ctrl_checkbox(s, "Disable Nagle's algorithm (TCP_NODELAY option)", + 'n', HELPCTX(connection_nodelay), + dlg_stdcheckbox_handler, + I(offsetof(Config,tcp_nodelay))); + ctrl_checkbox(s, "Enable TCP keepalives (SO_KEEPALIVE option)", + 'p', HELPCTX(connection_tcpkeepalive), + dlg_stdcheckbox_handler, + I(offsetof(Config,tcp_keepalives))); +#ifndef NO_IPV6 + s = ctrl_getset(b, "Connection", "ipversion", + "Internet protocol version"); + ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, + HELPCTX(connection_ipversion), + dlg_stdradiobutton_handler, + I(offsetof(Config, addressfamily)), + "Auto", 'u', I(ADDRTYPE_UNSPEC), + "IPv4", '4', I(ADDRTYPE_IPV4), + "IPv6", '6', I(ADDRTYPE_IPV6), + NULL); +#endif + + { + char *label = backend_from_proto(PROT_SSH) ? + "Logical name of remote host (e.g. for SSH key lookup):" : + "Logical name of remote host:"; + s = ctrl_getset(b, "Connection", "identity", + "Logical name of remote host"); + ctrl_editbox(s, label, 'm', 100, + HELPCTX(connection_loghost), + dlg_stdeditbox_handler, I(offsetof(Config,loghost)), + I(sizeof(((Config *)0)->loghost))); + } + } + + /* + * A sub-panel Connection/Data, containing options that + * decide on data to send to the server. + */ + if (!midsession) { + ctrl_settitle(b, "Connection/Data", "Data to send to the server"); + + s = ctrl_getset(b, "Connection/Data", "login", + "Login details"); + ctrl_editbox(s, "Auto-login username", 'u', 50, + HELPCTX(connection_username), + dlg_stdeditbox_handler, I(offsetof(Config,username)), + I(sizeof(((Config *)0)->username))); + { + /* We assume the local username is sufficiently stable + * to include on the dialog box. */ + char *user = get_username(); + char *userlabel = dupprintf("Use system username (%s)", + user ? user : ""); + sfree(user); + ctrl_radiobuttons(s, "When username is not specified:", 'n', 4, + HELPCTX(connection_username_from_env), + dlg_stdradiobutton_handler, + I(offsetof(Config, username_from_env)), + "Prompt", I(FALSE), + userlabel, I(TRUE), + NULL); + sfree(userlabel); + } + + s = ctrl_getset(b, "Connection/Data", "term", + "Terminal details"); + ctrl_editbox(s, "Terminal-type string", 't', 50, + HELPCTX(connection_termtype), + dlg_stdeditbox_handler, I(offsetof(Config,termtype)), + I(sizeof(((Config *)0)->termtype))); + ctrl_editbox(s, "Terminal speeds", 's', 50, + HELPCTX(connection_termspeed), + dlg_stdeditbox_handler, I(offsetof(Config,termspeed)), + I(sizeof(((Config *)0)->termspeed))); + + s = ctrl_getset(b, "Connection/Data", "env", + "Environment variables"); + ctrl_columns(s, 2, 80, 20); + ed = (struct environ_data *) + ctrl_alloc(b, sizeof(struct environ_data)); + ed->varbox = ctrl_editbox(s, "Variable", 'v', 60, + HELPCTX(telnet_environ), + environ_handler, P(ed), P(NULL)); + ed->varbox->generic.column = 0; + ed->valbox = ctrl_editbox(s, "Value", 'l', 60, + HELPCTX(telnet_environ), + environ_handler, P(ed), P(NULL)); + ed->valbox->generic.column = 0; + ed->addbutton = ctrl_pushbutton(s, "Add", 'd', + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->addbutton->generic.column = 1; + ed->rembutton = ctrl_pushbutton(s, "Remove", 'r', + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->rembutton->generic.column = 1; + ctrl_columns(s, 1, 100); + ed->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->listbox->listbox.height = 3; + ed->listbox->listbox.ncols = 2; + ed->listbox->listbox.percentages = snewn(2, int); + ed->listbox->listbox.percentages[0] = 30; + ed->listbox->listbox.percentages[1] = 70; + } + + } + + if (!midsession) { + /* + * The Connection/Proxy panel. + */ + ctrl_settitle(b, "Connection/Proxy", + "Options controlling proxy usage"); + + s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); + ctrl_radiobuttons(s, "Proxy type:", 't', 3, + HELPCTX(proxy_type), + dlg_stdradiobutton_handler, + I(offsetof(Config, proxy_type)), + "None", I(PROXY_NONE), + "SOCKS 4", I(PROXY_SOCKS4), + "SOCKS 5", I(PROXY_SOCKS5), + "HTTP", I(PROXY_HTTP), + "Telnet", I(PROXY_TELNET), + NULL); + ctrl_columns(s, 2, 80, 20); + c = ctrl_editbox(s, "Proxy hostname", 'y', 100, + HELPCTX(proxy_main), + dlg_stdeditbox_handler, + I(offsetof(Config,proxy_host)), + I(sizeof(((Config *)0)->proxy_host))); + c->generic.column = 0; + c = ctrl_editbox(s, "Port", 'p', 100, + HELPCTX(proxy_main), + dlg_stdeditbox_handler, + I(offsetof(Config,proxy_port)), + I(-1)); + c->generic.column = 1; + ctrl_columns(s, 1, 100); + ctrl_editbox(s, "Exclude Hosts/IPs", 'e', 100, + HELPCTX(proxy_exclude), + dlg_stdeditbox_handler, + I(offsetof(Config,proxy_exclude_list)), + I(sizeof(((Config *)0)->proxy_exclude_list))); + ctrl_checkbox(s, "Consider proxying local host connections", 'x', + HELPCTX(proxy_exclude), + dlg_stdcheckbox_handler, + I(offsetof(Config,even_proxy_localhost))); + ctrl_radiobuttons(s, "Do DNS name lookup at proxy end:", 'd', 3, + HELPCTX(proxy_dns), + dlg_stdradiobutton_handler, + I(offsetof(Config, proxy_dns)), + "No", I(FORCE_OFF), + "Auto", I(AUTO), + "Yes", I(FORCE_ON), NULL); + ctrl_editbox(s, "Username", 'u', 60, + HELPCTX(proxy_auth), + dlg_stdeditbox_handler, + I(offsetof(Config,proxy_username)), + I(sizeof(((Config *)0)->proxy_username))); + c = ctrl_editbox(s, "Password", 'w', 60, + HELPCTX(proxy_auth), + dlg_stdeditbox_handler, + I(offsetof(Config,proxy_password)), + I(sizeof(((Config *)0)->proxy_password))); + c->editbox.password = 1; + ctrl_editbox(s, "Telnet command", 'm', 100, + HELPCTX(proxy_command), + dlg_stdeditbox_handler, + I(offsetof(Config,proxy_telnet_command)), + I(sizeof(((Config *)0)->proxy_telnet_command))); + } + + /* + * The Telnet panel exists in the base config box, and in a + * mid-session reconfig box _if_ we're using Telnet. + */ + if (!midsession || protocol == PROT_TELNET) { + /* + * The Connection/Telnet panel. + */ + ctrl_settitle(b, "Connection/Telnet", + "Options controlling Telnet connections"); + + s = ctrl_getset(b, "Connection/Telnet", "protocol", + "Telnet protocol adjustments"); + + if (!midsession) { + ctrl_radiobuttons(s, "Handling of OLD_ENVIRON ambiguity:", + NO_SHORTCUT, 2, + HELPCTX(telnet_oldenviron), + dlg_stdradiobutton_handler, + I(offsetof(Config, rfc_environ)), + "BSD (commonplace)", 'b', I(0), + "RFC 1408 (unusual)", 'f', I(1), NULL); + ctrl_radiobuttons(s, "Telnet negotiation mode:", 't', 2, + HELPCTX(telnet_passive), + dlg_stdradiobutton_handler, + I(offsetof(Config, passive_telnet)), + "Passive", I(1), "Active", I(0), NULL); + } + ctrl_checkbox(s, "Keyboard sends Telnet special commands", 'k', + HELPCTX(telnet_specialkeys), + dlg_stdcheckbox_handler, + I(offsetof(Config,telnet_keyboard))); + ctrl_checkbox(s, "Return key sends Telnet New Line instead of ^M", + 'm', HELPCTX(telnet_newline), + dlg_stdcheckbox_handler, + I(offsetof(Config,telnet_newline))); + } + + if (!midsession) { + + /* + * The Connection/Rlogin panel. + */ + ctrl_settitle(b, "Connection/Rlogin", + "Options controlling Rlogin connections"); + + s = ctrl_getset(b, "Connection/Rlogin", "data", + "Data to send to the server"); + ctrl_editbox(s, "Local username:", 'l', 50, + HELPCTX(rlogin_localuser), + dlg_stdeditbox_handler, I(offsetof(Config,localusername)), + I(sizeof(((Config *)0)->localusername))); + + } + + /* + * All the SSH stuff is omitted in PuTTYtel, or in a reconfig + * when we're not doing SSH. + */ + + if (backend_from_proto(PROT_SSH) && (!midsession || protocol == PROT_SSH)) { + + /* + * The Connection/SSH panel. + */ + ctrl_settitle(b, "Connection/SSH", + "Options controlling SSH connections"); + + if (midsession && protcfginfo == 1) { + s = ctrl_getset(b, "Connection/SSH", "disclaimer", NULL); + ctrl_text(s, "Nothing on this panel may be reconfigured in mid-" + "session; it is only here so that sub-panels of it can " + "exist without looking strange.", HELPCTX(no_help)); + } + + if (!midsession) { + + s = ctrl_getset(b, "Connection/SSH", "data", + "Data to send to the server"); + ctrl_editbox(s, "Remote command:", 'r', 100, + HELPCTX(ssh_command), + dlg_stdeditbox_handler, I(offsetof(Config,remote_cmd)), + I(sizeof(((Config *)0)->remote_cmd))); + + s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); + ctrl_checkbox(s, "Don't start a shell or command at all", 'n', + HELPCTX(ssh_noshell), + dlg_stdcheckbox_handler, + I(offsetof(Config,ssh_no_shell))); + } + + if (!midsession || protcfginfo != 1) { + s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); + + ctrl_checkbox(s, "Enable compression", 'e', + HELPCTX(ssh_compress), + dlg_stdcheckbox_handler, + I(offsetof(Config,compression))); + } + + if (!midsession) { + s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); + + ctrl_radiobuttons(s, "Preferred SSH protocol version:", NO_SHORTCUT, 4, + HELPCTX(ssh_protocol), + dlg_stdradiobutton_handler, + I(offsetof(Config, sshprot)), + "1 only", 'l', I(0), + "1", '1', I(1), + "2", '2', I(2), + "2 only", 'y', I(3), NULL); + } + + if (!midsession || protcfginfo != 1) { + s = ctrl_getset(b, "Connection/SSH", "encryption", "Encryption options"); + c = ctrl_draglist(s, "Encryption cipher selection policy:", 's', + HELPCTX(ssh_ciphers), + cipherlist_handler, P(NULL)); + c->listbox.height = 6; + + ctrl_checkbox(s, "Enable legacy use of single-DES in SSH-2", 'i', + HELPCTX(ssh_ciphers), + dlg_stdcheckbox_handler, + I(offsetof(Config,ssh2_des_cbc))); + } + + /* + * The Connection/SSH/Kex panel. (Owing to repeat key + * exchange, this is all meaningful in mid-session _if_ + * we're using SSH-2 or haven't decided yet.) + */ + if (protcfginfo != 1) { + ctrl_settitle(b, "Connection/SSH/Kex", + "Options controlling SSH key exchange"); + + s = ctrl_getset(b, "Connection/SSH/Kex", "main", + "Key exchange algorithm options"); + c = ctrl_draglist(s, "Algorithm selection policy:", 's', + HELPCTX(ssh_kexlist), + kexlist_handler, P(NULL)); + c->listbox.height = 5; + + s = ctrl_getset(b, "Connection/SSH/Kex", "repeat", + "Options controlling key re-exchange"); + + ctrl_editbox(s, "Max minutes before rekey (0 for no limit)", 't', 20, + HELPCTX(ssh_kex_repeat), + dlg_stdeditbox_handler, + I(offsetof(Config,ssh_rekey_time)), + I(-1)); + ctrl_editbox(s, "Max data before rekey (0 for no limit)", 'x', 20, + HELPCTX(ssh_kex_repeat), + dlg_stdeditbox_handler, + I(offsetof(Config,ssh_rekey_data)), + I(16)); + ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)", + HELPCTX(ssh_kex_repeat)); + } + + if (!midsession) { + + /* + * The Connection/SSH/Auth panel. + */ + ctrl_settitle(b, "Connection/SSH/Auth", + "Options controlling SSH authentication"); + + s = ctrl_getset(b, "Connection/SSH/Auth", "main", NULL); + ctrl_checkbox(s, "Bypass authentication entirely (SSH-2 only)", 'b', + HELPCTX(ssh_auth_bypass), + dlg_stdcheckbox_handler, + I(offsetof(Config,ssh_no_userauth))); + ctrl_checkbox(s, "Display pre-authentication banner (SSH-2 only)", + 'd', HELPCTX(ssh_auth_banner), + dlg_stdcheckbox_handler, + I(offsetof(Config,ssh_show_banner))); + + s = ctrl_getset(b, "Connection/SSH/Auth", "methods", + "Authentication methods"); + ctrl_checkbox(s, "Attempt authentication using Pageant", 'p', + HELPCTX(ssh_auth_pageant), + dlg_stdcheckbox_handler, + I(offsetof(Config,tryagent))); + ctrl_checkbox(s, "Attempt TIS or CryptoCard auth (SSH-1)", 'm', + HELPCTX(ssh_auth_tis), + dlg_stdcheckbox_handler, + I(offsetof(Config,try_tis_auth))); + ctrl_checkbox(s, "Attempt \"keyboard-interactive\" auth (SSH-2)", + 'i', HELPCTX(ssh_auth_ki), + dlg_stdcheckbox_handler, + I(offsetof(Config,try_ki_auth))); + + s = ctrl_getset(b, "Connection/SSH/Auth", "params", + "Authentication parameters"); + ctrl_checkbox(s, "Allow agent forwarding", 'f', + HELPCTX(ssh_auth_agentfwd), + dlg_stdcheckbox_handler, I(offsetof(Config,agentfwd))); + ctrl_checkbox(s, "Allow attempted changes of username in SSH-2", NO_SHORTCUT, + HELPCTX(ssh_auth_changeuser), + dlg_stdcheckbox_handler, + I(offsetof(Config,change_username))); + ctrl_filesel(s, "Private key file for authentication:", 'k', + FILTER_KEY_FILES, FALSE, "Select private key file", + HELPCTX(ssh_auth_privkey), + dlg_stdfilesel_handler, I(offsetof(Config, keyfile))); + +#ifndef NO_GSSAPI + /* + * Connection/SSH/Auth/GSSAPI, which sadly won't fit on + * the main Auth panel. + */ + ctrl_settitle(b, "Connection/SSH/Auth/GSSAPI", + "Options controlling GSSAPI authentication"); + s = ctrl_getset(b, "Connection/SSH/Auth/GSSAPI", "gssapi", NULL); + + ctrl_checkbox(s, "Attempt GSSAPI authentication (SSH-2 only)", + 't', HELPCTX(ssh_gssapi), + dlg_stdcheckbox_handler, + I(offsetof(Config,try_gssapi_auth))); + + ctrl_checkbox(s, "Allow GSSAPI credential delegation", 'l', + HELPCTX(ssh_gssapi_delegation), + dlg_stdcheckbox_handler, + I(offsetof(Config,gssapifwd))); + + /* + * GSSAPI library selection. + */ + if (ngsslibs > 1) { + c = ctrl_draglist(s, "Preference order for GSSAPI libraries:", + 'p', HELPCTX(ssh_gssapi_libraries), + gsslist_handler, P(NULL)); + c->listbox.height = ngsslibs; + + /* + * I currently assume that if more than one GSS + * library option is available, then one of them is + * 'user-supplied' and so we should present the + * following file selector. This is at least half- + * reasonable, because if we're using statically + * linked GSSAPI then there will only be one option + * and no way to load from a user-supplied library, + * whereas if we're using dynamic libraries then + * there will almost certainly be some default + * option in addition to a user-supplied path. If + * anyone ever ports PuTTY to a system on which + * dynamic-library GSSAPI is available but there is + * absolutely no consensus on where to keep the + * libraries, there'll need to be a flag alongside + * ngsslibs to control whether the file selector is + * displayed. + */ + + ctrl_filesel(s, "User-supplied GSSAPI library path:", 's', + FILTER_DYNLIB_FILES, FALSE, "Select library file", + HELPCTX(ssh_gssapi_libraries), + dlg_stdfilesel_handler, + I(offsetof(Config, ssh_gss_custom))); + } +#endif + } + + if (!midsession) { + /* + * The Connection/SSH/TTY panel. + */ + ctrl_settitle(b, "Connection/SSH/TTY", "Remote terminal settings"); + + s = ctrl_getset(b, "Connection/SSH/TTY", "sshtty", NULL); + ctrl_checkbox(s, "Don't allocate a pseudo-terminal", 'p', + HELPCTX(ssh_nopty), + dlg_stdcheckbox_handler, + I(offsetof(Config,nopty))); + + s = ctrl_getset(b, "Connection/SSH/TTY", "ttymodes", + "Terminal modes"); + td = (struct ttymodes_data *) + ctrl_alloc(b, sizeof(struct ttymodes_data)); + ctrl_columns(s, 2, 75, 25); + c = ctrl_text(s, "Terminal modes to send:", HELPCTX(ssh_ttymodes)); + c->generic.column = 0; + td->rembutton = ctrl_pushbutton(s, "Remove", 'r', + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td)); + td->rembutton->generic.column = 1; + td->rembutton->generic.tabdelay = 1; + ctrl_columns(s, 1, 100); + td->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td)); + td->listbox->listbox.multisel = 1; + td->listbox->listbox.height = 4; + td->listbox->listbox.ncols = 2; + td->listbox->listbox.percentages = snewn(2, int); + td->listbox->listbox.percentages[0] = 40; + td->listbox->listbox.percentages[1] = 60; + ctrl_tabdelay(s, td->rembutton); + ctrl_columns(s, 2, 75, 25); + td->modelist = ctrl_droplist(s, "Mode:", 'm', 67, + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td)); + td->modelist->generic.column = 0; + td->addbutton = ctrl_pushbutton(s, "Add", 'd', + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td)); + td->addbutton->generic.column = 1; + td->addbutton->generic.tabdelay = 1; + ctrl_columns(s, 1, 100); /* column break */ + /* Bit of a hack to get the value radio buttons and + * edit-box on the same row. */ + ctrl_columns(s, 3, 25, 50, 25); + c = ctrl_text(s, "Value:", HELPCTX(ssh_ttymodes)); + c->generic.column = 0; + td->valradio = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 2, + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td), + "Auto", NO_SHORTCUT, P(NULL), + "This:", NO_SHORTCUT, P(NULL), + NULL); + td->valradio->generic.column = 1; + td->valbox = ctrl_editbox(s, NULL, NO_SHORTCUT, 100, + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td), P(NULL)); + td->valbox->generic.column = 2; + ctrl_tabdelay(s, td->addbutton); + + } + + if (!midsession) { + /* + * The Connection/SSH/X11 panel. + */ + ctrl_settitle(b, "Connection/SSH/X11", + "Options controlling SSH X11 forwarding"); + + s = ctrl_getset(b, "Connection/SSH/X11", "x11", "X11 forwarding"); + ctrl_checkbox(s, "Enable X11 forwarding", 'e', + HELPCTX(ssh_tunnels_x11), + dlg_stdcheckbox_handler,I(offsetof(Config,x11_forward))); + ctrl_editbox(s, "X display location", 'x', 50, + HELPCTX(ssh_tunnels_x11), + dlg_stdeditbox_handler, I(offsetof(Config,x11_display)), + I(sizeof(((Config *)0)->x11_display))); + ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2, + HELPCTX(ssh_tunnels_x11auth), + dlg_stdradiobutton_handler, + I(offsetof(Config, x11_auth)), + "MIT-Magic-Cookie-1", I(X11_MIT), + "XDM-Authorization-1", I(X11_XDM), NULL); + } + + /* + * The Tunnels panel _is_ still available in mid-session. + */ + ctrl_settitle(b, "Connection/SSH/Tunnels", + "Options controlling SSH port forwarding"); + + s = ctrl_getset(b, "Connection/SSH/Tunnels", "portfwd", + "Port forwarding"); + ctrl_checkbox(s, "Local ports accept connections from other hosts",'t', + HELPCTX(ssh_tunnels_portfwd_localhost), + dlg_stdcheckbox_handler, + I(offsetof(Config,lport_acceptall))); + ctrl_checkbox(s, "Remote ports do the same (SSH-2 only)", 'p', + HELPCTX(ssh_tunnels_portfwd_localhost), + dlg_stdcheckbox_handler, + I(offsetof(Config,rport_acceptall))); + + ctrl_columns(s, 3, 55, 20, 25); + c = ctrl_text(s, "Forwarded ports:", HELPCTX(ssh_tunnels_portfwd)); + c->generic.column = COLUMN_FIELD(0,2); + /* You want to select from the list, _then_ hit Remove. So tab order + * should be that way round. */ + pfd = (struct portfwd_data *)ctrl_alloc(b,sizeof(struct portfwd_data)); + pfd->rembutton = ctrl_pushbutton(s, "Remove", 'r', + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd)); + pfd->rembutton->generic.column = 2; + pfd->rembutton->generic.tabdelay = 1; + pfd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd)); + pfd->listbox->listbox.height = 3; + pfd->listbox->listbox.ncols = 2; + pfd->listbox->listbox.percentages = snewn(2, int); + pfd->listbox->listbox.percentages[0] = 20; + pfd->listbox->listbox.percentages[1] = 80; + ctrl_tabdelay(s, pfd->rembutton); + ctrl_text(s, "Add new forwarded port:", HELPCTX(ssh_tunnels_portfwd)); + /* You want to enter source, destination and type, _then_ hit Add. + * Again, we adjust the tab order to reflect this. */ + pfd->addbutton = ctrl_pushbutton(s, "Add", 'd', + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd)); + pfd->addbutton->generic.column = 2; + pfd->addbutton->generic.tabdelay = 1; + pfd->sourcebox = ctrl_editbox(s, "Source port", 's', 40, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd), P(NULL)); + pfd->sourcebox->generic.column = 0; + pfd->destbox = ctrl_editbox(s, "Destination", 'i', 67, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd), P(NULL)); + pfd->direction = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd), + "Local", 'l', P(NULL), + "Remote", 'm', P(NULL), + "Dynamic", 'y', P(NULL), + NULL); +#ifndef NO_IPV6 + pfd->addressfamily = + ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, + HELPCTX(ssh_tunnels_portfwd_ipversion), + portfwd_handler, P(pfd), + "Auto", 'u', I(ADDRTYPE_UNSPEC), + "IPv4", '4', I(ADDRTYPE_IPV4), + "IPv6", '6', I(ADDRTYPE_IPV6), + NULL); +#endif + ctrl_tabdelay(s, pfd->addbutton); + ctrl_columns(s, 1, 100); + + if (!midsession) { + /* + * The Connection/SSH/Bugs panel. + */ + ctrl_settitle(b, "Connection/SSH/Bugs", + "Workarounds for SSH server bugs"); + + s = ctrl_getset(b, "Connection/SSH/Bugs", "main", + "Detection of known bugs in SSH servers"); + ctrl_droplist(s, "Chokes on SSH-1 ignore messages", 'i', 20, + HELPCTX(ssh_bugs_ignore1), + sshbug_handler, I(offsetof(Config,sshbug_ignore1))); + ctrl_droplist(s, "Refuses all SSH-1 password camouflage", 's', 20, + HELPCTX(ssh_bugs_plainpw1), + sshbug_handler, I(offsetof(Config,sshbug_plainpw1))); + ctrl_droplist(s, "Chokes on SSH-1 RSA authentication", 'r', 20, + HELPCTX(ssh_bugs_rsa1), + sshbug_handler, I(offsetof(Config,sshbug_rsa1))); + ctrl_droplist(s, "Chokes on SSH-2 ignore messages", '2', 20, + HELPCTX(ssh_bugs_ignore2), + sshbug_handler, I(offsetof(Config,sshbug_ignore2))); + ctrl_droplist(s, "Miscomputes SSH-2 HMAC keys", 'm', 20, + HELPCTX(ssh_bugs_hmac2), + sshbug_handler, I(offsetof(Config,sshbug_hmac2))); + ctrl_droplist(s, "Miscomputes SSH-2 encryption keys", 'e', 20, + HELPCTX(ssh_bugs_derivekey2), + sshbug_handler, I(offsetof(Config,sshbug_derivekey2))); + ctrl_droplist(s, "Requires padding on SSH-2 RSA signatures", 'p', 20, + HELPCTX(ssh_bugs_rsapad2), + sshbug_handler, I(offsetof(Config,sshbug_rsapad2))); + ctrl_droplist(s, "Misuses the session ID in SSH-2 PK auth", 'n', 20, + HELPCTX(ssh_bugs_pksessid2), + sshbug_handler, I(offsetof(Config,sshbug_pksessid2))); + ctrl_droplist(s, "Handles SSH-2 key re-exchange badly", 'k', 20, + HELPCTX(ssh_bugs_rekey2), + sshbug_handler, I(offsetof(Config,sshbug_rekey2))); + ctrl_droplist(s, "Ignores SSH-2 maximum packet size", 'x', 20, + HELPCTX(ssh_bugs_maxpkt2), + sshbug_handler, I(offsetof(Config,sshbug_maxpkt2))); + } + } +} diff --git a/putty/CONTRIB/CYGTERMD/MAIN.C b/putty/CONTRIB/CYGTERMD/MAIN.C new file mode 100644 index 0000000..f499b3c --- /dev/null +++ b/putty/CONTRIB/CYGTERMD/MAIN.C @@ -0,0 +1,174 @@ +/* + * Main program. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "sel.h" +#include "pty.h" +#include "telnet.h" + +int signalpipe[2]; + +sel *asel; +sel_rfd *netr, *ptyr, *sigr; +int ptyfd; +sel_wfd *netw, *ptyw; +Telnet telnet; + +#define BUF 65536 + +void sigchld(int signum) +{ + write(signalpipe[1], "C", 1); +} + +void fatal(const char *fmt, ...) +{ + va_list ap; + fprintf(stderr, "FIXME: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(1); +} + +void net_readdata(sel_rfd *rfd, void *data, size_t len) +{ + if (len == 0) + exit(0); /* EOF on network - client went away */ + telnet_from_net(telnet, data, len); + if (sel_write(netw, NULL, 0) > BUF) + sel_rfd_freeze(ptyr); + if (sel_write(ptyw, NULL, 0) > BUF) + sel_rfd_freeze(netr); +} + +void net_readerr(sel_rfd *rfd, int error) +{ + fprintf(stderr, "standard input: read: %s\n", strerror(errno)); + exit(1); +} + +void net_written(sel_wfd *wfd, size_t bufsize) +{ + if (bufsize < BUF) + sel_rfd_unfreeze(ptyr); +} + +void net_writeerr(sel_wfd *wfd, int error) +{ + fprintf(stderr, "standard input: write: %s\n", strerror(errno)); + exit(1); +} + +void pty_readdata(sel_rfd *rfd, void *data, size_t len) +{ + if (len == 0) + exit(0); /* EOF on pty */ + telnet_from_pty(telnet, data, len); + if (sel_write(netw, NULL, 0) > BUF) + sel_rfd_freeze(ptyr); + if (sel_write(ptyw, NULL, 0) > BUF) + sel_rfd_freeze(netr); +} + +void pty_readerr(sel_rfd *rfd, int error) +{ + if (error == EIO) /* means EOF, on a pty */ + exit(0); + fprintf(stderr, "pty: read: %s\n", strerror(errno)); + exit(1); +} + +void pty_written(sel_wfd *wfd, size_t bufsize) +{ + if (bufsize < BUF) + sel_rfd_unfreeze(netr); +} + +void pty_writeerr(sel_wfd *wfd, int error) +{ + fprintf(stderr, "pty: write: %s\n", strerror(errno)); + exit(1); +} + +void sig_readdata(sel_rfd *rfd, void *data, size_t len) +{ + char *p = data; + + while (len > 0) { + if (*p == 'C') { + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); + if (WIFEXITED(status) || WIFSIGNALED(status)) + exit(0); /* child process vanished */ + } + } +} + +void sig_readerr(sel_rfd *rfd, int error) +{ + fprintf(stderr, "signal pipe: read: %s\n", strerror(errno)); + exit(1); +} + +int main(int argc, char **argv) +{ + int ret; + int shell_started = 0; + char *directory = NULL; + char **program_args = NULL; + + if (argc > 1 && argv[1][0]) { + directory = argv[1]; + argc--, argv++; + } + if (argc > 1) { + program_args = argv + 1; + } + + pty_preinit(); + + asel = sel_new(NULL); + netr = sel_rfd_add(asel, 0, net_readdata, net_readerr, NULL); + netw = sel_wfd_add(asel, 1, net_written, net_writeerr, NULL); + ptyr = sel_rfd_add(asel, -1, pty_readdata, pty_readerr, NULL); + ptyw = sel_wfd_add(asel, -1, pty_written, pty_writeerr, NULL); + + telnet = telnet_new(netw, ptyw); + + if (pipe(signalpipe) < 0) { + perror("pipe"); + return 1; + } + sigr = sel_rfd_add(asel, signalpipe[0], sig_readdata, + sig_readerr, NULL); + + signal(SIGCHLD, sigchld); + + do { + struct shell_data shdata; + + ret = sel_iterate(asel, -1); + if (!shell_started && telnet_shell_ok(telnet, &shdata)) { + ptyfd = run_program_in_pty(&shdata, directory, program_args); + sel_rfd_setfd(ptyr, ptyfd); + sel_wfd_setfd(ptyw, ptyfd); + shell_started = 1; + } + } while (ret == 0); + + return 0; +} diff --git a/putty/CONTRIB/CYGTERMD/MAKEFILE b/putty/CONTRIB/CYGTERMD/MAKEFILE new file mode 100644 index 0000000..adb5584 --- /dev/null +++ b/putty/CONTRIB/CYGTERMD/MAKEFILE @@ -0,0 +1,2 @@ +cygtermd.exe: main.c sel.c telnet.c pty.c malloc.c + gcc -o cygtermd.exe main.c sel.c telnet.c pty.c malloc.c diff --git a/putty/CONTRIB/CYGTERMD/MALLOC.C b/putty/CONTRIB/CYGTERMD/MALLOC.C new file mode 100644 index 0000000..0397181 --- /dev/null +++ b/putty/CONTRIB/CYGTERMD/MALLOC.C @@ -0,0 +1,43 @@ +/* + * malloc.c: implementation of malloc.h + */ + +#include +#include + +#include "malloc.h" + +extern void fatal(const char *, ...); + +void *smalloc(size_t size) { + void *p; + p = malloc(size); + if (!p) { + fatal("out of memory"); + } + return p; +} + +void sfree(void *p) { + if (p) { + free(p); + } +} + +void *srealloc(void *p, size_t size) { + void *q; + if (p) { + q = realloc(p, size); + } else { + q = malloc(size); + } + if (!q) + fatal("out of memory"); + return q; +} + +char *dupstr(const char *s) { + char *r = smalloc(1+strlen(s)); + strcpy(r,s); + return r; +} diff --git a/putty/CONTRIB/CYGTERMD/MALLOC.H b/putty/CONTRIB/CYGTERMD/MALLOC.H new file mode 100644 index 0000000..0fd4a36 --- /dev/null +++ b/putty/CONTRIB/CYGTERMD/MALLOC.H @@ -0,0 +1,56 @@ +/* + * malloc.h: safe wrappers around malloc, realloc, free, strdup + */ + +#ifndef UMLWRAP_MALLOC_H +#define UMLWRAP_MALLOC_H + +#include + +/* + * smalloc should guarantee to return a useful pointer - Halibut + * can do nothing except die when it's out of memory anyway. + */ +void *smalloc(size_t size); + +/* + * srealloc should guaranteeably be able to realloc NULL + */ +void *srealloc(void *p, size_t size); + +/* + * sfree should guaranteeably deal gracefully with freeing NULL + */ +void sfree(void *p); + +/* + * dupstr is like strdup, but with the never-return-NULL property + * of smalloc (and also reliably defined in all environments :-) + */ +char *dupstr(const char *s); + +/* + * snew allocates one instance of a given type, and casts the + * result so as to type-check that you're assigning it to the + * right kind of pointer. Protects against allocation bugs + * involving allocating the wrong size of thing. + */ +#define snew(type) \ + ( (type *) smalloc (sizeof (type)) ) + +/* + * snewn allocates n instances of a given type, for arrays. + */ +#define snewn(number, type) \ + ( (type *) smalloc ((number) * sizeof (type)) ) + +/* + * sresize wraps realloc so that you specify the new number of + * elements and the type of the element, with the same type- + * checking advantages. Also type-checks the input pointer. + */ +#define sresize(array, number, type) \ + ( (void)sizeof((array)-(type *)0), \ + (type *) srealloc ((array), (number) * sizeof (type)) ) + +#endif /* UMLWRAP_MALLOC_H */ diff --git a/putty/CONTRIB/CYGTERMD/PTY.C b/putty/CONTRIB/CYGTERMD/PTY.C new file mode 100644 index 0000000..dd4d1a4 --- /dev/null +++ b/putty/CONTRIB/CYGTERMD/PTY.C @@ -0,0 +1,188 @@ +/* + * pty.c - pseudo-terminal handling + */ + +#define _XOPEN_SOURCE +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "pty.h" +#include "malloc.h" + +static char ptyname[FILENAME_MAX]; +int master = -1; + +void pty_preinit(void) +{ + /* + * Allocate the pty. + */ + master = open("/dev/ptmx", O_RDWR); + if (master < 0) { + perror("/dev/ptmx: open"); + exit(1); + } + + if (grantpt(master) < 0) { + perror("grantpt"); + exit(1); + } + + if (unlockpt(master) < 0) { + perror("unlockpt"); + exit(1); + } +} + +void pty_resize(int w, int h) +{ + struct winsize sz; + + assert(master >= 0); + + sz.ws_row = h; + sz.ws_col = w; + sz.ws_xpixel = sz.ws_ypixel = 0; + ioctl(master, TIOCSWINSZ, &sz); +} + +int run_program_in_pty(const struct shell_data *shdata, + char *directory, char **program_args) +{ + int slave, pid; + char *fallback_args[2]; + + assert(master >= 0); + + ptyname[FILENAME_MAX-1] = '\0'; + strncpy(ptyname, ptsname(master), FILENAME_MAX-1); + +#if 0 + { + struct winsize ws; + struct termios ts; + + /* + * FIXME: think up some good defaults here + */ + + if (!ioctl(0, TIOCGWINSZ, &ws)) + ioctl(master, TIOCSWINSZ, &ws); + if (!tcgetattr(0, &ts)) + tcsetattr(master, TCSANOW, &ts); + } +#endif + + slave = open(ptyname, O_RDWR | O_NOCTTY); + if (slave < 0) { + perror("slave pty: open"); + return 1; + } + + /* + * Fork and execute the command. + */ + pid = fork(); + if (pid < 0) { + perror("fork"); + return 1; + } + + if (pid == 0) { + int i, fd; + + /* + * We are the child. + */ + close(master); + + fcntl(slave, F_SETFD, 0); /* don't close on exec */ + dup2(slave, 0); + dup2(slave, 1); + if (slave != 0 && slave != 1) + close(slave); + dup2(1, 2); + setsid(); + setpgrp(); + i = 0; +#ifdef TIOCNOTTY + if ((fd = open("/dev/tty", O_RDWR)) >= 0) { + ioctl(fd, TIOCNOTTY, &i); + close(fd); + } +#endif +#ifdef TIOCSCTTY + ioctl(0, TIOCSCTTY, &i); +#endif + tcsetpgrp(0, getpgrp()); + + for (i = 0; i < shdata->nenvvars; i++) + putenv(shdata->envvars[i]); + if (shdata->termtype) + putenv(shdata->termtype); + + if (directory) + chdir(directory); + + /* + * Use the provided shell program name, if the user gave + * one. Failing that, use $SHELL; failing that, look up + * the user's default shell in the password file; failing + * _that_, revert to the bog-standard /bin/sh. + */ + if (!program_args) { + char *shell; + + shell = getenv("SHELL"); + if (!shell) { + const char *login; + uid_t uid; + struct passwd *pwd; + + /* + * For maximum generality in the face of multiple + * /etc/passwd entries with different login names and + * shells but a shared uid, we start by using + * getpwnam(getlogin()) if it's available - but we + * insist that its uid must match our real one, or we + * give up and fall back to getpwuid(getuid()). + */ + uid = getuid(); + login = getlogin(); + if (login && (pwd = getpwnam(login)) && pwd->pw_uid == uid) + shell = pwd->pw_shell; + else if ((pwd = getpwuid(uid))) + shell = pwd->pw_shell; + } + if (!shell) + shell = "/bin/sh"; + + fallback_args[0] = shell; + fallback_args[1] = NULL; + program_args = fallback_args; + } + + execv(program_args[0], program_args); + + /* + * If we're here, exec has gone badly foom. + */ + perror("exec"); + exit(127); + } + + close(slave); + + return master; +} diff --git a/putty/CONTRIB/CYGTERMD/PTY.H b/putty/CONTRIB/CYGTERMD/PTY.H new file mode 100644 index 0000000..d4c22e2 --- /dev/null +++ b/putty/CONTRIB/CYGTERMD/PTY.H @@ -0,0 +1,28 @@ +/* + * pty.h - FIXME + */ + +#ifndef FIXME_PTY_H +#define FIXME_PTY_H + +#include "telnet.h" /* for struct shdata */ + +/* + * Called at program startup to actually allocate a pty, so that + * we can start passing in resize events as soon as they arrive. + */ +void pty_preinit(void); + +/* + * Set the terminal size for the pty. + */ +void pty_resize(int w, int h); + +/* + * Start a program in a subprocess running in the pty we allocated. + * Returns the fd of the pty master. + */ +int run_program_in_pty(const struct shell_data *shdata, + char *directory, char **program_args); + +#endif /* FIXME_PTY_H */ diff --git a/putty/CONTRIB/CYGTERMD/README b/putty/CONTRIB/CYGTERMD/README new file mode 100644 index 0000000..c4adcc9 --- /dev/null +++ b/putty/CONTRIB/CYGTERMD/README @@ -0,0 +1,11 @@ +This directory contains 'cygtermd', a small and specialist Telnet +server designed to act as middleware between PuTTY and a Cygwin shell +session running on the same machine, so that PuTTY can act as an +xterm-alike for Cygwin. + +To install it, you must compile it from source using Cygwin gcc, +install it in Cygwin's /bin, and configure PuTTY to use it as a local +proxy process. For detailed instructions, see the PuTTY Wishlist page +at + +http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/cygwin-terminal-window.html diff --git a/putty/CONTRIB/CYGTERMD/SEL.C b/putty/CONTRIB/CYGTERMD/SEL.C new file mode 100644 index 0000000..7a26567 --- /dev/null +++ b/putty/CONTRIB/CYGTERMD/SEL.C @@ -0,0 +1,386 @@ +/* + * sel.c: implementation of sel.h. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "sel.h" +#include "malloc.h" + +/* ---------------------------------------------------------------------- + * Chunk of code lifted from PuTTY's misc.c to manage buffers of + * data to be written to an fd. + */ + +#define BUFFER_GRANULE 512 + +typedef struct bufchain_tag { + struct bufchain_granule *head, *tail; + size_t buffersize; /* current amount of buffered data */ +} bufchain; +struct bufchain_granule { + struct bufchain_granule *next; + size_t buflen, bufpos; + char buf[BUFFER_GRANULE]; +}; + +static void bufchain_init(bufchain *ch) +{ + ch->head = ch->tail = NULL; + ch->buffersize = 0; +} + +static void bufchain_clear(bufchain *ch) +{ + struct bufchain_granule *b; + while (ch->head) { + b = ch->head; + ch->head = ch->head->next; + sfree(b); + } + ch->tail = NULL; + ch->buffersize = 0; +} + +static size_t bufchain_size(bufchain *ch) +{ + return ch->buffersize; +} + +static void bufchain_add(bufchain *ch, const void *data, size_t len) +{ + const char *buf = (const char *)data; + + if (len == 0) return; + + ch->buffersize += len; + + if (ch->tail && ch->tail->buflen < BUFFER_GRANULE) { + size_t copylen = BUFFER_GRANULE - ch->tail->buflen; + if (copylen > len) + copylen = len; + memcpy(ch->tail->buf + ch->tail->buflen, buf, copylen); + buf += copylen; + len -= copylen; + ch->tail->buflen += copylen; + } + while (len > 0) { + struct bufchain_granule *newbuf; + size_t grainlen = BUFFER_GRANULE; + if (grainlen > len) + grainlen = len; + newbuf = snew(struct bufchain_granule); + newbuf->bufpos = 0; + newbuf->buflen = grainlen; + memcpy(newbuf->buf, buf, grainlen); + buf += grainlen; + len -= grainlen; + if (ch->tail) + ch->tail->next = newbuf; + else + ch->head = ch->tail = newbuf; + newbuf->next = NULL; + ch->tail = newbuf; + } +} + +static void bufchain_consume(bufchain *ch, size_t len) +{ + struct bufchain_granule *tmp; + + assert(ch->buffersize >= len); + while (len > 0) { + size_t remlen = len; + assert(ch->head != NULL); + if (remlen >= ch->head->buflen - ch->head->bufpos) { + remlen = ch->head->buflen - ch->head->bufpos; + tmp = ch->head; + ch->head = tmp->next; + sfree(tmp); + if (!ch->head) + ch->tail = NULL; + } else + ch->head->bufpos += remlen; + ch->buffersize -= remlen; + len -= remlen; + } +} + +static void bufchain_prefix(bufchain *ch, void **data, size_t *len) +{ + *len = ch->head->buflen - ch->head->bufpos; + *data = ch->head->buf + ch->head->bufpos; +} + +/* ---------------------------------------------------------------------- + * The actual implementation of the sel interface. + */ + +struct sel { + void *ctx; + sel_rfd *rhead, *rtail; + sel_wfd *whead, *wtail; +}; + +struct sel_rfd { + sel *parent; + sel_rfd *prev, *next; + sel_readdata_fn_t readdata; + sel_readerr_fn_t readerr; + void *ctx; + int fd; + int frozen; +}; + +struct sel_wfd { + sel *parent; + sel_wfd *prev, *next; + sel_written_fn_t written; + sel_writeerr_fn_t writeerr; + void *ctx; + int fd; + bufchain buf; +}; + +sel *sel_new(void *ctx) +{ + sel *sel = snew(struct sel); + + sel->ctx = ctx; + sel->rhead = sel->rtail = NULL; + sel->whead = sel->wtail = NULL; + + return sel; +} + +sel_wfd *sel_wfd_add(sel *sel, int fd, + sel_written_fn_t written, sel_writeerr_fn_t writeerr, + void *ctx) +{ + sel_wfd *wfd = snew(sel_wfd); + + wfd->written = written; + wfd->writeerr = writeerr; + wfd->ctx = ctx; + wfd->fd = fd; + bufchain_init(&wfd->buf); + + wfd->next = NULL; + wfd->prev = sel->wtail; + if (sel->wtail) + sel->wtail->next = wfd; + else + sel->whead = wfd; + sel->wtail = wfd; + wfd->parent = sel; + + return wfd; +} + +sel_rfd *sel_rfd_add(sel *sel, int fd, + sel_readdata_fn_t readdata, sel_readerr_fn_t readerr, + void *ctx) +{ + sel_rfd *rfd = snew(sel_rfd); + + rfd->readdata = readdata; + rfd->readerr = readerr; + rfd->ctx = ctx; + rfd->fd = fd; + rfd->frozen = 0; + + rfd->next = NULL; + rfd->prev = sel->rtail; + if (sel->rtail) + sel->rtail->next = rfd; + else + sel->rhead = rfd; + sel->rtail = rfd; + rfd->parent = sel; + + return rfd; +} + +size_t sel_write(sel_wfd *wfd, const void *data, size_t len) +{ + bufchain_add(&wfd->buf, data, len); + return bufchain_size(&wfd->buf); +} + +void sel_wfd_setfd(sel_wfd *wfd, int fd) +{ + wfd->fd = fd; +} + +void sel_rfd_setfd(sel_rfd *rfd, int fd) +{ + rfd->fd = fd; +} + +void sel_rfd_freeze(sel_rfd *rfd) +{ + rfd->frozen = 1; +} + +void sel_rfd_unfreeze(sel_rfd *rfd) +{ + rfd->frozen = 0; +} + +int sel_wfd_delete(sel_wfd *wfd) +{ + sel *sel = wfd->parent; + int ret; + + if (wfd->prev) + wfd->prev->next = wfd->next; + else + sel->whead = wfd->next; + if (wfd->next) + wfd->next->prev = wfd->prev; + else + sel->wtail = wfd->prev; + + bufchain_clear(&wfd->buf); + + ret = wfd->fd; + sfree(wfd); + return ret; +} + +int sel_rfd_delete(sel_rfd *rfd) +{ + sel *sel = rfd->parent; + int ret; + + if (rfd->prev) + rfd->prev->next = rfd->next; + else + sel->rhead = rfd->next; + if (rfd->next) + rfd->next->prev = rfd->prev; + else + sel->rtail = rfd->prev; + + ret = rfd->fd; + sfree(rfd); + return ret; +} + +void sel_free(sel *sel) +{ + while (sel->whead) + sel_wfd_delete(sel->whead); + while (sel->rhead) + sel_rfd_delete(sel->rhead); + sfree(sel); +} + +void *sel_get_ctx(sel *sel) { return sel->ctx; } +void sel_set_ctx(sel *sel, void *ctx) { sel->ctx = ctx; } +void *sel_wfd_get_ctx(sel_wfd *wfd) { return wfd->ctx; } +void sel_wfd_set_ctx(sel_wfd *wfd, void *ctx) { wfd->ctx = ctx; } +void *sel_rfd_get_ctx(sel_rfd *rfd) { return rfd->ctx; } +void sel_rfd_set_ctx(sel_rfd *rfd, void *ctx) { rfd->ctx = ctx; } + +int sel_iterate(sel *sel, long timeout) +{ + sel_rfd *rfd; + sel_wfd *wfd; + fd_set rset, wset; + int maxfd = 0; + struct timeval tv, *ptv; + char buf[65536]; + int ret; + + FD_ZERO(&rset); + FD_ZERO(&wset); + + for (rfd = sel->rhead; rfd; rfd = rfd->next) { + if (rfd->fd >= 0 && !rfd->frozen) { + FD_SET(rfd->fd, &rset); + if (maxfd < rfd->fd + 1) + maxfd = rfd->fd + 1; + } + } + + for (wfd = sel->whead; wfd; wfd = wfd->next) { + if (wfd->fd >= 0 && bufchain_size(&wfd->buf)) { + FD_SET(wfd->fd, &wset); + if (maxfd < wfd->fd + 1) + maxfd = wfd->fd + 1; + } + } + + if (timeout < 0) { + ptv = NULL; + } else { + ptv = &tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = 1000 * (timeout % 1000); + } + + do { + ret = select(maxfd, &rset, &wset, NULL, ptv); + } while (ret < 0 && (errno == EINTR || errno == EAGAIN)); + + if (ret < 0) + return errno; + + /* + * Just in case one of the callbacks destroys an rfd or wfd we + * had yet to get round to, we must loop from the start every + * single time. Algorithmically irritating, but necessary + * unless we want to store the rfd structures in a heavyweight + * tree sorted by fd. And let's face it, if we cared about + * good algorithmic complexity it's not at all clear we'd be + * using select in the first place. + */ + do { + for (wfd = sel->whead; wfd; wfd = wfd->next) + if (wfd->fd >= 0 && FD_ISSET(wfd->fd, &wset)) { + void *data; + size_t len; + + FD_CLR(wfd->fd, &wset); + bufchain_prefix(&wfd->buf, &data, &len); + ret = write(wfd->fd, data, len); + assert(ret != 0); + if (ret < 0) { + if (wfd->writeerr) + wfd->writeerr(wfd, errno); + } else { + bufchain_consume(&wfd->buf, len); + if (wfd->written) + wfd->written(wfd, bufchain_size(&wfd->buf)); + } + break; + } + } while (wfd); + do { + for (rfd = sel->rhead; rfd; rfd = rfd->next) + if (rfd->fd >= 0 && !rfd->frozen && FD_ISSET(rfd->fd, &rset)) { + FD_CLR(rfd->fd, &rset); + ret = read(rfd->fd, buf, sizeof(buf)); + if (ret < 0) { + if (rfd->readerr) + rfd->readerr(rfd, errno); + } else { + if (rfd->readdata) + rfd->readdata(rfd, buf, ret); + } + break; + } + } while (rfd); + + return 0; +} diff --git a/putty/CONTRIB/CYGTERMD/SEL.H b/putty/CONTRIB/CYGTERMD/SEL.H new file mode 100644 index 0000000..b350c48 --- /dev/null +++ b/putty/CONTRIB/CYGTERMD/SEL.H @@ -0,0 +1,161 @@ +/* + * sel.h: subsystem to manage the grubby details of a select loop, + * buffering data to write, and performing the actual writes and + * reads. + */ + +#ifndef FIXME_SEL_H +#define FIXME_SEL_H + +typedef struct sel sel; +typedef struct sel_wfd sel_wfd; +typedef struct sel_rfd sel_rfd; + +/* + * Callback called when some data is written to a wfd. "bufsize" + * is the remaining quantity of data buffered in that wfd. + */ +typedef void (*sel_written_fn_t)(sel_wfd *wfd, size_t bufsize); + +/* + * Callback called when an error occurs on a wfd, preventing + * further writing to it. "error" is the errno value. + */ +typedef void (*sel_writeerr_fn_t)(sel_wfd *wfd, int error); + +/* + * Callback called when some data is read from an rfd. On EOF, + * this will be called with len==0. + */ +typedef void (*sel_readdata_fn_t)(sel_rfd *rfd, void *data, size_t len); + +/* + * Callback called when an error occurs on an rfd, preventing + * further reading from it. "error" is the errno value. + */ +typedef void (*sel_readerr_fn_t)(sel_rfd *rfd, int error); + +/* + * Create a sel structure, which will oversee a select loop. + * + * "ctx" is user-supplied data stored in the sel structure; it can + * be read and written with sel_get_ctx() and sel_set_ctx(). + */ +sel *sel_new(void *ctx); + +/* + * Add a new fd for writing. Returns a sel_wfd which identifies + * that fd in the sel structure, e.g. for putting data into its + * output buffer. + * + * "ctx" is user-supplied data stored in the sel structure; it can + * be read and written with sel_wfd_get_ctx() and sel_wfd_set_ctx(). + * + * "written" and "writeerr" are called from the event loop when + * things happen. + * + * The fd passed in can be -1, in which case it will be assumed to + * be unwritable at all times. An actual fd can be passed in later + * using sel_wfd_setfd. + */ +sel_wfd *sel_wfd_add(sel *sel, int fd, + sel_written_fn_t written, sel_writeerr_fn_t writeerr, + void *ctx); + +/* + * Add a new fd for reading. Returns a sel_rfd which identifies + * that fd in the sel structure. + * + * "ctx" is user-supplied data stored in the sel structure; it can + * be read and written with sel_rfd_get_ctx() and sel_rfd_set_ctx(). + * + * "readdata" and "readerr" are called from the event loop when + * things happen. "ctx" is passed to both of them. + */ +sel_rfd *sel_rfd_add(sel *sel, int fd, + sel_readdata_fn_t readdata, sel_readerr_fn_t readerr, + void *ctx); + +/* + * Write data into the output buffer of a wfd. Returns the new + * size of the output buffer. (You can call it with len==0 if you + * just want to know the buffer size; in that situation data==NULL + * is also safe.) + */ +size_t sel_write(sel_wfd *wfd, const void *data, size_t len); + +/* + * Freeze and unfreeze an rfd. When frozen, sel will temporarily + * not attempt to read from it, but all its state is retained so + * it can be conveniently unfrozen later. (You might use this + * facility, for instance, if what you were doing with the + * incoming data could only accept it at a certain rate: freeze + * the rfd when you've got lots of backlog, and unfreeze it again + * when things get calmer.) + */ +void sel_rfd_freeze(sel_rfd *rfd); +void sel_rfd_unfreeze(sel_rfd *rfd); + +/* + * Delete a wfd structure from its containing sel. Returns the + * underlying fd, which the client may now consider itself to own + * once more. + */ +int sel_wfd_delete(sel_wfd *wfd); + +/* + * Delete an rfd structure from its containing sel. Returns the + * underlying fd, which the client may now consider itself to own + * once more. + */ +int sel_rfd_delete(sel_rfd *rfd); + +/* + * NOT IMPLEMENTED YET: useful functions here might be ones which + * enumerated all the wfds/rfds in a sel structure in some + * fashion, so you could go through them and remove them all while + * doing sensible things to them. Or, at the very least, just + * return an arbitrary one of the wfds/rfds. + */ + +/* + * Free a sel structure and all its remaining wfds and rfds. + */ +void sel_free(sel *sel); + +/* + * Read and write the ctx parameters in sel, sel_wfd and sel_rfd. + */ +void *sel_get_ctx(sel *sel); +void sel_set_ctx(sel *sel, void *ctx); +void *sel_wfd_get_ctx(sel_wfd *wfd); +void sel_wfd_set_ctx(sel_wfd *wfd, void *ctx); +void *sel_rfd_get_ctx(sel_rfd *rfd); +void sel_rfd_set_ctx(sel_rfd *rfd, void *ctx); + +/* + * Run one iteration of the sel event loop, calling callbacks as + * necessary. Returns zero on success; in the event of a fatal + * error, returns the errno value. + * + * "timeout" is a value in microseconds to limit the length of the + * select call. Less than zero means to wait indefinitely. + */ +int sel_iterate(sel *sel, long timeout); + +/* + * Change the underlying fd in a wfd. If set to -1, no write + * attempts will take place and the wfd's buffer will simply store + * everything passed to sel_write(). If later set to something + * other than -1, all that buffered data will become eligible for + * real writing. + */ +void sel_wfd_setfd(sel_wfd *wfd, int fd); + +/* + * Change the underlying fd in a rfd. If set to -1, no read + * attempts will take place. + */ +void sel_rfd_setfd(sel_rfd *rfd, int fd); + +#endif /* FIXME_SEL_H */ diff --git a/putty/CONTRIB/CYGTERMD/TELNET.C b/putty/CONTRIB/CYGTERMD/TELNET.C new file mode 100644 index 0000000..9602fd7 --- /dev/null +++ b/putty/CONTRIB/CYGTERMD/TELNET.C @@ -0,0 +1,570 @@ +/* + * Simple Telnet server code, adapted from PuTTY's own Telnet + * client code for use as a Cygwin local pty proxy. + */ + +#include +#include +#include + +#include "sel.h" +#include "telnet.h" +#include "malloc.h" +#include "pty.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#define IAC 255 /* interpret as command: */ +#define DONT 254 /* you are not to use option */ +#define DO 253 /* please, you use option */ +#define WONT 252 /* I won't use option */ +#define WILL 251 /* I will use option */ +#define SB 250 /* interpret as subnegotiation */ +#define SE 240 /* end sub negotiation */ + +#define GA 249 /* you may reverse the line */ +#define EL 248 /* erase the current line */ +#define EC 247 /* erase the current character */ +#define AYT 246 /* are you there */ +#define AO 245 /* abort output--but let prog finish */ +#define IP 244 /* interrupt process--permanently */ +#define BREAK 243 /* break */ +#define DM 242 /* data mark--for connect. cleaning */ +#define NOP 241 /* nop */ +#define EOR 239 /* end of record (transparent mode) */ +#define ABORT 238 /* Abort process */ +#define SUSP 237 /* Suspend process */ +#define xEOF 236 /* End of file: EOF is already used... */ + +#define TELOPTS(X) \ + X(BINARY, 0) /* 8-bit data path */ \ + X(ECHO, 1) /* echo */ \ + X(RCP, 2) /* prepare to reconnect */ \ + X(SGA, 3) /* suppress go ahead */ \ + X(NAMS, 4) /* approximate message size */ \ + X(STATUS, 5) /* give status */ \ + X(TM, 6) /* timing mark */ \ + X(RCTE, 7) /* remote controlled transmission and echo */ \ + X(NAOL, 8) /* negotiate about output line width */ \ + X(NAOP, 9) /* negotiate about output page size */ \ + X(NAOCRD, 10) /* negotiate about CR disposition */ \ + X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \ + X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \ + X(NAOFFD, 13) /* negotiate about formfeed disposition */ \ + X(NAOVTS, 14) /* negotiate about vertical tab stops */ \ + X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \ + X(NAOLFD, 16) /* negotiate about output LF disposition */ \ + X(XASCII, 17) /* extended ascic character set */ \ + X(LOGOUT, 18) /* force logout */ \ + X(BM, 19) /* byte macro */ \ + X(DET, 20) /* data entry terminal */ \ + X(SUPDUP, 21) /* supdup protocol */ \ + X(SUPDUPOUTPUT, 22) /* supdup output */ \ + X(SNDLOC, 23) /* send location */ \ + X(TTYPE, 24) /* terminal type */ \ + X(EOR, 25) /* end or record */ \ + X(TUID, 26) /* TACACS user identification */ \ + X(OUTMRK, 27) /* output marking */ \ + X(TTYLOC, 28) /* terminal location number */ \ + X(3270REGIME, 29) /* 3270 regime */ \ + X(X3PAD, 30) /* X.3 PAD */ \ + X(NAWS, 31) /* window size */ \ + X(TSPEED, 32) /* terminal speed */ \ + X(LFLOW, 33) /* remote flow control */ \ + X(LINEMODE, 34) /* Linemode option */ \ + X(XDISPLOC, 35) /* X Display Location */ \ + X(OLD_ENVIRON, 36) /* Old - Environment variables */ \ + X(AUTHENTICATION, 37) /* Authenticate */ \ + X(ENCRYPT, 38) /* Encryption option */ \ + X(NEW_ENVIRON, 39) /* New - Environment variables */ \ + X(TN3270E, 40) /* TN3270 enhancements */ \ + X(XAUTH, 41) \ + X(CHARSET, 42) /* Character set */ \ + X(RSP, 43) /* Remote serial port */ \ + X(COM_PORT_OPTION, 44) /* Com port control */ \ + X(SLE, 45) /* Suppress local echo */ \ + X(STARTTLS, 46) /* Start TLS */ \ + X(KERMIT, 47) /* Automatic Kermit file transfer */ \ + X(SEND_URL, 48) \ + X(FORWARD_X, 49) \ + X(PRAGMA_LOGON, 138) \ + X(SSPI_LOGON, 139) \ + X(PRAGMA_HEARTBEAT, 140) \ + X(EXOPL, 255) /* extended-options-list */ + +#define telnet_enum(x,y) TELOPT_##x = y, +enum { TELOPTS(telnet_enum) dummy=0 }; +#undef telnet_enum + +#define TELQUAL_IS 0 /* option is... */ +#define TELQUAL_SEND 1 /* send option */ +#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */ +#define BSD_VAR 1 +#define BSD_VALUE 0 +#define RFC_VAR 0 +#define RFC_VALUE 1 + +#define CR 13 +#define LF 10 +#define NUL 0 + +#define iswritable(x) ( (x) != IAC && (x) != CR ) + +static char *telopt(int opt) +{ +#define telnet_str(x,y) case TELOPT_##x: return #x; + switch (opt) { + TELOPTS(telnet_str) + default: + return ""; + } +#undef telnet_str +} + +static void telnet_size(void *handle, int width, int height); + +struct Opt { + int send; /* what we initially send */ + int nsend; /* -ve send if requested to stop it */ + int ack, nak; /* +ve and -ve acknowledgements */ + int option; /* the option code */ + int index; /* index into telnet->opt_states[] */ + enum { + REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE + } initial_state; +}; + +enum { + OPTINDEX_NAWS, + OPTINDEX_TSPEED, + OPTINDEX_TTYPE, + OPTINDEX_OENV, + OPTINDEX_NENV, + OPTINDEX_ECHO, + OPTINDEX_WE_SGA, + OPTINDEX_THEY_SGA, + OPTINDEX_WE_BIN, + OPTINDEX_THEY_BIN, + NUM_OPTS +}; + +static const struct Opt o_naws = + { DO, DONT, WILL, WONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED }; +static const struct Opt o_ttype = + { DO, DONT, WILL, WONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED }; +static const struct Opt o_oenv = + { DO, DONT, WILL, WONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE }; +static const struct Opt o_nenv = + { DO, DONT, WILL, WONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED }; +static const struct Opt o_echo = + { WILL, WONT, DO, DONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED }; +static const struct Opt o_they_sga = + { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED }; +static const struct Opt o_we_sga = + { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED }; + +static const struct Opt *const opts[] = { + &o_echo, &o_we_sga, &o_they_sga, &o_naws, &o_ttype, &o_oenv, &o_nenv, NULL +}; + +struct telnet_tag { + int opt_states[NUM_OPTS]; + + int sb_opt, sb_len; + unsigned char *sb_buf; + int sb_size; + + enum { + TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, + SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR + } state; + + sel_wfd *net, *pty; + + /* + * Options we must finish processing before launching the shell + */ + int old_environ_done, new_environ_done, ttype_done; + + /* + * Ready to start shell? + */ + int shell_ok; + int envvarsize; + struct shell_data shdata; +}; + +#define TELNET_MAX_BACKLOG 4096 + +#define SB_DELTA 1024 + +static void send_opt(Telnet telnet, int cmd, int option) +{ + unsigned char b[3]; + + b[0] = IAC; + b[1] = cmd; + b[2] = option; + sel_write(telnet->net, (char *)b, 3); +} + +static void deactivate_option(Telnet telnet, const struct Opt *o) +{ + if (telnet->opt_states[o->index] == REQUESTED || + telnet->opt_states[o->index] == ACTIVE) + send_opt(telnet, o->nsend, o->option); + telnet->opt_states[o->index] = REALLY_INACTIVE; +} + +/* + * Generate side effects of enabling or disabling an option. + */ +static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled) +{ +} + +static void activate_option(Telnet telnet, const struct Opt *o) +{ + if (o->option == TELOPT_NEW_ENVIRON || + o->option == TELOPT_OLD_ENVIRON || + o->option == TELOPT_TTYPE) { + char buf[6]; + buf[0] = IAC; + buf[1] = SB; + buf[2] = o->option; + buf[3] = TELQUAL_SEND; + buf[4] = IAC; + buf[5] = SE; + sel_write(telnet->net, buf, 6); + } + option_side_effects(telnet, o, 1); +} + +static void done_option(Telnet telnet, int option) +{ + if (option == TELOPT_OLD_ENVIRON) + telnet->old_environ_done = 1; + else if (option == TELOPT_NEW_ENVIRON) + telnet->new_environ_done = 1; + else if (option == TELOPT_TTYPE) + telnet->ttype_done = 1; + + if (telnet->old_environ_done && telnet->new_environ_done && + telnet->ttype_done) { + telnet->shell_ok = 1; + } +} + +static void refused_option(Telnet telnet, const struct Opt *o) +{ + done_option(telnet, o->option); + if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON && + telnet->opt_states[o_oenv.index] == INACTIVE) { + send_opt(telnet, WILL, TELOPT_OLD_ENVIRON); + telnet->opt_states[o_oenv.index] = REQUESTED; + telnet->old_environ_done = 0; + } + option_side_effects(telnet, o, 0); +} + +static void proc_rec_opt(Telnet telnet, int cmd, int option) +{ + const struct Opt *const *o; + + for (o = opts; *o; o++) { + if ((*o)->option == option && (*o)->ack == cmd) { + switch (telnet->opt_states[(*o)->index]) { + case REQUESTED: + telnet->opt_states[(*o)->index] = ACTIVE; + activate_option(telnet, *o); + break; + case ACTIVE: + break; + case INACTIVE: + telnet->opt_states[(*o)->index] = ACTIVE; + send_opt(telnet, (*o)->send, option); + activate_option(telnet, *o); + break; + case REALLY_INACTIVE: + send_opt(telnet, (*o)->nsend, option); + break; + } + return; + } else if ((*o)->option == option && (*o)->nak == cmd) { + switch (telnet->opt_states[(*o)->index]) { + case REQUESTED: + telnet->opt_states[(*o)->index] = INACTIVE; + refused_option(telnet, *o); + break; + case ACTIVE: + telnet->opt_states[(*o)->index] = INACTIVE; + send_opt(telnet, (*o)->nsend, option); + option_side_effects(telnet, *o, 0); + break; + case INACTIVE: + case REALLY_INACTIVE: + break; + } + return; + } + } + /* + * If we reach here, the option was one we weren't prepared to + * cope with. If the request was positive (WILL or DO), we send + * a negative ack to indicate refusal. If the request was + * negative (WONT / DONT), we must do nothing. + */ + if (cmd == WILL || cmd == DO) + send_opt(telnet, (cmd == WILL ? DONT : WONT), option); +} + +static void process_subneg(Telnet telnet) +{ + unsigned char b[2048], *p, *q; + int var, value, n; + char *e; + + switch (telnet->sb_opt) { + case TELOPT_OLD_ENVIRON: + case TELOPT_NEW_ENVIRON: + if (telnet->sb_buf[0] == TELQUAL_IS) { + if (telnet->sb_opt == TELOPT_NEW_ENVIRON) { + var = RFC_VAR; + value = RFC_VALUE; + } else { + if (telnet->sb_len > 1 && !(telnet->sb_buf[0] &~ 1)) { + var = telnet->sb_buf[0]; + value = BSD_VAR ^ BSD_VALUE ^ var; + } else { + var = BSD_VAR; + value = BSD_VALUE; + } + } + } + n = 1; + while (n < telnet->sb_len && telnet->sb_buf[n] == var) { + int varpos, varlen, valpos, vallen; + char *result; + + varpos = ++n; + while (n < telnet->sb_len && telnet->sb_buf[n] != value) + n++; + if (n == telnet->sb_len) + break; + varlen = n - varpos; + valpos = ++n; + while (n < telnet->sb_len && telnet->sb_buf[n] != var) + n++; + vallen = n - valpos; + + result = snewn(varlen + vallen + 2, char); + sprintf(result, "%.*s=%.*s", + varlen, telnet->sb_buf+varpos, + vallen, telnet->sb_buf+valpos); + if (telnet->shdata.nenvvars >= telnet->envvarsize) { + telnet->envvarsize = telnet->shdata.nenvvars * 3 / 2 + 16; + telnet->shdata.envvars = sresize(telnet->shdata.envvars, + telnet->envvarsize, char *); + } + telnet->shdata.envvars[telnet->shdata.nenvvars++] = result; + } + done_option(telnet, telnet->sb_opt); + break; + case TELOPT_TTYPE: + if (telnet->sb_len >= 1 && telnet->sb_buf[0] == TELQUAL_IS) { + telnet->shdata.termtype = snewn(5 + telnet->sb_len, char); + strcpy(telnet->shdata.termtype, "TERM="); + for (n = 0; n < telnet->sb_len-1; n++) { + char c = telnet->sb_buf[n+1]; + if (c >= 'A' && c <= 'Z') + c = c + 'a' - 'A'; + telnet->shdata.termtype[n+5] = c; + } + telnet->shdata.termtype[telnet->sb_len+5-1] = '\0'; + } + done_option(telnet, telnet->sb_opt); + break; + case TELOPT_NAWS: + if (telnet->sb_len == 4) { + int w, h; + w = (unsigned char)telnet->sb_buf[0]; + w = (w << 8) | (unsigned char)telnet->sb_buf[1]; + h = (unsigned char)telnet->sb_buf[2]; + h = (h << 8) | (unsigned char)telnet->sb_buf[3]; + pty_resize(w, h); + } + break; + } +} + +void telnet_from_net(Telnet telnet, char *buf, int len) +{ + while (len--) { + int c = (unsigned char) *buf++; + + switch (telnet->state) { + case TOP_LEVEL: + case SEENCR: + /* + * PuTTY sends Telnet's new line sequence (CR LF on + * the wire) in response to the return key. We must + * therefore treat that as equivalent to CR NUL, and + * send CR to the pty. + */ + if ((c == NUL || c == '\n') && telnet->state == SEENCR) + telnet->state = TOP_LEVEL; + else if (c == IAC) + telnet->state = SEENIAC; + else { + char cc = c; + sel_write(telnet->pty, &cc, 1); + + telnet->state = SEENCR; + } + break; + case SEENIAC: + if (c == DO) + telnet->state = SEENDO; + else if (c == DONT) + telnet->state = SEENDONT; + else if (c == WILL) + telnet->state = SEENWILL; + else if (c == WONT) + telnet->state = SEENWONT; + else if (c == SB) + telnet->state = SEENSB; + else if (c == DM) + telnet->state = TOP_LEVEL; + else { + /* ignore everything else; print it if it's IAC */ + if (c == IAC) { + char cc = c; + sel_write(telnet->pty, &cc, 1); + } + telnet->state = TOP_LEVEL; + } + break; + case SEENWILL: + proc_rec_opt(telnet, WILL, c); + telnet->state = TOP_LEVEL; + break; + case SEENWONT: + proc_rec_opt(telnet, WONT, c); + telnet->state = TOP_LEVEL; + break; + case SEENDO: + proc_rec_opt(telnet, DO, c); + telnet->state = TOP_LEVEL; + break; + case SEENDONT: + proc_rec_opt(telnet, DONT, c); + telnet->state = TOP_LEVEL; + break; + case SEENSB: + telnet->sb_opt = c; + telnet->sb_len = 0; + telnet->state = SUBNEGOT; + break; + case SUBNEGOT: + if (c == IAC) + telnet->state = SUBNEG_IAC; + else { + subneg_addchar: + if (telnet->sb_len >= telnet->sb_size) { + telnet->sb_size += SB_DELTA; + telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size, + unsigned char); + } + telnet->sb_buf[telnet->sb_len++] = c; + telnet->state = SUBNEGOT; /* in case we came here by goto */ + } + break; + case SUBNEG_IAC: + if (c != SE) + goto subneg_addchar; /* yes, it's a hack, I know, but... */ + else { + process_subneg(telnet); + telnet->state = TOP_LEVEL; + } + break; + } + } +} + +Telnet telnet_new(sel_wfd *net, sel_wfd *pty) +{ + Telnet telnet; + + telnet = snew(struct telnet_tag); + telnet->sb_buf = NULL; + telnet->sb_size = 0; + telnet->state = TOP_LEVEL; + telnet->net = net; + telnet->pty = pty; + telnet->shdata.envvars = NULL; + telnet->shdata.nenvvars = telnet->envvarsize = 0; + telnet->shdata.termtype = NULL; + + /* + * Initialise option states. + */ + { + const struct Opt *const *o; + + for (o = opts; *o; o++) { + telnet->opt_states[(*o)->index] = (*o)->initial_state; + if (telnet->opt_states[(*o)->index] == REQUESTED) + send_opt(telnet, (*o)->send, (*o)->option); + } + } + + telnet->old_environ_done = 1; /* initially don't want to bother */ + telnet->new_environ_done = 0; + telnet->ttype_done = 0; + telnet->shell_ok = 0; + + return telnet; +} + +void telnet_free(Telnet telnet) +{ + sfree(telnet->sb_buf); + sfree(telnet); +} + +void telnet_from_pty(Telnet telnet, char *buf, int len) +{ + unsigned char *p, *end; + static const unsigned char iac[2] = { IAC, IAC }; + static const unsigned char cr[2] = { CR, NUL }; +#if 0 + static const unsigned char nl[2] = { CR, LF }; +#endif + + p = (unsigned char *)buf; + end = (unsigned char *)(buf + len); + while (p < end) { + unsigned char *q = p; + + while (p < end && iswritable(*p)) + p++; + sel_write(telnet->net, (char *)q, p - q); + + while (p < end && !iswritable(*p)) { + sel_write(telnet->net, (char *)(*p == IAC ? iac : cr), 2); + p++; + } + } +} + +int telnet_shell_ok(Telnet telnet, struct shell_data *shdata) +{ + if (telnet->shell_ok) + *shdata = telnet->shdata; /* structure copy */ + return telnet->shell_ok; +} diff --git a/putty/CONTRIB/CYGTERMD/TELNET.H b/putty/CONTRIB/CYGTERMD/TELNET.H new file mode 100644 index 0000000..3ddb621 --- /dev/null +++ b/putty/CONTRIB/CYGTERMD/TELNET.H @@ -0,0 +1,41 @@ +/* + * Header declaring Telnet-handling functions. + */ + +#ifndef FIXME_TELNET_H +#define FIXME_TELNET_H + +#include "sel.h" + +typedef struct telnet_tag *Telnet; + +struct shell_data { + char **envvars; /* array of "VAR=value" terms */ + int nenvvars; + char *termtype; +}; + +/* + * Create and destroy a Telnet structure. + */ +Telnet telnet_new(sel_wfd *net, sel_wfd *pty); +void telnet_free(Telnet telnet); + +/* + * Process data read from the pty. + */ +void telnet_from_pty(Telnet telnet, char *buf, int len); + +/* + * Process Telnet protocol data received from the network. + */ +void telnet_from_net(Telnet telnet, char *buf, int len); + +/* + * Return true if pre-shell-startup negotiations are complete and + * it's safe to start the shell subprocess now. On a true return, + * also fills in the shell_data structure. + */ +int telnet_shell_ok(Telnet telnet, struct shell_data *shdata); + +#endif /* FIXME_TELNET_H */ diff --git a/putty/CONTRIB/KH2REG.PY b/putty/CONTRIB/KH2REG.PY new file mode 100644 index 0000000..fe78267 --- /dev/null +++ b/putty/CONTRIB/KH2REG.PY @@ -0,0 +1,157 @@ +#! /usr/bin/env python + +# $Id: kh2reg.py 8519 2009-04-26 23:44:28Z jacob $ +# Convert OpenSSH known_hosts and known_hosts2 files to "new format" PuTTY +# host keys. +# usage: +# kh2reg.py [ --win ] known_hosts1 2 3 4 ... > hosts.reg +# Creates a Windows .REG file (double-click to install). +# kh2reg.py --unix known_hosts1 2 3 4 ... > sshhostkeys +# Creates data suitable for storing in ~/.putty/sshhostkeys (Unix). +# Line endings are someone else's problem as is traditional. +# Developed for Python 1.5.2. + +import fileinput +import base64 +import struct +import string +import re +import sys +import getopt + +def winmungestr(s): + "Duplicate of PuTTY's mungestr() in winstore.c:1.10 for Registry keys" + candot = 0 + r = "" + for c in s: + if c in ' \*?%~' or ord(c)%luB" % len(s), s) + return reduce ((lambda a, b: (long(a) << 8) + long(b)), bytes) + +def longtohex(n): + """Convert long int to lower-case hex. + + Ick, Python (at least in 1.5.2) doesn't appear to have a way to + turn a long int into an unadorned hex string -- % gets upset if the + number is too big, and raw hex() uses uppercase (sometimes), and + adds unwanted "0x...L" around it.""" + + plain=string.lower(re.match(r"0x([0-9A-Fa-f]*)l?$", hex(n), re.I).group(1)) + return "0x" + plain + +output_type = 'windows' + +try: + optlist, args = getopt.getopt(sys.argv[1:], '', [ 'win', 'unix' ]) + if filter(lambda x: x[0] == '--unix', optlist): + output_type = 'unix' +except getopt.error, e: + sys.stderr.write(str(e) + "\n") + sys.exit(1) + +if output_type == 'windows': + # Output REG file header. + sys.stdout.write("""REGEDIT4 + +[HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys] +""") + +# Now process all known_hosts input. +for line in fileinput.input(args): + + try: + # Remove leading/trailing whitespace (should zap CR and LF) + line = string.strip (line) + + # Skip blanks and comments + if line == '' or line[0] == '#': + raise "Skipping input line" + + # Split line on spaces. + fields = string.split (line, ' ') + + # Common fields + hostpat = fields[0] + magicnumbers = [] # placeholder + keytype = "" # placeholder + + # Grotty heuristic to distinguish known_hosts from known_hosts2: + # is second field entirely decimal digits? + if re.match (r"\d*$", fields[1]): + + # Treat as SSH-1-type host key. + # Format: hostpat bits10 exp10 mod10 comment... + # (PuTTY doesn't store the number of bits.) + magicnumbers = map (long, fields[2:4]) + keytype = "rsa" + + else: + + # Treat as SSH-2-type host key. + # Format: hostpat keytype keyblob64 comment... + sshkeytype, blob = fields[1], base64.decodestring (fields[2]) + + # 'blob' consists of a number of + # uint32 N (big-endian) + # uint8[N] field_data + subfields = [] + while blob: + sizefmt = ">L" + (size,) = struct.unpack (sizefmt, blob[0:4]) + size = int(size) # req'd for slicage + (data,) = struct.unpack (">%lus" % size, blob[4:size+4]) + subfields.append(data) + blob = blob [struct.calcsize(sizefmt) + size : ] + + # The first field is keytype again, and the rest we can treat as + # an opaque list of bignums (same numbers and order as stored + # by PuTTY). (currently embedded keytype is ignored entirely) + magicnumbers = map (strtolong, subfields[1:]) + + # Translate key type into something PuTTY can use. + if sshkeytype == "ssh-rsa": keytype = "rsa2" + elif sshkeytype == "ssh-dss": keytype = "dss" + else: + raise "Unknown SSH key type", sshkeytype + + # Now print out one line per host pattern, discarding wildcards. + for host in string.split (hostpat, ','): + if re.search (r"[*?!]", host): + sys.stderr.write("Skipping wildcard host pattern '%s'\n" + % host) + continue + elif re.match (r"\|", host): + sys.stderr.write("Skipping hashed hostname '%s'\n" % host) + continue + else: + m = re.match (r"\[([^]]*)\]:(\d*)$", host) + if m: + (host, port) = m.group(1,2) + port = int(port) + else: + port = 22 + # Slightly bizarre output key format: 'type@port:hostname' + # XXX: does PuTTY do anything useful with literal IP[v4]s? + key = keytype + ("@%d:%s" % (port, host)) + value = string.join (map (longtohex, magicnumbers), ',') + if output_type == 'unix': + # Unix format. + sys.stdout.write('%s %s\n' % (key, value)) + else: + # Windows format. + # XXX: worry about double quotes? + sys.stdout.write("\"%s\"=\"%s\"\n" + % (winmungestr(key), value)) + + except "Unknown SSH key type", k: + sys.stderr.write("Unknown SSH key type '%s', skipping\n" % k) + except "Skipping input line": + pass diff --git a/putty/CPROXY.C b/putty/CPROXY.C new file mode 100644 index 0000000..5537fca --- /dev/null +++ b/putty/CPROXY.C @@ -0,0 +1,190 @@ +/* + * Routines to do cryptographic interaction with proxies in PuTTY. + * This is in a separate module from proxy.c, so that it can be + * conveniently removed in PuTTYtel by replacing this module with + * the stub version nocproxy.c. + */ + +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "ssh.h" /* For MD5 support */ +#include "network.h" +#include "proxy.h" + +static void hmacmd5_chap(const unsigned char *challenge, int challen, + const char *passwd, unsigned char *response) +{ + void *hmacmd5_ctx; + int pwlen; + + hmacmd5_ctx = hmacmd5_make_context(); + + pwlen = strlen(passwd); + if (pwlen>64) { + unsigned char md5buf[16]; + MD5Simple(passwd, pwlen, md5buf); + hmacmd5_key(hmacmd5_ctx, md5buf, 16); + } else { + hmacmd5_key(hmacmd5_ctx, passwd, pwlen); + } + + hmacmd5_do_hmac(hmacmd5_ctx, challenge, challen, response); + hmacmd5_free_context(hmacmd5_ctx); +} + +void proxy_socks5_offerencryptedauth(char *command, int *len) +{ + command[*len] = 0x03; /* CHAP */ + (*len)++; +} + +int proxy_socks5_handlechap (Proxy_Socket p) +{ + + /* CHAP authentication reply format: + * version number (1 bytes) = 1 + * number of commands (1 byte) + * + * For each command: + * command identifier (1 byte) + * data length (1 byte) + */ + unsigned char data[260]; + unsigned char outbuf[20]; + + while(p->chap_num_attributes == 0 || + p->chap_num_attributes_processed < p->chap_num_attributes) { + if (p->chap_num_attributes == 0 || + p->chap_current_attribute == -1) { + /* CHAP normally reads in two bytes, either at the + * beginning or for each attribute/value pair. But if + * we're waiting for the value's data, we might not want + * to read 2 bytes. + */ + + if (bufchain_size(&p->pending_input_data) < 2) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 2); + bufchain_consume(&p->pending_input_data, 2); + } + + if (p->chap_num_attributes == 0) { + /* If there are no attributes, this is our first msg + * with the server, where we negotiate version and + * number of attributes + */ + if (data[0] != 0x01) { + plug_closing(p->plug, "Proxy error: SOCKS proxy wants" + " a different CHAP version", + PROXY_ERROR_GENERAL, 0); + return 1; + } + if (data[1] == 0x00) { + plug_closing(p->plug, "Proxy error: SOCKS proxy won't" + " negotiate CHAP with us", + PROXY_ERROR_GENERAL, 0); + return 1; + } + p->chap_num_attributes = data[1]; + } else { + if (p->chap_current_attribute == -1) { + /* We have to read in each attribute/value pair - + * those we don't understand can be ignored, but + * there are a few we'll need to handle. + */ + p->chap_current_attribute = data[0]; + p->chap_current_datalen = data[1]; + } + if (bufchain_size(&p->pending_input_data) < + p->chap_current_datalen) + return 1; /* not got everything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, + p->chap_current_datalen); + + bufchain_consume(&p->pending_input_data, + p->chap_current_datalen); + + switch (p->chap_current_attribute) { + case 0x00: + /* Successful authentication */ + if (data[0] == 0x00) + p->state = 2; + else { + plug_closing(p->plug, "Proxy error: SOCKS proxy" + " refused CHAP authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + break; + case 0x03: + outbuf[0] = 0x01; /* Version */ + outbuf[1] = 0x01; /* One attribute */ + outbuf[2] = 0x04; /* Response */ + outbuf[3] = 0x10; /* Length */ + hmacmd5_chap(data, p->chap_current_datalen, + p->cfg.proxy_password, &outbuf[4]); + sk_write(p->sub_socket, (char *)outbuf, 20); + break; + case 0x11: + /* Chose a protocol */ + if (data[0] != 0x85) { + plug_closing(p->plug, "Proxy error: Server chose " + "CHAP of other than HMAC-MD5 but we " + "didn't offer it!", + PROXY_ERROR_GENERAL, 0); + return 1; + } + break; + } + p->chap_current_attribute = -1; + p->chap_num_attributes_processed++; + } + if (p->state == 8 && + p->chap_num_attributes_processed >= p->chap_num_attributes) { + p->chap_num_attributes = 0; + p->chap_num_attributes_processed = 0; + p->chap_current_datalen = 0; + } + } + return 0; +} + +int proxy_socks5_selectchap(Proxy_Socket p) +{ + if (p->cfg.proxy_username[0] || p->cfg.proxy_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(p->cfg.proxy_username); + if (ulen > 255) ulen = 255; if (ulen < 1) ulen = 1; + + chapbuf[6] = ulen; + memcpy(chapbuf+7, p->cfg.proxy_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/putty/DIALOG.C b/putty/DIALOG.C new file mode 100644 index 0000000..55bece8 --- /dev/null +++ b/putty/DIALOG.C @@ -0,0 +1,595 @@ +/* + * dialog.c - a reasonably platform-independent mechanism for + * describing dialog boxes. + */ + +#include +#include +#include +#include + +#define DEFINE_INTORPTR_FNS + +#include "putty.h" +#include "dialog.h" + +int ctrl_path_elements(char *path) +{ + int i = 1; + while (*path) { + if (*path == '/') i++; + path++; + } + return i; +} + +/* Return the number of matching path elements at the starts of p1 and p2, + * or INT_MAX if the paths are identical. */ +int ctrl_path_compare(char *p1, char *p2) +{ + int i = 0; + while (*p1 || *p2) { + if ((*p1 == '/' || *p1 == '\0') && + (*p2 == '/' || *p2 == '\0')) + i++; /* a whole element matches, ooh */ + if (*p1 != *p2) + return i; /* mismatch */ + p1++, p2++; + } + return INT_MAX; /* exact match */ +} + +struct controlbox *ctrl_new_box(void) +{ + struct controlbox *ret = snew(struct controlbox); + + ret->nctrlsets = ret->ctrlsetsize = 0; + ret->ctrlsets = NULL; + ret->nfrees = ret->freesize = 0; + ret->frees = NULL; + + return ret; +} + +void ctrl_free_box(struct controlbox *b) +{ + int i; + + for (i = 0; i < b->nctrlsets; i++) { + ctrl_free_set(b->ctrlsets[i]); + } + for (i = 0; i < b->nfrees; i++) + sfree(b->frees[i]); + sfree(b->ctrlsets); + sfree(b->frees); + sfree(b); +} + +void ctrl_free_set(struct controlset *s) +{ + int i; + + sfree(s->pathname); + sfree(s->boxname); + sfree(s->boxtitle); + for (i = 0; i < s->ncontrols; i++) { + ctrl_free(s->ctrls[i]); + } + sfree(s->ctrls); + sfree(s); +} + +/* + * Find the index of first controlset in a controlbox for a given + * path. If that path doesn't exist, return the index where it + * should be inserted. + */ +static int ctrl_find_set(struct controlbox *b, char *path, int start) +{ + int i, last, thisone; + + last = 0; + for (i = 0; i < b->nctrlsets; i++) { + thisone = ctrl_path_compare(path, b->ctrlsets[i]->pathname); + /* + * If `start' is true and there exists a controlset with + * exactly the path we've been given, we should return the + * index of the first such controlset we find. Otherwise, + * we should return the index of the first entry in which + * _fewer_ path elements match than they did last time. + */ + if ((start && thisone == INT_MAX) || thisone < last) + return i; + last = thisone; + } + return b->nctrlsets; /* insert at end */ +} + +/* + * Find the index of next controlset in a controlbox for a given + * path, or -1 if no such controlset exists. If -1 is passed as + * input, finds the first. + */ +int ctrl_find_path(struct controlbox *b, char *path, int index) +{ + if (index < 0) + index = ctrl_find_set(b, path, 1); + else + index++; + + if (index < b->nctrlsets && !strcmp(path, b->ctrlsets[index]->pathname)) + return index; + else + return -1; +} + +/* Set up a panel title. */ +struct controlset *ctrl_settitle(struct controlbox *b, + char *path, char *title) +{ + + struct controlset *s = snew(struct controlset); + int index = ctrl_find_set(b, path, 1); + s->pathname = dupstr(path); + s->boxname = NULL; + s->boxtitle = dupstr(title); + s->ncontrols = s->ctrlsize = 0; + s->ncolumns = 0; /* this is a title! */ + s->ctrls = NULL; + if (b->nctrlsets >= b->ctrlsetsize) { + b->ctrlsetsize = b->nctrlsets + 32; + b->ctrlsets = sresize(b->ctrlsets, b->ctrlsetsize,struct controlset *); + } + if (index < b->nctrlsets) + memmove(&b->ctrlsets[index+1], &b->ctrlsets[index], + (b->nctrlsets-index) * sizeof(*b->ctrlsets)); + b->ctrlsets[index] = s; + b->nctrlsets++; + return s; +} + +/* Retrieve a pointer to a controlset, creating it if absent. */ +struct controlset *ctrl_getset(struct controlbox *b, + char *path, char *name, char *boxtitle) +{ + struct controlset *s; + int index = ctrl_find_set(b, path, 1); + while (index < b->nctrlsets && + !strcmp(b->ctrlsets[index]->pathname, path)) { + if (b->ctrlsets[index]->boxname && + !strcmp(b->ctrlsets[index]->boxname, name)) + return b->ctrlsets[index]; + index++; + } + s = snew(struct controlset); + s->pathname = dupstr(path); + s->boxname = dupstr(name); + s->boxtitle = boxtitle ? dupstr(boxtitle) : NULL; + s->ncolumns = 1; + s->ncontrols = s->ctrlsize = 0; + s->ctrls = NULL; + if (b->nctrlsets >= b->ctrlsetsize) { + b->ctrlsetsize = b->nctrlsets + 32; + b->ctrlsets = sresize(b->ctrlsets, b->ctrlsetsize,struct controlset *); + } + if (index < b->nctrlsets) + memmove(&b->ctrlsets[index+1], &b->ctrlsets[index], + (b->nctrlsets-index) * sizeof(*b->ctrlsets)); + b->ctrlsets[index] = s; + b->nctrlsets++; + return s; +} + +/* Allocate some private data in a controlbox. */ +void *ctrl_alloc(struct controlbox *b, size_t size) +{ + void *p; + /* + * This is an internal allocation routine, so it's allowed to + * use smalloc directly. + */ + p = smalloc(size); + if (b->nfrees >= b->freesize) { + b->freesize = b->nfrees + 32; + b->frees = sresize(b->frees, b->freesize, void *); + } + b->frees[b->nfrees++] = p; + return p; +} + +static union control *ctrl_new(struct controlset *s, int type, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = snew(union control); + if (s->ncontrols >= s->ctrlsize) { + s->ctrlsize = s->ncontrols + 32; + s->ctrls = sresize(s->ctrls, s->ctrlsize, union control *); + } + s->ctrls[s->ncontrols++] = c; + /* + * Fill in the standard fields. + */ + c->generic.type = type; + c->generic.tabdelay = 0; + c->generic.column = COLUMN_FIELD(0, s->ncolumns); + c->generic.helpctx = helpctx; + c->generic.handler = handler; + c->generic.context = context; + c->generic.label = NULL; + return c; +} + +/* `ncolumns' is followed by that many percentages, as integers. */ +union control *ctrl_columns(struct controlset *s, int ncolumns, ...) +{ + union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL)); + assert(s->ncolumns == 1 || ncolumns == 1); + c->columns.ncols = ncolumns; + s->ncolumns = ncolumns; + if (ncolumns == 1) { + c->columns.percentages = NULL; + } else { + va_list ap; + int i; + c->columns.percentages = snewn(ncolumns, int); + va_start(ap, ncolumns); + for (i = 0; i < ncolumns; i++) + c->columns.percentages[i] = va_arg(ap, int); + va_end(ap); + } + return c; +} + +union control *ctrl_editbox(struct controlset *s, char *label, char shortcut, + int percentage, + intorptr helpctx, handler_fn handler, + intorptr context, intorptr context2) +{ + union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); + c->editbox.label = label ? dupstr(label) : NULL; + c->editbox.shortcut = shortcut; + c->editbox.percentwidth = percentage; + c->editbox.password = 0; + c->editbox.has_list = 0; + c->editbox.context2 = context2; + return c; +} + +union control *ctrl_combobox(struct controlset *s, char *label, char shortcut, + int percentage, + intorptr helpctx, handler_fn handler, + intorptr context, intorptr context2) +{ + union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); + c->editbox.label = label ? dupstr(label) : NULL; + c->editbox.shortcut = shortcut; + c->editbox.percentwidth = percentage; + c->editbox.password = 0; + c->editbox.has_list = 1; + c->editbox.context2 = context2; + return c; +} + +/* + * `ncolumns' is followed by (alternately) radio button titles and + * intorptrs, until a NULL in place of a title string is seen. Each + * title is expected to be followed by a shortcut _iff_ `shortcut' + * is NO_SHORTCUT. + */ +union control *ctrl_radiobuttons(struct controlset *s, char *label, + char shortcut, int ncolumns, intorptr helpctx, + handler_fn handler, intorptr context, ...) +{ + va_list ap; + int i; + union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context); + c->radio.label = label ? dupstr(label) : NULL; + c->radio.shortcut = shortcut; + c->radio.ncolumns = ncolumns; + /* + * Initial pass along variable argument list to count the + * buttons. + */ + va_start(ap, context); + i = 0; + while (va_arg(ap, char *) != NULL) { + i++; + if (c->radio.shortcut == NO_SHORTCUT) + (void)va_arg(ap, int); /* char promotes to int in arg lists */ + (void)va_arg(ap, intorptr); + } + va_end(ap); + c->radio.nbuttons = i; + if (c->radio.shortcut == NO_SHORTCUT) + c->radio.shortcuts = snewn(c->radio.nbuttons, char); + else + c->radio.shortcuts = NULL; + c->radio.buttons = snewn(c->radio.nbuttons, char *); + c->radio.buttondata = snewn(c->radio.nbuttons, intorptr); + /* + * Second pass along variable argument list to actually fill in + * the structure. + */ + va_start(ap, context); + for (i = 0; i < c->radio.nbuttons; i++) { + c->radio.buttons[i] = dupstr(va_arg(ap, char *)); + if (c->radio.shortcut == NO_SHORTCUT) + c->radio.shortcuts[i] = va_arg(ap, int); + /* char promotes to int in arg lists */ + c->radio.buttondata[i] = va_arg(ap, intorptr); + } + va_end(ap); + return c; +} + +union control *ctrl_pushbutton(struct controlset *s,char *label,char shortcut, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context); + c->button.label = label ? dupstr(label) : NULL; + c->button.shortcut = shortcut; + c->button.isdefault = 0; + c->button.iscancel = 0; + return c; +} + +union control *ctrl_listbox(struct controlset *s,char *label,char shortcut, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->listbox.label = label ? dupstr(label) : NULL; + c->listbox.shortcut = shortcut; + c->listbox.height = 5; /* *shrug* a plausible default */ + c->listbox.draglist = 0; + c->listbox.multisel = 0; + c->listbox.percentwidth = 100; + c->listbox.ncols = 0; + c->listbox.percentages = NULL; + return c; +} + +union control *ctrl_droplist(struct controlset *s, char *label, char shortcut, + int percentage, intorptr helpctx, + handler_fn handler, intorptr context) +{ + union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->listbox.label = label ? dupstr(label) : NULL; + c->listbox.shortcut = shortcut; + c->listbox.height = 0; /* means it's a drop-down list */ + c->listbox.draglist = 0; + c->listbox.multisel = 0; + c->listbox.percentwidth = percentage; + c->listbox.ncols = 0; + c->listbox.percentages = NULL; + return c; +} + +union control *ctrl_draglist(struct controlset *s,char *label,char shortcut, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->listbox.label = label ? dupstr(label) : NULL; + c->listbox.shortcut = shortcut; + c->listbox.height = 5; /* *shrug* a plausible default */ + c->listbox.draglist = 1; + c->listbox.multisel = 0; + c->listbox.percentwidth = 100; + c->listbox.ncols = 0; + c->listbox.percentages = NULL; + return c; +} + +union control *ctrl_filesel(struct controlset *s,char *label,char shortcut, + char const *filter, int write, char *title, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context); + c->fileselect.label = label ? dupstr(label) : NULL; + c->fileselect.shortcut = shortcut; + c->fileselect.filter = filter; + c->fileselect.for_writing = write; + c->fileselect.title = dupstr(title); + return c; +} + +union control *ctrl_fontsel(struct controlset *s,char *label,char shortcut, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context); + c->fontselect.label = label ? dupstr(label) : NULL; + c->fontselect.shortcut = shortcut; + return c; +} + +union control *ctrl_tabdelay(struct controlset *s, union control *ctrl) +{ + union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL)); + c->tabdelay.ctrl = ctrl; + return c; +} + +union control *ctrl_text(struct controlset *s, char *text, intorptr helpctx) +{ + union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL)); + c->text.label = dupstr(text); + return c; +} + +union control *ctrl_checkbox(struct controlset *s, char *label, char shortcut, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context); + c->checkbox.label = label ? dupstr(label) : NULL; + c->checkbox.shortcut = shortcut; + return c; +} + +void ctrl_free(union control *ctrl) +{ + int i; + + sfree(ctrl->generic.label); + switch (ctrl->generic.type) { + case CTRL_RADIO: + for (i = 0; i < ctrl->radio.nbuttons; i++) + sfree(ctrl->radio.buttons[i]); + sfree(ctrl->radio.buttons); + sfree(ctrl->radio.shortcuts); + sfree(ctrl->radio.buttondata); + break; + case CTRL_COLUMNS: + sfree(ctrl->columns.percentages); + break; + case CTRL_LISTBOX: + sfree(ctrl->listbox.percentages); + break; + case CTRL_FILESELECT: + sfree(ctrl->fileselect.title); + break; + } + sfree(ctrl); +} + +void dlg_stdradiobutton_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int button; + /* + * For a standard radio button set, the context parameter gives + * offsetof(targetfield, Config), and the extra data per button + * gives the value the target field should take if that button + * is the one selected. + */ + if (event == EVENT_REFRESH) { + for (button = 0; button < ctrl->radio.nbuttons; button++) + if (*(int *)ATOFFSET(data, ctrl->radio.context.i) == + ctrl->radio.buttondata[button].i) + break; + /* We expected that `break' to happen, in all circumstances. */ + assert(button < ctrl->radio.nbuttons); + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + *(int *)ATOFFSET(data, ctrl->radio.context.i) = + ctrl->radio.buttondata[button].i; + } +} + +void dlg_stdcheckbox_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + int offset, invert; + + /* + * For a standard checkbox, the context parameter gives + * offsetof(targetfield, Config), optionally ORed with + * CHECKBOX_INVERT. + */ + offset = ctrl->checkbox.context.i; + if (offset & CHECKBOX_INVERT) { + offset &= ~CHECKBOX_INVERT; + invert = 1; + } else + invert = 0; + + /* + * C lacks a logical XOR, so the following code uses the idiom + * (!a ^ !b) to obtain the logical XOR of a and b. (That is, 1 + * iff exactly one of a and b is nonzero, otherwise 0.) + */ + + if (event == EVENT_REFRESH) { + dlg_checkbox_set(ctrl,dlg, (!*(int *)ATOFFSET(data,offset) ^ !invert)); + } else if (event == EVENT_VALCHANGE) { + *(int *)ATOFFSET(data, offset) = !dlg_checkbox_get(ctrl,dlg) ^ !invert; + } +} + +void dlg_stdeditbox_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + /* + * The standard edit-box handler expects the main `context' + * field to contain the `offsetof' a field in the structure + * pointed to by `data'. The secondary `context2' field + * indicates the type of this field: + * + * - if context2 > 0, the field is a char array and context2 + * gives its size. + * - if context2 == -1, the field is an int and the edit box + * is numeric. + * - if context2 < -1, the field is an int and the edit box is + * _floating_, and (-context2) gives the scale. (E.g. if + * context2 == -1000, then typing 1.2 into the box will set + * the field to 1200.) + */ + int offset = ctrl->editbox.context.i; + int length = ctrl->editbox.context2.i; + + if (length > 0) { + char *field = (char *)ATOFFSET(data, offset); + if (event == EVENT_REFRESH) { + dlg_editbox_set(ctrl, dlg, field); + } else if (event == EVENT_VALCHANGE) { + dlg_editbox_get(ctrl, dlg, field, length); + } + } else if (length < 0) { + int *field = (int *)ATOFFSET(data, offset); + char data[80]; + if (event == EVENT_REFRESH) { + if (length == -1) + sprintf(data, "%d", *field); + else + sprintf(data, "%g", (double)*field / (double)(-length)); + dlg_editbox_set(ctrl, dlg, data); + } else if (event == EVENT_VALCHANGE) { + dlg_editbox_get(ctrl, dlg, data, lenof(data)); + if (length == -1) + *field = atoi(data); + else + *field = (int)((-length) * atof(data)); + } + } +} + +void dlg_stdfilesel_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + /* + * The standard file-selector handler expects the `context' + * field to contain the `offsetof' a Filename field in the + * structure pointed to by `data'. + */ + int offset = ctrl->fileselect.context.i; + + if (event == EVENT_REFRESH) { + dlg_filesel_set(ctrl, dlg, *(Filename *)ATOFFSET(data, offset)); + } else if (event == EVENT_VALCHANGE) { + dlg_filesel_get(ctrl, dlg, (Filename *)ATOFFSET(data, offset)); + } +} + +void dlg_stdfontsel_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + /* + * The standard file-selector handler expects the `context' + * field to contain the `offsetof' a FontSpec field in the + * structure pointed to by `data'. + */ + int offset = ctrl->fontselect.context.i; + + if (event == EVENT_REFRESH) { + dlg_fontsel_set(ctrl, dlg, *(FontSpec *)ATOFFSET(data, offset)); + } else if (event == EVENT_VALCHANGE) { + dlg_fontsel_get(ctrl, dlg, (FontSpec *)ATOFFSET(data, offset)); + } +} diff --git a/putty/DIALOG.H b/putty/DIALOG.H new file mode 100644 index 0000000..67f79ed --- /dev/null +++ b/putty/DIALOG.H @@ -0,0 +1,708 @@ +/* + * Exports and types from dialog.c. + */ + +/* + * This will come in handy for generic control handlers. Anyone + * knows how to make this more portable, let me know :-) + */ +#define ATOFFSET(data, offset) ( (void *) ( (char *)(data) + (offset) ) ) + +/* + * This is the big union which defines a single control, of any + * type. + * + * General principles: + * - _All_ pointers in this structure are expected to point to + * dynamically allocated things, unless otherwise indicated. + * - `char' fields giving keyboard shortcuts are expected to be + * NO_SHORTCUT if no shortcut is desired for a particular control. + * - The `label' field can often be NULL, which will cause the + * control to not have a label at all. This doesn't apply to + * checkboxes and push buttons, in which the label is not + * separate from the control. + */ + +#define NO_SHORTCUT '\0' + +enum { + CTRL_TEXT, /* just a static line of text */ + CTRL_EDITBOX, /* label plus edit box */ + CTRL_RADIO, /* label plus radio buttons */ + CTRL_CHECKBOX, /* checkbox (contains own label) */ + CTRL_BUTTON, /* simple push button (no label) */ + CTRL_LISTBOX, /* label plus list box */ + CTRL_COLUMNS, /* divide window into columns */ + CTRL_FILESELECT, /* label plus filename selector */ + CTRL_FONTSELECT, /* label plus font selector */ + CTRL_TABDELAY /* see `tabdelay' below */ +}; + +/* + * Many controls have `intorptr' unions for storing user data, + * since the user might reasonably want to store either an integer + * or a void * pointer. Here I define a union, and two convenience + * functions to create that union from actual integers or pointers. + * + * The convenience functions are declared as inline if possible. + * Otherwise, they're declared here and defined when this header is + * included with DEFINE_INTORPTR_FNS defined. This is a total pain, + * but such is life. + */ +typedef union { void *p; int i; } intorptr; + +#ifndef INLINE +intorptr I(int i); +intorptr P(void *p); +#endif + +#if defined DEFINE_INTORPTR_FNS || defined INLINE +#ifdef INLINE +#define PREFIX INLINE +#else +#define PREFIX +#endif +PREFIX intorptr I(int i) { intorptr ret; ret.i = i; return ret; } +PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; } +#undef PREFIX +#endif + +/* + * Each control has an `int' field specifying which columns it + * occupies in a multi-column part of the dialog box. These macros + * pack and unpack that field. + * + * If a control belongs in exactly one column, just specifying the + * column number is perfectly adequate. + */ +#define COLUMN_FIELD(start, span) ( (((span)-1) << 16) + (start) ) +#define COLUMN_START(field) ( (field) & 0xFFFF ) +#define COLUMN_SPAN(field) ( (((field) >> 16) & 0xFFFF) + 1 ) + +union control; + +/* + * The number of event types is being deliberately kept small, on + * the grounds that not all platforms might be able to report a + * large number of subtle events. We have: + * - the special REFRESH event, called when a control's value + * needs setting + * - the ACTION event, called when the user does something that + * positively requests action (double-clicking a list box item, + * or pushing a push-button) + * - the VALCHANGE event, called when the user alters the setting + * of the control in a way that is usually considered to alter + * the underlying data (toggling a checkbox or radio button, + * moving the items around in a drag-list, editing an edit + * control) + * - the SELCHANGE event, called when the user alters the setting + * of the control in a more minor way (changing the selected + * item in a list box). + * - the CALLBACK event, which happens after the handler routine + * has requested a subdialog (file selector, font selector, + * colour selector) and it has come back with information. + */ +enum { + EVENT_REFRESH, + EVENT_ACTION, + EVENT_VALCHANGE, + EVENT_SELCHANGE, + EVENT_CALLBACK +}; +typedef void (*handler_fn)(union control *ctrl, void *dlg, + void *data, int event); + +#define STANDARD_PREFIX \ + int type; \ + char *label; \ + int tabdelay; \ + int column; \ + handler_fn handler; \ + intorptr context; \ + intorptr helpctx + +union control { + /* + * The first possibility in this union is the generic header + * shared by all the structures, which we are therefore allowed + * to access through any one of them. + */ + struct { + int type; + /* + * Every control except CTRL_COLUMNS has _some_ sort of + * label. By putting it in the `generic' union as well as + * everywhere else, we avoid having to have an irritating + * switch statement when we go through and deallocate all + * the memory in a config-box structure. + * + * Yes, this does mean that any non-NULL value in this + * field is expected to be dynamically allocated and + * freeable. + * + * For CTRL_COLUMNS, this field MUST be NULL. + */ + char *label; + /* + * If `tabdelay' is non-zero, it indicates that this + * particular control should not yet appear in the tab + * order. A subsequent CTRL_TABDELAY entry will place it. + */ + int tabdelay; + /* + * Indicate which column(s) this control occupies. This can + * be unpacked into starting column and column span by the + * COLUMN macros above. + */ + int column; + /* + * Most controls need to provide a function which gets + * called when that control's setting is changed, or when + * the control's setting needs initialising. + * + * The `data' parameter points to the writable data being + * modified as a result of the configuration activity; for + * example, the PuTTY `Config' structure, although not + * necessarily. + * + * The `dlg' parameter is passed back to the platform- + * specific routines to read and write the actual control + * state. + */ + handler_fn handler; + /* + * Almost all of the above functions will find it useful to + * be able to store a piece of `void *' or `int' data. + */ + intorptr context; + /* + * For any control, we also allow the storage of a piece of + * data for use by context-sensitive help. For example, on + * Windows you can click the magic question mark and then + * click a control, and help for that control should spring + * up. Hence, here is a slot in which to store per-control + * data that a particular platform-specific driver can use + * to ensure it brings up the right piece of help text. + */ + intorptr helpctx; + } generic; + struct { + STANDARD_PREFIX; + union control *ctrl; + } tabdelay; + struct { + STANDARD_PREFIX; + } text; + struct { + STANDARD_PREFIX; + char shortcut; /* keyboard shortcut */ + /* + * Percentage of the dialog-box width used by the edit box. + * If this is set to 100, the label is on its own line; + * otherwise the label is on the same line as the box + * itself. + */ + int percentwidth; + int password; /* details of input are hidden */ + /* + * A special case of the edit box is the combo box, which + * has a drop-down list built in. (Note that a _non_- + * editable drop-down list is done as a special case of a + * list box.) + * + * Don't try setting has_list and password on the same + * control; front ends are not required to support that + * combination. + */ + int has_list; + /* + * Edit boxes tend to need two items of context, so here's + * a spare. + */ + intorptr context2; + } editbox; + struct { + STANDARD_PREFIX; + /* + * `shortcut' here is a single keyboard shortcut which is + * expected to select the whole group of radio buttons. It + * can be NO_SHORTCUT if required, and there is also a way + * to place individual shortcuts on each button; see below. + */ + char shortcut; + /* + * There are separate fields for `ncolumns' and `nbuttons' + * for several reasons. + * + * Firstly, we sometimes want the last of a set of buttons + * to have a longer label than the rest; we achieve this by + * setting `ncolumns' higher than `nbuttons', and the + * layout code is expected to understand that the final + * button should be given all the remaining space on the + * line. This sounds like a ludicrously specific special + * case (if we're doing this sort of thing, why not have + * the general ability to have a particular button span + * more than one column whether it's the last one or not?) + * but actually it's reasonably common for the sort of + * three-way control you get a lot of in PuTTY: `yes' + * versus `no' versus `some more complex way to decide'. + * + * Secondly, setting `nbuttons' higher than `ncolumns' lets + * us have more than one line of radio buttons for a single + * setting. A very important special case of this is + * setting `ncolumns' to 1, so that each button is on its + * own line. + */ + int ncolumns; + int nbuttons; + /* + * This points to a dynamically allocated array of `char *' + * pointers, each of which points to a dynamically + * allocated string. + */ + char **buttons; /* `nbuttons' button labels */ + /* + * This points to a dynamically allocated array of `char' + * giving the individual keyboard shortcuts for each radio + * button. The array may be NULL if none are required. + */ + char *shortcuts; /* `nbuttons' shortcuts; may be NULL */ + /* + * This points to a dynamically allocated array of + * intorptr, giving helpful data for each button. + */ + intorptr *buttondata; /* `nbuttons' entries; may be NULL */ + } radio; + struct { + STANDARD_PREFIX; + char shortcut; + } checkbox; + struct { + STANDARD_PREFIX; + char shortcut; + /* + * At least Windows has the concept of a `default push + * button', which gets implicitly pressed when you hit + * Return even if it doesn't have the input focus. + */ + int isdefault; + /* + * Also, the reverse of this: a default cancel-type button, + * which is implicitly pressed when you hit Escape. + */ + int iscancel; + } button; + struct { + STANDARD_PREFIX; + char shortcut; /* keyboard shortcut */ + /* + * Height of the list box, in approximate number of lines. + * If this is zero, the list is a drop-down list. + */ + int height; /* height in lines */ + /* + * If this is set, the list elements can be reordered by + * the user (by drag-and-drop or by Up and Down buttons, + * whatever the per-platform implementation feels + * comfortable with). This is not guaranteed to work on a + * drop-down list, so don't try it! + */ + int draglist; + /* + * If this is non-zero, the list can have more than one + * element selected at a time. This is not guaranteed to + * work on a drop-down list, so don't try it! + * + * Different non-zero values request slightly different + * types of multi-selection (this may well be meaningful + * only in GTK, so everyone else can ignore it if they + * want). 1 means the list box expects to have individual + * items selected, whereas 2 means it expects the user to + * want to select a large contiguous range at a time. + */ + int multisel; + /* + * Percentage of the dialog-box width used by the list box. + * If this is set to 100, the label is on its own line; + * otherwise the label is on the same line as the box + * itself. Setting this to anything other than 100 is not + * guaranteed to work on a _non_-drop-down list, so don't + * try it! + */ + int percentwidth; + /* + * Some list boxes contain strings that contain tab + * characters. If `ncols' is greater than 0, then + * `percentages' is expected to be non-zero and to contain + * the respective widths of `ncols' columns, which together + * will exactly fit the width of the list box. Otherwise + * `percentages' must be NULL. + * + * There should never be more than one column in a + * drop-down list (one with height==0), because front ends + * may have to implement it as a special case of an + * editable combo box. + */ + int ncols; /* number of columns */ + int *percentages; /* % width of each column */ + } listbox; + struct { + STANDARD_PREFIX; + char shortcut; + /* + * `filter' dictates what type of files will be selected by + * default; for example, when selecting private key files + * the file selector would do well to only show .PPK files + * (on those systems where this is the chosen extension). + * + * The precise contents of `filter' are platform-defined, + * unfortunately. The special value NULL means `all files' + * and is always a valid fallback. + * + * Unlike almost all strings in this structure, this value + * is NOT expected to require freeing (although of course + * you can always use ctrl_alloc if you do need to create + * one on the fly). This is because the likely mode of use + * is to define string constants in a platform-specific + * header file, and directly reference those. Or worse, a + * particular platform might choose to cast integers into + * this pointer type... + */ + char const *filter; + /* + * Some systems like to know whether a file selector is + * choosing a file to read or one to write (and possibly + * create). + */ + int for_writing; + /* + * On at least some platforms, the file selector is a + * separate dialog box, and contains a user-settable title. + * + * This value _is_ expected to require freeing. + */ + char *title; + } fileselect; + struct { + /* In this variant, `label' MUST be NULL. */ + STANDARD_PREFIX; + int ncols; /* number of columns */ + int *percentages; /* % width of each column */ + /* + * Every time this control type appears, exactly one of + * `ncols' and the previous number of columns MUST be one. + * Attempting to allow a seamless transition from a four- + * to a five-column layout, for example, would be way more + * trouble than it was worth. If you must lay things out + * like that, define eight unevenly sized columns and use + * column-spanning a lot. But better still, just don't. + * + * `percentages' may be NULL if ncols==1, to save space. + */ + } columns; + struct { + STANDARD_PREFIX; + char shortcut; + } fontselect; +}; + +#undef STANDARD_PREFIX + +/* + * `controlset' is a container holding an array of `union control' + * structures, together with a panel name and a title for the whole + * set. In Windows and any similar-looking GUI, each `controlset' + * in the config will be a container box within a panel. + * + * Special case: if `boxname' is NULL, the control set gives an + * overall title for an entire panel of controls. + */ +struct controlset { + char *pathname; /* panel path, e.g. "SSH/Tunnels" */ + char *boxname; /* internal short name of controlset */ + char *boxtitle; /* title of container box */ + int ncolumns; /* current no. of columns at bottom */ + int ncontrols; /* number of `union control' in array */ + int ctrlsize; /* allocated size of array */ + union control **ctrls; /* actual array */ +}; + +/* + * This is the container structure which holds a complete set of + * controls. + */ +struct controlbox { + int nctrlsets; /* number of ctrlsets */ + int ctrlsetsize; /* ctrlset size */ + struct controlset **ctrlsets; /* actual array of ctrlsets */ + int nfrees; + int freesize; + void **frees; /* array of aux data areas to free */ +}; + +struct controlbox *ctrl_new_box(void); +void ctrl_free_box(struct controlbox *); + +/* + * Standard functions used for populating a controlbox structure. + */ + +/* Set up a panel title. */ +struct controlset *ctrl_settitle(struct controlbox *, + char *path, char *title); +/* Retrieve a pointer to a controlset, creating it if absent. */ +struct controlset *ctrl_getset(struct controlbox *, + char *path, char *name, char *boxtitle); +void ctrl_free_set(struct controlset *); + +void ctrl_free(union control *); + +/* + * This function works like `malloc', but the memory it returns + * will be automatically freed when the controlbox is freed. Note + * that a controlbox is a dialog-box _template_, not an instance, + * and so data allocated through this function is better not used + * to hold modifiable per-instance things. It's mostly here for + * allocating structures to be passed as control handler params. + */ +void *ctrl_alloc(struct controlbox *b, size_t size); + +/* + * Individual routines to create `union control' structures in a controlset. + * + * Most of these routines allow the most common fields to be set + * directly, and put default values in the rest. Each one returns a + * pointer to the `union control' it created, so that final tweaks + * can be made. + */ + +/* `ncolumns' is followed by that many percentages, as integers. */ +union control *ctrl_columns(struct controlset *, int ncolumns, ...); +union control *ctrl_editbox(struct controlset *, char *label, char shortcut, + int percentage, intorptr helpctx, + handler_fn handler, + intorptr context, intorptr context2); +union control *ctrl_combobox(struct controlset *, char *label, char shortcut, + int percentage, intorptr helpctx, + handler_fn handler, + intorptr context, intorptr context2); +/* + * `ncolumns' is followed by (alternately) radio button titles and + * intorptrs, until a NULL in place of a title string is seen. Each + * title is expected to be followed by a shortcut _iff_ `shortcut' + * is NO_SHORTCUT. + */ +union control *ctrl_radiobuttons(struct controlset *, char *label, + char shortcut, int ncolumns, + intorptr helpctx, + handler_fn handler, intorptr context, ...); +union control *ctrl_pushbutton(struct controlset *,char *label,char shortcut, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_listbox(struct controlset *,char *label,char shortcut, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_droplist(struct controlset *, char *label, char shortcut, + int percentage, intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_draglist(struct controlset *,char *label,char shortcut, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_filesel(struct controlset *,char *label,char shortcut, + char const *filter, int write, char *title, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_fontsel(struct controlset *,char *label,char shortcut, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_text(struct controlset *, char *text, intorptr helpctx); +union control *ctrl_checkbox(struct controlset *, char *label, char shortcut, + intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_tabdelay(struct controlset *, union control *); + +/* + * Standard handler routines to cover most of the common cases in + * the config box. + */ +/* + * The standard radio-button handler expects the main `context' + * field to contain the `offsetof' of an int field in the structure + * pointed to by `data', and expects each of the individual button + * data to give a value for that int field. + */ +void dlg_stdradiobutton_handler(union control *ctrl, void *dlg, + void *data, int event); +/* + * The standard checkbox handler expects the main `context' field + * to contain the `offsetof' an int field in the structure pointed + * to by `data', optionally ORed with CHECKBOX_INVERT to indicate + * that the sense of the datum is opposite to the sense of the + * checkbox. + */ +#define CHECKBOX_INVERT (1<<30) +void dlg_stdcheckbox_handler(union control *ctrl, void *dlg, + void *data, int event); +/* + * The standard edit-box handler expects the main `context' field + * to contain the `offsetof' a field in the structure pointed to by + * `data'. The secondary `context2' field indicates the type of + * this field: + * + * - if context2 > 0, the field is a char array and context2 gives + * its size. + * - 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.) + */ +void dlg_stdeditbox_handler(union control *ctrl, void *dlg, + void *data, int event); +/* + * The standard file-selector handler expects the main `context' + * field to contain the `offsetof' a Filename field in the + * structure pointed to by `data'. + */ +void dlg_stdfilesel_handler(union control *ctrl, void *dlg, + void *data, int event); +/* + * The standard font-selector handler expects the main `context' + * field to contain the `offsetof' a Font field in the structure + * pointed to by `data'. + */ +void dlg_stdfontsel_handler(union control *ctrl, void *dlg, + void *data, int event); + +/* + * Routines the platform-independent dialog code can call to read + * and write the values of controls. + */ +void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton); +int dlg_radiobutton_get(union control *ctrl, void *dlg); +void dlg_checkbox_set(union control *ctrl, void *dlg, int checked); +int dlg_checkbox_get(union control *ctrl, void *dlg); +void dlg_editbox_set(union control *ctrl, void *dlg, char const *text); +void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length); +/* The `listbox' functions can also apply to combo boxes. */ +void dlg_listbox_clear(union control *ctrl, void *dlg); +void dlg_listbox_del(union control *ctrl, void *dlg, int index); +void dlg_listbox_add(union control *ctrl, void *dlg, char const *text); +/* + * Each listbox entry may have a numeric id associated with it. + * Note that some front ends only permit a string to be stored at + * each position, which means that _if_ you put two identical + * strings in any listbox then you MUST not assign them different + * IDs and expect to get meaningful results back. + */ +void dlg_listbox_addwithid(union control *ctrl, void *dlg, + char const *text, int id); +int dlg_listbox_getid(union control *ctrl, void *dlg, int index); +/* dlg_listbox_index returns <0 if no single element is selected. */ +int dlg_listbox_index(union control *ctrl, void *dlg); +int dlg_listbox_issel(union control *ctrl, void *dlg, int index); +void dlg_listbox_select(union control *ctrl, void *dlg, int index); +void dlg_text_set(union control *ctrl, void *dlg, char const *text); +void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn); +void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn); +void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fn); +void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fn); +/* + * Bracketing a large set of updates in these two functions will + * cause the front end (if possible) to delay updating the screen + * until it's all complete, thus avoiding flicker. + */ +void dlg_update_start(union control *ctrl, void *dlg); +void dlg_update_done(union control *ctrl, void *dlg); +/* + * Set input focus into a particular control. + */ +void dlg_set_focus(union control *ctrl, void *dlg); +/* + * Change the label text on a control. + */ +void dlg_label_change(union control *ctrl, void *dlg, char const *text); +/* + * Return the `ctrl' structure for the most recent control that had + * the input focus apart from the one mentioned. This is NOT + * GUARANTEED to work on all platforms, so don't base any critical + * functionality on it! + */ +union control *dlg_last_focused(union control *ctrl, void *dlg); +/* + * During event processing, you might well want to give an error + * indication to the user. dlg_beep() is a quick and easy generic + * error; dlg_error() puts up a message-box or equivalent. + */ +void dlg_beep(void *dlg); +void dlg_error_msg(void *dlg, char *msg); +/* + * This function signals to the front end that the dialog's + * processing is completed, and passes an integer value (typically + * a success status). + */ +void dlg_end(void *dlg, int value); + +/* + * Routines to manage a (per-platform) colour selector. + * dlg_coloursel_start() is called in an event handler, and + * schedules the running of a colour selector after the event + * handler returns. The colour selector will send EVENT_CALLBACK to + * the control that spawned it, when it's finished; + * dlg_coloursel_results() fetches the results, as integers from 0 + * to 255; it returns nonzero on success, or zero if the colour + * selector was dismissed by hitting Cancel or similar. + * + * dlg_coloursel_start() accepts an RGB triple which is used to + * initialise the colour selector to its starting value. + */ +void dlg_coloursel_start(union control *ctrl, void *dlg, + int r, int g, int b); +int dlg_coloursel_results(union control *ctrl, void *dlg, + int *r, int *g, int *b); + +/* + * This routine is used by the platform-independent code to + * indicate that the value of a particular control is likely to + * have changed. It triggers a call of the handler for that control + * with `event' set to EVENT_REFRESH. + * + * If `ctrl' is NULL, _all_ controls in the dialog get refreshed + * (for loading or saving entire sets of settings). + */ +void dlg_refresh(union control *ctrl, void *dlg); + +/* + * It's perfectly possible that individual controls might need to + * allocate or store per-dialog-instance data, so here's a + * mechanism. + * + * `dlg_get_privdata' and `dlg_set_privdata' allow the user to get + * and set a void * pointer associated with the control in + * question. `dlg_alloc_privdata' will allocate memory, store a + * pointer to that memory in the private data field, and arrange + * for it to be automatically deallocated on dialog cleanup. + */ +void *dlg_get_privdata(union control *ctrl, void *dlg); +void dlg_set_privdata(union control *ctrl, void *dlg, void *ptr); +void *dlg_alloc_privdata(union control *ctrl, void *dlg, size_t size); + +/* + * Standard helper functions for reading a controlbox structure. + */ + +/* + * Find the index of next controlset in a controlbox for a given + * path, or -1 if no such controlset exists. If -1 is passed as + * input, finds the first. Intended usage is something like + * + * for (index=-1; (index=ctrl_find_path(ctrlbox, index, path)) >= 0 ;) { + * ... process this controlset ... + * } + */ +int ctrl_find_path(struct controlbox *b, char *path, int index); +int ctrl_path_elements(char *path); +/* Return the number of matching path elements at the starts of p1 and p2, + * or INT_MAX if the paths are identical. */ +int ctrl_path_compare(char *p1, char *p2); diff --git a/putty/DOC/BLURB.BUT b/putty/DOC/BLURB.BUT new file mode 100644 index 0000000..f03341d --- /dev/null +++ b/putty/DOC/BLURB.BUT @@ -0,0 +1,36 @@ +\define{versionidblurb} \versionid $Id: blurb.but 9072 2011-01-05 12:01:00Z jacob $ + +\title PuTTY User Manual + +\cfg{xhtml-leaf-level}{1} +\cfg{xhtml-leaf-smallest-contents}{2} +\cfg{xhtml-leaf-contains-contents}{true} +\cfg{xhtml-body-end}{

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

} + +\cfg{html-template-fragment}{%k}{%b} + +\cfg{info-max-file-size}{0} + +\cfg{xhtml-contents-filename}{index.html} +\cfg{text-filename}{puttydoc.txt} +\cfg{winhelp-filename}{putty.hlp} +\cfg{info-filename}{putty.info} + +PuTTY is a free (MIT-licensed) Win32 Telnet and SSH client. This +manual documents PuTTY, and its companion utilities PSCP, PSFTP, +Plink, Pageant and PuTTYgen. + +\e{Note to Unix users:} this manual currently primarily documents the +Windows versions of the PuTTY utilities. Some options are therefore +mentioned that are absent from the \i{Unix version}; the Unix version has +features not described here; and the \i\cw{pterm} and command-line +\cw{puttygen} utilities are not described at all. The only +Unix-specific documentation that currently exists is the +\I{man pages for PuTTY tools}man pages. + +\copyright This manual is copyright 2001-2011 Simon Tatham. All +rights reserved. You may distribute this documentation under the MIT +licence. See \k{licence} for the licence text in full. diff --git a/putty/DOC/CHM.BUT b/putty/DOC/CHM.BUT new file mode 100644 index 0000000..ae4d402 --- /dev/null +++ b/putty/DOC/CHM.BUT @@ -0,0 +1,23 @@ +\# File containing the magic HTML configuration directives to create +\# an MS HTML Help project. We put this on the end of the PuTTY +\# docs build command line to build the HHP and friends. + +\cfg{html-leaf-level}{infinite} +\cfg{html-leaf-contains-contents}{false} +\cfg{html-suppress-navlinks}{true} +\cfg{html-suppress-address}{true} + +\cfg{html-contents-filename}{index.html} +\cfg{html-template-filename}{%k.html} +\cfg{html-template-fragment}{%k} + +\cfg{html-mshtmlhelp-chm}{putty.chm} +\cfg{html-mshtmlhelp-project}{putty.hhp} +\cfg{html-mshtmlhelp-contents}{putty.hhc} +\cfg{html-mshtmlhelp-index}{putty.hhk} + +\cfg{html-body-end}{} + +\cfg{html-head-end}{} + +\versionid $Id: chm.but 7272 2007-02-11 18:13:56Z jacob $ diff --git a/putty/DOC/CHM.CSS b/putty/DOC/CHM.CSS new file mode 100644 index 0000000..6e49b94 --- /dev/null +++ b/putty/DOC/CHM.CSS @@ -0,0 +1,7 @@ +/* Stylesheet for a Windows .CHM help file */ + +body { font-size: 75%; font-family: Verdana, Arial, Helvetica, Sans-Serif; } + +h1 { font-weight: bold; font-size: 150%; } +h2 { font-weight: bold; font-size: 130%; } +h3 { font-weight: bold; font-size: 120%; } diff --git a/putty/DOC/CONFIG.BUT b/putty/DOC/CONFIG.BUT new file mode 100644 index 0000000..2a87f3a --- /dev/null +++ b/putty/DOC/CONFIG.BUT @@ -0,0 +1,3341 @@ +\define{versionidconfig} \versionid $Id: config.but 9063 2010-12-29 14:11:25Z simon $ + +\C{config} Configuring PuTTY + +This chapter describes all the \i{configuration options} in PuTTY. + +PuTTY is configured using the control panel that comes up before you +start a session. Some options can also be changed in the middle of a +session, by selecting \q{Change Settings} from the window menu. + +\H{config-session} The Session panel + +The Session configuration panel contains the basic options you need +to specify in order to open a session at all, and also allows you to +save your settings to be reloaded later. + +\S{config-hostname} The \i{host name} section + +\cfg{winhelp-topic}{session.hostname} + +The top box on the Session panel, labelled \q{Specify your +connection by host name}, contains the details that need to be +filled in before PuTTY can open a session at all. + +\b The \q{Host Name} box is where you type the name, or the \i{IP +address}, of the server you want to connect to. + +\b The \q{Connection type} radio buttons let you choose what type of +connection you want to make: a \I{raw TCP connections}raw +connection, a \i{Telnet} connection, an \i{Rlogin} connection, an +\i{SSH} connection, or a connection to a local \i{serial line}. (See +\k{which-one} for a summary of the differences between SSH, Telnet +and rlogin; see \k{using-rawprot} for an explanation of \q{raw} +connections; see \k{using-serial} for information about using a +serial line.) + +\b The \q{Port} box lets you specify which \i{port number} on the +server to connect to. If you select Telnet, Rlogin, or SSH, this box +will be filled in automatically to the usual value, and you will +only need to change it if you have an unusual server. If you select +Raw mode, you will almost certainly need to fill in the \q{Port} box +yourself. + +If you select \q{Serial} from the \q{Connection type} radio buttons, +the \q{Host Name} and \q{Port} boxes are replaced by \q{Serial line} +and \q{Speed}; see \k{config-serial} for more details of these. + +\S{config-saving} \ii{Loading and storing saved sessions} + +\cfg{winhelp-topic}{session.saved} + +The next part of the Session configuration panel allows you to save +your preferred PuTTY options so they will appear automatically the +next time you start PuTTY. It also allows you to create \e{saved +sessions}, which contain a full set of configuration options plus a +host name and protocol. A saved session contains all the information +PuTTY needs to start exactly the session you want. + +\b To save your default settings: first set up the settings the way +you want them saved. Then come back to the Session panel. Select the +\q{\i{Default Settings}} entry in the saved sessions list, with a single +click. Then press the \q{Save} button. + +If there is a specific host you want to store the details of how to +connect to, you should create a saved session, which will be +separate from the Default Settings. + +\b To save a session: first go through the rest of the configuration +box setting up all the options you want. Then come back to the +Session panel. Enter a name for the saved session in the \q{Saved +Sessions} input box. (The server name is often a good choice for a +saved session name.) Then press the \q{Save} button. Your saved +session name should now appear in the list box. + +\lcont{ +You can also save settings in mid-session, from the \q{Change Settings} +dialog. Settings changed since the start of the session will be saved +with their current values; as well as settings changed through the +dialog, this includes changes in window size, window title changes +sent by the server, and so on. +} + +\b To reload a saved session: single-click to select the session +name in the list box, and then press the \q{Load} button. Your saved +settings should all appear in the configuration panel. + +\b To modify a saved session: first load it as described above. Then +make the changes you want. Come back to the Session panel, and press +the \q{Save} button. The new settings will be saved over the top of +the old ones. + +\lcont{ +To save the new settings under a different name, you can enter the new +name in the \q{Saved Sessions} box, or single-click to select a +session name in the list box to overwrite that session. To save +\q{Default Settings}, you must single-click the name before saving. +} + +\b To start a saved session immediately: double-click on the session +name in the list box. + +\b To delete a saved session: single-click to select the session +name in the list box, and then press the \q{Delete} button. + +Each saved session is independent of the Default Settings +configuration. If you change your preferences and update Default +Settings, you must also update every saved session separately. + +Saved sessions are stored in the \i{Registry}, at the location + +\c HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions + +If you need to store them in a file, you could try the method +described in \k{config-file}. + +\S{config-closeonexit} \q{\ii{Close Window} on Exit} + +\cfg{winhelp-topic}{session.coe} + +Finally in the Session panel, there is an option labelled \q{Close +Window on Exit}. This controls whether the PuTTY \i{terminal window} +disappears as soon as the session inside it terminates. If you are +likely to want to copy and paste text out of the session after it +has terminated, or restart the session, you should arrange for this +option to be off. + +\q{Close Window On Exit} has three settings. \q{Always} means always +close the window on exit; \q{Never} means never close on exit +(always leave the window open, but \I{inactive window}inactive). The +third setting, and the default one, is \q{Only on clean exit}. In this +mode, a session which terminates normally will cause its window to +close, but one which is aborted unexpectedly by network trouble or a +confusing message from the server will leave the window up. + +\H{config-logging} The Logging panel + +\cfg{winhelp-topic}{logging.main} + +The Logging configuration panel allows you to save \i{log file}s of your +PuTTY sessions, for debugging, analysis or future reference. + +The main option is a radio-button set that specifies whether PuTTY +will log anything at all. The options are: + +\b \q{None}. This is the default option; in this mode PuTTY will not +create a log file at all. + +\b \q{Printable output}. In this mode, a log file will be +created and written to, but only printable text will be saved into +it. The various terminal control codes that are typically sent down +an interactive session alongside the printable text will be omitted. +This might be a useful mode if you want to read a log file in a text +editor and hope to be able to make sense of it. + +\b \q{All session output}. In this mode, \e{everything} sent by +the server into your terminal session is logged. If you view the log +file in a text editor, therefore, you may well find it full of +strange control characters. This is a particularly useful mode if +you are experiencing problems with PuTTY's terminal handling: you +can record everything that went to the terminal, so that someone +else can replay the session later in slow motion and watch to see +what went wrong. + +\b \I{SSH packet log}\q{SSH packets}. In this mode (which is only used +by SSH connections), the SSH message packets sent over the encrypted +connection are written to the log file (as well as \i{Event Log} +entries). You might need this to debug a network-level problem, or +more likely to send to the PuTTY authors as part of a bug report. +\e{BE WARNED} that if you log in using a password, the password can +appear in the log file; see \k{config-logssh} for options that may +help to remove sensitive material from the log file before you send it +to anyone else. + +\b \q{SSH packets and raw data}. In this mode, as well as the +decrypted packets (as in the previous mode), the \e{raw} (encrypted, +compressed, etc) packets are \e{also} logged. This could be useful to +diagnose corruption in transit. (The same caveats as the previous mode +apply, of course.) + +Note that the non-SSH logging options (\q{Printable output} and +\q{All session output}) only work with PuTTY proper; in programs +without terminal emulation (such as Plink), they will have no effect, +even if enabled via saved settings. + +\S{config-logfilename} \q{Log file name} + +\cfg{winhelp-topic}{logging.filename} + +In this edit box you enter the name of the file you want to log the +session to. The \q{Browse} button will let you look around your file +system to find the right place to put the file; or if you already +know exactly where you want it to go, you can just type a pathname +into the edit box. + +There are a few special features in this box. If you use the \c{&} +character in the file name box, PuTTY will insert details of the +current session in the name of the file it actually opens. The +precise replacements it will do are: + +\b \c{&Y} will be replaced by the current year, as four digits. + +\b \c{&M} will be replaced by the current month, as two digits. + +\b \c{&D} will be replaced by the current day of the month, as two +digits. + +\b \c{&T} will be replaced by the current time, as six digits +(HHMMSS) with no punctuation. + +\b \c{&H} will be replaced by the host name you are connecting to. + +For example, if you enter the host name +\c{c:\\puttylogs\\log-&h-&y&m&d-&t.dat}, you will end up with files looking +like + +\c log-server1.example.com-20010528-110859.dat +\c log-unixbox.somewhere.org-20010611-221001.dat + +\S{config-logfileexists} \q{What to do if the log file already exists} + +\cfg{winhelp-topic}{logging.exists} + +This control allows you to specify what PuTTY should do if it tries +to start writing to a log file and it finds the file already exists. +You might want to automatically destroy the existing log file and +start a new one with the same name. Alternatively, you might want to +open the existing log file and add data to the \e{end} of it. +Finally (the default option), you might not want to have any +automatic behaviour, but to ask the user every time the problem +comes up. + +\S{config-logflush} \I{log file, flushing}\q{Flush log file frequently} + +\cfg{winhelp-topic}{logging.flush} + +This option allows you to control how frequently logged data is +flushed to disc. By default, PuTTY will flush data as soon as it is +displayed, so that if you view the log file while a session is still +open, it will be up to date; and if the client system crashes, there's +a greater chance that the data will be preserved. + +However, this can incur a performance penalty. If PuTTY is running +slowly with logging enabled, you could try unchecking this option. Be +warned that the log file may not always be up to date as a result +(although it will of course be flushed when it is closed, for instance +at the end of a session). + +\S{config-logssh} Options specific to \i{SSH packet log}ging + +These options only apply if SSH packet data is being logged. + +The following options allow particularly sensitive portions of +unencrypted packets to be automatically left out of the log file. +They are only intended to deter casual nosiness; an attacker could +glean a lot of useful information from even these obfuscated logs +(e.g., length of password). + +\S2{config-logssh-omitpw} \q{Omit known password fields} + +\cfg{winhelp-topic}{logging.ssh.omitpassword} + +When checked, decrypted password fields are removed from the log of +transmitted packets. (This includes any user responses to +challenge-response authentication methods such as +\q{keyboard-interactive}.) This does not include X11 authentication +data if using X11 forwarding. + +Note that this will only omit data that PuTTY \e{knows} to be a +password. However, if you start another login session within your +PuTTY session, for instance, any password used will appear in the +clear in the packet log. The next option may be of use to protect +against this. + +This option is enabled by default. + +\S2{config-logssh-omitdata} \q{Omit session data} + +\cfg{winhelp-topic}{logging.ssh.omitdata} + +When checked, all decrypted \q{session data} is omitted; this is +defined as data in terminal sessions and in forwarded channels (TCP, +X11, and authentication agent). This will usually substantially reduce +the size of the resulting log file. + +This option is disabled by default. + +\H{config-terminal} The Terminal panel + +The Terminal configuration panel allows you to control the behaviour +of PuTTY's \i{terminal emulation}. + +\S{config-autowrap} \q{Auto wrap mode initially on} + +\cfg{winhelp-topic}{terminal.autowrap} + +\ii{Auto wrap mode} controls what happens when text printed in a PuTTY +window reaches the right-hand edge of the window. + +With auto wrap mode on, if a long line of text reaches the +right-hand edge, it will wrap over on to the next line so you can +still see all the text. With auto wrap mode off, the cursor will +stay at the right-hand edge of the screen, and all the characters in +the line will be printed on top of each other. + +If you are running a full-screen application and you occasionally +find the screen scrolling up when it looks as if it shouldn't, you +could try turning this option off. + +Auto wrap mode can be turned on and off by \i{control sequence}s sent by +the server. This configuration option controls the \e{default} +state, which will be restored when you reset the terminal (see +\k{reset-terminal}). However, if you modify this option in +mid-session using \q{Change Settings}, it will take effect +immediately. + +\S{config-decom} \q{DEC Origin Mode initially on} + +\cfg{winhelp-topic}{terminal.decom} + +\i{DEC Origin Mode} is a minor option which controls how PuTTY +interprets cursor-position \i{control sequence}s sent by the server. + +The server can send a control sequence that restricts the \i{scrolling +region} of the display. For example, in an editor, the server might +reserve a line at the top of the screen and a line at the bottom, +and might send a control sequence that causes scrolling operations +to affect only the remaining lines. + +With DEC Origin Mode on, \i{cursor coordinates} are counted from the top +of the scrolling region. With it turned off, cursor coordinates are +counted from the top of the whole screen regardless of the scrolling +region. + +It is unlikely you would need to change this option, but if you find +a full-screen application is displaying pieces of text in what looks +like the wrong part of the screen, you could try turning DEC Origin +Mode on to see whether that helps. + +DEC Origin Mode can be turned on and off by control sequences sent +by the server. This configuration option controls the \e{default} +state, which will be restored when you reset the terminal (see +\k{reset-terminal}). However, if you modify this option in +mid-session using \q{Change Settings}, it will take effect +immediately. + +\S{config-crlf} \q{Implicit CR in every LF} + +\cfg{winhelp-topic}{terminal.lfhascr} + +Most servers send two control characters, \i{CR} and \i{LF}, to start a +\i{new line} of the screen. The CR character makes the cursor return to the +left-hand side of the screen. The LF character makes the cursor move +one line down (and might make the screen scroll). + +Some servers only send LF, and expect the terminal to move the +cursor over to the left automatically. If you come across a server +that does this, you will see a \I{stair-stepping}stepped effect on the +screen, like this: + +\c First line of text +\c Second line +\c Third line + +If this happens to you, try enabling the \q{Implicit CR in every LF} +option, and things might go back to normal: + +\c First line of text +\c Second line +\c Third line + +\S{config-lfcr} \q{Implicit LF in every CR} + +\cfg{winhelp-topic}{terminal.crhaslf} + +Most servers send two control characters, \i{CR} and \i{LF}, to start a +\i{new line} of the screen. The CR character makes the cursor return to the +left-hand side of the screen. The LF character makes the cursor move +one line down (and might make the screen scroll). + +Some servers only send CR, and so the newly +written line is overwritten by the following line. This option causes +a line feed so that all lines are displayed. + +\S{config-erase} \q{Use \i{background colour} to erase screen} + +\cfg{winhelp-topic}{terminal.bce} + +Not all terminals agree on what colour to turn the screen when the +server sends a \q{\i{clear screen}} sequence. Some terminals believe the +screen should always be cleared to the \e{default} background +colour. Others believe the screen should be cleared to whatever the +server has selected as a background colour. + +There exist applications that expect both kinds of behaviour. +Therefore, PuTTY can be configured to do either. + +With this option disabled, screen clearing is always done in the +default background colour. With this option enabled, it is done in +the \e{current} background colour. + +Background-colour erase can be turned on and off by \i{control +sequences} sent by the server. This configuration option controls the +\e{default} state, which will be restored when you reset the +terminal (see \k{reset-terminal}). However, if you modify this +option in mid-session using \q{Change Settings}, it will take effect +immediately. + +\S{config-blink} \q{Enable \i{blinking text}} + +\cfg{winhelp-topic}{terminal.blink} + +The server can ask PuTTY to display text that blinks on and off. +This is very distracting, so PuTTY allows you to turn blinking text +off completely. + +When blinking text is disabled and the server attempts to make some +text blink, PuTTY will instead display the text with a \I{background +colour, bright}bolded background colour. + +Blinking text can be turned on and off by \i{control sequence}s sent by +the server. This configuration option controls the \e{default} +state, which will be restored when you reset the terminal (see +\k{reset-terminal}). However, if you modify this option in +mid-session using \q{Change Settings}, it will take effect +immediately. + +\S{config-answerback} \q{\ii{Answerback} to ^E} + +\cfg{winhelp-topic}{terminal.answerback} + +This option controls what PuTTY will send back to the server if the +server sends it the ^E \i{enquiry character}. Normally it just sends +the string \q{PuTTY}. + +If you accidentally write the contents of a binary file to your +terminal, you will probably find that it contains more than one ^E +character, and as a result your next command line will probably read +\q{PuTTYPuTTYPuTTY...} as if you had typed the answerback string +multiple times at the keyboard. If you set the answerback string to +be empty, this problem should go away, but doing so might cause +other problems. + +Note that this is \e{not} the feature of PuTTY which the server will +typically use to determine your terminal type. That feature is the +\q{\ii{Terminal-type} string} in the Connection panel; see +\k{config-termtype} for details. + +You can include control characters in the answerback string using +\c{^C} notation. (Use \c{^~} to get a literal \c{^}.) + +\S{config-localecho} \q{\ii{Local echo}} + +\cfg{winhelp-topic}{terminal.localecho} + +With local echo disabled, characters you type into the PuTTY window +are not echoed in the window \e{by PuTTY}. They are simply sent to +the server. (The \e{server} might choose to \I{remote echo}echo them +back to you; this can't be controlled from the PuTTY control panel.) + +Some types of session need local echo, and many do not. In its +default mode, PuTTY will automatically attempt to deduce whether or +not local echo is appropriate for the session you are working in. If +you find it has made the wrong decision, you can use this +configuration option to override its choice: you can force local +echo to be turned on, or force it to be turned off, instead of +relying on the automatic detection. + +\S{config-localedit} \q{\ii{Local line editing}} + +\cfg{winhelp-topic}{terminal.localedit} + +Normally, every character you type into the PuTTY window is sent +immediately to the server the moment you type it. + +If you enable local line editing, this changes. PuTTY will let you +edit a whole line at a time locally, and the line will only be sent +to the server when you press Return. If you make a mistake, you can +use the Backspace key to correct it before you press Return, and the +server will never see the mistake. + +Since it is hard to edit a line locally without being able to see +it, local line editing is mostly used in conjunction with \i{local echo} +(\k{config-localecho}). This makes it ideal for use in raw mode +\#{FIXME} or when connecting to \i{MUD}s or \i{talker}s. (Although some more +advanced MUDs do occasionally turn local line editing on and turn +local echo off, in order to accept a password from the user.) + +Some types of session need local line editing, and many do not. In +its default mode, PuTTY will automatically attempt to deduce whether +or not local line editing is appropriate for the session you are +working in. If you find it has made the wrong decision, you can use +this configuration option to override its choice: you can force +local line editing to be turned on, or force it to be turned off, +instead of relying on the automatic detection. + +\S{config-printing} \ii{Remote-controlled printing} + +\cfg{winhelp-topic}{terminal.printing} + +A lot of VT100-compatible terminals support printing under control +of the remote server. PuTTY supports this feature as well, but it is +turned off by default. + +To enable remote-controlled printing, choose a printer from the +\q{Printer to send ANSI printer output to} drop-down list box. This +should allow you to select from all the printers you have installed +drivers for on your computer. Alternatively, you can type the +network name of a networked printer (for example, +\c{\\\\printserver\\printer1}) even if you haven't already +installed a driver for it on your own machine. + +When the remote server attempts to print some data, PuTTY will send +that data to the printer \e{raw} - without translating it, +attempting to format it, or doing anything else to it. It is up to +you to ensure your remote server knows what type of printer it is +talking to. + +Since PuTTY sends data to the printer raw, it cannot offer options +such as portrait versus landscape, print quality, or paper tray +selection. All these things would be done by your PC printer driver +(which PuTTY bypasses); if you need them done, you will have to find +a way to configure your remote server to do them. + +To disable remote printing again, choose \q{None (printing +disabled)} from the printer selection list. This is the default +state. + +\H{config-keyboard} The Keyboard panel + +The Keyboard configuration panel allows you to control the behaviour +of the \i{keyboard} in PuTTY. The correct state for many of these +settings depends on what the server to which PuTTY is connecting +expects. With a \i{Unix} server, this is likely to depend on the +\i\c{termcap} or \i\c{terminfo} entry it uses, which in turn is likely to +be controlled by the \q{\ii{Terminal-type} string} setting in the Connection +panel; see \k{config-termtype} for details. If none of the settings here +seems to help, you may find \k{faq-keyboard} to be useful. + +\S{config-backspace} Changing the action of the \ii{Backspace key} + +\cfg{winhelp-topic}{keyboard.backspace} + +Some terminals believe that the Backspace key should send the same +thing to the server as \i{Control-H} (ASCII code 8). Other terminals +believe that the Backspace key should send ASCII code 127 (usually +known as \i{Control-?}) so that it can be distinguished from Control-H. +This option allows you to choose which code PuTTY generates when you +press Backspace. + +If you are connecting over SSH, PuTTY by default tells the server +the value of this option (see \k{config-ttymodes}), so you may find +that the Backspace key does the right thing either way. Similarly, +if you are connecting to a \i{Unix} system, you will probably find that +the Unix \i\c{stty} command lets you configure which the server +expects to see, so again you might not need to change which one PuTTY +generates. On other systems, the server's expectation might be fixed +and you might have no choice but to configure PuTTY. + +If you do have the choice, we recommend configuring PuTTY to +generate Control-? and configuring the server to expect it, because +that allows applications such as \c{emacs} to use Control-H for +help. + +(Typing \i{Shift-Backspace} will cause PuTTY to send whichever code +isn't configured here as the default.) + +\S{config-homeend} Changing the action of the \i{Home and End keys} + +\cfg{winhelp-topic}{keyboard.homeend} + +The Unix terminal emulator \i\c{rxvt} disagrees with the rest of the +world about what character sequences should be sent to the server by +the Home and End keys. + +\i\c{xterm}, and other terminals, send \c{ESC [1~} for the Home key, +and \c{ESC [4~} for the End key. \c{rxvt} sends \c{ESC [H} for the +Home key and \c{ESC [Ow} for the End key. + +If you find an application on which the Home and End keys aren't +working, you could try switching this option to see if it helps. + +\S{config-funkeys} Changing the action of the \i{function keys} and +\i{keypad} + +\cfg{winhelp-topic}{keyboard.funkeys} + +This option affects the function keys (F1 to F12) and the top row of +the numeric keypad. + +\b In the default mode, labelled \c{ESC [n~}, the function keys +generate sequences like \c{ESC [11~}, \c{ESC [12~} and so on. This +matches the general behaviour of Digital's terminals. + +\b In Linux mode, F6 to F12 behave just like the default mode, but +F1 to F5 generate \c{ESC [[A} through to \c{ESC [[E}. This mimics the +\i{Linux virtual console}. + +\b In \I{xterm}Xterm R6 mode, F5 to F12 behave like the default mode, but F1 +to F4 generate \c{ESC OP} through to \c{ESC OS}, which are the +sequences produced by the top row of the \e{keypad} on Digital's +terminals. + +\b In \i{VT400} mode, all the function keys behave like the default +mode, but the actual top row of the numeric keypad generates \c{ESC +OP} through to \c{ESC OS}. + +\b In \i{VT100+} mode, the function keys generate \c{ESC OP} through to +\c{ESC O[} + +\b In \i{SCO} mode, the function keys F1 to F12 generate \c{ESC [M} +through to \c{ESC [X}. Together with shift, they generate \c{ESC [Y} +through to \c{ESC [j}. With control they generate \c{ESC [k} through +to \c{ESC [v}, and with shift and control together they generate +\c{ESC [w} through to \c{ESC [\{}. + +If you don't know what any of this means, you probably don't need to +fiddle with it. + +\S{config-appcursor} Controlling \i{Application Cursor Keys} mode + +\cfg{winhelp-topic}{keyboard.appcursor} + +Application Cursor Keys mode is a way for the server to change the +control sequences sent by the arrow keys. In normal mode, the arrow +keys send \c{ESC [A} through to \c{ESC [D}. In application mode, +they send \c{ESC OA} through to \c{ESC OD}. + +Application Cursor Keys mode can be turned on and off by the server, +depending on the application. PuTTY allows you to configure the +initial state. + +You can also disable application cursor keys mode completely, using +the \q{Features} configuration panel; see +\k{config-features-application}. + +\S{config-appkeypad} Controlling \i{Application Keypad} mode + +\cfg{winhelp-topic}{keyboard.appkeypad} + +Application Keypad mode is a way for the server to change the +behaviour of the numeric keypad. + +In normal mode, the keypad behaves like a normal Windows keypad: +with \i{NumLock} on, the number keys generate numbers, and with NumLock +off they act like the arrow keys and Home, End etc. + +In application mode, all the keypad keys send special control +sequences, \e{including} Num Lock. Num Lock stops behaving like Num +Lock and becomes another function key. + +Depending on which version of Windows you run, you may find the Num +Lock light still flashes on and off every time you press Num Lock, +even when application mode is active and Num Lock is acting like a +function key. This is unavoidable. + +Application keypad mode can be turned on and off by the server, +depending on the application. PuTTY allows you to configure the +initial state. + +You can also disable application keypad mode completely, using the +\q{Features} configuration panel; see +\k{config-features-application}. + +\S{config-nethack} Using \i{NetHack keypad mode} + +\cfg{winhelp-topic}{keyboard.nethack} + +PuTTY has a special mode for playing NetHack. You can enable it by +selecting \q{NetHack} in the \q{Initial state of numeric keypad} +control. + +In this mode, the numeric keypad keys 1-9 generate the NetHack +movement commands (\cw{hjklyubn}). The 5 key generates the \c{.} +command (do nothing). + +In addition, pressing Shift or Ctrl with the keypad keys generate +the Shift- or Ctrl-keys you would expect (e.g. keypad-7 generates +\cq{y}, so Shift-keypad-7 generates \cq{Y} and Ctrl-keypad-7 +generates Ctrl-Y); these commands tell NetHack to keep moving you in +the same direction until you encounter something interesting. + +For some reason, this feature only works properly when \i{Num Lock} is +on. We don't know why. + +\S{config-compose} Enabling a DEC-like \ii{Compose key} + +\cfg{winhelp-topic}{keyboard.compose} + +DEC terminals have a Compose key, which provides an easy-to-remember +way of typing \i{accented characters}. You press Compose and then type +two more characters. The two characters are \q{combined} to produce +an accented character. The choices of character are designed to be +easy to remember; for example, composing \q{e} and \q{`} produces +the \q{\u00e8{e-grave}} character. + +If your keyboard has a Windows \i{Application key}, it acts as a Compose +key in PuTTY. Alternatively, if you enable the \q{\i{AltGr} acts as +Compose key} option, the AltGr key will become a Compose key. + +\S{config-ctrlalt} \q{Control-Alt is different from \i{AltGr}} + +\cfg{winhelp-topic}{keyboard.ctrlalt} + +Some old keyboards do not have an AltGr key, which can make it +difficult to type some characters. PuTTY can be configured to treat +the key combination Ctrl + Left Alt the same way as the AltGr key. + +By default, this checkbox is checked, and the key combination Ctrl + +Left Alt does something completely different. PuTTY's usual handling +of the left Alt key is to prefix the Escape (Control-\cw{[}) +character to whatever character sequence the rest of the keypress +would generate. For example, Alt-A generates Escape followed by +\c{a}. So Alt-Ctrl-A would generate Escape, followed by Control-A. + +If you uncheck this box, Ctrl-Alt will become a synonym for AltGr, +so you can use it to type extra graphic characters if your keyboard +has any. + +(However, Ctrl-Alt will never act as a Compose key, regardless of the +setting of \q{AltGr acts as Compose key} described in +\k{config-compose}.) + +\H{config-bell} The Bell panel + +The Bell panel controls the \i{terminal bell} feature: the server's +ability to cause PuTTY to beep at you. + +In the default configuration, when the server sends the character +with ASCII code 7 (Control-G), PuTTY will play the \i{Windows Default +Beep} sound. This is not always what you want the terminal bell +feature to do; the Bell panel allows you to configure alternative +actions. + +\S{config-bellstyle} \q{Set the style of bell} + +\cfg{winhelp-topic}{bell.style} + +This control allows you to select various different actions to occur +on a terminal bell: + +\b Selecting \q{None} \I{terminal bell, disabling}disables the bell +completely. In this mode, the server can send as many Control-G +characters as it likes and nothing at all will happen. + +\b \q{Make default system alert sound} is the default setting. It +causes the Windows \q{Default Beep} sound to be played. To change +what this sound is, or to test it if nothing seems to be happening, +use the Sound configurer in the Windows Control Panel. + +\b \q{\ii{Visual bell}} is a silent alternative to a beeping computer. In +this mode, when the server sends a Control-G, the whole PuTTY window +will flash white for a fraction of a second. + +\b \q{Beep using the \i{PC speaker}} is self-explanatory. + +\b \q{Play a custom \i{sound file}} allows you to specify a particular +sound file to be used by PuTTY alone, or even by a particular +individual PuTTY session. This allows you to distinguish your PuTTY +beeps from any other beeps on the system. If you select this option, +you will also need to enter the name of your sound file in the edit +control \q{Custom sound file to play as a bell}. + +\S{config-belltaskbar} \q{\ii{Taskbar}/\I{window caption}caption +indication on bell} + +\cfg{winhelp-topic}{bell.taskbar} + +This feature controls what happens to the PuTTY window's entry in +the Windows Taskbar if a bell occurs while the window does not have +the input focus. + +In the default state (\q{Disabled}) nothing unusual happens. + +If you select \q{Steady}, then when a bell occurs and the window is +not in focus, the window's Taskbar entry and its title bar will +change colour to let you know that PuTTY session is asking for your +attention. The change of colour will persist until you select the +window, so you can leave several PuTTY windows minimised in your +terminal, go away from your keyboard, and be sure not to have missed +any important beeps when you get back. + +\q{Flashing} is even more eye-catching: the Taskbar entry will +continuously flash on and off until you select the window. + +\S{config-bellovl} \q{Control the \i{bell overload} behaviour} + +\cfg{winhelp-topic}{bell.overload} + +A common user error in a terminal session is to accidentally run the +Unix command \c{cat} (or equivalent) on an inappropriate file type, +such as an executable, image file, or ZIP file. This produces a huge +stream of non-text characters sent to the terminal, which typically +includes a lot of bell characters. As a result of this the terminal +often doesn't stop beeping for ten minutes, and everybody else in +the office gets annoyed. + +To try to avoid this behaviour, or any other cause of excessive +beeping, PuTTY includes a bell overload management feature. In the +default configuration, receiving more than five bell characters in a +two-second period will cause the overload feature to activate. Once +the overload feature is active, further bells will \I{terminal bell, +disabling} have no effect at all, so the rest of your binary file +will be sent to the screen in silence. After a period of five seconds +during which no further bells are received, the overload feature will +turn itself off again and bells will be re-enabled. + +If you want this feature completely disabled, you can turn it off +using the checkbox \q{Bell is temporarily disabled when over-used}. + +Alternatively, if you like the bell overload feature but don't agree +with the settings, you can configure the details: how many bells +constitute an overload, how short a time period they have to arrive +in to do so, and how much silent time is required before the +overload feature will deactivate itself. + +Bell overload mode is always deactivated by any keypress in the +terminal. This means it can respond to large unexpected streams of +data, but does not interfere with ordinary command-line activities +that generate beeps (such as filename completion). + +\H{config-features} The Features panel + +PuTTY's \i{terminal emulation} is very highly featured, and can do a lot +of things under remote server control. Some of these features can +cause problems due to buggy or strangely configured server +applications. + +The Features configuration panel allows you to disable some of +PuTTY's more advanced terminal features, in case they cause trouble. + +\S{config-features-application} Disabling application keypad and cursor keys + +\cfg{winhelp-topic}{features.application} + +\I{Application Keypad}Application keypad mode (see +\k{config-appkeypad}) and \I{Application Cursor Keys}application +cursor keys mode (see \k{config-appcursor}) alter the behaviour of +the keypad and cursor keys. Some applications enable these modes but +then do not deal correctly with the modified keys. You can force +these modes to be permanently disabled no matter what the server +tries to do. + +\S{config-features-mouse} Disabling \cw{xterm}-style \i{mouse reporting} + +\cfg{winhelp-topic}{features.mouse} + +PuTTY allows the server to send \i{control codes} that let it take over +the mouse and use it for purposes other than \i{copy and paste}. +Applications which use this feature include the text-mode web +browser \i\c{links}, the Usenet newsreader \i\c{trn} version 4, and the +file manager \i\c{mc} (Midnight Commander). + +If you find this feature inconvenient, you can disable it using the +\q{Disable xterm-style mouse reporting} control. With this box +ticked, the mouse will \e{always} do copy and paste in the normal +way. + +Note that even if the application takes over the mouse, you can +still manage PuTTY's copy and paste by holding down the Shift key +while you select and paste, unless you have deliberately turned this +feature off (see \k{config-mouseshift}). + +\S{config-features-resize} Disabling remote \i{terminal resizing} + +\cfg{winhelp-topic}{features.resize} + +PuTTY has the ability to change the terminal's size and position in +response to commands from the server. If you find PuTTY is doing +this unexpectedly or inconveniently, you can tell PuTTY not to +respond to those server commands. + +\S{config-features-altscreen} Disabling switching to the \i{alternate screen} + +\cfg{winhelp-topic}{features.altscreen} + +Many terminals, including PuTTY, support an \q{alternate screen}. +This is the same size as the ordinary terminal screen, but separate. +Typically a screen-based program such as a text editor might switch +the terminal to the alternate screen before starting up. Then at the +end of the run, it switches back to the primary screen, and you see +the screen contents just as they were before starting the editor. + +Some people prefer this not to happen. If you want your editor to +run in the same screen as the rest of your terminal activity, you +can disable the alternate screen feature completely. + +\S{config-features-retitle} Disabling remote \i{window title} changing + +\cfg{winhelp-topic}{features.retitle} + +PuTTY has the ability to change the window title in response to +commands from the server. If you find PuTTY is doing this +unexpectedly or inconveniently, you can tell PuTTY not to respond to +those server commands. + +\S{config-features-qtitle} Response to remote \i{window title} querying + +\cfg{winhelp-topic}{features.qtitle} + +PuTTY can optionally provide the xterm service of allowing server +applications to find out the local window title. This feature is +disabled by default, but you can turn it on if you really want it. + +NOTE that this feature is a \e{potential \i{security hazard}}. If a +malicious application can write data to your terminal (for example, +if you merely \c{cat} a file owned by someone else on the server +machine), it can change your window title (unless you have disabled +this as mentioned in \k{config-features-retitle}) and then use this +service to have the new window title sent back to the server as if +typed at the keyboard. This allows an attacker to fake keypresses +and potentially cause your server-side applications to do things you +didn't want. Therefore this feature is disabled by default, and we +recommend you do not set it to \q{Window title} unless you \e{really} +know what you are doing. + +There are three settings for this option: + +\dt \q{None} + +\dd PuTTY makes no response whatsoever to the relevant escape +sequence. This may upset server-side software that is expecting some +sort of response. + +\dt \q{Empty string} + +\dd PuTTY makes a well-formed response, but leaves it blank. Thus, +server-side software that expects a response is kept happy, but an +attacker cannot influence the response string. This is probably the +setting you want if you have no better ideas. + +\dt \q{Window title} + +\dd PuTTY responds with the actual window title. This is dangerous for +the reasons described above. + +\S{config-features-dbackspace} Disabling \i{destructive backspace} + +\cfg{winhelp-topic}{features.dbackspace} + +Normally, when PuTTY receives character 127 (^?) from the server, it +will perform a \q{destructive backspace}: move the cursor one space +left and delete the character under it. This can apparently cause +problems in some applications, so PuTTY provides the ability to +configure character 127 to perform a normal backspace (without +deleting a character) instead. + +\S{config-features-charset} Disabling remote \i{character set} +configuration + +\cfg{winhelp-topic}{features.charset} + +PuTTY has the ability to change its character set configuration in +response to commands from the server. Some programs send these +commands unexpectedly or inconveniently. In particular, \i{BitchX} (an +IRC client) seems to have a habit of reconfiguring the character set +to something other than the user intended. + +If you find that accented characters are not showing up the way you +expect them to, particularly if you're running BitchX, you could try +disabling the remote character set configuration commands. + +\S{config-features-shaping} Disabling \i{Arabic text shaping} + +\cfg{winhelp-topic}{features.arabicshaping} + +PuTTY supports shaping of Arabic text, which means that if your +server sends text written in the basic \i{Unicode} Arabic alphabet then +it will convert it to the correct display forms before printing it +on the screen. + +If you are using full-screen software which was not expecting this +to happen (especially if you are not an Arabic speaker and you +unexpectedly find yourself dealing with Arabic text files in +applications which are not Arabic-aware), you might find that the +\i{display becomes corrupted}. By ticking this box, you can disable +Arabic text shaping so that PuTTY displays precisely the characters +it is told to display. + +You may also find you need to disable bidirectional text display; +see \k{config-features-bidi}. + +\S{config-features-bidi} Disabling \i{bidirectional text} display + +\cfg{winhelp-topic}{features.bidi} + +PuTTY supports bidirectional text display, which means that if your +server sends text written in a language which is usually displayed +from right to left (such as \i{Arabic} or \i{Hebrew}) then PuTTY will +automatically flip it round so that it is displayed in the right +direction on the screen. + +If you are using full-screen software which was not expecting this +to happen (especially if you are not an Arabic speaker and you +unexpectedly find yourself dealing with Arabic text files in +applications which are not Arabic-aware), you might find that the +\i{display becomes corrupted}. By ticking this box, you can disable +bidirectional text display, so that PuTTY displays text from left to +right in all situations. + +You may also find you need to disable Arabic text shaping; +see \k{config-features-shaping}. + +\H{config-window} The Window panel + +The Window configuration panel allows you to control aspects of the +\i{PuTTY window}. + +\S{config-winsize} Setting the \I{window size}size of the PuTTY window + +\cfg{winhelp-topic}{window.size} + +The \q{\ii{Columns}} and \q{\ii{Rows}} boxes let you set the PuTTY +window to a precise size. Of course you can also \I{window resizing}drag +the window to a new size while a session is running. + +\S{config-winsizelock} What to do when the window is resized + +\cfg{winhelp-topic}{window.resize} + +These options allow you to control what happens when the user tries +to \I{window resizing}resize the PuTTY window using its window furniture. + +There are four options here: + +\b \q{Change the number of rows and columns}: the font size will not +change. (This is the default.) + +\b \q{Change the size of the font}: the number of rows and columns in +the terminal will stay the same, and the \i{font size} will change. + +\b \q{Change font size when maximised}: when the window is resized, +the number of rows and columns will change, \e{except} when the window +is \i{maximise}d (or restored), when the font size will change. (In +this mode, holding down the Alt key while resizing will also cause the +font size to change.) + +\b \q{Forbid resizing completely}: the terminal will refuse to be +resized at all. + +\S{config-scrollback} Controlling \i{scrollback} + +\cfg{winhelp-topic}{window.scrollback} + +These options let you configure the way PuTTY keeps text after it +scrolls off the top of the screen (see \k{using-scrollback}). + +The \q{Lines of scrollback} box lets you configure how many lines of +text PuTTY keeps. The \q{Display scrollbar} options allow you to +hide the \i{scrollbar} (although you can still view the scrollback using +the keyboard as described in \k{using-scrollback}). You can separately +configure whether the scrollbar is shown in \i{full-screen} mode and in +normal modes. + +If you are viewing part of the scrollback when the server sends more +text to PuTTY, the screen will revert to showing the current +terminal contents. You can disable this behaviour by turning off +\q{Reset scrollback on display activity}. You can also make the +screen revert when you press a key, by turning on \q{Reset +scrollback on keypress}. + +\S{config-erasetoscrollback} \q{Push erased text into scrollback} + +\cfg{winhelp-topic}{window.erased} + +When this option is enabled, the contents of the terminal screen +will be pushed into the scrollback when a server-side application +clears the screen, so that your scrollback will contain a better +record of what was on your screen in the past. + +If the application switches to the \i{alternate screen} (see +\k{config-features-altscreen} for more about this), then the +contents of the primary screen will be visible in the scrollback +until the application switches back again. + +This option is enabled by default. + +\H{config-appearance} The Appearance panel + +The Appearance configuration panel allows you to control aspects of +the appearance of \I{PuTTY window}PuTTY's window. + +\S{config-cursor} Controlling the appearance of the \i{cursor} + +\cfg{winhelp-topic}{appearance.cursor} + +The \q{Cursor appearance} option lets you configure the cursor to be +a block, an underline, or a vertical line. A block cursor becomes an +empty box when the window loses focus; an underline or a vertical +line becomes dotted. + +The \q{\ii{Cursor blinks}} option makes the cursor blink on and off. This +works in any of the cursor modes. + +\S{config-font} Controlling the \i{font} used in the terminal window + +\cfg{winhelp-topic}{appearance.font} + +This option allows you to choose what font, in what \I{font size}size, +the PuTTY terminal window uses to display the text in the session. + +By default, you will be offered a choice from all the fixed-width +fonts installed on the system, since VT100-style terminal handling +expects a fixed-width font. If you tick the box marked \q{Allow +selection of variable-pitch fonts}, however, PuTTY will offer +variable-width fonts as well: if you select one of these, the font +will be coerced into fixed-size character cells, which will probably +not look very good (but can work OK with some fonts). + +\S{config-mouseptr} \q{Hide \i{mouse pointer} when typing in window} + +\cfg{winhelp-topic}{appearance.hidemouse} + +If you enable this option, the mouse pointer will disappear if the +PuTTY window is selected and you press a key. This way, it will not +obscure any of the text in the window while you work in your +session. As soon as you move the mouse, the pointer will reappear. + +This option is disabled by default, so the mouse pointer remains +visible at all times. + +\S{config-winborder} Controlling the \i{window border} + +\cfg{winhelp-topic}{appearance.border} + +PuTTY allows you to configure the appearance of the window border to +some extent. + +The checkbox marked \q{Sunken-edge border} changes the appearance of +the window border to something more like a DOS box: the inside edge +of the border is highlighted as if it sank down to meet the surface +inside the window. This makes the border a little bit thicker as +well. It's hard to describe well. Try it and see if you like it. + +You can also configure a completely blank gap between the text in +the window and the border, using the \q{Gap between text and window +edge} control. By default this is set at one pixel. You can reduce +it to zero, or increase it further. + +\H{config-behaviour} The Behaviour panel + +The Behaviour configuration panel allows you to control aspects of +the behaviour of \I{PuTTY window}PuTTY's window. + +\S{config-title} Controlling the \i{window title} + +\cfg{winhelp-topic}{appearance.title} + +The \q{Window title} edit box allows you to set the title of the +PuTTY window. By default the window title will contain the \i{host name} +followed by \q{PuTTY}, for example \c{server1.example.com - PuTTY}. +If you want a different window title, this is where to set it. + +PuTTY allows the server to send \c{xterm} \i{control sequence}s which +modify the title of the window in mid-session (unless this is disabled - +see \k{config-features-retitle}); the title string set here +is therefore only the \e{initial} window title. + +As well as the \e{window} title, there is also an \c{xterm} +sequence to modify the \I{icon title}title of the window's \e{icon}. +This makes sense in a windowing system where the window becomes an +icon when minimised, such as Windows 3.1 or most X Window System +setups; but in the Windows 95-like user interface it isn't as +applicable. + +By default, PuTTY only uses the server-supplied \e{window} title, and +ignores the icon title entirely. If for some reason you want to see +both titles, check the box marked \q{Separate window and icon titles}. +If you do this, PuTTY's window title and Taskbar \I{window caption}caption will +change into the server-supplied icon title if you \i{minimise} the PuTTY +window, and change back to the server-supplied window title if you +restore it. (If the server has not bothered to supply a window or +icon title, none of this will happen.) + +\S{config-warnonclose} \q{Warn before \i{closing window}} + +\cfg{winhelp-topic}{behaviour.closewarn} + +If you press the \i{Close button} in a PuTTY window that contains a +running session, PuTTY will put up a warning window asking if you +really meant to close the window. A window whose session has already +terminated can always be closed without a warning. + +If you want to be able to close a window quickly, you can disable +the \q{Warn before closing window} option. + +\S{config-altf4} \q{Window closes on \i{ALT-F4}} + +\cfg{winhelp-topic}{behaviour.altf4} + +By default, pressing ALT-F4 causes the \I{closing window}window to +close (or a warning box to appear; see \k{config-warnonclose}). If you +disable the \q{Window closes on ALT-F4} option, then pressing ALT-F4 +will simply send a key sequence to the server. + +\S{config-altspace} \q{\ii{System menu} appears on \i{ALT-Space}} + +\cfg{winhelp-topic}{behaviour.altspace} + +If this option is enabled, then pressing ALT-Space will bring up the +PuTTY window's menu, like clicking on the top left corner. If it is +disabled, then pressing ALT-Space will just send \c{ESC SPACE} to +the server. + +Some \i{accessibility} programs for Windows may need this option +enabling to be able to control PuTTY's window successfully. For +instance, \i{Dragon NaturallySpeaking} requires it both to open the +system menu via voice, and to close, minimise, maximise and restore +the window. + +\S{config-altonly} \q{\ii{System menu} appears on \i{Alt} alone} + +\cfg{winhelp-topic}{behaviour.altonly} + +If this option is enabled, then pressing and releasing ALT will +bring up the PuTTY window's menu, like clicking on the top left +corner. If it is disabled, then pressing and releasing ALT will have +no effect. + +\S{config-alwaysontop} \q{Ensure window is \i{always on top}} + +\cfg{winhelp-topic}{behaviour.alwaysontop} + +If this option is enabled, the PuTTY window will stay on top of all +other windows. + +\S{config-fullscreen} \q{\ii{Full screen} on Alt-Enter} + +\cfg{winhelp-topic}{behaviour.altenter} + +If this option is enabled, then pressing Alt-Enter will cause the +PuTTY window to become full-screen. Pressing Alt-Enter again will +restore the previous window size. + +The full-screen feature is also available from the \ii{System menu}, even +when it is configured not to be available on the Alt-Enter key. See +\k{using-fullscreen}. + +\H{config-translation} The Translation panel + +The Translation configuration panel allows you to control the +translation between the \i{character set} understood by the server and +the character set understood by PuTTY. + +\S{config-charset} Controlling character set translation + +\cfg{winhelp-topic}{translation.codepage} + +During an interactive session, PuTTY receives a stream of 8-bit +bytes from the server, and in order to display them on the screen it +needs to know what character set to interpret them in. Similarly, +PuTTY needs to know how to translate your keystrokes into the encoding +the server expects. Unfortunately, there is no satisfactory +mechanism for PuTTY and the server to communicate this information, +so it must usually be manually configured. + +There are a lot of character sets to choose from. The \q{Remote +character set} option lets you select one. By default PuTTY will +attempt to choose a character set that is right for your \i{locale} as +reported by Windows; if it gets it wrong, you can select a different +one using this control. + +A few notable character sets are: + +\b The \i{ISO-8859} series are all standard character sets that include +various accented characters appropriate for different sets of +languages. + +\b The \i{Win125x} series are defined by Microsoft, for similar +purposes. In particular Win1252 is almost equivalent to ISO-8859-1, +but contains a few extra characters such as matched quotes and the +Euro symbol. + +\b If you want the old IBM PC character set with block graphics and +line-drawing characters, you can select \q{\i{CP437}}. + +\b PuTTY also supports \i{Unicode} mode, in which the data coming from +the server is interpreted as being in the \i{UTF-8} encoding of Unicode, +and keystrokes are sent UTF-8 encoded. If you select \q{UTF-8} as a +character set you can use this mode. Not all server-side applications +will support it. + +If you need support for a numeric \i{code page} which is not listed in +the drop-down list, such as code page 866, then you can try entering +its name manually (\c{\i{CP866}} for example) in the list box. If the +underlying version of Windows has the appropriate translation table +installed, PuTTY will use it. + +\S{config-cjk-ambig-wide} \q{Treat \i{CJK} ambiguous characters as wide} + +\cfg{winhelp-topic}{translation.cjkambigwide} + +There are \I{East Asian Ambiguous characters}some Unicode characters +whose \I{character width}width is not well-defined. In most contexts, such +characters should be treated as single-width for the purposes of \I{wrapping, +terminal}wrapping and so on; however, in some CJK contexts, they are better +treated as double-width for historical reasons, and some server-side +applications may expect them to be displayed as such. Setting this option +will cause PuTTY to take the double-width interpretation. + +If you use legacy CJK applications, and you find your lines are +wrapping in the wrong places, or you are having other display +problems, you might want to play with this setting. + +This option only has any effect in \i{UTF-8} mode (see \k{config-charset}). + +\S{config-cyr} \q{\i{Caps Lock} acts as \i{Cyrillic} switch} + +\cfg{winhelp-topic}{translation.cyrillic} + +This feature allows you to switch between a US/UK keyboard layout +and a Cyrillic keyboard layout by using the Caps Lock key, if you +need to type (for example) \i{Russian} and English side by side in the +same document. + +Currently this feature is not expected to work properly if your +native keyboard layout is not US or UK. + +\S{config-linedraw} Controlling display of \i{line-drawing characters} + +\cfg{winhelp-topic}{translation.linedraw} + +VT100-series terminals allow the server to send \i{control sequence}s that +shift temporarily into a separate character set for drawing simple +lines and boxes. However, there are a variety of ways in which PuTTY +can attempt to find appropriate characters, and the right one to use +depends on the locally configured \i{font}. In general you should probably +try lots of options until you find one that your particular font +supports. + +\b \q{Use Unicode line drawing code points} tries to use the box +characters that are present in \i{Unicode}. For good Unicode-supporting +fonts this is probably the most reliable and functional option. + +\b \q{Poor man's line drawing} assumes that the font \e{cannot} +generate the line and box characters at all, so it will use the +\c{+}, \c{-} and \c{|} characters to draw approximations to boxes. +You should use this option if none of the other options works. + +\b \q{Font has XWindows encoding} is for use with fonts that have a +special encoding, where the lowest 32 character positions (below the +ASCII printable range) contain the line-drawing characters. This is +unlikely to be the case with any standard Windows font; it will +probably only apply to custom-built fonts or fonts that have been +automatically converted from the X Window System. + +\b \q{Use font in both ANSI and OEM modes} tries to use the same +font in two different character sets, to obtain a wider range of +characters. This doesn't always work; some fonts claim to be a +different size depending on which character set you try to use. + +\b \q{Use font in OEM mode only} is more reliable than that, but can +miss out other characters from the main character set. + +\S{config-linedrawpaste} Controlling \i{copy and paste} of line drawing +characters + +\cfg{winhelp-topic}{selection.linedraw} + +By default, when you copy and paste a piece of the PuTTY screen that +contains VT100 line and box drawing characters, PuTTY will paste +them in the form they appear on the screen: either \i{Unicode} line +drawing code points, or the \q{poor man's} line-drawing characters +\c{+}, \c{-} and \c{|}. The checkbox \q{Copy and paste VT100 line +drawing chars as lqqqk} disables this feature, so line-drawing +characters will be pasted as the \i{ASCII} characters that were printed +to produce them. This will typically mean they come out mostly as +\c{q} and \c{x}, with a scattering of \c{jklmntuvw} at the corners. +This might be useful if you were trying to recreate the same box +layout in another program, for example. + +Note that this option only applies to line-drawing characters which +\e{were} printed by using the VT100 mechanism. Line-drawing +characters that were received as Unicode code points will paste as +Unicode always. + +\H{config-selection} The Selection panel + +The Selection panel allows you to control the way \i{copy and paste} +work in the PuTTY window. + +\S{config-rtfpaste} Pasting in \i{Rich Text Format} + +\cfg{winhelp-topic}{selection.rtf} + +If you enable \q{Paste to clipboard in RTF as well as plain text}, +PuTTY will write formatting information to the clipboard as well as +the actual text you copy. The effect of this is +that if you paste into (say) a word processor, the text will appear +in the word processor in the same \i{font}, \i{colour}, and style +(e.g. bold, underline) PuTTY was using to display it. + +This option can easily be inconvenient, so by default it is +disabled. + +\S{config-mouse} Changing the actions of the mouse buttons + +\cfg{winhelp-topic}{selection.buttons} + +PuTTY's copy and paste mechanism is by default modelled on the Unix +\c{xterm} application. The X Window System uses a three-button mouse, +and the convention is that the \i{left button} \I{selecting text}selects, +the \i{right button} extends an existing selection, and the +\i{middle button} pastes. + +Windows often only has two mouse buttons, so in PuTTY's default +configuration (\q{Compromise}), the \e{right} button pastes, and the +\e{middle} button (if you have one) \I{adjusting a selection}extends +a selection. + +If you have a \i{three-button mouse} and you are already used to the +\c{xterm} arrangement, you can select it using the \q{Action of +mouse buttons} control. + +Alternatively, with the \q{Windows} option selected, the middle +button extends, and the right button brings up a \i{context menu} (on +which one of the options is \q{Paste}). (This context menu is always +available by holding down Ctrl and right-clicking, regardless of the +setting of this option.) + +\S{config-mouseshift} \q{Shift overrides application's use of mouse} + +\cfg{winhelp-topic}{selection.shiftdrag} + +PuTTY allows the server to send \i{control codes} that let it +\I{mouse reporting}take over the mouse and use it for purposes other +than \i{copy and paste}. +Applications which use this feature include the text-mode web +browser \c{links}, the Usenet newsreader \c{trn} version 4, and the +file manager \c{mc} (Midnight Commander). + +When running one of these applications, pressing the mouse buttons +no longer performs copy and paste. If you do need to copy and paste, +you can still do so if you hold down Shift while you do your mouse +clicks. + +However, it is possible in theory for applications to even detect +and make use of Shift + mouse clicks. We don't know of any +applications that do this, but in case someone ever writes one, +unchecking the \q{Shift overrides application's use of mouse} +checkbox will cause Shift + mouse clicks to go to the server as well +(so that mouse-driven copy and paste will be completely disabled). + +If you want to prevent the application from taking over the mouse at +all, you can do this using the Features control panel; see +\k{config-features-mouse}. + +\S{config-rectselect} Default selection mode + +\cfg{winhelp-topic}{selection.rect} + +As described in \k{using-selection}, PuTTY has two modes of +selecting text to be copied to the clipboard. In the default mode +(\q{Normal}), dragging the mouse from point A to point B selects to +the end of the line containing A, all the lines in between, and from +the very beginning of the line containing B. In the other mode +(\q{Rectangular block}), dragging the mouse between two points +defines a rectangle, and everything within that rectangle is copied. + +Normally, you have to hold down Alt while dragging the mouse to +select a rectangular block. Using the \q{Default selection mode} +control, you can set \i{rectangular selection} as the default, and then +you have to hold down Alt to get the \e{normal} behaviour. + +\S{config-charclasses} Configuring \i{word-by-word selection} + +\cfg{winhelp-topic}{selection.charclasses} + +PuTTY will select a word at a time in the terminal window if you +\i{double-click} to begin the drag. This panel allows you to control +precisely what is considered to be a word. + +Each character is given a \e{class}, which is a small number +(typically 0, 1 or 2). PuTTY considers a single word to be any +number of adjacent characters in the same class. So by modifying the +assignment of characters to classes, you can modify the word-by-word +selection behaviour. + +In the default configuration, the \i{character classes} are: + +\b Class 0 contains \i{white space} and control characters. + +\b Class 1 contains most \i{punctuation}. + +\b Class 2 contains letters, numbers and a few pieces of punctuation +(the double quote, minus sign, period, forward slash and +underscore). + +So, for example, if you assign the \c{@} symbol into character class +2, you will be able to select an e-mail address with just a double +click. + +In order to adjust these assignments, you start by selecting a group +of characters in the list box. Then enter a class number in the edit +box below, and press the \q{Set} button. + +This mechanism currently only covers ASCII characters, because it +isn't feasible to expand the list to cover the whole of Unicode. + +Character class definitions can be modified by \i{control sequence}s +sent by the server. This configuration option controls the +\e{default} state, which will be restored when you reset the +terminal (see \k{reset-terminal}). However, if you modify this +option in mid-session using \q{Change Settings}, it will take effect +immediately. + +\H{config-colours} The Colours panel + +The Colours panel allows you to control PuTTY's use of \i{colour}. + +\S{config-ansicolour} \q{Allow terminal to specify \i{ANSI colours}} + +\cfg{winhelp-topic}{colours.ansi} + +This option is enabled by default. If it is disabled, PuTTY will +ignore any \i{control sequence}s sent by the server to request coloured +text. + +If you have a particularly garish application, you might want to +turn this option off and make PuTTY only use the default foreground +and background colours. + +\S{config-xtermcolour} \q{Allow terminal to use xterm \i{256-colour mode}} + +\cfg{winhelp-topic}{colours.xterm256} + +This option is enabled by default. If it is disabled, PuTTY will +ignore any control sequences sent by the server which use the +extended 256-colour mode supported by recent versions of \cw{xterm}. + +If you have an application which is supposed to use 256-colour mode +and it isn't working, you may find you need to tell your server that +your terminal supports 256 colours. On Unix, you do this by ensuring +that the setting of \i\cw{TERM} describes a 256-colour-capable +terminal. You can check this using a command such as \c{infocmp}: + +\c $ infocmp | grep colors +\c colors#256, cols#80, it#8, lines#24, pairs#256, +\e bbbbbbbbbb + +If you do not see \cq{colors#256} in the output, you may need to +change your terminal setting. On modern Linux machines, you could +try \cq{xterm-256color}. + +\S{config-boldcolour} \q{Bolded text is a different colour} + +\cfg{winhelp-topic}{colours.bold} + +When the server sends a \i{control sequence} indicating that some text +should be displayed in \i{bold}, PuTTY can handle this two ways. It can +either change the \i{font} for a bold version, or use the same font in a +brighter colour. This control lets you choose which. + +By default the box is checked, so non-bold text is displayed in +light grey and bold text is displayed in bright white (and similarly +in other colours). If you uncheck the box, bold and non-bold text +will be displayed in the same colour, and instead the font will +change to indicate the difference. + +\S{config-logpalette} \q{Attempt to use \i{logical palettes}} + +\cfg{winhelp-topic}{colours.logpal} + +Logical palettes are a mechanism by which a Windows application +running on an \i{8-bit colour} display can select precisely the colours +it wants instead of going with the Windows standard defaults. + +If you are not getting the colours you ask for on an 8-bit display, +you can try enabling this option. However, be warned that it's never +worked very well. + +\S{config-syscolour} \q{Use \i{system colours}} + +\cfg{winhelp-topic}{colours.system} + +Enabling this option will cause PuTTY to ignore the configured colours +for \I{default background}\I{default foreground}\q{Default +Background/Foreground} and \I{cursor colour}\q{Cursor Colour/Text} (see +\k{config-colourcfg}), instead going with the system-wide defaults. + +Note that non-bold and \i{bold text} will be the same colour if this +option is enabled. You might want to change to indicating bold text +by font changes (see \k{config-boldcolour}). + +\S{config-colourcfg} Adjusting the colours in the \i{terminal window} + +\cfg{winhelp-topic}{colours.config} + +The main colour control allows you to specify exactly what colours +things should be displayed in. To modify one of the PuTTY colours, +use the list box to select which colour you want to modify. The \i{RGB +values} for that colour will appear on the right-hand side of the +list box. Now, if you press the \q{Modify} button, you will be +presented with a colour selector, in which you can choose a new +colour to go in place of the old one. (You may also edit the RGB +values directly in the edit boxes, if you wish; each value is an +integer from 0 to 255.) + +PuTTY allows you to set the \i{cursor colour}, the \i{default foreground} +and \I{default background}background, and the precise shades of all the +\I{ANSI colours}ANSI configurable colours (black, red, green, yellow, blue, +magenta, cyan, and white). You can also modify the precise shades used for +the \i{bold} versions of these colours; these are used to display bold text +if you have selected \q{Bolded text is a different colour}, and can also be +used if the server asks specifically to use them. (Note that \q{Default +Bold Background} is \e{not} the background colour used for bold text; +it is only used if the server specifically asks for a bold +background.) + +\H{config-connection} The Connection panel + +The Connection panel allows you to configure options that apply to +more than one type of \i{connection}. + +\S{config-keepalive} Using \i{keepalives} to prevent disconnection + +\cfg{winhelp-topic}{connection.keepalive} + +If you find your sessions are closing unexpectedly (most often with +\q{Connection reset by peer}) after they have been idle for a while, +you might want to try using this option. + +Some network \i{routers} and \i{firewalls} need to keep track of all +connections through them. Usually, these firewalls will assume a +connection is dead if no data is transferred in either direction +after a certain time interval. This can cause PuTTY sessions to be +unexpectedly closed by the firewall if no traffic is seen in the +session for some time. + +The keepalive option (\q{Seconds between keepalives}) allows you to +configure PuTTY to send data through the session at regular +intervals, in a way that does not disrupt the actual terminal +session. If you find your firewall is cutting \i{idle connections} off, +you can try entering a non-zero value in this field. The value is +measured in seconds; so, for example, if your firewall cuts +connections off after ten minutes then you might want to enter 300 +seconds (5 minutes) in the box. + +Note that keepalives are not always helpful. They help if you have a +firewall which drops your connection after an idle period; but if +the network between you and the server suffers from \i{breaks in +connectivity} then keepalives can actually make things worse. If a +session is idle, and connectivity is temporarily lost between the +endpoints, but the connectivity is restored before either side tries +to send anything, then there will be no problem - neither endpoint +will notice that anything was wrong. However, if one side does send +something during the break, it will repeatedly try to re-send, and +eventually give up and abandon the connection. Then when +connectivity is restored, the other side will find that the first +side doesn't believe there is an open connection any more. +Keepalives can make this sort of problem worse, because they +increase the probability that PuTTY will attempt to send data during +a break in connectivity. (Other types of periodic network activity +can cause this behaviour; in particular, SSH-2 re-keys can have +this effect. See \k{config-ssh-kex-rekey}.) + +Therefore, you might find that keepalives help +connection loss, or you might find they make it worse, depending on +what \e{kind} of network problems you have between you and the +server. + +Keepalives are only supported in Telnet and SSH; the Rlogin and Raw +protocols offer no way of implementing them. (For an alternative, see +\k{config-tcp-keepalives}.) + +Note that if you are using \i{SSH-1} and the server has a bug that makes +it unable to deal with SSH-1 ignore messages (see +\k{config-ssh-bug-ignore1}), enabling keepalives will have no effect. + +\S{config-nodelay} \q{Disable \i{Nagle's algorithm}} + +\cfg{winhelp-topic}{connection.nodelay} + +Nagle's algorithm is a detail of TCP/IP implementations that tries +to minimise the number of small data packets sent down a network +connection. With Nagle's algorithm enabled, PuTTY's \i{bandwidth} usage +will be slightly more efficient; with it disabled, you may find you +get a faster response to your keystrokes when connecting to some +types of server. + +The Nagle algorithm is disabled by default for \i{interactive connections}. + +\S{config-tcp-keepalives} \q{Enable \i{TCP keepalives}} + +\cfg{winhelp-topic}{connection.tcpkeepalive} + +\e{NOTE:} TCP keepalives should not be confused with the +application-level keepalives described in \k{config-keepalive}. If in +doubt, you probably want application-level keepalives; TCP keepalives +are provided for completeness. + +The idea of TCP keepalives is similar to application-level keepalives, +and the same caveats apply. The main differences are: + +\b TCP keepalives are available on \e{all} connection types, including +Raw and Rlogin. + +\b The interval between TCP keepalives is usually much longer, +typically two hours; this is set by the operating system, and cannot +be configured within PuTTY. + +\b If the operating system does not receive a response to a keepalive, +it may send out more in quick succession and terminate the connection +if no response is received. + +TCP keepalives may be more useful for ensuring that \i{half-open connections} +are terminated than for keeping a connection alive. + +TCP keepalives are disabled by default. + +\S{config-address-family} \I{Internet protocol version}\q{Internet protocol} + +\cfg{winhelp-topic}{connection.ipversion} + +This option allows the user to select between the old and new +Internet protocols and addressing schemes (\i{IPv4} and \i{IPv6}). +The selected protocol will be used for most outgoing network +connections (including connections to \I{proxy}proxies); however, +tunnels have their own configuration, for which see +\k{config-ssh-portfwd-address-family}. + +The default setting is \q{Auto}, which means PuTTY will do something +sensible and try to guess which protocol you wanted. (If you specify +a literal \i{Internet address}, it will use whichever protocol that +address implies. If you provide a \i{hostname}, it will see what kinds +of address exist for that hostname; it will use IPv6 if there is an +IPv6 address available, and fall back to IPv4 if not.) + +If you need to force PuTTY to use a particular protocol, you can +explicitly set this to \q{IPv4} or \q{IPv6}. + +\S{config-loghost} \I{logical host name}\q{Logical name of remote host} + +\cfg{winhelp-topic}{connection.loghost} + +This allows you to tell PuTTY that the host it will really end up +connecting to is different from where it thinks it is making a +network connection. + +You might use this, for instance, if you had set up an SSH port +forwarding in one PuTTY session so that connections to some +arbitrary port (say, \cw{localhost} port 10022) were forwarded to a +second machine's SSH port (say, \cw{foovax} port 22), and then +started a second PuTTY connecting to the forwarded port. + +In normal usage, the second PuTTY will access the host key cache +under the host name and port it actually connected to (i.e. +\cw{localhost} port 10022 in this example). Using the logical host +name option, however, you can configure the second PuTTY to cache +the host key under the name of the host \e{you} know that it's +\e{really} going to end up talking to (here \c{foovax}). + +This can be useful if you expect to connect to the same actual +server through many different channels (perhaps because your port +forwarding arrangements keep changing): by consistently setting the +logical host name, you can arrange that PuTTY will not keep asking +you to reconfirm its host key. Conversely, if you expect to use the +same local port number for port forwardings to lots of different +servers, you probably didn't want any particular server's host key +cached under that local port number. + +If you just enter a host name for this option, PuTTY will cache the +SSH host key under the default SSH port for that host, irrespective +of the port you really connected to (since the typical scenario is +like the above example: you connect to a silly real port number and +your connection ends up forwarded to the normal port-22 SSH server +of some other machine). To override this, you can append a port +number to the logical host name, separated by a colon. E.g. entering +\cq{foovax:2200} as the logical host name will cause the host key to +be cached as if you had connected to port 2200 of \c{foovax}. + +If you provide a host name using this option, it is also displayed +in other locations which contain the remote host name, such as the +default window title and the default SSH password prompt. This +reflects the fact that this is the host you're \e{really} connecting +to, which is more important than the mere means you happen to be +using to contact that host. (This applies even if you're using a +protocol other than SSH.) + +\H{config-data} The Data panel + +The Data panel allows you to configure various pieces of data which +can be sent to the server to affect your connection at the far end. + +Each option on this panel applies to more than one protocol. +Options which apply to only one protocol appear on that protocol's +configuration panels. + +\S{config-username} \q{\ii{Auto-login username}} + +\cfg{winhelp-topic}{connection.username} + +All three of the SSH, Telnet and Rlogin protocols allow you to +specify what user name you want to log in as, without having to type +it explicitly every time. (Some Telnet servers don't support this.) + +In this box you can type that user name. + +\S{config-username-from-env} Use of system username + +\cfg{winhelp-topic}{connection.usernamefromenv} + +When the previous box (\k{config-username}) is left blank, by default, +PuTTY will prompt for a username at the time you make a connection. + +In some environments, such as the networks of large organisations +implementing \i{single sign-on}, a more sensible default may be to use +the name of the user logged in to the local operating system (if any); +this is particularly likely to be useful with \i{GSSAPI} authentication +(see \k{config-ssh-auth-gssapi}). This control allows you to change +the default behaviour. + +The current system username is displayed in the dialog as a +convenience. It is not saved in the configuration; if a saved session +is later used by a different user, that user's name will be used. + +\S{config-termtype} \q{\ii{Terminal-type} string} + +\cfg{winhelp-topic}{connection.termtype} + +Most servers you might connect to with PuTTY are designed to be +connected to from lots of different types of terminal. In order to +send the right \i{control sequence}s to each one, the server will need +to know what type of terminal it is dealing with. Therefore, each of +the SSH, Telnet and Rlogin protocols allow a text string to be sent +down the connection describing the terminal. On a \i{Unix} server, +this selects an entry from the \i\c{termcap} or \i\c{terminfo} database +that tells applications what \i{control sequences} to send to the +terminal, and what character sequences to expect the \i{keyboard} +to generate. + +PuTTY attempts to emulate the Unix \i\c{xterm} program, and by default +it reflects this by sending \c{xterm} as a terminal-type string. If +you find this is not doing what you want - perhaps the remote +system reports \q{Unknown terminal type} - you could try setting +this to something different, such as \i\c{vt220}. + +If you're not sure whether a problem is due to the terminal type +setting or not, you probably need to consult the manual for your +application or your server. + +\S{config-termspeed} \q{\ii{Terminal speed}s} + +\cfg{winhelp-topic}{connection.termspeed} + +The Telnet, Rlogin, and SSH protocols allow the client to specify +terminal speeds to the server. + +This parameter does \e{not} affect the actual speed of the connection, +which is always \q{as fast as possible}; it is just a hint that is +sometimes used by server software to modify its behaviour. For +instance, if a slow speed is indicated, the server may switch to a +less \i{bandwidth}-hungry display mode. + +The value is usually meaningless in a network environment, but +PuTTY lets you configure it, in case you find the server is reacting +badly to the default value. + +The format is a pair of numbers separated by a comma, for instance, +\c{38400,38400}. The first number represents the output speed +(\e{from} the server) in bits per second, and the second is the input +speed (\e{to} the server). (Only the first is used in the Rlogin +protocol.) + +This option has no effect on Raw connections. + +\S{config-environ} Setting \i{environment variables} on the server + +\cfg{winhelp-topic}{telnet.environ} + +The Telnet protocol provides a means for the client to pass +environment variables to the server. Many Telnet servers have +stopped supporting this feature due to security flaws, but PuTTY +still supports it for the benefit of any servers which have found +other ways around the security problems than just disabling the +whole mechanism. + +Version 2 of the SSH protocol also provides a similar mechanism, +which is easier to implement without security flaws. Newer \i{SSH-2} +servers are more likely to support it than older ones. + +This configuration data is not used in the SSH-1, rlogin or raw +protocols. + +To add an environment variable to the list transmitted down the +connection, you enter the variable name in the \q{Variable} box, +enter its value in the \q{Value} box, and press the \q{Add} button. +To remove one from the list, select it in the list box and press +\q{Remove}. + +\H{config-proxy} The Proxy panel + +\cfg{winhelp-topic}{proxy.main} + +The \ii{Proxy} panel allows you to configure PuTTY to use various types +of proxy in order to make its network connections. The settings in +this panel affect the primary network connection forming your PuTTY +session, and also any extra connections made as a result of SSH \i{port +forwarding} (see \k{using-port-forwarding}). + +Note that unlike some software (such as web browsers), PuTTY does not +attempt to automatically determine whether to use a proxy and (if so) +which one to use for a given destination. If you need to use a proxy, +it must always be explicitly configured. + +\S{config-proxy-type} Setting the proxy type + +\cfg{winhelp-topic}{proxy.type} + +The \q{Proxy type} radio buttons allow 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 \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 +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. + +\b Selecting \I{Local proxy}\q{Local} allows you to specify an arbitrary +command on the local machine to act as a proxy. When the session is +started, instead of creating a TCP connection, PuTTY runs the command +(specified in \k{config-proxy-command}), and uses its standard input and +output streams. + +\lcont{ +This could be used, for instance, to talk to some kind of network proxy +that PuTTY does not natively support; or you could tunnel a connection +over something other than TCP/IP entirely. + +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. +} + +\S{config-proxy-exclude} Excluding parts of the network from proxying + +\cfg{winhelp-topic}{proxy.exclude} + +Typically you will only need to use a proxy to connect to non-local +parts of your network; for example, your proxy might be required for +connections outside your company's internal network. In the +\q{Exclude Hosts/IPs} box you can enter ranges of IP addresses, or +ranges of DNS names, for which PuTTY will avoid using the proxy and +make a direct connection instead. + +The \q{Exclude Hosts/IPs} box may contain more than one exclusion +range, separated by commas. Each range can be an IP address or a DNS +name, with a \c{*} character allowing wildcards. For example: + +\c *.example.com + +This excludes any host with a name ending in \c{.example.com} from +proxying. + +\c 192.168.88.* + +This excludes any host with an IP address starting with 192.168.88 +from proxying. + +\c 192.168.88.*,*.example.com + +This excludes both of the above ranges at once. + +Connections to the local host (the host name \i\c{localhost}, and any +\i{loopback IP address}) are never proxied, even if the proxy exclude +list does not explicitly contain them. It is very unlikely that this +behaviour would ever cause problems, but if it does you can change +it by enabling \q{Consider proxying local host connections}. + +Note that if you are doing \I{proxy DNS}DNS at the proxy (see +\k{config-proxy-dns}), you should make sure that your proxy +exclusion settings do not depend on knowing the IP address of a +host. If the name is passed on to the proxy without PuTTY looking it +up, it will never know the IP address and cannot check it against +your list. + +\S{config-proxy-dns} \I{proxy DNS}\ii{Name resolution} when using a proxy + +\cfg{winhelp-topic}{proxy.dns} + +If you are using a proxy to access a private network, it can make a +difference whether \i{DNS} name resolution is performed by PuTTY itself +(on the client machine) or performed by the proxy. + +The \q{Do DNS name lookup at proxy end} configuration option allows +you to control this. If you set it to \q{No}, PuTTY will always do +its own DNS, and will always pass an IP address to the proxy. If you +set it to \q{Yes}, PuTTY will always pass host names straight to the +proxy without trying to look them up first. + +If you set this option to \q{Auto} (the default), PuTTY will do +something it considers appropriate for each type of proxy. Telnet, +HTTP, and SOCKS5 proxies will have host names passed straight to +them; SOCKS4 proxies will not. + +Note that if you are doing DNS at the proxy, you should make sure +that your proxy exclusion settings (see \k{config-proxy-exclude}) do +not depend on knowing the IP address of a host. If the name is +passed on to the proxy without PuTTY looking it up, it will never +know the IP address and cannot check it against your list. + +The original SOCKS 4 protocol does not support proxy-side DNS. There +is a protocol extension (SOCKS 4A) which does support it, but not +all SOCKS 4 servers provide this extension. If you enable proxy DNS +and your SOCKS 4 server cannot deal with it, this might be why. + +\S{config-proxy-auth} \I{proxy username}Username and \I{proxy password}password + +\cfg{winhelp-topic}{proxy.auth} + +If your proxy requires \I{proxy authentication}authentication, you can +enter a username and a password in the \q{Username} and \q{Password} boxes. + +\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. + +Authentication is not fully supported for all forms of proxy: + +\b Username and password authentication is supported for HTTP +proxies and SOCKS 5 proxies. + +\lcont{ + +\b With SOCKS 5, authentication is via \i{CHAP} if the proxy +supports it (this is not supported in \i{PuTTYtel}); otherwise the +password is sent to the proxy in \I{plaintext password}plain text. + +\b With HTTP proxying, 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 SOCKS 4 can use the \q{Username} field, but does not support +passwords. + +\b You can specify a way to include a username and password in the +Telnet/Local proxy command (see \k{config-proxy-command}). + +\S{config-proxy-command} Specifying the Telnet or Local proxy command + +\cfg{winhelp-topic}{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. + +If you are using the \i{Local proxy} type, the local command to run +is specified here. + +In this string, you can use \c{\\n} to represent a new-line, \c{\\r} +to represent a carriage return, \c{\\t} to represent a tab +character, and \c{\\x} followed by two hex digits to represent any +other character. \c{\\\\} is used to encode the \c{\\} character +itself. + +Also, the special strings \c{%host} and \c{%port} will be replaced +by the host name and port number you want to connect to. 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} +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 a Telnet proxy server prompts for a username and password +before commands can be sent, you can use a command such as: + +\c %user\n%pass\nconnect %host %port\n + +This will send your username and password as the first two lines to +the proxy, followed by a command to connect to the desired host and +port. Note that if you do not include the \c{%user} or \c{%pass} +tokens in the Telnet command, then the \q{Username} and \q{Password} +configuration fields will be ignored. + +\H{config-telnet} The \i{Telnet} panel + +The Telnet panel allows you to configure options that only apply to +Telnet sessions. + +\S{config-oldenviron} \q{Handling of OLD_ENVIRON ambiguity} + +\cfg{winhelp-topic}{telnet.oldenviron} + +The original Telnet mechanism for passing \i{environment variables} was +badly specified. At the time the standard (RFC 1408) was written, +BSD telnet implementations were already supporting the feature, and +the intention of the standard was to describe the behaviour the BSD +implementations were already using. + +Sadly there was a typing error in the standard when it was issued, +and two vital function codes were specified the wrong way round. BSD +implementations did not change, and the standard was not corrected. +Therefore, it's possible you might find either \i{BSD} or \i{RFC}-compliant +implementations out there. This switch allows you to choose which +one PuTTY claims to be. + +The problem was solved by issuing a second standard, defining a new +Telnet mechanism called \i\cw{NEW_ENVIRON}, which behaved exactly like +the original \i\cw{OLD_ENVIRON} but was not encumbered by existing +implementations. Most Telnet servers now support this, and it's +unambiguous. This feature should only be needed if you have trouble +passing environment variables to quite an old server. + +\S{config-ptelnet} Passive and active \i{Telnet negotiation} modes + +\cfg{winhelp-topic}{telnet.passive} + +In a Telnet connection, there are two types of data passed between +the client and the server: actual text, and \e{negotiations} about +which Telnet extra features to use. + +PuTTY can use two different strategies for negotiation: + +\b In \I{active Telnet negotiation}\e{active} mode, PuTTY starts to send +negotiations as soon as the connection is opened. + +\b In \I{passive Telnet negotiation}\e{passive} mode, PuTTY will wait to +negotiate until it sees a negotiation from the server. + +The obvious disadvantage of passive mode is that if the server is +also operating in a passive mode, then negotiation will never begin +at all. For this reason PuTTY defaults to active mode. + +However, sometimes passive mode is required in order to successfully +get through certain types of firewall and \i{Telnet proxy} server. If +you have confusing trouble with a \i{firewall}, you could try enabling +passive mode to see if it helps. + +\S{config-telnetkey} \q{Keyboard sends \i{Telnet special commands}} + +\cfg{winhelp-topic}{telnet.specialkeys} + +If this box is checked, several key sequences will have their normal +actions modified: + +\b the Backspace key on the keyboard will send the \I{Erase Character, +Telnet special command}Telnet special backspace code; + +\b Control-C will send the Telnet special \I{Interrupt Process, Telnet +special command}Interrupt Process code; + +\b Control-Z will send the Telnet special \I{Suspend Process, Telnet +special command}Suspend Process code. + +You probably shouldn't enable this +unless you know what you're doing. + +\S{config-telnetnl} \q{Return key sends \i{Telnet New Line} instead of ^M} + +\cfg{winhelp-topic}{telnet.newline} + +Unlike most other remote login protocols, the Telnet protocol has a +special \q{\i{new line}} code that is not the same as the usual line +endings of Control-M or Control-J. By default, PuTTY sends the +Telnet New Line code when you press Return, instead of sending +Control-M as it does in most other protocols. + +Most Unix-style Telnet servers don't mind whether they receive +Telnet New Line or Control-M; some servers do expect New Line, and +some servers prefer to see ^M. If you are seeing surprising +behaviour when you press Return in a Telnet session, you might try +turning this option off to see if it helps. + +\H{config-rlogin} The Rlogin panel + +The \i{Rlogin} panel allows you to configure options that only apply to +Rlogin sessions. + +\S{config-rlogin-localuser} \I{local username in Rlogin}\q{Local username} + +\cfg{winhelp-topic}{rlogin.localuser} + +Rlogin allows an automated (password-free) form of login by means of +a file called \i\c{.rhosts} on the server. You put a line in your +\c{.rhosts} file saying something like \c{jbloggs@pc1.example.com}, +and then when you make an Rlogin connection the client transmits the +username of the user running the Rlogin client. The server checks +the username and hostname against \c{.rhosts}, and if they match it +\I{passwordless login}does not ask for a password. + +This only works because Unix systems contain a safeguard to stop a +user from pretending to be another user in an Rlogin connection. +Rlogin connections have to come from \I{privileged port}port numbers below +1024, and Unix systems prohibit this to unprivileged processes; so when the +server sees a connection from a low-numbered port, it assumes the +client end of the connection is held by a privileged (and therefore +trusted) process, so it believes the claim of who the user is. + +Windows does not have this restriction: \e{any} user can initiate an +outgoing connection from a low-numbered port. Hence, the Rlogin +\c{.rhosts} mechanism is completely useless for securely +distinguishing several different users on a Windows machine. If you +have a \c{.rhosts} entry pointing at a Windows PC, you should assume +that \e{anyone} using that PC can \i{spoof} your username in +an Rlogin connection and access your account on the server. + +The \q{Local username} control allows you to specify what user name +PuTTY should claim you have, in case it doesn't match your \i{Windows +user name} (or in case you didn't bother to set up a Windows user +name). + +\H{config-ssh} The SSH panel + +The \i{SSH} panel allows you to configure options that only apply to +SSH sessions. + +\S{config-command} Executing a specific command on the server + +\cfg{winhelp-topic}{ssh.command} + +In SSH, you don't have to run a general shell session on the server. +Instead, you can choose to run a single specific command (such as a +mail user agent, for example). If you want to do this, enter the +command in the \q{\ii{Remote command}} box. + +Note that most servers will close the session after executing the +command. + +\S{config-ssh-noshell} \q{Don't start a \I{remote shell}shell or +\I{remote command}command at all} + +\cfg{winhelp-topic}{ssh.noshell} + +If you tick this box, PuTTY will not attempt to run a shell or +command after connecting to the remote server. You might want to use +this option if you are only using the SSH connection for \i{port +forwarding}, and your user account on the server does not have the +ability to run a shell. + +This feature is only available in \i{SSH protocol version 2} (since the +version 1 protocol assumes you will always want to run a shell). + +This feature can also be enabled using the \c{-N} command-line +option; see \k{using-cmdline-noshell}. + +If you use this feature in Plink, you will not be able to terminate +the Plink process by any graceful means; the only way to kill it +will be by pressing Control-C or sending a kill signal from another +program. + +\S{config-ssh-comp} \q{Enable \i{compression}} + +\cfg{winhelp-topic}{ssh.compress} + +This enables data compression in the SSH connection: data sent by +the server is compressed before sending, and decompressed at the +client end. Likewise, data sent by PuTTY to the server is compressed +first and the server decompresses it at the other end. This can help +make the most of a low-\i{bandwidth} connection. + +\S{config-ssh-prot} \q{Preferred \i{SSH protocol version}} + +\cfg{winhelp-topic}{ssh.protocol} + +This allows you to select whether you would like to use \i{SSH protocol +version 1} or \I{SSH-2}version 2. \#{FIXME: say something about this elsewhere?} + +PuTTY will attempt to use protocol 1 if the server you connect to +does not offer protocol 2, and vice versa. + +If you select \q{1 only} or \q{2 only} here, PuTTY will only connect +if the server you connect to offers the SSH protocol version you +have specified. + +\S{config-ssh-encryption} \ii{Encryption} algorithm selection + +\cfg{winhelp-topic}{ssh.ciphers} + +PuTTY supports a variety of different \i{encryption algorithm}s, and +allows you to choose which one you prefer to use. You can do this by +dragging the algorithms up and down in the list box (or moving them +using the Up and Down buttons) to specify a preference order. When +you make an SSH connection, PuTTY will search down the list from the +top until it finds an algorithm supported by the server, and then +use that. + +PuTTY currently supports the following algorithms: + +\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC (SSH-2 only) + +\b \i{Arcfour} (RC4) - 256 or 128-bit stream cipher (SSH-2 only) + +\b \i{Blowfish} - 256-bit SDCTR (SSH-2 only) or 128-bit CBC + +\b \ii{Triple-DES} - 168-bit SDCTR (SSH-2 only) or CBC + +\b \ii{Single-DES} - 56-bit CBC (see below for SSH-2) + +If the algorithm PuTTY finds is below the \q{warn below here} line, +you will see a warning box when you make the connection: + +\c The first cipher supported by the server +\c is single-DES, which is below the configured +\c warning threshold. +\c Do you want to continue with this connection? + +This warns you that the first available encryption is not a very +secure one. Typically you would put the \q{warn below here} line +between the encryptions you consider secure and the ones you +consider substandard. By default, PuTTY supplies a preference order +intended to reflect a reasonable preference in terms of security and +speed. + +In SSH-2, the encryption algorithm is negotiated independently for +each direction of the connection, although PuTTY does not support +separate configuration of the preference orders. As a result you may +get two warnings similar to the one above, possibly with different +encryptions. + +Single-DES is not recommended in the SSH-2 protocol +standards, but one or two server implementations do support it. +PuTTY can use single-DES to interoperate with +these servers if you enable the \q{Enable legacy use of single-DES in +SSH-2} option; by default this is disabled and PuTTY will stick to +recommended ciphers. + +\H{config-ssh-kex} The Kex panel + +\# FIXME: This whole section is draft. Feel free to revise. + +The Kex panel (short for \q{\i{key exchange}}) allows you to configure +options related to SSH-2 key exchange. + +Key exchange occurs at the start of an SSH connection (and +occasionally thereafter); it establishes a \i{shared secret} that is used +as the basis for all of SSH's security features. It is therefore very +important for the security of the connection that the key exchange is +secure. + +Key exchange is a cryptographically intensive process; if either the +client or the server is a relatively slow machine, the slower methods +may take several tens of seconds to complete. + +If connection startup is too slow, or the connection hangs +periodically, you may want to try changing these settings. + +If you don't understand what any of this means, it's safe to leave +these settings alone. + +This entire panel is only relevant to SSH protocol version 2; none of +these settings affect SSH-1 at all. + +\S{config-ssh-kex-order} \ii{Key exchange algorithm} selection + +\cfg{winhelp-topic}{ssh.kex.order} + +PuTTY supports a variety of SSH-2 key exchange methods, and allows you +to choose which one you prefer to use; configuration is similar to +cipher selection (see \k{config-ssh-encryption}). + +PuTTY currently supports the following varieties of \i{Diffie-Hellman key +exchange}: + +\b \q{Group 14}: a well-known 2048-bit group. + +\b \q{Group 1}: a well-known 1024-bit group. This is less secure +\#{FIXME better words} than group 14, but may be faster with slow +client or server machines, and may be the only method supported by +older server software. + +\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, if possible. + +In addition, PuTTY supports \i{RSA key exchange}, which 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. + +If the first algorithm PuTTY finds is below the \q{warn below here} +line, you will see a warning box when you make the connection, similar +to that for cipher selection (see \k{config-ssh-encryption}). + +\S{config-ssh-kex-rekey} \ii{Repeat key exchange} + +\cfg{winhelp-topic}{ssh.kex.repeat} + +If the session key negotiated at connection startup is used too much +or for too long, it may become feasible to mount attacks against the +SSH connection. Therefore, the SSH-2 protocol specifies that a new key +exchange should take place every so often; this can be initiated by +either the client or the server. + +While this renegotiation is taking place, no data can pass through +the SSH connection, so it may appear to \q{freeze}. (The occurrence of +repeat key exchange is noted in the Event Log; see +\k{using-eventlog}.) Usually the same algorithm is used as at the +start of the connection, with a similar overhead. + +These options control how often PuTTY will initiate a repeat key +exchange (\q{rekey}). You can also force a key exchange at any time +from the Special Commands menu (see \k{using-specials}). + +\# FIXME: do we have any additions to the SSH-2 specs' advice on +these values? Do we want to enforce any limits? + +\b \q{Max minutes before rekey} specifies the amount of time that is +allowed to elapse before a rekey is initiated. If this is set to zero, +PuTTY will not rekey due to elapsed time. The SSH-2 protocol +specification recommends a timeout of at most 60 minutes. + +You might have a need to disable time-based rekeys completely for the same +reasons that \i{keepalives} aren't always helpful. If you anticipate +suffering a network dropout of several hours in the middle of an SSH +connection, but were not actually planning to send \e{data} down +that connection during those hours, then an attempted rekey in the +middle of the dropout will probably cause the connection to be +abandoned, whereas if rekeys are disabled then the connection should +in principle survive (in the absence of interfering \i{firewalls}). See +\k{config-keepalive} for more discussion of these issues; for these +purposes, rekeys have much the same properties as keepalives. +(Except that rekeys have cryptographic value in themselves, so you +should bear that in mind when deciding whether to turn them off.) +Note, however, the the SSH \e{server} can still initiate rekeys. + +\b \q{Max data before rekey} specifies the amount of data (in bytes) +that is permitted to flow in either direction before a rekey is +initiated. If this is set to zero, PuTTY will not rekey due to +transferred data. The SSH-2 protocol specification recommends a limit +of at most 1 gigabyte. + +\lcont{ + +As well as specifying a value in bytes, the following shorthand can be +used: + +\b \cq{1k} specifies 1 kilobyte (1024 bytes). + +\b \cq{1M} specifies 1 megabyte (1024 kilobytes). + +\b \cq{1G} specifies 1 gigabyte (1024 megabytes). + +} + +Disabling data-based rekeys entirely is a bad idea. The \i{integrity}, +and to a lesser extent, \i{confidentiality} of the SSH-2 protocol depend +in part on rekeys occuring before a 32-bit packet sequence number +wraps around. Unlike time-based rekeys, data-based rekeys won't occur +when the SSH connection is idle, so they shouldn't cause the same +problems. The SSH-1 protocol, incidentally, has even weaker integrity +protection than SSH-2 without rekeys. + +\H{config-ssh-auth} The Auth panel + +The Auth panel allows you to configure \i{authentication} options for +SSH sessions. + +\S{config-ssh-noauth} \q{Bypass authentication entirely} + +\cfg{winhelp-topic}{ssh.auth.bypass} + +In SSH-2, it is possible to establish a connection without using SSH's +mechanisms to identify or authenticate oneself to the server. Some +servers may prefer to handle authentication in the data channel, for +instance, or may simply require no authentication whatsoever. + +By default, PuTTY assumes the server requires authentication (most +do), and thus must provide a username. If you find you are getting +unwanted username prompts, you could try checking this option. + +This option only affects SSH-2 connections. SSH-1 connections always +require an authentication step. + +\S{config-ssh-banner} \q{Display pre-authentication banner} + +\cfg{winhelp-topic}{ssh.auth.banner} + +SSH-2 servers can provide a message for clients to display to the +prospective user before the user logs in; this is sometimes known as a +pre-authentication \q{\i{banner}}. Typically this is used to provide +information about the server and legal notices. + +By default, PuTTY displays this message before prompting for a +password or similar credentials (although, unfortunately, not before +prompting for a login name, due to the nature of the protocol design). +By unchecking this option, display of the banner can be suppressed +entirely. + +\S{config-ssh-tryagent} \q{Attempt authentication using Pageant} + +\cfg{winhelp-topic}{ssh.auth.pageant} + +If this option is enabled, then PuTTY will look for Pageant (the SSH +private-key storage agent) and attempt to authenticate with any +suitable public keys Pageant currently holds. + +This behaviour is almost always desirable, and is therefore enabled +by default. In rare cases you might need to turn it off in order to +force authentication by some non-public-key method such as +passwords. + +This option can also be controlled using the \c{-noagent} +command-line option. See \k{using-cmdline-agentauth}. + +See \k{pageant} for more information about Pageant in general. + +\S{config-ssh-tis} \q{Attempt \I{TIS authentication}TIS or +\i{CryptoCard authentication}} + +\cfg{winhelp-topic}{ssh.auth.tis} + +TIS and CryptoCard authentication are (despite their names) generic +forms of simple \I{challenge/response authentication}challenge/response +authentication available in SSH protocol version 1 only. You might use +them if you were using \i{S/Key} \i{one-time passwords}, for example, +or if you had a physical \i{security token} that generated responses +to authentication challenges. They can even be used to prompt for +simple passwords. + +With this switch enabled, PuTTY will attempt these forms of +authentication if the server is willing to try them. You will be +presented with a challenge string (which may be different every +time) and must supply the correct response in order to log in. If +your server supports this, you should talk to your system +administrator about precisely what form these challenges and +responses take. + +\S{config-ssh-ki} \q{Attempt \i{keyboard-interactive authentication}} + +\cfg{winhelp-topic}{ssh.auth.ki} + +The SSH-2 equivalent of TIS authentication is called +\q{keyboard-interactive}. It is a flexible authentication method +using an arbitrary sequence of requests and responses; so it is not +only useful for \I{challenge/response authentication}challenge/response +mechanisms such as \i{S/Key}, but it can also be used for (for example) +asking the user for a \I{password expiry}new password when the old one +has expired. + +PuTTY leaves this option enabled by default, but supplies a switch +to turn it off in case you should have trouble with it. + +\S{config-ssh-agentfwd} \q{Allow \i{agent forwarding}} + +\cfg{winhelp-topic}{ssh.auth.agentfwd} + +This option allows the SSH server to open forwarded connections back +to your local copy of \i{Pageant}. If you are not running Pageant, this +option will do nothing. + +See \k{pageant} for general information on Pageant, and +\k{pageant-forward} for information on agent forwarding. Note that +there is a security risk involved with enabling this option; see +\k{pageant-security} for details. + +\S{config-ssh-changeuser} \q{Allow attempted \i{changes of username} in SSH-2} + +\cfg{winhelp-topic}{ssh.auth.changeuser} + +In the SSH-1 protocol, it is impossible to change username after +failing to authenticate. So if you mis-type your username at the +PuTTY \q{login as:} prompt, you will not be able to change it except +by restarting PuTTY. + +The SSH-2 protocol \e{does} allow changes of username, in principle, +but does not make it mandatory for SSH-2 servers to accept them. In +particular, \i{OpenSSH} does not accept a change of username; once you +have sent one username, it will reject attempts to try to +authenticate as another user. (Depending on the version of OpenSSH, +it may quietly return failure for all login attempts, or it may send +an error message.) + +For this reason, PuTTY will by default not prompt you for your +username more than once, in case the server complains. If you know +your server can cope with it, you can enable the \q{Allow attempted +changes of username} option to modify PuTTY's behaviour. + +\S{config-ssh-privkey} \q{\ii{Private key} file for authentication} + +\cfg{winhelp-topic}{ssh.auth.privkey} + +This box is where you enter the name of your private key file if you +are using \i{public key authentication}. See \k{pubkey} for information +about public key authentication in SSH. + +This key must be in PuTTY's native format (\c{*.\i{PPK}}). If you have a +private key in another format that you want to use with PuTTY, see +\k{puttygen-conversions}. + +If a key file is specified here, and \i{Pageant} is running (see +\k{pageant}), PuTTY will first try asking Pageant to authenticate with +that key, and ignore any other keys Pageant may have. If that fails, +PuTTY will ask for a passphrase as normal. + +\H{config-ssh-auth-gssapi} The \i{GSSAPI} panel + +\cfg{winhelp-topic}{ssh.auth.gssapi} + +The \q{GSSAPI} subpanel of the \q{Auth} panel controls the use of +GSSAPI authentication. This is a mechanism which delegates the +authentication exchange to a library elsewhere on the client +machine, which in principle can authenticate in many different ways +but in practice is usually used with the \i{Kerberos} \i{single sign-on} +protocol. + +GSSAPI is only available in the SSH-2 protocol. + +The topmost control on the GSSAPI subpanel is the checkbox labelled +\q{Attempt GSSAPI authentication}. If this is disabled, GSSAPI will +not be attempted at all and the rest of this panel is unused. If it +is enabled, GSSAPI authentication will be attempted, and (typically) +if your client machine has valid Kerberos credentials loaded, then +PuTTY should be able to authenticate automatically to servers that +support Kerberos logins. + +\S{config-ssh-auth-gssapi-delegation} \q{Allow GSSAPI credential +delegation} + +\cfg{winhelp-topic}{ssh.auth.gssapi.delegation} + +\i{GSSAPI credential delegation} is a mechanism for passing on your +Kerberos (or other) identity to the session on the SSH server. If +you enable this option, then not only will PuTTY be able to log in +automatically to a server that accepts your Kerberos credentials, +but also you will be able to connect out from that server to other +Kerberos-supporting services and use the same credentials just as +automatically. + +(This option is the Kerberos analogue of SSH agent forwarding; see +\k{pageant-forward} for some information on that.) + +Note that, like SSH agent forwarding, there is a security +implication in the use of this option: the administrator of the +server you connect to, or anyone else who has cracked the +administrator account on that server, could fake your identity when +connecting to further Kerberos-supporting services. However, +Kerberos sites are typically run by a central authority, so the +administrator of one server is likely to already have access to the +other services too; so this would typically be less of a risk than +SSH agent forwarding. + +\S{config-ssh-auth-gssapi-libraries} Preference order for GSSAPI +libraries + +\cfg{winhelp-topic}{ssh.auth.gssapi.libraries} + +GSSAPI is a mechanism which allows more than one authentication +method to be accessed through the same interface. Therefore, more +than one authentication library may exist on your system which can +be accessed using GSSAPI. + +PuTTY contains native support for a few well-known such libraries, +and will look for all of them on your system and use whichever it +finds. If more than one exists on your system and you need to use a +specific one, you can adjust the order in which it will search using +this preference list control. + +One of the options in the preference list is to use a user-specified +GSSAPI library. If the library you want to use is not mentioned by +name in PuTTY's list of options, you can enter its full pathname in +the \q{User-supplied GSSAPI library path} field, and move the +\q{User-supplied GSSAPI library} option in the preference list to +make sure it is selected before anything else. + +\H{config-ssh-tty} The TTY panel + +The TTY panel lets you configure the remote pseudo-terminal. + +\S{config-ssh-pty} \I{pseudo-terminal allocation}\q{Don't allocate +a pseudo-terminal} + +\cfg{winhelp-topic}{ssh.nopty} + +When connecting to a \i{Unix} system, most \I{interactive +connections}interactive shell sessions are run in a \e{pseudo-terminal}, +which allows the Unix system to pretend it's talking to a real physical +terminal device but allows the SSH server to catch all the data coming +from that fake device and send it back to the client. + +Occasionally you might find you have a need to run a session \e{not} +in a pseudo-terminal. In PuTTY, this is generally only useful for +very specialist purposes; although in Plink (see \k{plink}) it is +the usual way of working. + +\S{config-ttymodes} Sending \i{terminal modes} + +\cfg{winhelp-topic}{ssh.ttymodes} + +The SSH protocol allows the client to send \q{terminal modes} for +the remote pseudo-terminal. These usually control the server's +expectation of the local terminal's behaviour. + +If your server does not have sensible defaults for these modes, you +may find that changing them here helps. If you don't understand any of +this, it's safe to leave these settings alone. + +(None of these settings will have any effect if no pseudo-terminal +is requested or allocated.) + +You can add or modify a mode by selecting it from the drop-down list, +choosing whether it's set automatically or to a specific value with +the radio buttons and edit box, and hitting \q{Add}. A mode (or +several) can be removed from the list by selecting them and hitting +\q{Remove}. The effect of the mode list is as follows: + +\b If a mode is not on the list, it will not be specified to the +server under any circumstances. + +\b If a mode is on the list: + +\lcont{ + +\b If the \q{Auto} option is selected, the PuTTY tools will decide +whether to specify that mode to the server, and if so, will send +a sensible value. + +\lcont{ + +PuTTY proper will send modes that it has an opinion on (currently only +the code for the Backspace key, \cw{ERASE}). Plink on Unix +will propagate appropriate modes from the local terminal, if any. + +} + +\b If a value is specified, it will be sent to the server under all +circumstances. The precise syntax of the value box depends on the +mode. + +} + +By default, all of the available modes are listed as \q{Auto}, +which should do the right thing in most circumstances. + +The precise effect of each setting, if any, is up to the server. Their +names come from \i{POSIX} and other Unix systems, and they are most +likely to have a useful effect on such systems. (These are the same +settings that can usually be changed using the \i\c{stty} command once +logged in to such servers.) + +Some notable modes are described below; for fuller explanations, see +your server documentation. + +\b \I{ERASE special character}\cw{ERASE} is the character that when typed +by the user will delete one space to the left. When set to \q{Auto} +(the default setting), this follows the setting of the local Backspace +key in PuTTY (see \k{config-backspace}). + +\lcont{ +This and other \i{special character}s are specified using \c{^C} notation +for Ctrl-C, and so on. Use \c{^<27>} or \c{^<0x1B>} to specify a +character numerically, and \c{^~} to get a literal \c{^}. Other +non-control characters are denoted by themselves. Leaving the box +entirely blank indicates that \e{no} character should be assigned to +the specified function, although this may not be supported by all +servers. +} + +\b \I{QUIT special character}\cw{QUIT} is a special character that +usually forcefully ends the current process on the server +(\cw{SIGQUIT}). On many servers its default setting is Ctrl-backslash +(\c{^\\}), which is easy to accidentally invoke on many keyboards. If +this is getting in your way, you may want to change it to another +character or turn it off entirely. + +\b Boolean modes such as \cw{ECHO} and \cw{ICANON} can be specified in +PuTTY in a variety of ways, such as \cw{true}/\cw{false}, +\cw{yes}/\cw{no}, and \cw{0}/\cw{1}. + +\b Terminal speeds are configured elsewhere; see \k{config-termspeed}. + +\H{config-ssh-x11} The X11 panel + +\cfg{winhelp-topic}{ssh.tunnels.x11} + +The X11 panel allows you to configure \i{forwarding of X11} over an +SSH connection. + +If your server lets you run X Window System applications, X11 +forwarding allows you to securely give those applications access to +a local X display on your PC. + +To enable X11 forwarding, check the \q{Enable X11 forwarding} box. +If your X display is somewhere unusual, you will need to enter its +location in the \q{X display location} box; if this is left blank, +PuTTY will try to find a sensible default in the environment, or use the +primary local display (\c{:0}) if that fails. + +See \k{using-x-forwarding} for more information about X11 +forwarding. + +\S{config-ssh-x11auth} Remote \i{X11 authentication} + +\cfg{winhelp-topic}{ssh.tunnels.x11auth} + +If you are using X11 forwarding, the virtual X server created on the +SSH server machine will be protected by authorisation data. This +data is invented, and checked, by PuTTY. + +The usual authorisation method used for this is called +\i\cw{MIT-MAGIC-COOKIE-1}. This is a simple password-style protocol: +the X client sends some cookie data to the server, and the server +checks that it matches the real cookie. The cookie data is sent over +an unencrypted X11 connection; so if you allow a client on a third +machine to access the virtual X server, then the cookie will be sent +in the clear. + +PuTTY offers the alternative protocol \i\cw{XDM-AUTHORIZATION-1}. This +is a cryptographically authenticated protocol: the data sent by the +X client is different every time, and it depends on the IP address +and port of the client's end of the connection and is also stamped +with the current time. So an eavesdropper who captures an +\cw{XDM-AUTHORIZATION-1} string cannot immediately re-use it for +their own X connection. + +PuTTY's support for \cw{XDM-AUTHORIZATION-1} is a somewhat +experimental feature, and may encounter several problems: + +\b Some X clients probably do not even support +\cw{XDM-AUTHORIZATION-1}, so they will not know what to do with the +data PuTTY has provided. + +\b This authentication mechanism will only work in SSH-2. In SSH-1, +the SSH server does not tell the client the source address of +a forwarded connection in a machine-readable format, so it's +impossible to verify the \cw{XDM-AUTHORIZATION-1} data. + +\b You may find this feature causes problems with some SSH servers, +which will not clean up \cw{XDM-AUTHORIZATION-1} data after a +session, so that if you then connect to the same server using +a client which only does \cw{MIT-MAGIC-COOKIE-1} and are allocated +the same remote display number, you might find that out-of-date +authentication data is still present on your server and your X +connections fail. + +PuTTY's default is \cw{MIT-MAGIC-COOKIE-1}. If you change it, you +should be sure you know what you're doing. + +\S{config-ssh-xauthority} X authority file for local display + +\cfg{winhelp-topic}{ssh.tunnels.xauthority} + +If you are using X11 forwarding, the local X server to which your +forwarded connections are eventually directed may itself require +authorisation. + +Some Windows X servers do not require this: they do authorisation by +simpler means, such as accepting any connection from the local +machine but not from anywhere else. However, if your X server does +require authorisation, then PuTTY needs to know what authorisation +is required. + +One way in which this data might be made available is for the X +server to store it somewhere in a file which has the same format +as the Unix \c{.Xauthority} file. If this is how your Windows X +server works, then you can tell PuTTY where to find this file by +configuring this option. By default, PuTTY will not attempt to find +any authorisation for your local display. + +\H{config-ssh-portfwd} \I{port forwarding}The Tunnels panel + +\cfg{winhelp-topic}{ssh.tunnels.portfwd} + +The Tunnels panel allows you to configure tunnelling of arbitrary +connection types through an SSH connection. + +Port forwarding allows you to tunnel other types of \i{network +connection} down an SSH session. See \k{using-port-forwarding} for a +general discussion of port forwarding and how it works. + +The port forwarding section in the Tunnels panel shows a list of all +the port forwardings that PuTTY will try to set up when it connects +to the server. By default no port forwardings are set up, so this +list is empty. + +To add a port forwarding: + +\b Set one of the \q{Local} or \q{Remote} radio buttons, depending +on whether you want to \I{local port forwarding}forward a local port +to a remote destination (\q{Local}) or \I{remote port forwarding}forward +a remote port to a local destination (\q{Remote}). Alternatively, +select \q{Dynamic} if you want PuTTY to \I{dynamic port forwarding}provide +a local SOCKS 4/4A/5 proxy on a local port (note that this proxy only +supports TCP connections; the SSH protocol does not support forwarding +\i{UDP}). + +\b Enter a source \i{port number} into the \q{Source port} box. For +local forwardings, PuTTY will listen on this port of your PC. For +remote forwardings, your SSH server will listen on this port of the +remote machine. Note that most servers will not allow you to listen +on \I{privileged port}port numbers less than 1024. + +\b If you have selected \q{Local} or \q{Remote} (this step is not +needed with \q{Dynamic}), enter a hostname and port number separated +by a colon, in the \q{Destination} box. Connections received on the +source port will be directed to this destination. For example, to +connect to a POP-3 server, you might enter +\c{popserver.example.com:110}. + +\b Click the \q{Add} button. Your forwarding details should appear +in the list box. + +To remove a port forwarding, simply select its details in the list +box, and click the \q{Remove} button. + +In the \q{Source port} box, you can also optionally enter an \I{listen +address}IP address to listen on, by specifying (for instance) +\c{127.0.0.5:79}. +See \k{using-port-forwarding} for more information on how this +works and its restrictions. + +In place of port numbers, you can enter \i{service names}, if they are +known to the local system. For instance, in the \q{Destination} box, +you could enter \c{popserver.example.com:pop3}. + +You can \I{port forwarding, changing mid-session}modify the currently +active set of port forwardings in mid-session using \q{Change +Settings} (see \k{using-changesettings}). If you delete a local or +dynamic port forwarding in mid-session, PuTTY will stop listening for +connections on that port, so it can be re-used by another program. If +you delete a remote port forwarding, note that: + +\b The SSH-1 protocol contains no mechanism for asking the server to +stop listening on a remote port. + +\b The SSH-2 protocol does contain such a mechanism, but not all SSH +servers support it. (In particular, \i{OpenSSH} does not support it in +any version earlier than 3.9.) + +If you ask to delete a remote port forwarding and PuTTY cannot make +the server actually stop listening on the port, it will instead just +start refusing incoming connections on that port. Therefore, +although the port cannot be reused by another program, you can at +least be reasonably sure that server-side programs can no longer +access the service at your end of the port forwarding. + +If you delete a forwarding, any existing connections established using +that forwarding remain open. Similarly, changes to global settings +such as \q{Local ports accept connections from other hosts} only take +effect on new forwardings. + +If the connection you are forwarding over SSH is itself a second SSH +connection made by another copy of PuTTY, you might find the +\q{logical host name} configuration option useful to warn PuTTY of +which host key it should be expecting. See \k{config-loghost} for +details of this. + +\S{config-ssh-portfwd-localhost} Controlling the visibility of +forwarded ports + +\cfg{winhelp-topic}{ssh.tunnels.portfwd.localhost} + +The source port for a forwarded connection usually does not accept +connections from any machine except the \I{localhost}SSH client or +server machine itself (for local and remote forwardings respectively). +There are controls in the Tunnels panel to change this: + +\b The \q{Local ports accept connections from other hosts} option +allows you to set up local-to-remote port forwardings in such a way +that machines other than your client PC can connect to the forwarded +port. (This also applies to dynamic SOCKS forwarding.) + +\b The \q{Remote ports do the same} option does the same thing for +remote-to-local port forwardings (so that machines other than the +SSH server machine can connect to the forwarded port.) Note that +this feature is only available in the SSH-2 protocol, and not all +SSH-2 servers support it (\i{OpenSSH} 3.0 does not, for example). + +\S{config-ssh-portfwd-address-family} Selecting \i{Internet protocol +version} for forwarded ports + +\cfg{winhelp-topic}{ssh.tunnels.portfwd.ipversion} + +This switch allows you to select a specific Internet protocol (\i{IPv4} +or \i{IPv6}) for the local end of a forwarded port. By default, it is +set on \q{Auto}, which means that: + +\b for a local-to-remote port forwarding, PuTTY will listen for +incoming connections in both IPv4 and (if available) IPv6 + +\b for a remote-to-local port forwarding, PuTTY will choose a +sensible protocol for the outgoing connection. + +This overrides the general Internet protocol version preference +on the Connection panel (see \k{config-address-family}). + +Note that some operating systems may listen for incoming connections +in IPv4 even if you specifically asked for IPv6, because their IPv4 +and IPv6 protocol stacks are linked together. Apparently \i{Linux} does +this, and Windows does not. So if you're running PuTTY on Windows +and you tick \q{IPv6} for a local or dynamic port forwarding, it +will \e{only} be usable by connecting to it using IPv6; whereas if +you do the same on Linux, you can also use it with IPv4. However, +ticking \q{Auto} should always give you a port which you can connect +to using either protocol. + +\H{config-ssh-bugs} \I{SSH server bugs}The Bugs panel + +Not all SSH servers work properly. Various existing servers have +bugs in them, which can make it impossible for a client to talk to +them unless it knows about the bug and works around it. + +Since most servers announce their software version number at the +beginning of the SSH connection, PuTTY will attempt to detect which +bugs it can expect to see in the server and automatically enable +workarounds. However, sometimes it will make mistakes; if the server +has been deliberately configured to conceal its version number, or +if the server is a version which PuTTY's bug database does not know +about, then PuTTY will not know what bugs to expect. + +The Bugs panel allows you to manually configure the bugs PuTTY +expects to see in the server. Each bug can be configured in three +states: + +\b \q{Off}: PuTTY will assume the server does not have the bug. + +\b \q{On}: PuTTY will assume the server \e{does} have the bug. + +\b \q{Auto}: PuTTY will use the server's version number announcement +to try to guess whether or not the server has the bug. + +\S{config-ssh-bug-ignore1} \q{Chokes on SSH-1 \i{ignore message}s} + +\cfg{winhelp-topic}{ssh.bugs.ignore1} + +An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol +which can be sent from the client to the server, or from the server +to the client, at any time. Either side is required to ignore the +message whenever it receives it. PuTTY uses ignore messages to +\I{password camouflage}hide the password packet in SSH-1, so that +a listener cannot tell the length of the user's password; it also +uses ignore messages for connection \i{keepalives} (see +\k{config-keepalive}). + +If this bug is detected, PuTTY will stop using ignore messages. This +means that keepalives will stop working, and PuTTY will have to fall +back to a secondary defence against SSH-1 password-length +eavesdropping. See \k{config-ssh-bug-plainpw1}. If this bug is +enabled when talking to a correct server, the session will succeed, +but keepalives will not work and the session might be more +vulnerable to eavesdroppers than it could be. + +\S{config-ssh-bug-plainpw1} \q{Refuses all SSH-1 \i{password camouflage}} + +\cfg{winhelp-topic}{ssh.bugs.plainpw1} + +When talking to an SSH-1 server which cannot deal with ignore +messages (see \k{config-ssh-bug-ignore1}), PuTTY will attempt to +disguise the length of the user's password by sending additional +padding \e{within} the password packet. This is technically a +violation of the SSH-1 specification, and so PuTTY will only do it +when it cannot use standards-compliant ignore messages as +camouflage. In this sense, for a server to refuse to accept a padded +password packet is not really a bug, but it does make life +inconvenient if the server can also not handle ignore messages. + +If this \q{bug} is detected, PuTTY will assume that neither ignore +messages nor padding are acceptable, and that it thus has no choice +but to send the user's password with no form of camouflage, so that +an eavesdropping user will be easily able to find out the exact length +of the password. If this bug is enabled when talking to a correct +server, the session will succeed, but will be more vulnerable to +eavesdroppers than it could be. + +This is an SSH-1-specific bug. SSH-2 is secure against this type of +attack. + +\S{config-ssh-bug-rsa1} \q{Chokes on SSH-1 \i{RSA} authentication} + +\cfg{winhelp-topic}{ssh.bugs.rsa1} + +Some SSH-1 servers cannot deal with RSA authentication messages at +all. If \i{Pageant} is running and contains any SSH-1 keys, PuTTY will +normally automatically try RSA authentication before falling back to +passwords, so these servers will crash when they see the RSA attempt. + +If this bug is detected, PuTTY will go straight to password +authentication. If this bug is enabled when talking to a correct +server, the session will succeed, but of course RSA authentication +will be impossible. + +This is an SSH-1-specific bug. + +\S{config-ssh-bug-ignore2} \q{Chokes on SSH-2 \i{ignore message}s} + +\cfg{winhelp-topic}{ssh.bugs.ignore2} + +An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol +which can be sent from the client to the server, or from the server +to the client, at any time. Either side is required to ignore the +message whenever it receives it. PuTTY uses ignore messages in SSH-2 +to confuse the encrypted data stream and make it harder to +cryptanalyse. It also uses ignore messages for connection +\i{keepalives} (see \k{config-keepalive}). + +If it believes the server to have this bug, PuTTY will stop using +ignore messages. If this bug is enabled when talking to a correct +server, the session will succeed, but keepalives will not work and +the session might be less cryptographically secure than it could be. + +\S{config-ssh-bug-hmac2} \q{Miscomputes SSH-2 HMAC keys} + +\cfg{winhelp-topic}{ssh.bugs.hmac2} + +Versions 2.3.0 and below of the SSH server software from +\cw{ssh.com} compute the keys for their \i{HMAC} \i{message authentication +code}s incorrectly. A typical symptom of this problem is that PuTTY +dies unexpectedly at the beginning of the session, saying +\q{Incorrect MAC received on packet}. + +If this bug is detected, PuTTY will compute its HMAC keys in the +same way as the buggy server, so that communication will still be +possible. If this bug is enabled when talking to a correct server, +communication will fail. + +This is an SSH-2-specific bug. + +\S{config-ssh-bug-derivekey2} \q{Miscomputes SSH-2 \i{encryption} keys} + +\cfg{winhelp-topic}{ssh.bugs.derivekey2} + +Versions below 2.0.11 of the SSH server software from \i\cw{ssh.com} +compute the keys for the session encryption incorrectly. This +problem can cause various error messages, such as \q{Incoming packet +was garbled on decryption}, or possibly even \q{Out of memory}. + +If this bug is detected, PuTTY will compute its encryption keys in +the same way as the buggy server, so that communication will still +be possible. If this bug is enabled when talking to a correct +server, communication will fail. + +This is an SSH-2-specific bug. + +\S{config-ssh-bug-sig} \q{Requires padding on SSH-2 \i{RSA} \i{signatures}} + +\cfg{winhelp-topic}{ssh.bugs.rsapad2} + +Versions below 3.3 of \i{OpenSSH} require SSH-2 RSA signatures to be +padded with zero bytes to the same length as the RSA key modulus. +The SSH-2 specification says that an unpadded signature MUST be +accepted, so this is a bug. A typical symptom of this problem is +that PuTTY mysteriously fails RSA authentication once in every few +hundred attempts, and falls back to passwords. + +If this bug is detected, PuTTY will pad its signatures in the way +OpenSSH expects. If this bug is enabled when talking to a correct +server, it is likely that no damage will be done, since correct +servers usually still accept padded signatures because they're used +to talking to OpenSSH. + +This is an SSH-2-specific bug. + +\S{config-ssh-bug-pksessid2} \q{Misuses the \i{session ID} in SSH-2 PK auth} + +\cfg{winhelp-topic}{ssh.bugs.pksessid2} + +Versions below 2.3 of \i{OpenSSH} require SSH-2 \i{public-key authentication} +to be done slightly differently: the data to be signed by the client +contains the session ID formatted in a different way. If public-key +authentication mysteriously does not work but the Event Log (see +\k{using-eventlog}) thinks it has successfully sent a signature, it +might be worth enabling the workaround for this bug to see if it +helps. + +If this bug is detected, PuTTY will sign data in the way OpenSSH +expects. If this bug is enabled when talking to a correct server, +SSH-2 public-key authentication will fail. + +This is an SSH-2-specific bug. + +\S{config-ssh-bug-rekey} \q{Handles SSH-2 key re-exchange badly} + +\cfg{winhelp-topic}{ssh.bugs.rekey2} + +Some SSH servers cannot cope with \i{repeat key exchange} at +all, and will ignore attempts by the client to start one. Since +PuTTY pauses the session while performing a repeat key exchange, the +effect of this would be to cause the session to hang after an hour +(unless you have your rekey timeout set differently; see +\k{config-ssh-kex-rekey} for more about rekeys). +Other, very old, SSH servers handle repeat key exchange even more +badly, and disconnect upon receiving a repeat key exchange request. + +If this bug is detected, PuTTY will never initiate a repeat key +exchange. If this bug is enabled when talking to a correct server, +the session should still function, but may be less secure than you +would expect. + +This is an SSH-2-specific bug. + +\S{config-ssh-bug-maxpkt2} \q{Ignores SSH-2 \i{maximum packet size}} + +\cfg{winhelp-topic}{ssh.bugs.maxpkt2} + +When an SSH-2 channel is set up, each end announces the maximum size +of data packet that it is willing to receive for that channel. Some +servers ignore PuTTY's announcement and send packets larger than PuTTY +is willing to accept, causing it to report \q{Incoming packet was +garbled on decryption}. + +If this bug is detected, PuTTY never allows the channel's +\i{flow-control window} to grow large enough to allow the server to +send an over-sized packet. If this bug is enabled when talking to a +correct server, the session will work correctly, but download +performance will be less than it could be. + +\H{config-serial} The Serial panel + +The \i{Serial} panel allows you to configure options that only apply +when PuTTY is connecting to a local \I{serial port}\i{serial line}. + +\S{config-serial-line} Selecting a serial line to connect to + +\cfg{winhelp-topic}{serial.line} + +The \q{Serial line to connect to} box allows you to choose which +serial line you want PuTTY to talk to, if your computer has more +than one serial port. + +On Windows, the first serial line is called \i\cw{COM1}, and if there +is a second it is called \cw{COM2}, and so on. + +This configuration setting is also visible on the Session panel, +where it replaces the \q{Host Name} box (see \k{config-hostname}) if +the connection type is set to \q{Serial}. + +\S{config-serial-speed} Selecting the speed of your serial line + +\cfg{winhelp-topic}{serial.speed} + +The \q{Speed} box allows you to choose the speed (or \q{baud rate}) +at which to talk to the serial line. Typical values might be 9600, +19200, 38400 or 57600. Which one you need will depend on the device +at the other end of the serial cable; consult the manual for that +device if you are in doubt. + +This configuration setting is also visible on the Session panel, +where it replaces the \q{Port} box (see \k{config-hostname}) if the +connection type is set to \q{Serial}. + +\S{config-serial-databits} Selecting the number of data bits + +\cfg{winhelp-topic}{serial.databits} + +The \q{Data bits} box allows you to choose how many data bits are +transmitted in each byte sent or received through the serial line. +Typical values are 7 or 8. + +\S{config-serial-stopbits} Selecting the number of stop bits + +\cfg{winhelp-topic}{serial.stopbits} + +The \q{Stop bits} box allows you to choose how many stop bits are +used in the serial line protocol. Typical values are 1, 1.5 or 2. + +\S{config-serial-parity} Selecting the serial parity checking scheme + +\cfg{winhelp-topic}{serial.parity} + +The \q{Parity} box allows you to choose what type of parity checking +is used on the serial line. The settings are: + +\b \q{None}: no parity bit is sent at all. + +\b \q{Odd}: an extra parity bit is sent alongside each byte, and +arranged so that the total number of 1 bits is odd. + +\b \q{Even}: an extra parity bit is sent alongside each byte, and +arranged so that the total number of 1 bits is even. + +\b \q{Mark}: an extra parity bit is sent alongside each byte, and +always set to 1. + +\b \q{Space}: an extra parity bit is sent alongside each byte, and +always set to 0. + +\S{config-serial-flow} Selecting the serial flow control scheme + +\cfg{winhelp-topic}{serial.flow} + +The \q{Flow control} box allows you to choose what type of flow +control checking is used on the serial line. The settings are: + +\b \q{None}: no flow control is done. Data may be lost if either +side attempts to send faster than the serial line permits. + +\b \q{XON/XOFF}: flow control is done by sending XON and XOFF +characters within the data stream. + +\b \q{RTS/CTS}: flow control is done using the RTS and CTS wires on +the serial line. + +\b \q{DSR/DTR}: flow control is done using the DSR and DTR wires on +the serial line. + +\H{config-file} \ii{Storing configuration in a file} + +PuTTY does not currently support storing its configuration in a file +instead of the \i{Registry}. However, you can work around this with a +couple of \i{batch file}s. + +You will need a file called (say) \c{PUTTY.BAT} which imports the +contents of a file into the Registry, then runs PuTTY, exports the +contents of the Registry back into the file, and deletes the +Registry entries. This can all be done using the Regedit command +line options, so it's all automatic. Here is what you need in +\c{PUTTY.BAT}: + +\c @ECHO OFF +\c regedit /s putty.reg +\c regedit /s puttyrnd.reg +\c start /w putty.exe +\c regedit /ea new.reg HKEY_CURRENT_USER\Software\SimonTatham\PuTTY +\c copy new.reg putty.reg +\c del new.reg +\c regedit /s puttydel.reg + +This batch file needs two auxiliary files: \c{PUTTYRND.REG} which +sets up an initial safe location for the \c{PUTTY.RND} random seed +file, and \c{PUTTYDEL.REG} which destroys everything in the Registry +once it's been successfully saved back to the file. + +Here is \c{PUTTYDEL.REG}: + +\c REGEDIT4 +\c +\c [-HKEY_CURRENT_USER\Software\SimonTatham\PuTTY] + +Here is an example \c{PUTTYRND.REG} file: + +\c REGEDIT4 +\c +\c [HKEY_CURRENT_USER\Software\SimonTatham\PuTTY] +\c "RandSeedFile"="a:\\putty.rnd" + +You should replace \c{a:\\putty.rnd} with the location where you +want to store your random number data. If the aim is to carry around +PuTTY and its settings on one floppy, you probably want to store it +on the floppy. diff --git a/putty/DOC/ERRORS.BUT b/putty/DOC/ERRORS.BUT new file mode 100644 index 0000000..4de6713 --- /dev/null +++ b/putty/DOC/ERRORS.BUT @@ -0,0 +1,356 @@ +\define{versioniderrors} \versionid $Id: errors.but 8897 2010-03-13 14:47:14Z jacob $ + +\C{errors} Common \i{error messages} + +This chapter lists a number of common error messages which PuTTY and +its associated tools can produce, and explains what they mean in +more detail. + +We do not attempt to list \e{all} error messages here: there are +many which should never occur, and some which should be +self-explanatory. If you get an error message which is not listed in +this chapter and which you don't understand, report it to us as a +bug (see \k{feedback}) and we will add documentation for it. + +\H{errors-hostkey-absent} \q{The server's host key is not cached in +the registry} + +\cfg{winhelp-topic}{errors.hostkey.absent} + +This error message occurs when PuTTY connects to a new SSH server. +Every server identifies itself by means of a host key; once PuTTY +knows the host key for a server, it will be able to detect if a +malicious attacker redirects your connection to another machine. + +If you see this message, it means that PuTTY has not seen this host +key before, and has no way of knowing whether it is correct or not. +You should attempt to verify the host key by other means, such as +asking the machine's administrator. + +If you see this message and you know that your installation of PuTTY +\e{has} connected to the same server before, it may have been +recently upgraded to SSH protocol version 2. SSH protocols 1 and 2 +use separate host keys, so when you first use \i{SSH-2} with a server +you have only used SSH-1 with before, you will see this message +again. You should verify the correctness of the key as before. + +See \k{gs-hostkey} for more information on host keys. + +\H{errors-hostkey-wrong} \q{WARNING - POTENTIAL SECURITY BREACH!} + +\cfg{winhelp-topic}{errors.hostkey.changed} + +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 +connected to the SSH server before, knows what its host key +\e{should} be, but has found a different one. + +This may mean that a malicious attacker has replaced your server +with a different one, or has redirected your network connection to +their own machine. On the other hand, it may simply mean that the +administrator of your server has accidentally changed the key while +upgrading the SSH software; this \e{shouldn't} happen but it is +unfortunately possible. + +You should contact your server's administrator and see whether they +expect the host key to have changed. If so, verify the new host key +in the same way as you would if it was new. + +See \k{gs-hostkey} for more information on host keys. + +\H{errors-portfwd-space} \q{Out of space for port forwardings} + +PuTTY has a fixed-size buffer which it uses to store the details of +all \i{port forwardings} you have set up in an SSH session. If you +specify too many port forwardings on the PuTTY or Plink command line +and this buffer becomes full, you will see this error message. + +We need to fix this (fixed-size buffers are almost always a mistake) +but we haven't got round to it. If you actually have trouble with +this, let us know and we'll move it up our priority list. + +If you're running into this limit, you may want to consider using +dynamic port forwarding instead; see \k{using-port-forwarding}. + +\H{errors-cipher-warning} \q{The first cipher supported by the server is +... below the configured warning threshold} + +This occurs when the SSH server does not offer any ciphers which you +have configured PuTTY to consider strong enough. By default, PuTTY +puts up this warning only for \ii{single-DES} and \i{Arcfour} encryption. + +See \k{config-ssh-encryption} for more information on this message. + +\H{errors-toomanyauth} \q{Server sent disconnect message type 2 +(protocol error): "Too many authentication failures for root"} + +This message is produced by an \i{OpenSSH} (or \i{Sun SSH}) server if it +receives more failed authentication attempts than it is willing to +tolerate. + +This can easily happen if you are using Pageant and have a +large number of keys loaded into it, since these servers count each +offer of a public key as an authentication attempt. This can be worked +around by specifying the key that's required for the authentication in +the PuTTY configuration (see \k{config-ssh-privkey}); PuTTY will ignore +any other keys Pageant may have, but will ask Pageant to do the +authentication, so that you don't have to type your passphrase. + +On the server, this can be worked around by disabling public-key +authentication or (for Sun SSH only) by increasing \c{MaxAuthTries} in +\c{sshd_config}. + +\H{errors-memory} \q{\ii{Out of memory}} + +This occurs when PuTTY tries to allocate more memory than the system +can give it. This \e{may} happen for genuine reasons: if the +computer really has run out of memory, or if you have configured an +extremely large number of lines of scrollback in your terminal. +PuTTY is not able to recover from running out of memory; it will +terminate immediately after giving this error. + +However, this error can also occur when memory is not running out at +all, because PuTTY receives data in the wrong format. In SSH-2 and +also in SFTP, the server sends the length of each message before the +message itself; so PuTTY will receive the length, try to allocate +space for the message, and then receive the rest of the message. If +the length PuTTY receives is garbage, it will try to allocate a +ridiculous amount of memory, and will terminate with an \q{Out of +memory} error. + +This can happen in SSH-2, if PuTTY and the server have not enabled +encryption in the same way (see \k{faq-outofmem} in the FAQ). Some +versions of \i{OpenSSH} have a known problem with this: see +\k{faq-openssh-bad-openssl}. + +This can also happen in PSCP or PSFTP, if your \i{login scripts} on the +server generate output: the client program will be expecting an SFTP +message starting with a length, and if it receives some text from +your login scripts instead it will try to interpret them as a +message length. See \k{faq-outofmem2} for details of this. + +\H{errors-internal} \q{\ii{Internal error}}, \q{\ii{Internal fault}}, +\q{\ii{Assertion failed}} + +Any error beginning with the word \q{Internal} should \e{never} +occur. If it does, there is a bug in PuTTY by definition; please see +\k{feedback} and report it to us. + +Similarly, any error message starting with \q{Assertion failed} is a +bug in PuTTY. Please report it to us, and include the exact text +from the error message box. + +\H{errors-cant-load-key} \q{Unable to use this private key file}, +\q{Couldn't load private key}, \q{Key is of wrong type} + +\cfg{winhelp-topic}{errors.cantloadkey} + +Various forms of this error are printed in the PuTTY window, or +written to the PuTTY Event Log (see \k{using-eventlog}) when trying +public-key authentication, or given by Pageant when trying to load a +private key. + +If you see one of these messages, it often indicates that you've tried +to load a key of an inappropriate type into PuTTY, Plink, PSCP, PSFTP, +or Pageant. + +You may have specified a key that's inappropriate for the connection +you're making. The SSH-1 and SSH-2 protocols require different private +key formats, and a SSH-1 key can't be used for a SSH-2 connection (or +vice versa). + +Alternatively, you may have tried to load an SSH-2 key in a \q{foreign} +format (OpenSSH or \cw{ssh.com}) directly into one of the PuTTY tools, +in which case you need to import it into PuTTY's native format +(\c{*.PPK}) using PuTTYgen - see \k{puttygen-conversions}. + +\H{errors-refused} \q{Server refused our public key} or \q{Key +refused} + +Various forms of this error are printed in the PuTTY window, or +written to the PuTTY Event Log (see \k{using-eventlog}) when trying +public-key authentication. + +If you see one of these messages, it means that PuTTY has sent a +public key to the server and offered to authenticate with it, and +the server has refused to accept authentication. This usually means +that the server is not configured to accept this key to authenticate +this user. + +This is almost certainly not a problem with PuTTY. If you see this +type of message, the first thing you should do is check your +\e{server} configuration carefully. Common errors include having +the wrong permissions or ownership set on the public key or the +user's home directory on the server. Also, read the PuTTY Event Log; +the server may have sent diagnostic messages explaining exactly what +problem it had with your setup. + +\K{pubkey-gettingready} has some hints on server-side public key +setup. + +\H{errors-access-denied} \q{Access denied}, \q{Authentication refused} + +Various forms of this error are printed in the PuTTY window, or +written to the PuTTY Event Log (see \k{using-eventlog}) during +authentication. + +If you see one of these messages, it means that the server has refused +all the forms of authentication PuTTY has tried and it has no further +ideas. + +It may be worth checking the Event Log for diagnostic messages from +the server giving more detail. + +This error can be caused by buggy SSH-1 servers that fail to cope with +the various strategies we use for camouflaging passwords in transit. +Upgrade your server, or use the workarounds described in +\k{config-ssh-bug-ignore1} and possibly \k{config-ssh-bug-plainpw1}. + +\H{errors-no-auth} \q{No supported authentication methods available} + +This error indicates that PuTTY has run out of ways to authenticate +you to an SSH server. This may be because PuTTY has TIS or +keyboard-interactive authentication disabled, in which case +\k{config-ssh-tis} and \k{config-ssh-ki}. + +\H{errors-crc} \q{Incorrect \i{CRC} received on packet} or \q{Incorrect +\i{MAC} received on packet} + +This error occurs when PuTTY decrypts an SSH packet and its checksum +is not correct. This probably means something has gone wrong in the +encryption or decryption process. It's difficult to tell from this +error message whether the problem is in the client, in the server, +or in between. + +In particular, if the network is corrupting data at the TCP level, it +may only be obvious with cryptographic protocols such as SSH, which +explicitly check the integrity of the transferred data and complain +loudly if the checks fail. Corruption of protocols without integrity +protection (such as HTTP) will manifest in more subtle failures (such +as misdisplayed text or images in a web browser) which may not be +noticed. + +A known server problem which can cause this error is described in +\k{faq-openssh-bad-openssl} in the FAQ. + +\H{errors-garbled} \q{Incoming packet was garbled on decryption} + +This error occurs when PuTTY decrypts an SSH packet and the +decrypted data makes no sense. This probably means something has +gone wrong in the encryption or decryption process. It's difficult +to tell from this error message whether the problem is in the client, +in the server, or in between. + +If you get this error, one thing you could try would be to fiddle with +the setting of \q{Miscomputes SSH-2 encryption keys} (see +\k{config-ssh-bug-derivekey2}) or \q{Ignores SSH-2 maximum packet +size} (see \k{config-ssh-bug-maxpkt2}) on the Bugs panel . + +Another known server problem which can cause this error is described +in \k{faq-openssh-bad-openssl} in the FAQ. + +\H{errors-x11-proxy} \q{PuTTY X11 proxy: \e{various errors}} + +This family of errors are reported when PuTTY is doing X forwarding. +They are sent back to the X application running on the SSH server, +which will usually report the error to the user. + +When PuTTY enables X forwarding (see \k{using-x-forwarding}) it +creates a virtual X display running on the SSH server. This display +requires authentication to connect to it (this is how PuTTY prevents +other users on your server machine from connecting through the PuTTY +proxy to your real X display). PuTTY also sends the server the +details it needs to enable clients to connect, and the server should +put this mechanism in place automatically, so your X applications +should just work. + +A common reason why people see one of these messages is because they +used SSH to log in as one user (let's say \q{fred}), and then used +the Unix \c{su} command to become another user (typically \q{root}). +The original user, \q{fred}, has access to the X authentication data +provided by the SSH server, and can run X applications which are +forwarded over the SSH connection. However, the second user +(\q{root}) does not automatically have the authentication data +passed on to it, so attempting to run an X application as that user +often fails with this error. + +If this happens, \e{it is not a problem with PuTTY}. You need to +arrange for your X authentication data to be passed from the user +you logged in as to the user you used \c{su} to become. How you do +this depends on your particular system; in fact many modern versions +of \c{su} do it automatically. + +\H{errors-connaborted} \q{Network error: Software caused connection +abort} + +This is a generic error produced by the Windows network code when it +kills an established connection for some reason. For example, it might +happen if you pull the network cable out of the back of an +Ethernet-connected computer, or if Windows has any other similar +reason to believe the entire network has become unreachable. + +Windows also generates this error if it has given up on the machine +at the other end of the connection ever responding to it. If the +network between your client and server goes down and your client +then tries to send some data, Windows will make several attempts to +send the data and will then give up and kill the connection. In +particular, this can occur even if you didn't type anything, if you +are using SSH-2 and PuTTY attempts a key re-exchange. (See +\k{config-ssh-kex-rekey} for more about key re-exchange.) + +(It can also occur if you are using keepalives in your connection. +Other people have reported that keepalives \e{fix} this error for +them. See \k{config-keepalive} for a discussion of the pros and cons +of keepalives.) + +We are not aware of any reason why this error might occur that would +represent a bug in PuTTY. The problem is between you, your Windows +system, your network and the remote system. + +\H{errors-connreset} \q{Network error: Connection reset by peer} + +This error occurs when the machines at each end of a network +connection lose track of the state of the connection between them. +For example, you might see it if your SSH server crashes, and +manages to reboot fully before you next attempt to send data to it. + +However, the most common reason to see this message is if you are +connecting through a \i{firewall} or a \i{NAT router} which has timed the +connection out. See \k{faq-idleout} in the FAQ for more details. You +may be able to improve the situation by using keepalives; see +\k{config-keepalive} for details on this. + +Note that Windows can produce this error in some circumstances without +seeing a connection reset from the server, for instance if the +connection to the network is lost. + +\H{errors-connrefused} \q{Network error: Connection refused} + +This error means that the network connection PuTTY tried to make to +your server was rejected by the server. Usually this happens because +the server does not provide the service which PuTTY is trying to +access. + +Check that you are connecting with the correct protocol (SSH, Telnet +or Rlogin), and check that the port number is correct. If that +fails, consult the administrator of your server. + +\H{errors-conntimedout} \q{Network error: Connection timed out} + +This error means that the network connection PuTTY tried to make to +your server received no response at all from the server. Usually +this happens because the server machine is completely isolated from +the network, or because it is turned off. + +Check that you have correctly entered the host name or IP address of +your server machine. If that fails, consult the administrator of +your server. + +\i{Unix} also generates this error when it tries to send data down a +connection and contact with the server has been completely lost +during a connection. (There is a delay of minutes before Unix gives +up on receiving a reply from the server.) This can occur if you type +things into PuTTY while the network is down, but it can also occur +if PuTTY decides of its own accord to send data: due to a repeat key +exchange in SSH-2 (see \k{config-ssh-kex-rekey}) or due to +keepalives (\k{config-keepalive}). diff --git a/putty/DOC/FAQ.BUT b/putty/DOC/FAQ.BUT new file mode 100644 index 0000000..913c254 --- /dev/null +++ b/putty/DOC/FAQ.BUT @@ -0,0 +1,1454 @@ +\define{versionidfaq} \versionid $Id: faq.but 8733 2009-11-01 22:06:05Z jacob $ + +\A{faq} PuTTY \i{FAQ} + +This FAQ is published on the PuTTY web site, and also provided as an +appendix in the manual. + +\H{faq-intro} Introduction + +\S{faq-what}{Question} What is PuTTY? + +PuTTY is a client program for the SSH, Telnet and Rlogin network +protocols. + +These protocols are all used to run a remote session on a computer, +over a network. PuTTY implements the client end of that session: the +end at which the session is displayed, rather than the end at which +it runs. + +In really simple terms: you run PuTTY on a Windows machine, and tell +it to connect to (for example) a Unix machine. PuTTY opens a window. +Then, anything you type into that window is sent straight to the +Unix machine, and everything the Unix machine sends back is +displayed in the window. So you can work on the Unix machine as if +you were sitting at its console, while actually sitting somewhere +else. + +\H{faq-support} Features supported in PuTTY + +\I{supported features}In general, if you want to know if PuTTY supports +a particular feature, you should look for it on the +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}{PuTTY web site}. +In particular: + +\b try the +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{changes +page}, and see if you can find the feature on there. If a feature is +listed there, it's been implemented. If it's listed as a change made +\e{since} the latest version, it should be available in the +development snapshots, in which case testing will be very welcome. + +\b try the +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist +page}, and see if you can find the feature there. If it's on there, +and not in the \q{Recently fixed} section, it probably \e{hasn't} been +implemented. + +\S{faq-ssh2}{Question} Does PuTTY support SSH-2? + +Yes. SSH-2 support has been available in PuTTY since version 0.50. + +Public key authentication (both RSA and DSA) in SSH-2 is new in +version 0.52. + +\S{faq-ssh2-keyfmt}{Question} Does PuTTY support reading OpenSSH or +\cw{ssh.com} SSH-2 private key files? + +PuTTY doesn't support this natively (see +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/key-formats-natively.html}{the wishlist entry} +for reasons why not), but as of 0.53 +PuTTYgen can convert both OpenSSH and \cw{ssh.com} private key +files into PuTTY's format. + +\S{faq-ssh1}{Question} Does PuTTY support SSH-1? + +Yes. SSH-1 support has always been available in PuTTY. + +\S{faq-localecho}{Question} Does PuTTY support \i{local echo}? + +Yes. Version 0.52 has proper support for local echo. + +In version 0.51 and before, local echo could not be separated from +local line editing (where you type a line of text locally, and it is +not sent to the server until you press Return, so you have the +chance to edit it and correct mistakes \e{before} the server sees +it). New in version 0.52, local echo and local line editing are +separate options, and by default PuTTY will try to determine +automatically whether to enable them or not, based on which protocol +you have selected and also based on hints from the server. If you +have a problem with PuTTY's default choice, you can force each +option to be enabled or disabled as you choose. The controls are in +the Terminal panel, in the section marked \q{Line discipline +options}. + +\S{faq-savedsettings}{Question} Does PuTTY support storing settings, +so I don't have to change them every time? + +Yes, all of PuTTY's settings can be saved in named session profiles. +You can also change the default settings that are used for new sessions. +See \k{config-saving} in the documentation for how to do this. + +\S{faq-disksettings}{Question} Does PuTTY support storing its +settings in a disk file? + +Not at present, although \k{config-file} in the documentation gives +a method of achieving the same effect. + +\S{faq-fullscreen}{Question} Does PuTTY support full-screen mode, +like a DOS box? + +Yes; this is a new feature in version 0.52. + +\S{faq-password-remember}{Question} Does PuTTY have the ability to +\i{remember my password} so I don't have to type it every time? + +No, it doesn't. + +Remembering your password is a bad plan for obvious security +reasons: anyone who gains access to your machine while you're away +from your desk can find out the remembered password, and use it, +abuse it or change it. + +In addition, it's not even \e{possible} for PuTTY to automatically +send your password in a Telnet session, because Telnet doesn't give +the client software any indication of which part of the login +process is the password prompt. PuTTY would have to guess, by +looking for words like \q{password} in the session data; and if your +login program is written in something other than English, this won't +work. + +In SSH, remembering your password would be possible in theory, but +there doesn't seem to be much point since SSH supports public key +authentication, which is more flexible and more secure. See +\k{pubkey} in the documentation for a full discussion of public key +authentication. + +\S{faq-hostkeys}{Question} Is there an option to turn off the +\I{verifying the host key}annoying host key prompts? + +No, there isn't. And there won't be. Even if you write it yourself +and send us the patch, we won't accept it. + +Those annoying host key prompts are the \e{whole point} of SSH. +Without them, all the cryptographic technology SSH uses to secure +your session is doing nothing more than making an attacker's job +slightly harder; instead of sitting between you and the server with +a packet sniffer, the attacker must actually subvert a router and +start modifying the packets going back and forth. But that's not all +that much harder than just sniffing; and without host key checking, +it will go completely undetected by client or server. + +Host key checking is your guarantee that the encryption you put on +your data at the client end is the \e{same} encryption taken off the +data at the server end; it's your guarantee that it hasn't been +removed and replaced somewhere on the way. Host key checking makes +the attacker's job \e{astronomically} hard, compared to packet +sniffing, and even compared to subverting a router. Instead of +applying a little intelligence and keeping an eye on Bugtraq, the +attacker must now perform a brute-force attack against at least one +military-strength cipher. That insignificant host key prompt really +does make \e{that} much difference. + +If you're having a specific problem with host key checking - perhaps +you want an automated batch job to make use of PSCP or Plink, and +the interactive host key prompt is hanging the batch process - then +the right way to fix it is to add the correct host key to the +Registry in advance. That way, you retain the \e{important} feature +of host key checking: the right key will be accepted and the wrong +ones will not. Adding an option to turn host key checking off +completely is the wrong solution and we will not do it. + +If you have host keys available in the common \i\c{known_hosts} format, +we have a script called +\W{http://svn.tartarus.org/sgt/putty/contrib/kh2reg.py?view=markup}\c{kh2reg.py} +to convert them to a Windows .REG file, which can be installed ahead of +time by double-clicking or using \c{REGEDIT}. + +\S{faq-server}{Question} Will you write an SSH server for the PuTTY +suite, to go with the client? + +No. The only reason we might want to would be if we could easily +re-use existing code and significantly cut down the effort. We don't +believe this is the case; there just isn't enough common ground +between an SSH client and server to make it worthwhile. + +If someone else wants to use bits of PuTTY in the process of writing +a Windows SSH server, they'd be perfectly welcome to of course, but +I really can't see it being a lot less effort for us to do that than +it would be for us to write a server from the ground up. We don't +have time, and we don't have motivation. The code is available if +anyone else wants to try it. + +\S{faq-pscp-ascii}{Question} Can PSCP or PSFTP transfer files in +\i{ASCII} mode? + +Unfortunately not. + +Until recently, this was a limitation of the file transfer protocols: +the SCP and SFTP protocols had no notion of transferring a file in +anything other than binary mode. (This is still true of SCP.) + +The current draft protocol spec of SFTP proposes a means of +implementing ASCII transfer. At some point PSCP/PSFTP may implement +this proposal. + +\H{faq-ports} Ports to other operating systems + +The eventual goal is for PuTTY to be a multi-platform program, able +to run on at least Windows, Mac OS and Unix. + +Porting will become easier once PuTTY has a generalised porting +layer, drawing a clear line between platform-dependent and +platform-independent code. The general intention was for this +porting layer to evolve naturally as part of the process of doing +the first port; a Unix port has now been released and the plan +seems to be working so far. + +\S{faq-ports-general}{Question} What ports of PuTTY exist? + +Currently, release versions of PuTTY tools only run on full Win32 +systems and Unix. \q{\i{Win32}} includes versions of Windows from +Windows 95 onwards (as opposed to the 16-bit Windows 3.1; see +\k{faq-win31}), up to and including Windows 7; and we know of no +reason why PuTTY should not continue to work on future versions +of Windows. + +The Windows executables we provide are for the 32-bit \q{\i{x86}} +processor architecture, but they should work fine on 64-bit +processors that are backward-compatible with that architecture. +(We used to also provide executables for Windows for the Alpha +processor, but stopped after 0.58 due to lack of interest.) + +In the development code, partial ports to the Mac OSes exist (see +\k{faq-mac-port}). + +Currently PuTTY does \e{not} run on Windows CE (see \k{faq-wince}). + +We do not have release-quality ports for any other systems at the +present time. If anyone told you we had an EPOC port, or an iPaq port, +or any other port of PuTTY, they were mistaken. We don't. + +There are some third-party ports to various platforms, mentioned +on the +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website}. + +\S{faq-unix}{Question} \I{Unix version}Is there a port to Unix? + +As of 0.54, 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} +in the source distribution. This should build you Unix +ports of Plink, PuTTY itself, PuTTYgen, PSCP, PSFTP, and also +\i\c{pterm} - an \cw{xterm}-type program which supports the same +terminal emulation as PuTTY. We do not yet have a Unix port of +Pageant. + +If you don't have \i{Gtk}, you should still be able to build the +command-line tools. + +Note that Unix PuTTY has mostly only been tested on Linux so far; +portability problems such as BSD-style ptys or different header file +requirements are expected. + +\S{faq-unix-why}{Question} What's the point of the Unix port? Unix +has OpenSSH. + +All sorts of little things. \c{pterm} is directly useful to anyone +who prefers PuTTY's terminal emulation to \c{xterm}'s, which at +least some people do. Unix Plink has apparently found a niche among +people who find the complexity of OpenSSL makes OpenSSH hard to +install (and who don't mind Plink not having as many features). Some +users want to generate a large number of SSH keys on Unix and then +copy them all into PuTTY, and the Unix PuTTYgen should allow them to +automate that conversion process. + +There were development advantages as well; porting PuTTY to Unix was +a valuable path-finding effort for other future ports, and also +allowed us to use the excellent Linux tool +\W{http://valgrind.kde.org/}{Valgrind} to help with debugging, which +has already improved PuTTY's stability on \e{all} platforms. + +However, if you're a Unix user and you can see no reason to switch +from OpenSSH to PuTTY/Plink, then you're probably right. We don't +expect our Unix port to be the right thing for everybody. + +\S{faq-wince}{Question} Will there be a port to Windows CE or PocketPC? + +We have done some work on such a port, but it only reached an early +stage, and certainly not a useful one. It's no longer being actively +worked on. + +However, there's a third-party port at +\W{http://www.pocketputty.net/}\c{http://www.pocketputty.net/}. + +\S{faq-win31}{Question} Is there a port to \i{Windows 3.1}? + +PuTTY is a 32-bit application from the ground up, so it won't run on +Windows 3.1 as a native 16-bit program; and it would be \e{very} +hard to port it to do so, because of Windows 3.1's vile memory +allocation mechanisms. + +However, it is possible in theory to compile the existing PuTTY +source in such a way that it will run under \i{Win32s} (an extension to +Windows 3.1 to let you run 32-bit programs). In order to do this +you'll need the right kind of C compiler - modern versions of Visual +C at least have stopped being backwards compatible to Win32s. Also, +the last time we tried this it didn't work very well. + +If you're interested in running PuTTY under Windows 3.1, help and +testing in this area would be very welcome! + +\S{faq-mac-port}{Question} Will there be a port to the \I{Mac OS}Mac? + +There are several answers to this question: + +\b The Unix/Gtk port is already fully working under Mac OS X as an X11 +application. + +\b A native (Cocoa) Mac OS X port has been started. It's just about +usable, but is of nowhere near release quality yet, and is likely to +behave in unexpected ways. Currently it's unlikely to be completed +unless someone steps in to help. + +\b A separate port to the classic Mac OS (pre-OSX) is also in +progress; it too is not ready yet. + +\S{faq-epoc}{Question} Will there be a port to EPOC? + +I hope so, but given that ports aren't really progressing very fast +even on systems the developers \e{do} already know how to program +for, it might be a long time before any of us get round to learning +a new system and doing the port for that. + +However, some of the work has been done by other people; see the +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website} +for various third-party ports. + +\S{faq-iphone}{Question} Will there be a port to the iPhone? + +We have no plans to write such a port ourselves; none of us has an +iPhone, and developing and publishing applications for it looks +awkward and expensive. Such a port would probably depend upon the +stalled Mac OS X port (see \k{faq-mac-port}). + +However, there is a third-party SSH client for the iPhone and +iPod\_Touch called \W{http://www.instantcocoa.com/products/pTerm/}{pTerm}, +which is apparently based on PuTTY. (This is nothing to do with our +similarly-named \c{pterm}, which is a standalone terminal emulator for +Unix systems; see \k{faq-unix}.) + +\H{faq-embedding} Embedding PuTTY in other programs + +\S{faq-dll}{Question} Is the SSH or Telnet code available as a DLL? + +No, it isn't. It would take a reasonable amount of rewriting for +this to be possible, and since the PuTTY project itself doesn't +believe in DLLs (they make installation more error-prone) none of us +has taken the time to do it. + +Most of the code cleanup work would be a good thing to happen in +general, so if anyone feels like helping, we wouldn't say no. + +See also +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/dll-frontend.html}{the wishlist entry}. + +\S{faq-vb}{Question} Is the SSH or Telnet code available as a Visual +Basic component? + +No, it isn't. None of the PuTTY team uses Visual Basic, and none of +us has any particular need to make SSH connections from a Visual +Basic application. In addition, all the preliminary work to turn it +into a DLL would be necessary first; and furthermore, we don't even +know how to write VB components. + +If someone offers to do some of this work for us, we might consider +it, but unless that happens I can't see VB integration being +anywhere other than the very bottom of our priority list. + +\S{faq-ipc}{Question} How can I use PuTTY to make an SSH connection +from within another program? + +Probably your best bet is to use Plink, the command-line connection +tool. If you can start Plink as a second Windows process, and +arrange for your primary process to be able to send data to the +Plink process, and receive data from it, through pipes, then you +should be able to make SSH connections from your program. + +This is what CVS for Windows does, for example. + +\H{faq-details} Details of PuTTY's operation + +\S{faq-term}{Question} What \i{terminal type} does PuTTY use? + +For most purposes, PuTTY can be considered to be an \cw{xterm} +terminal. + +PuTTY also supports some terminal \i{control sequences} not supported by +the real \cw{xterm}: notably the Linux console sequences that +reconfigure the colour palette, and the title bar control sequences +used by \i\cw{DECterm} (which are different from the \cw{xterm} ones; +PuTTY supports both). + +By default, PuTTY announces its terminal type to the server as +\c{xterm}. If you have a problem with this, you can reconfigure it +to say something else; \c{vt220} might help if you have trouble. + +\S{faq-settings}{Question} Where does PuTTY store its data? + +On Windows, PuTTY stores most of its data (saved sessions, SSH host +keys) in the \i{Registry}. The precise location is + +\c HKEY_CURRENT_USER\Software\SimonTatham\PuTTY + +and within that area, saved sessions are stored under \c{Sessions} +while host keys are stored under \c{SshHostKeys}. + +PuTTY also requires a random number seed file, to improve the +unpredictability of randomly chosen data needed as part of the SSH +cryptography. This is stored by default in a file called \i\c{PUTTY.RND}; +this is stored by default in the \q{Application Data} directory, +or failing that, one of a number of fallback locations. If you +want to change the location of the random number seed file, you can +put your chosen pathname in the Registry, at + +\c HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\RandSeedFile + +You can ask PuTTY to delete all this data; see \k{faq-cleanup}. + +On Unix, PuTTY stores all of this data in a directory \cw{~/.putty}. + +\H{faq-howto} HOWTO questions + +\S{faq-login}{Question} What login name / password should I use? + +This is not a question you should be asking \e{us}. + +PuTTY is a communications tool, for making connections to other +computers. We maintain the tool; we \e{don't} administer any computers +that you're likely to be able to use, in the same way that the people +who make web browsers aren't responsible for most of the content you can +view in them. \#{FIXME: less technical analogy?} We cannot help with +questions of this sort. + +If you know the name of the computer you want to connect to, but don't +know what login name or password to use, you should talk to whoever +administers that computer. If you don't know who that is, see the next +question for some possible ways to find out. + +\# FIXME: some people ask us to provide them with a login name +apparently as random members of the public rather than in the +belief that we run a server belonging to an organisation they already +have some relationship with. Not sure what to say to such people. + +\S{faq-commands}{Question} \I{commands on the server}What commands +can I type into my PuTTY terminal window? + +Again, this is not a question you should be asking \e{us}. You need +to read the manuals, or ask the administrator, of \e{the computer +you have connected to}. + +PuTTY does not process the commands you type into it. It's only a +communications tool. It makes a connection to another computer; it +passes the commands you type to that other computer; and it passes +the other computer's responses back to you. Therefore, the precise +range of commands you can use will not depend on PuTTY, but on what +kind of computer you have connected to and what software is running +on it. The PuTTY team cannot help you with that. + +(Think of PuTTY as being a bit like a telephone. If you phone +somebody up and you don't know what language to speak to make them +understand you, it isn't \e{the telephone company}'s job to find +that out for you. We just provide the means for you to get in touch; +making yourself understood is somebody else's problem.) + +If you are unsure of where to start looking for the administrator of +your server, a good place to start might be to remember how you +found out the host name in the PuTTY configuration. If you were +given that host name by e-mail, for example, you could try asking +the person who sent you that e-mail. If your company's IT department +provided you with ready-made PuTTY saved sessions, then that IT +department can probably also tell you something about what commands +you can type during those sessions. But the PuTTY maintainer team +does not administer any server you are likely to be connecting to, +and cannot help you with questions of this type. + +\S{faq-startmax}{Question} How can I make PuTTY start up \i{maximise}d? + +Create a Windows shortcut to start PuTTY from, and set it as \q{Run +Maximized}. + +\S{faq-startsess}{Question} How can I create a \i{Windows shortcut} to +start a particular saved session directly? + +To run a PuTTY session saved under the name \q{\cw{mysession}}, +create a Windows shortcut that invokes PuTTY with a command line +like + +\c \path\name\to\putty.exe -load "mysession" + +(Note: prior to 0.53, the syntax was \c{@session}. This is now +deprecated and may be removed at some point.) + +\S{faq-startssh}{Question} How can I start an SSH session straight +from the command line? + +Use the command line \c{putty -ssh host.name}. Alternatively, create +a saved session that specifies the SSH protocol, and start the saved +session as shown in \k{faq-startsess}. + +\S{faq-cutpaste}{Question} How do I \i{copy and paste} between PuTTY and +other Windows applications? + +Copy and paste works similarly to the X Window System. You use the +left mouse button to select text in the PuTTY window. The act of +selection \e{automatically} copies the text to the clipboard: there +is no need to press Ctrl-Ins or Ctrl-C or anything else. In fact, +pressing Ctrl-C will send a Ctrl-C character to the other end of +your connection (just like it does the rest of the time), which may +have unpleasant effects. The \e{only} thing you need to do, to copy +text to the clipboard, is to select it. + +To paste the clipboard contents into a PuTTY window, by default you +click the right mouse button. If you have a three-button mouse and +are used to X applications, you can configure pasting to be done by +the middle button instead, but this is not the default because most +Windows users don't have a middle button at all. + +You can also paste by pressing Shift-Ins. + +\S{faq-options}{Question} How do I use all PuTTY's features (public +keys, proxying, cipher selection, etc.) in PSCP, PSFTP and Plink? + +Most major features (e.g., public keys, port forwarding) are available +through command line options. See the documentation. + +Not all features are accessible from the command line yet, although +we'd like to fix this. In the meantime, you can use most of +PuTTY's features if you create a PuTTY saved session, and then use +the name of the saved session on the command line in place of a +hostname. This works for PSCP, PSFTP and Plink (but don't expect +port forwarding in the file transfer applications!). + +\S{faq-pscp}{Question} How do I use PSCP.EXE? When I double-click it +gives me a command prompt window which then closes instantly. + +PSCP is a command-line application, not a GUI application. If you +run it without arguments, it will simply print a help message and +terminate. + +To use PSCP properly, run it from a Command Prompt window. See +\k{pscp} in the documentation for more details. + +\S{faq-pscp-spaces}{Question} \I{spaces in filenames}How do I use +PSCP to copy a file whose name has spaces in? + +If PSCP is using the 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: + +\c pscp "local filename with spaces" user@host: +\c pscp user@host:myfile "local filename with spaces" + +But if the filename you're specifying is on the \e{remote} side, you +have to use backslashes and two sets of quotes: + +\c pscp user@host:"\"remote filename with spaces\"" local_filename +\c pscp local_filename user@host:"\"remote filename with spaces\"" + +Worse still, in a remote-to-local copy you have to specify the local +file name explicitly, otherwise PSCP will complain that they don't +match (unless you specified the \c{-unsafe} option). The following +command will give an error message: + +\c c:\>pscp user@host:"\"oo er\"" . +\c warning: remote host tried to write to a file called 'oo er' +\c when we requested a file called '"oo er"'. + +Instead, you need to specify the local file name in full: + +\c c:\>pscp user@host:"\"oo er\"" "oo er" + +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" . + +\H{faq-trouble} Troubleshooting + +\S{faq-incorrect-mac}{Question} Why do I see \q{Incorrect MAC +received on packet}? + +One possible cause of this that used to be common is a bug in old +SSH-2 servers distributed by \cw{ssh.com}. (This is not the only +possible cause; see \k{errors-crc} in the documentation.) +Version 2.3.0 and below of their SSH-2 server +constructs Message Authentication Codes in the wrong way, and +expects the client to construct them in the same wrong way. PuTTY +constructs the MACs correctly by default, and hence these old +servers will fail to work with it. + +If you are using PuTTY version 0.52 or better, this should work +automatically: PuTTY should detect the buggy servers from their +version number announcement, and automatically start to construct +its MACs in the same incorrect manner as they do, so it will be able +to work with them. + +If you are using PuTTY version 0.51 or below, you can enable the +workaround by going to the SSH panel and ticking the box labelled +\q{Imitate SSH2 MAC bug}. It's possible that you might have to do +this with 0.52 as well, if a buggy server exists that PuTTY doesn't +know about. + +In this context MAC stands for \ii{Message Authentication Code}. It's a +cryptographic term, and it has nothing at all to do with Ethernet +MAC (Media Access Control) addresses. + +\S{faq-pscp-protocol}{Question} Why do I see \q{Fatal: Protocol +error: Expected control record} in PSCP? + +This happens because PSCP was expecting to see data from the server +that was part of the PSCP protocol exchange, and instead it saw data +that it couldn't make any sense of at all. + +This almost always happens because the \i{startup scripts} in your +account on the server machine are generating output. This is +impossible for PSCP, or any other SCP client, to work around. You +should never use startup files (\c{.bashrc}, \c{.cshrc} and so on) +which generate output in non-interactive sessions. + +This is not actually a PuTTY problem. If PSCP fails in this way, +then all other SCP clients are likely to fail in exactly the same +way. The problem is at the server end. + +\S{faq-colours}{Question} I clicked on a colour in the \ii{Colours} +panel, and the colour didn't change in my terminal. + +That isn't how you're supposed to use the Colours panel. + +During the course of a session, PuTTY potentially uses \e{all} the +colours listed in the Colours panel. It's not a question of using +only one of them and you choosing which one; PuTTY will use them +\e{all}. The purpose of the Colours panel is to let you adjust the +appearance of all the colours. So to change the colour of the +cursor, for example, you would select \q{Cursor Colour}, press the +\q{Modify} button, and select a new colour from the dialog box that +appeared. Similarly, if you want your session to appear in green, +you should select \q{Default Foreground} and press \q{Modify}. +Clicking on \q{ANSI Green} won't turn your session green; it will +only allow you to adjust the \e{shade} of green used when PuTTY is +instructed by the server to display green text. + +\S{faq-winsock2}{Question} Plink on \i{Windows 95} says it can't find +\i\cw{WS2_32.DLL}. + +Plink requires the extended Windows network library, WinSock version +2. This is installed as standard on Windows 98 and above, and on +Windows NT, and even on later versions of Windows 95; but early +Win95 installations don't have it. + +In order to use Plink on these systems, you will need to download +the +\W{http://www.microsoft.com/windows95/downloads/contents/wuadmintools/s_wunetworkingtools/w95sockets2/}{WinSock 2 upgrade}: + +\c http://www.microsoft.com/windows95/downloads/contents/ +\c wuadmintools/s_wunetworkingtools/w95sockets2/ + +\S{faq-outofmem}{Question} After trying to establish an SSH-2 +connection, PuTTY says \q{\ii{Out of memory}} and dies. + +If this happens just while the connection is starting up, this often +indicates that for some reason the client and server have failed to +establish a session encryption key. Somehow, they have performed +calculations that should have given each of them the same key, but +have ended up with different keys; so data encrypted by one and +decrypted by the other looks like random garbage. + +This causes an \q{out of memory} error because the first encrypted +data PuTTY expects to see is the length of an SSH message. Normally +this will be something well under 100 bytes. If the decryption has +failed, PuTTY will see a completely random length in the region of +two \e{gigabytes}, and will try to allocate enough memory to store +this non-existent message. This will immediately lead to it thinking +it doesn't have enough memory, and panicking. + +If this happens to you, it is quite likely to still be a PuTTY bug +and you should report it (although it might be a bug in your SSH +server instead); but it doesn't necessarily mean you've actually run +out of memory. + +\S{faq-outofmem2}{Question} When attempting a file transfer, either +PSCP or PSFTP says \q{\ii{Out of memory}} and dies. + +This is almost always caused by your \i{login scripts} on the server +generating output. PSCP or PSFTP will receive that output when they +were expecting to see the start of a file transfer protocol, and +they will attempt to interpret the output as file-transfer protocol. +This will usually lead to an \q{out of memory} error for much the +same reasons as given in \k{faq-outofmem}. + +This is a setup problem in your account on your server, \e{not} a +PSCP/PSFTP bug. Your login scripts should \e{never} generate output +during non-interactive sessions; secure file transfer is not the +only form of remote access that will break if they do. + +On Unix, a simple fix is to ensure that all the parts of your login +script that might generate output are in \c{.profile} (if you use a +Bourne shell derivative) or \c{.login} (if you use a C shell). +Putting them in more general files such as \c{.bashrc} or \c{.cshrc} +is liable to lead to problems. + +\S{faq-psftp-slow}{Question} PSFTP transfers files much slower than PSCP. + +The throughput of PSFTP 0.54 should be much better than 0.53b and +prior; we've added code to the SFTP backend to queue several blocks +of data rather than waiting for an acknowledgement for each. (The +SCP backend did not suffer from this performance issue because SCP +is a much simpler protocol.) + +\S{faq-bce}{Question} When I run full-colour applications, I see +areas of black space where colour ought to be, or vice versa. + +You almost certainly need to change the \q{Use \i{background colour} to +erase screen} setting in the Terminal panel. If there is too much +black space (the commoner situation), you should enable it, while if +there is too much colour, you should disable it. (See \k{config-erase}.) + +In old versions of PuTTY, this was disabled by default, and would not +take effect until you reset the terminal (see \k{faq-resetterm}). +Since 0.54, it is enabled by default, and changes take effect +immediately. + +\S{faq-resetterm}{Question} When I change some terminal settings, +nothing happens. + +Some of the terminal options (notably \ii{Auto Wrap} and +background-colour screen erase) actually represent the \e{default} +setting, rather than the currently active setting. The server can +send sequences that modify these options in mid-session, but when +the terminal is reset (by server action, or by you choosing \q{Reset +Terminal} from the System menu) the defaults are restored. + +In versions 0.53b and prior, if you change one of these options in +the middle of a session, you will find that the change does not +immediately take effect. It will only take effect once you reset +the terminal. + +In version 0.54, the behaviour has changed - changes to these +settings take effect immediately. + +\S{faq-idleout}{Question} My PuTTY sessions unexpectedly close after +they are \I{idle connections}idle for a while. + +Some types of \i{firewall}, and almost any router doing Network Address +Translation (\i{NAT}, also known as IP masquerading), will forget about +a connection through them if the connection does nothing for too +long. This will cause the connection to be rudely cut off when +contact is resumed. + +You can try to combat this by telling PuTTY to send \e{keepalives}: +packets of data which have no effect on the actual session, but +which reassure the router or firewall that the network connection is +still active and worth remembering about. + +Keepalives don't solve everything, unfortunately; although they +cause greater robustness against this sort of router, they can also +cause a \e{loss} of robustness against network dropouts. See +\k{config-keepalive} in the documentation for more discussion of +this. + +\S{faq-timeout}{Question} PuTTY's network connections time out too +quickly when \I{breaks in connectivity}network connectivity is +temporarily lost. + +This is a Windows problem, not a PuTTY problem. The timeout value +can't be set on per application or per session basis. To increase +the TCP timeout globally, you need to tinker with the Registry. + +On Windows 95, 98 or ME, the registry key you need to create or +change is + +\c HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\ +\c MSTCP\MaxDataRetries + +(it must be of type DWORD in Win95, or String in Win98/ME). +(See MS Knowledge Base article +\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;158474}{158474} +for more information.) + +On Windows NT, 2000, or XP, the registry key to create or change is + +\c HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\ +\c Parameters\TcpMaxDataRetransmissions + +and it must be of type DWORD. +(See MS Knowledge Base articles +\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;120642}{120642} +and +\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;314053}{314053} +for more information.) + +Set the key's value to something like 10. This will cause Windows to +try harder to keep connections alive instead of abandoning them. + +\S{faq-puttyputty}{Question} When I \cw{cat} a binary file, I get +\q{PuTTYPuTTYPuTTY} on my command line. + +Don't do that, then. + +This is designed behaviour; when PuTTY receives the character +Control-E from the remote server, it interprets it as a request to +identify itself, and so it sends back the string \q{\cw{PuTTY}} as +if that string had been entered at the keyboard. Control-E should +only be sent by programs that are prepared to deal with the +response. Writing a binary file to your terminal is likely to output +many Control-E characters, and cause this behaviour. Don't do it. +It's a bad plan. + +To mitigate the effects, you could configure the answerback string +to be empty (see \k{config-answerback}); but writing binary files to +your terminal is likely to cause various other unpleasant behaviour, +so this is only a small remedy. + +\S{faq-wintitle}{Question} When I \cw{cat} a binary file, my \i{window +title} changes to a nonsense string. + +Don't do that, then. + +It is designed behaviour that PuTTY should have the ability to +adjust the window title on instructions from the server. Normally +the control sequence that does this should only be sent +deliberately, by programs that know what they are doing and intend +to put meaningful text in the window title. Writing a binary file to +your terminal runs the risk of sending the same control sequence by +accident, and cause unexpected changes in the window title. Don't do +it. + +\S{faq-password-fails}{Question} My \i{keyboard} stops working once +PuTTY displays the \i{password prompt}. + +No, it doesn't. PuTTY just doesn't display the password you type, so +that someone looking at your screen can't see what it is. + +Unlike the Windows login prompts, PuTTY doesn't display the password +as a row of asterisks either. This is so that someone looking at +your screen can't even tell how \e{long} your password is, which +might be valuable information. + +\S{faq-keyboard}{Question} One or more \I{keyboard}\i{function keys} +don't do what I expected in a server-side application. + +If you've already tried all the relevant options in the PuTTY +Keyboard panel, you may need to mail the PuTTY maintainers and ask. + +It is \e{not} usually helpful just to tell us which application, +which server operating system, and which key isn't working; in order +to replicate the problem we would need to have a copy of every +operating system, and every application, that anyone has ever +complained about. + +PuTTY responds to function key presses by sending a sequence of +control characters to the server. If a function key isn't doing what +you expect, it's likely that the character sequence your application +is expecting to receive is not the same as the one PuTTY is sending. +Therefore what we really need to know is \e{what} sequence the +application is expecting. + +The simplest way to investigate this is to find some other terminal +environment, in which that function key \e{does} work; and then +investigate what sequence the function key is sending in that +situation. One reasonably easy way to do this on a \i{Unix} system is to +type the command \i\c{cat}, and then press the function key. This is +likely to produce output of the form \c{^[[11~}. You can also do +this in PuTTY, to find out what sequence the function key is +producing in that. Then you can mail the PuTTY maintainers and tell +us \q{I wanted the F1 key to send \c{^[[11~}, but instead it's +sending \c{^[OP}, can this be done?}, or something similar. + +You should still read the +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/feedback.html}{Feedback +page} on the PuTTY website (also provided as \k{feedback} in the +manual), and follow the guidelines contained in that. + +\S{faq-openssh-bad-openssl}{Question} Since my SSH server was upgraded +to \i{OpenSSH} 3.1p1/3.4p1, I can no longer connect with PuTTY. + +There is a known problem when OpenSSH has been built against an +incorrect version of OpenSSL; the quick workaround is to configure +PuTTY to use SSH protocol 2 and the Blowfish cipher. + +For more details and OpenSSH patches, see +\W{http://bugzilla.mindrot.org/show_bug.cgi?id=138}{bug 138} in the +OpenSSH BTS. + +This is not a PuTTY-specific problem; if you try to connect with +another client you'll likely have similar problems. (Although PuTTY's +default cipher differs from many other clients.) + +\e{OpenSSH 3.1p1:} configurations known to be broken (and symptoms): + +\b SSH-2 with AES cipher (PuTTY says \q{Assertion failed! Expression: +(len & 15) == 0} in \cw{sshaes.c}, or \q{Out of memory}, or crashes) + +\b SSH-2 with 3DES (PuTTY says \q{Incorrect MAC received on packet}) + +\b SSH-1 with Blowfish (PuTTY says \q{Incorrect CRC received on +packet}) + +\b SSH-1 with 3DES + +\e{OpenSSH 3.4p1:} as of 3.4p1, only the problem with SSH-1 and +Blowfish remains. Rebuild your server, apply the patch linked to from +bug 138 above, or use another cipher (e.g., 3DES) instead. + +\e{Other versions:} we occasionally get reports of the same symptom +and workarounds with older versions of OpenSSH, although it's not +clear the underlying cause is the same. + +\S{faq-ssh2key-ssh1conn}{Question} Why do I see \q{Couldn't load +private key from ...}? Why can PuTTYgen load my key but not PuTTY? + +It's likely that you've generated an SSH protocol 2 key with PuTTYgen, +but you're trying to use it in an SSH-1 connection. SSH-1 and SSH-2 keys +have different formats, and (at least in 0.52) PuTTY's reporting of a +key in the wrong format isn't optimal. + +To connect using SSH-2 to a server that supports both versions, you +need to change the configuration from the default (see \k{faq-ssh2}). + +\S{faq-rh8-utf8}{Question} When I'm connected to a \i{Red Hat Linux} 8.0 +system, some characters don't display properly. + +A common complaint is that hyphens in man pages show up as a-acute. + +With release 8.0, Red Hat appear to have made \i{UTF-8} the default +character set. There appears to be no way for terminal emulators such +as PuTTY to know this (as far as we know, the appropriate escape +sequence to switch into UTF-8 mode isn't sent). + +A fix is to configure sessions to RH8 systems to use UTF-8 +translation - see \k{config-charset} in the documentation. (Note that +if you use \q{Change Settings}, changes may not take place immediately +- see \k{faq-resetterm}.) + +If you really want to change the character set used by the server, the +right place is \c{/etc/sysconfig/i18n}, but this shouldn't be +necessary. + +\S{faq-screen}{Question} Since I upgraded to PuTTY 0.54, the +scrollback has stopped working when I run \c{screen}. + +PuTTY's terminal emulator has always had the policy that when the +\q{\i{alternate screen}} is in use, nothing is added to the scrollback. +This is because the usual sorts of programs which use the alternate +screen are things like text editors, which tend to scroll back and +forth in the same document a lot; so (a) they would fill up the +scrollback with a large amount of unhelpfully disordered text, and +(b) they contain their \e{own} method for the user to scroll back to +the bit they were interested in. We have generally found this policy +to do the Right Thing in almost all situations. + +Unfortunately, \c{screen} is one exception: it uses the alternate +screen, but it's still usually helpful to have PuTTY's scrollback +continue working. The simplest solution is to go to the Features +control panel and tick \q{Disable switching to alternate terminal +screen}. (See \k{config-features-altscreen} for more details.) +Alternatively, you can tell \c{screen} itself not to use the +alternate screen: the +\W{http://www4.informatik.uni-erlangen.de/~jnweiger/screen-faq.html}{\c{screen} +FAQ} suggests adding the line \cq{termcapinfo xterm ti@:te@} to your +\cw{.screenrc} file. + +The reason why this only started to be a problem in 0.54 is because +\c{screen} typically uses an unusual control sequence to switch to +the alternate screen, and previous versions of PuTTY did not support +this sequence. + +\S{faq-alternate-localhost}{Question} Since I upgraded \i{Windows XP} +to Service Pack 2, I can't use addresses like \cw{127.0.0.2}. + +Some people who ask PuTTY to listen on \i{localhost} addresses other +than \cw{127.0.0.1} to forward services such as \i{SMB} and \i{Windows +Terminal Services} have found that doing so no longer works since +they upgraded to WinXP SP2. + +This is apparently an issue with SP2 that is acknowledged by Microsoft +in MS Knowledge Base article +\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;884020}{884020}. +The article links to a fix you can download. + +(\e{However}, we've been told that SP2 \e{also} fixes the bug that +means you need to use non-\cw{127.0.0.1} addresses to forward +Terminal Services in the first place.) + +\S{faq-missing-slash}{Question} PSFTP commands seem to be missing a +directory separator (slash). + +Some people have reported the following incorrect behaviour with +PSFTP: + +\c psftp> pwd +\e iii +\c Remote directory is /dir1/dir2 +\c psftp> get filename.ext +\e iiiiiiiiiiiiiiii +\c /dir1/dir2filename.ext: no such file or directory + +This is not a bug in PSFTP. There is a known bug in some versions of +portable \i{OpenSSH} +(\W{http://bugzilla.mindrot.org/show_bug.cgi?id=697}{bug 697}) that +causes these symptoms; it appears to have been introduced around +3.7.x. It manifests only on certain platforms (AIX is what has been +reported to us). + +There is a patch for OpenSSH attached to that bug; it's also fixed in +recent versions of portable OpenSSH (from around 3.8). + +\S{faq-connaborted}{Question} Do you want to hear about \q{Software +caused connection abort}? + +In the documentation for PuTTY 0.53 and 0.53b, we mentioned that we'd +like to hear about any occurrences of this error. Since the release +of PuTTY 0.54, however, we've been convinced that this error doesn't +indicate that PuTTY's doing anything wrong, and we don't need to hear +about further occurrences. See \k{errors-connaborted} for our current +documentation of this error. + +\S{faq-rekey}{Question} My SSH-2 session \I{locking up, SSH-2 +sessions}locks up for a few seconds every so often. + +Recent versions of PuTTY automatically initiate \i{repeat key +exchange} once per hour, to improve session security. If your client +or server machine is slow, you may experience this as a delay of +anything up to thirty seconds or so. + +These \I{delays, in SSH-2 sessions}delays are inconvenient, but they +are there for your protection. If they really cause you a problem, +you can choose to turn off periodic rekeying using the \q{Kex} +configuration panel (see \k{config-ssh-kex}), but be aware that you +will be sacrificing security for this. (Falling back to SSH-1 would +also remove the delays, but would lose a \e{lot} more security +still. We do not recommend it.) + +\S{faq-xpwontrun}{Question} PuTTY fails to start up. Windows claims that +\q{the application configuration is incorrect}. + +This is caused by a bug in certain versions of \i{Windows XP} which +is triggered by PuTTY 0.58. This was fixed in 0.59. The +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/xp-wont-run}{\q{xp-wont-run}} +entry in PuTTY's wishlist has more details. + +\H{faq-secure} Security questions + +\S{faq-publicpc}{Question} Is it safe for me to download PuTTY and +use it on a public PC? + +It depends on whether you trust that PC. If you don't trust the +public PC, don't use PuTTY on it, and don't use any other software +you plan to type passwords into either. It might be watching your +keystrokes, or it might tamper with the PuTTY binary you download. +There is \e{no} program safe enough that you can run it on an +actively malicious PC and get away with typing passwords into it. + +If you do trust the PC, then it's probably OK to use PuTTY on it +(but if you don't trust the network, then the PuTTY download might +be tampered with, so it would be better to carry PuTTY with you on a +floppy). + +\S{faq-cleanup}{Question} What does PuTTY leave on a system? How can +I \i{clean up} after it? + +PuTTY will leave some Registry entries, and a random seed file, on +the PC (see \k{faq-settings}). If you are using PuTTY on a public +PC, or somebody else's PC, you might want to clean these up when you +leave. You can do that automatically, by running the command +\c{putty -cleanup}. (Note that this only removes settings for +the currently logged-in user on \i{multi-user systems}.) + +If PuTTY was installed from the installer package, it will also +appear in \q{Add/Remove Programs}. Older versions of the uninstaller +do not remove the above-mentioned registry entries and file. + +\S{faq-dsa}{Question} How come PuTTY now supports \i{DSA}, when the +website used to say how insecure it was? + +DSA has a major weakness \e{if badly implemented}: it relies on a +random number generator to far too great an extent. If the random +number generator produces a number an attacker can predict, the DSA +private key is exposed - meaning that the attacker can log in as you +on all systems that accept that key. + +The PuTTY policy changed because the developers were informed of +ways to implement DSA which do not suffer nearly as badly from this +weakness, and indeed which don't need to rely on random numbers at +all. For this reason we now believe PuTTY's DSA implementation is +probably OK. However, if you have the choice, we still recommend you +use RSA instead. + +\S{faq-virtuallock}{Question} Couldn't Pageant use +\cw{VirtualLock()} to stop private keys being written to disk? + +Unfortunately not. The \cw{VirtualLock()} function in the Windows +API doesn't do a proper job: it may prevent small pieces of a +process's memory from being paged to disk while the process is +running, but it doesn't stop the process's memory as a whole from +being swapped completely out to disk when the process is long-term +inactive. And Pageant spends most of its time inactive. + +\H{faq-admin} Administrative questions + +\S{faq-domain}{Question} Would you like me to register you a nicer +domain name? + +No, thank you. Even if you can find one (most of them seem to have +been registered already, by people who didn't ask whether we +actually wanted it before they applied), we're happy with the PuTTY +web site being exactly where it is. It's not hard to find (just type +\q{putty} into \W{http://www.google.com/}{google.com} and we're the +first link returned), and we don't believe the administrative hassle +of moving the site would be worth the benefit. + +In addition, if we \e{did} want a custom domain name, we would want +to run it ourselves, so we knew for certain that it would continue +to point where we wanted it, and wouldn't suddenly change or do +strange things. Having it registered for us by a third party who we +don't even know is not the best way to achieve this. + +\S{faq-webhosting}{Question} Would you like free web hosting for the +PuTTY web site? + +We already have some, thanks. + +\S{faq-link}{Question} Would you link to my web site from the PuTTY +web site? + +Only if the content of your web page is of definite direct interest +to PuTTY users. If your content is unrelated, or only tangentially +related, to PuTTY, then the link would simply be advertising for +you. + +One very nice effect of the Google ranking mechanism is that by and +large, the most popular web sites get the highest rankings. This +means that when an ordinary person does a search, the top item in +the search is very likely to be a high-quality site or the site they +actually wanted, rather than the site which paid the most money for +its ranking. + +The PuTTY web site is held in high esteem by Google, for precisely +this reason: lots of people have linked to it simply because they +like PuTTY, without us ever having to ask anyone to link to us. We +feel that it would be an abuse of this esteem to use it to boost the +ranking of random advertisers' web sites. If you want your web site +to have a high Google ranking, we'd prefer that you achieve this the +way we did - by being good enough at what you do that people will +link to you simply because they like you. + +In particular, we aren't interested in trading links for money (see +above), and we \e{certainly} aren't interested in trading links for +other links (since we have no advertising on our web site, our +Google ranking is not even directly worth anything to us). If we +don't want to link to you for free, then we probably won't want to +link to you at all. + +If you have software based on PuTTY, or specifically designed to +interoperate with PuTTY, or in some other way of genuine interest to +PuTTY users, then we will probably be happy to add a link to you on +our Links page. And if you're running a particularly valuable mirror +of the PuTTY web site, we might be interested in linking to you from +our Mirrors page. + +\S{faq-sourceforge}{Question} Why don't you move PuTTY to +SourceForge? + +Partly, because we don't want to move the web site location (see +\k{faq-domain}). + +Also, security reasons. PuTTY is a security product, and as such it +is particularly important to guard the code and the web site against +unauthorised modifications which might introduce subtle security +flaws. Therefore, we prefer that the Subversion repository, web site and +FTP site remain where they are, under the direct control of system +administrators we know and trust personally, rather than being run +by a large organisation full of people we've never met and which is +known to have had breakins in the past. + +No offence to SourceForge; I think they do a wonderful job. But +they're not ideal for everyone, and in particular they're not ideal +for us. + +\S{faq-mailinglist1}{Question} Why can't I subscribe to the +putty-bugs mailing list? + +Because you're not a member of the PuTTY core development team. The +putty-bugs mailing list is not a general newsgroup-like discussion +forum; it's a contact address for the core developers, and an +\e{internal} mailing list for us to discuss things among ourselves. +If we opened it up for everybody to subscribe to, it would turn into +something more like a newsgroup and we would be completely +overwhelmed by the volume of traffic. It's hard enough to keep up +with the list as it is. + +\S{faq-mailinglist2}{Question} If putty-bugs isn't a +general-subscription mailing list, what is? + +There isn't one, that we know of. + +If someone else wants to set up a mailing list or other forum for +PuTTY users to help each other with common problems, that would be +fine with us, though the PuTTY team would almost certainly not have the +time to read it. It's probably better to use one of the established +newsgroups for this purpose (see \k{feedback-other-fora}). + +\S{faq-donations}{Question} How can I donate to PuTTY development? + +Please, \e{please} don't feel you have to. PuTTY is completely free +software, and not shareware. We think it's very important that +\e{everybody} who wants to use PuTTY should be able to, whether they +have any money or not; so the last thing we would want is for a +PuTTY user to feel guilty because they haven't paid us any money. If +you want to keep your money, please do keep it. We wouldn't dream of +asking for any. + +Having said all that, if you still really \e{want} to give us money, +we won't argue :-) The easiest way for us to accept donations is if +you send money to \cw{} using PayPal +(\W{http://www.paypal.com/}\cw{www.paypal.com}). If you don't like +PayPal, talk to us; we can probably arrange some alternative means. + +Small donations (tens of dollars or tens of euros) will probably be +spent on beer or curry, which helps motivate our volunteer team to +continue doing this for the world. Larger donations will be spent on +something that actually helps development, if we can find anything +(perhaps new hardware, or a copy of Windows XP), but if we can't +find anything then we'll just distribute the money among the +developers. If you want to be sure your donation is going towards +something worthwhile, ask us first. If you don't like these terms, +feel perfectly free not to donate. We don't mind. + +\S{faq-permission}{Question} Can I have permission to put PuTTY on a +cover disk / distribute it with other software / etc? + +Yes. For most things, you need not bother asking us explicitly for +permission; our licence already grants you permission. + +See \k{feedback-permission} for more details. + +\S{faq-indemnity}{Question} Can you sign an agreement indemnifying +us against security problems in PuTTY? + +No! + +A vendor of physical security products (e.g. locks) might plausibly +be willing to accept financial liability for a product that failed +to perform as advertised and resulted in damage (e.g. valuables +being stolen). The reason they can afford to do this is because they +sell a \e{lot} of units, and only a small proportion of them will +fail; so they can meet their financial liability out of the income +from all the rest of their sales, and still have enough left over to +make a profit. Financial liability is intrinsically linked to +selling your product for money. + +There are two reasons why PuTTY is not analogous to a physical lock +in this context. One is that software products don't exhibit random +variation: \e{if} PuTTY has a security hole (which does happen, +although we do our utmost to prevent it and to respond quickly when +it does), every copy of PuTTY will have the same hole, so it's +likely to affect all the users at the same time. So even if our +users were all paying us to use PuTTY, we wouldn't be able to +\e{simultaneously} pay every affected user compensation in excess of +the amount they had paid us in the first place. It just wouldn't +work. + +The second, much more important, reason is that PuTTY users +\e{don't} pay us. The PuTTY team does not have an income; it's a +volunteer effort composed of people spending their spare time to try +to write useful software. We aren't even a company or any kind of +legally recognised organisation. We're just a bunch of people who +happen to do some stuff in our spare time. + +Therefore, to ask us to assume financial liability is to ask us to +assume a risk of having to pay it out of our own \e{personal} +pockets: out of the same budget from which we buy food and clothes +and pay our rent. That's more than we're willing to give. We're +already giving a lot of our spare \e{time} to developing software +for free; if we had to pay our own \e{money} to do it as well, we'd +start to wonder why we were bothering. + +Free software fundamentally does not work on the basis of financial +guarantees. Your guarantee of the software functioning correctly is +simply that you have the source code and can check it before you use +it. If you want to be sure there aren't any security holes, do a +security audit of the PuTTY code, or hire a security engineer if you +don't have the necessary skills yourself: instead of trying to +ensure you can get compensation in the event of a disaster, try to +ensure there isn't a disaster in the first place. + +If you \e{really} want financial security, see if you can find a +security engineer who will take financial responsibility for the +correctness of their review. (This might be less likely to suffer +from the everything-failing-at-once problem mentioned above, because +such an engineer would probably be reviewing a lot of \e{different} +products which would tend to fail independently.) Failing that, see +if you can persuade an insurance company to insure you against +security incidents, and if the insurer demands it as a condition +then get our code reviewed by a security engineer they're happy +with. + +\S{faq-permission-form}{Question} Can you sign this form granting us +permission to use/distribute PuTTY? + +If your form contains any clause along the lines of \q{the +undersigned represents and warrants}, we're not going to sign it. +This is particularly true if it asks us to warrant that PuTTY is +secure; see \k{faq-indemnity} for more discussion of this. But it +doesn't really matter what we're supposed to be warranting: even if +it's something we already believe is true, such as that we don't +infringe any third-party copyright, we will not sign a document +accepting any legal or financial liability. This is simply because +the PuTTY development project has no income out of which to satisfy +that liability, or pay legal costs, should it become necessary. We +cannot afford to be sued. We are assuring you that \e{we have done +our best}; if that isn't good enough for you, tough. + +The existing PuTTY licence document already gives you permission to +use or distribute PuTTY in pretty much any way which does not +involve pretending you wrote it or suing us if it goes wrong. We +think that really ought to be enough for anybody. + +See also \k{faq-permission-general} for another reason why we don't +want to do this sort of thing. + +\S{faq-permission-future}{Question} Can you write us a formal notice +of permission to use PuTTY? + +We could, in principle, but it isn't clear what use it would be. If +you think there's a serious chance of one of the PuTTY copyright +holders suing you (which we don't!), you would presumably want a +signed notice from \e{all} of them; and we couldn't provide that +even if we wanted to, because many of the copyright holders are +people who contributed some code in the past and with whom we +subsequently lost contact. Therefore the best we would be able to do +\e{even in theory} would be to have the core development team sign +the document, which wouldn't guarantee you that some other copyright +holder might not sue. + +See also \k{faq-permission-general} for another reason why we don't +want to do this sort of thing. + +\S{faq-permission-general}{Question} Can you sign \e{anything} for +us? + +Not unless there's an incredibly good reason. + +We are generally unwilling to set a precedent that involves us +having to enter into individual agreements with PuTTY users. We +estimate that we have literally \e{millions} of users, and we +absolutely would not have time to go round signing specific +agreements with every one of them. So if you want us to sign +something specific for you, you might usefully stop to consider +whether there's anything special that distinguishes you from 999,999 +other users, and therefore any reason we should be willing to sign +something for you without it setting such a precedent. + +If your company policy requires you to have an individual agreement +with the supplier of any software you use, then your company policy +is simply not well suited to using popular free software, and we +urge you to consider this as a flaw in your policy. + +\S{faq-permission-assurance}{Question} If you won't sign anything, +can you give us some sort of assurance that you won't make PuTTY +closed-source in future? + +Yes and no. + +If what you want is an assurance that some \e{current version} of +PuTTY which you've already downloaded will remain free, then you +already have that assurance: it's called the PuTTY Licence. It +grants you permission to use, distribute and copy the software to +which it applies; once we've granted that permission (which we +have), we can't just revoke it. + +On the other hand, if you want an assurance that \e{future} versions +of PuTTY won't be closed-source, that's more difficult. We could in +principle sign a document stating that we would never release a +closed-source PuTTY, but that wouldn't assure you that we \e{would} +keep releasing \e{open}-source PuTTYs: we would still have the +option of ceasing to develop PuTTY at all, which would surely be +even worse for you than making it closed-source! (And we almost +certainly wouldn't \e{want} to sign a document guaranteeing that we +would actually continue to do development work on PuTTY; we +certainly wouldn't sign it for free. Documents like that are called +contracts of employment, and are generally not signed except in +return for a sizeable salary.) + +If we \e{were} to stop developing PuTTY, or to decide to make all +future releases closed-source, then you would still be free to copy +the last open release in accordance with the current licence, and in +particular you could start your own fork of the project from that +release. If this happened, I confidently predict that \e{somebody} +would do that, and that some kind of a free PuTTY would continue to +be developed. There's already precedent for that sort of thing +happening in free software. We can't guarantee that somebody +\e{other than you} would do it, of course; you might have to do it +yourself. But we can assure you that there would be nothing +\e{preventing} anyone from continuing free development if we +stopped. + +(Finally, we can also confidently predict that if we made PuTTY +closed-source and someone made an open-source fork, most people +would switch to the latter. Therefore, it would be pretty stupid of +us to try it.) + +\S{faq-export-cert}{Question} Can you provide us with export control +information / FIPS certification for PuTTY? + +Some people have asked us for an Export Control Classification Number +(ECCN) for PuTTY. We don't know whether we have one, and as a team of +free software developers based in the UK we don't have the time, +money, or effort to deal with US bureaucracy to investigate any +further. We believe that PuTTY falls under 5D002 on the US Commerce +Control List, but that shouldn't be taken as definitive. If you need +to know more you should seek professional legal advice. The same +applies to any other country's legal requirements and restrictions. + +Similarly, some people have asked us for FIPS certification of the +PuTTY tools. Unless someone else is prepared to do the necessary work +and pay any costs, we can't provide this. + +\H{faq-misc} Miscellaneous questions + +\S{faq-openssh}{Question} Is PuTTY a port of \i{OpenSSH}, or based on +OpenSSH or OpenSSL? + +No, it isn't. PuTTY is almost completely composed of code written +from scratch for PuTTY. The only code we share with OpenSSH is the +detector for SSH-1 CRC compensation attacks, written by CORE SDI +S.A; we share no code at all with OpenSSL. + +\S{faq-sillyputty}{Question} Where can I buy silly putty? + +You're looking at the wrong web site; the only PuTTY we know about +here is the name of a computer program. + +If you want the kind of putty you can buy as an executive toy, the +PuTTY team can personally recommend Thinking Putty, which you can +buy from Crazy Aaron's Putty World, at +\W{http://www.puttyworld.com}\cw{www.puttyworld.com}. + +\S{faq-meaning}{Question} What does \q{PuTTY} mean? + +It's the name of a popular SSH and Telnet client. Any other meaning +is in the eye of the beholder. It's been rumoured that \q{PuTTY} +is the antonym of \q{\cw{getty}}, or that it's the stuff that makes your +Windows useful, or that it's a kind of plutonium Teletype. We +couldn't possibly comment on such allegations. + +\S{faq-pronounce}{Question} How do I pronounce \q{PuTTY}? + +Exactly like the English word \q{putty}, which we pronounce +/\u02C8{'}p\u028C{V}ti/. diff --git a/putty/DOC/FEEDBACK.BUT b/putty/DOC/FEEDBACK.BUT new file mode 100644 index 0000000..0f9f494 --- /dev/null +++ b/putty/DOC/FEEDBACK.BUT @@ -0,0 +1,423 @@ +\define{versionidfeedback} \versionid $Id: feedback.but 7824 2007-12-20 11:03:45Z simon $ + +\A{feedback} \ii{Feedback} and \i{bug reporting} + +This is a guide to providing feedback to the PuTTY development team. +It is provided as both a web page on the PuTTY site, and an appendix +in the PuTTY manual. + +\K{feedback-general} gives some general guidelines for sending any +kind of e-mail to the development team. Following sections give more +specific guidelines for particular types of e-mail, such as bug +reports and feature requests. + +\H{feedback-general} General guidelines + +The PuTTY development team gets a \e{lot} of mail. If you can +possibly solve your own problem by reading the manual, reading the +FAQ, reading the web site, asking a fellow user, perhaps posting to a +newsgroup (see \k{feedback-other-fora}), or some other means, then it +would make our lives much easier. + +We get so much e-mail that we literally do not have time to answer +it all. We regret this, but there's nothing we can do about it. So +if you can \e{possibly} avoid sending mail to the PuTTY team, we +recommend you do so. In particular, support requests +(\k{feedback-support}) are probably better sent to newsgroups, or +passed to a local expert if possible. + +The PuTTY contact email address is a private \i{mailing list} containing +four or five core developers. Don't be put off by it being a mailing +list: if you need to send confidential data as part of a bug report, +you can trust the people on the list to respect that confidence. +Also, the archives aren't publicly available, so you shouldn't be +letting yourself in for any spam by sending us mail. + +Please use a meaningful subject line on your message. We get a lot of +mail, and it's hard to find the message we're looking for if they all +have subject lines like \q{PuTTY bug}. + +\S{feedback-largefiles} Sending large attachments + +Since the PuTTY contact address is a mailing list, e-mails larger +than 40Kb will be held for inspection by the list administrator, and +will not be allowed through unless they really appear to be worth +their large size. + +If you are considering sending any kind of large data file to the +PuTTY team, it's almost always a bad idea, or at the very least it +would be better to ask us first whether we actually need the file. +Alternatively, you could put the file on a web site and just send us +the URL; that way, we don't have to download it unless we decide we +actually need it, and only one of us needs to download it instead of +it being automatically copied to all the developers. + +Some people like to send mail in MS Word format. Please \e{don't} +send us bug reports, or any other mail, as a Word document. Word +documents are roughly fifty times larger than writing the same +report in plain text. In addition, most of the PuTTY team read their +e-mail on Unix machines, so copying the file to a Windows box to run +Word is very inconvenient. Not only that, but several of us don't +even \e{have} a copy of Word! + +Some people like to send us screen shots when demonstrating a +problem. Please don't do this without checking with us first - we +almost never actually need the information in the screen shot. +Sending a screen shot of an error box is almost certainly +unnecessary when you could just tell us in plain text what the error +was. (On some versions of Windows, pressing Ctrl-C when the error +box is displayed will copy the text of the message to the clipboard.) +Sending a full-screen shot is \e{occasionally} useful, but it's +probably still wise to check whether we need it before sending it. + +If you \e{must} mail a screen shot, don't send it as a \cw{.BMP} +file. \cw{BMP}s have no compression and they are \e{much} larger +than other image formats such as PNG, TIFF and GIF. Convert the file +to a properly compressed image format before sending it. + +Please don't mail us executables, at all. Our mail server blocks all +incoming e-mail containing executables, as a defence against the +vast numbers of e-mail viruses we receive every day. If you mail us +an executable, it will just bounce. + +If you have made a tiny modification to the PuTTY code, please send +us a \e{patch} to the source code if possible, rather than sending +us a huge \cw{.ZIP} file containing the complete sources plus your +modification. If you've only changed 10 lines, we'd prefer to +receive a mail that's 30 lines long than one containing multiple +megabytes of data we already have. + +\S{feedback-other-fora} Other places to ask for help + +There are two Usenet newsgroups that are particularly relevant to the +PuTTY tools: + +\b \W{news:comp.security.ssh}\c{comp.security.ssh}, for questions +specific to using the SSH protocol; + +\b \W{news:comp.terminals}\c{comp.terminals}, for issues relating to +terminal emulation (for instance, keyboard problems). + +Please use the newsgroup most appropriate to your query, and remember +that these are general newsgroups, not specifically about PuTTY. + +If you don't have direct access to Usenet, you can access these +newsgroups through Google Groups +(\W{http://groups.google.com/}\cw{groups.google.com}). + +\H{feedback-bugs} Reporting bugs + +If you think you have found a bug in PuTTY, your first steps should +be: + +\b Check the +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist +page} on the PuTTY website, and see if we already know about the +problem. If we do, it is almost certainly not necessary to mail us +about it, unless you think you have extra information that might be +helpful to us in fixing it. (Of course, if we actually \e{need} +specific extra information about a particular bug, the Wishlist page +will say so.) + +\b Check the +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change +Log} on the PuTTY website, and see if we have already fixed the bug +in the \i{development snapshots}. + +\b Check the +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/faq.html}{FAQ} +on the PuTTY website (also provided as \k{faq} in the manual), and +see if it answers your question. The FAQ lists the most common +things which people think are bugs, but which aren't bugs. + +\b Download the latest development snapshot and see if the problem +still happens with that. This really is worth doing. As a general +rule we aren't very interested in bugs that appear in the release +version but not in the development version, because that usually +means they are bugs we have \e{already fixed}. On the other hand, if +you can find a bug in the development version that doesn't appear in +the release, that's likely to be a new bug we've introduced since +the release and we're definitely interested in it. + +If none of those options solved your problem, and you still need to +report a bug to us, it is useful if you include some general +information: + +\b Tell us what \i{version of PuTTY} you are running. To find this out, +use the \q{About PuTTY} option from the System menu. Please \e{do +not} just tell us \q{I'm running the latest version}; e-mail can be +delayed and it may not be obvious which version was the latest at +the time you sent the message. + +\b PuTTY is a multi-platform application; tell us what version of what +OS you are running PuTTY on. (If you're running on Unix, or Windows +for Alpha, tell us, or we'll assume you're running on Windows for +Intel as this is overwhelmingly the case.) + +\b Tell us what protocol you are connecting with: SSH, Telnet, +Rlogin or Raw mode. + +\b Tell us what kind of server you are connecting to; what OS, and +if possible what SSH server (if you're using SSH). You can get some +of this information from the PuTTY Event Log (see \k{using-eventlog} +in the manual). + +\b Send us the contents of the PuTTY Event Log, unless you +have a specific reason not to (for example, if it contains +confidential information that you think we should be able to solve +your problem without needing to know). + +\b Try to give us as much information as you can to help us +see the problem for ourselves. If possible, give us a step-by-step +sequence of \e{precise} instructions for reproducing the fault. + +\b Don't just tell us that PuTTY \q{does the wrong thing}; tell us +exactly and precisely what it did, and also tell us exactly and +precisely what you think it should have done instead. Some people +tell us PuTTY does the wrong thing, and it turns out that it was +doing the right thing and their expectations were wrong. Help to +avoid this problem by telling us exactly what you think it should +have done, and exactly what it did do. + +\b If you think you can, you're welcome to try to fix the problem +yourself. A \i{patch} to the code which fixes a bug is an excellent +addition to a bug report. However, a patch is never a \e{substitute} +for a good bug report; if your patch is wrong or inappropriate, and +you haven't supplied us with full information about the actual bug, +then we won't be able to find a better solution. + +\b +\W{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html}\cw{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html} +is an article on how to report bugs effectively in general. If your +bug report is \e{particularly} unclear, we may ask you to go away, +read this article, and then report the bug again. + +It is reasonable to report bugs in PuTTY's documentation, if you +think the documentation is unclear or unhelpful. But we do need to +be given exact details of \e{what} you think the documentation has +failed to tell you, or \e{how} you think it could be made clearer. +If your problem is simply that you don't \e{understand} the +documentation, we suggest posting to a newsgroup (see +\k{feedback-other-fora}) and seeing if someone +will explain what you need to know. \e{Then}, if you think the +documentation could usefully have told you that, send us a bug +report and explain how you think we should change it. + +\H{feedback-features} Requesting extra features + +If you want to request a new feature in PuTTY, the very first things +you should do are: + +\b Check the +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist +page} on the PuTTY website, and see if your feature is already on +the list. If it is, it probably won't achieve very much to repeat +the request. (But see \k{feedback-feature-priority} if you want to +persuade us to give your particular feature higher priority.) + +\b Check the Wishlist and +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change +Log} on the PuTTY website, and see if we have already added your +feature in the development snapshots. If it isn't clear, download +the latest development snapshot and see if the feature is present. +If it is, then it will also be in the next release and there is no +need to mail us at all. + +If you can't find your feature in either the development snapshots +\e{or} the Wishlist, then you probably do need to submit a feature +request. Since the PuTTY authors are very busy, it helps if you try +to do some of the work for us: + +\b Do as much of the design as you can. Think about \q{corner +cases}; think about how your feature interacts with other existing +features. Think about the user interface; if you can't come up with +a simple and intuitive interface to your feature, you shouldn't be +surprised if we can't either. Always imagine whether it's possible +for there to be more than one, or less than one, of something you'd +assumed there would be one of. (For example, if you were to want +PuTTY to put an icon in the System tray rather than the Taskbar, you +should think about what happens if there's more than one PuTTY +active; how would the user tell which was which?) + +\b If you can program, it may be worth offering to write the feature +yourself and send us a patch. However, it is likely to be helpful +if you confer with us first; there may be design issues you haven't +thought of, or we may be about to make big changes to the code which +your patch would clash with, or something. If you check with the +maintainers first, there is a better chance of your code actually +being usable. Also, read the design principles listed in \k{udp}: if +you do not conform to them, we will probably not be able to accept +your patch. + +\H{feedback-feature-priority} Requesting features that have already +been requested + +If a feature is already listed on the Wishlist, then it usually +means we would like to add it to PuTTY at some point. However, this +may not be in the near future. If there's a feature on the Wishlist +which you would like to see in the \e{near} future, there are +several things you can do to try to increase its priority level: + +\b Mail us and vote for it. (Be sure to mention that you've seen it +on the Wishlist, or we might think you haven't even \e{read} the +Wishlist). This probably won't have very \e{much} effect; if a huge +number of people vote for something then it may make a difference, +but one or two extra votes for a particular feature are unlikely to +change our priority list immediately. Offering a new and compelling +justification might help. Also, don't expect a reply. + +\b Offer us money if we do the work sooner rather than later. This +sometimes works, but not always. The PuTTY team all have full-time +jobs and we're doing all of this work in our free time; we may +sometimes be willing to give up some more of our free time in +exchange for some money, but if you try to bribe us for a \e{big} +feature it's entirely possible that we simply won't have the time to +spare - whether you pay us or not. (Also, we don't accept bribes to +add \e{bad} features to the Wishlist, because our desire to provide +high-quality software to the users comes first.) + +\b Offer to help us write the code. This is probably the \e{only} +way to get a feature implemented quickly, if it's a big one that we +don't have time to do ourselves. + +\H{feedback-support} \ii{Support requests} + +If you're trying to make PuTTY do something for you and it isn't +working, but you're not sure whether it's a bug or not, then +\e{please} consider looking for help somewhere else. This is one of +the most common types of mail the PuTTY team receives, and we simply +don't have time to answer all the questions. Questions of this type +include: + +\b If you want to do something with PuTTY but have no idea where to +start, and reading the manual hasn't helped, try posting to a +newsgroup (see \k{feedback-other-fora}) and see if someone can explain +it to you. + +\b If you have tried to do something with PuTTY but it hasn't +worked, and you aren't sure whether it's a bug in PuTTY or a bug in +your SSH server or simply that you're not doing it right, then try +posting to a newsgroup (see \k{feedback-other-fora}) and see +if someone can solve your problem. Or try doing the same thing with +a different SSH client and see if it works with that. Please do not +report it as a PuTTY bug unless you are really sure it \e{is} a bug +in PuTTY. + +\b If someone else installed PuTTY for you, or you're using PuTTY on +someone else's computer, try asking them for help first. They're more +likely to understand how they installed it and what they expected you +to use it for than we are. + +\b If you have successfully made a connection to your server and now +need to know what to type at the server's command prompt, or other +details of how to use the server-end software, talk to your server's +system administrator. This is not the PuTTY team's problem. PuTTY is +only a communications tool, like a telephone; if you can't speak the +same language as the person at the other end of the phone, it isn't +the telephone company's job to teach it to you. + +If you absolutely cannot get a support question answered any other +way, you can try mailing it to us, but we can't guarantee to have +time to answer it. + +\H{feedback-webadmin} Web server administration + +If the PuTTY \i{web site} is down (Connection Timed Out), please don't +bother mailing us to tell us about it. Most of us read our e-mail on +the same machines that host the web site, so if those machines are +down then we will notice \e{before} we read our e-mail. So there's +no point telling us our servers are down. + +Of course, if the web site has some other error (Connection Refused, +404 Not Found, 403 Forbidden, or something else) then we might +\e{not} have noticed and it might still be worth telling us about it. + +If you want to report a problem with our web site, check that you're +looking at our \e{real} web site and not a mirror. The real web site +is at +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\c{http://www.chiark.greenend.org.uk/~sgtatham/putty/}; +if that's not where you're reading this, then don't report the +problem to us until you've checked that it's really a problem with +the main site. If it's only a problem with the mirror, you should +try to contact the administrator of that mirror site first, and only +contact us if that doesn't solve the problem (in case we need to +remove the mirror from our list). + +\H{feedback-permission} Asking permission for things + +PuTTY is distributed under the MIT Licence (see \k{licence} for +details). This means you can do almost \e{anything} you like with +our software, our source code, and our documentation. The only +things you aren't allowed to do are to remove our copyright notices +or the licence text itself, or to hold us legally responsible if +something goes wrong. + +So if you want permission to include PuTTY on a magazine cover disk, +or as part of a collection of useful software on a CD or a web site, +then \e{permission is already granted}. You don't have to mail us +and ask. Just go ahead and do it. We don't mind. + +(If you want to distribute PuTTY alongside your own application for +use with that application, or if you want to distribute PuTTY within +your own organisation, then we recommend, but do not insist, that +you offer your own first-line technical support, to answer questions +about the interaction of PuTTY with your environment. If your users +mail us directly, we won't be able to tell them anything useful about +your specific setup.) + +If you want to use parts of the PuTTY source code in another +program, then it might be worth mailing us to talk about technical +details, but if all you want is to ask permission then you don't +need to bother. You already have permission. + +If you just want to link to our web site, just go ahead. (It's not +clear that we \e{could} stop you doing this, even if we wanted to!) + +\H{feedback-mirrors} Mirroring the PuTTY web site + +\# the next two paragraphs also on the Mirrors page itself, with +\# minor context changes + +If you want to set up a mirror of the PuTTY website, go ahead and +set one up. Please don't bother asking us for permission before +setting up a mirror. You already have permission. + +If the mirror is in a country where we don't already have plenty of +mirrors, we may be willing to add it to the list on our +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html}{mirrors +page}. Read the guidelines on that page, make sure your mirror +works, and email us the information listed at the bottom of the +page. + +Note that we do not \e{promise} to list your mirror: we get a lot of +mirror notifications and yours may not happen to find its way to the +top of the list. + +Also note that we link to all our mirror sites using the +\c{rel="nofollow"} attribute. Running a PuTTY mirror is not intended +to be a cheap way to gain search rankings. + +If you have technical questions about the process of mirroring, then +you might want to mail us before setting up the mirror (see also the +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html#guidelines}{guidelines on the Mirrors page}); +but if you just want to ask for permission, you don't need to. You +already have permission. + +\H{feedback-compliments} Praise and compliments + +One of the most rewarding things about maintaining free software is +getting e-mails that just say \q{thanks}. We are always happy to +receive e-mails of this type. + +Regrettably we don't have time to answer them all in person. If you +mail us a compliment and don't receive a reply, \e{please} don't +think we've ignored you. We did receive it and we were happy about +it; we just didn't have time to tell you so personally. + +To everyone who's ever sent us praise and compliments, in the past +and the future: \e{you're welcome}! + +\H{feedback-address} E-mail address + +The actual address to mail is +\cw{<\W{mailto:putty@projects.tartarus.org}{putty@projects.tartarus.org}>}. diff --git a/putty/DOC/GS.BUT b/putty/DOC/GS.BUT new file mode 100644 index 0000000..b6d8a82 --- /dev/null +++ b/putty/DOC/GS.BUT @@ -0,0 +1,155 @@ +\define{versionidgs} \versionid $Id: gs.but 6815 2006-08-28 10:35:12Z simon $ + +\C{gs} Getting started with PuTTY + +This chapter gives a quick guide to the simplest types of +interactive login session using PuTTY. + +\H{gs-insecure} \ii{Starting a session} + +When you start PuTTY, you will see a \i{dialog box}. This dialog box +allows you to control everything PuTTY can do. See \k{config} for +details of all the things you can control. + +You don't usually need to change most of the configuration options. +To start the simplest kind of session, all you need to do is to +enter a few basic parameters. + +In the \q{Host Name} box, enter the Internet \i{host name} of the server +you want to connect to. You should have been told this by the +provider of your login account. + +Now select a login \i{protocol} to use, from the \q{Connection type} +buttons. For a login session, you should select \i{Telnet}, +\i{Rlogin} or \i{SSH}. See \k{which-one} for a description of the +differences between the three protocols, and advice on which one to +use. The fourth protocol, \I{raw protocol}\e{Raw}, is not used for +interactive login sessions; you would usually use this for debugging +other Internet services (see \k{using-rawprot}). The fifth option, +\e{Serial}, is used for connecting to a local serial line, and works +somewhat differently: see \k{using-serial} for more information on +this. + +When you change the selected protocol, the number in the \q{Port} +box will change. This is normal: it happens because the various +login services are usually provided on different network ports by +the server machine. Most servers will use the standard port numbers, +so you will not need to change the port setting. If your server +provides login services on a non-standard port, your system +administrator should have told you which one. (For example, many +\i{MUDs} run Telnet service on a port other than 23.) + +Once you have filled in the \q{Host Name}, \q{Protocol}, and +possibly \q{Port} settings, you are ready to connect. Press the +\q{Open} button at the bottom of the dialog box, and PuTTY will +begin trying to connect you to the server. + +\H{gs-hostkey} \ii{Verifying the host key} (SSH only) + +If you are not using the \i{SSH} protocol, you can skip this +section. + +If you are using SSH to connect to a server for the first time, you +will probably see a message looking something like this: + +\c The server's host key is not cached in the registry. You +\c have no guarantee that the server is the computer you +\c think it is. +\c The server's rsa2 key fingerprint is: +\c ssh-rsa 1024 7b:e5:6f:a7:f4:f9:81:62:5c:e3:1f:bf:8b:57:6c:5a +\c If you trust this host, hit Yes to add the key to +\c PuTTY's cache and carry on connecting. +\c If you want to carry on connecting just once, without +\c adding the key to the cache, hit No. +\c If you do not trust this host, hit Cancel to abandon the +\c connection. + +This is a feature of the SSH protocol. It is designed to protect you +against a network attack known as \i\e{spoofing}: secretly +redirecting your connection to a different computer, so that you +send your password to the wrong machine. Using this technique, an +attacker would be able to learn the password that guards your login +account, and could then log in as if they were you and use the +account for their own purposes. + +To prevent this attack, each server has a unique identifying code, +called a \e{host key}. These keys are created in a way that prevents +one server from forging another server's key. So if you connect to a +server and it sends you a different host key from the one you were +expecting, PuTTY can warn you that the server may have been switched +and that a spoofing attack might be in progress. + +PuTTY 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. + +However, when you connect to a server you have not connected to +before, PuTTY has no way of telling whether the host key is the +right one or not. So it gives the warning shown above, and asks you +whether you want to \I{trusting host keys}trust this host key or +not. + +Whether or not to trust the host key is your choice. If you are +connecting within a company network, you might feel that all the +network users are on the same side and spoofing attacks are +unlikely, so you might choose to trust the key without checking it. +If you are connecting across a hostile network (such as the +Internet), you should check with your system administrator, perhaps +by telephone or in person. (Some modern servers have more than one +host key. If the system administrator sends you more than one +\I{host key fingerprint}fingerprint, you should make sure the one +PuTTY shows you is on the list, but it doesn't matter which one it is.) + +\# FIXME: this is all very fine but of course in practice the world +doesn't work that way. Ask the team if they have any good ideas for +changes to this section! + +\H{gs-login} \ii{Logging in} + +After you have connected, and perhaps verified the server's host +key, you will be asked to log in, probably using a \i{username} and +a \i{password}. Your system administrator should have provided you +with these. Enter the username and the password, and the server +should grant you access and begin your session. If you have +\I{mistyping a password}mistyped your password, most servers will +give you several chances to get it right. + +If you are using SSH, be careful not to type your username wrongly, +because you will not have a chance to correct it after you press +Return; many SSH servers do not permit you to make two login attempts +using \i{different usernames}. If you type your username wrongly, you +must close PuTTY and start again. + +If your password is refused but you are sure you have typed it +correctly, check that Caps Lock is not enabled. Many login servers, +particularly Unix computers, treat upper case and lower case as +different when checking your password; so if Caps Lock is on, your +password will probably be refused. + +\H{gs-session} After logging in + +After you log in to the server, what happens next is up to the +server! Most servers will print some sort of login message and then +present a \i{prompt}, at which you can type +\I{commands on the server}commands which the +server will carry out. Some servers will offer you on-line help; +others might not. If you are in doubt about what to do next, consult +your system administrator. + +\H{gs-logout} \ii{Logging out} + +When you have finished your session, you should log out by typing +the server's own logout command. This might vary between servers; if +in doubt, try \c{logout} or \c{exit}, or consult a manual or your +system administrator. When the server processes your logout command, +the PuTTY window should close itself automatically. + +You \e{can} close a PuTTY session using the \i{Close button} in the +window border, but this might confuse the server - a bit like +hanging up a telephone unexpectedly in the middle of a conversation. +We recommend you do not do this unless the server has stopped +responding to you and you cannot close the window any other way. diff --git a/putty/DOC/INDEX.BUT b/putty/DOC/INDEX.BUT new file mode 100644 index 0000000..aded2e9 --- /dev/null +++ b/putty/DOC/INDEX.BUT @@ -0,0 +1,851 @@ +\define{versionidindex} \versionid $Id: index.but 9009 2010-09-25 16:18:02Z jacob $ + +\IM{Unix version} Unix version of PuTTY tools +\IM{Unix version} Linux version of PuTTY tools + +\IM{Unix} Unix +\IM{Unix} Linux + +\IM{Command Prompt}{command prompt window}{MS-DOS Prompt}{console window} Command Prompt +\IM{Command Prompt}{command prompt window}{MS-DOS Prompt}{console window} MS-DOS Prompt +\IM{Command Prompt}{command prompt window}{MS-DOS Prompt}{console window} console window + +\IM{spoof}{spoofed}{spoofing} spoofing + +\IM{verifying the host key} verifying the host key +\IM{verifying the host key} host key, verifying + +\IM{trusting host keys} trusting host keys +\IM{trusting host keys} host keys, trusting + +\IM{host key fingerprint} fingerprint, of SSH host key +\IM{host key fingerprint} host key fingerprint (SSH) +\IM{host key fingerprint} SSH host key fingerprint + +\IM{starting a session} starting a session +\IM{starting a session} session, starting + +\IM{commands on the server}{remote command} commands on the server +\IM{commands on the server}{remote command} remote commands +\IM{commands on the server}{remote command} server, commands on + +\IM{mistyping a password} mistyping a password +\IM{mistyping a password} password, mistyping + +\IM{different usernames}{changes of username} different user names +\IM{different usernames}{changes of username} changing user names +\IM{different usernames}{changes of username} user names, different +\IM{different usernames}{changes of username} login names, different +\IM{different usernames}{changes of username} account names, different + +\IM{differences between SSH, Telnet and Rlogin} differences between +SSH, Telnet and Rlogin +\IM{differences between SSH, Telnet and Rlogin} protocols, +differences between +\IM{differences between SSH, Telnet and Rlogin} SSH, differences +from Telnet and Rlogin +\IM{differences between SSH, Telnet and Rlogin} Telnet, differences +from SSH and Rlogin +\IM{differences between SSH, Telnet and Rlogin} Rlogin, differences +from SSH and Telnet +\IM{differences between SSH, Telnet and Rlogin} selecting a protocol +\IM{differences between SSH, Telnet and Rlogin} choosing a protocol + +\IM{MUD}{MUDs} MUDs + +\IM{talker}{talker systems} talker systems + +\IM{security hazard}{security risk} security hazard + +\IM{SSH-1}{SSH protocol version 1} SSH-1 +\IM{SSH-2}{SSH protocol version 2} SSH-2 + +\IM{terminal window}{PuTTY window} terminal window +\IM{terminal window}{PuTTY window} PuTTY terminal window +\IM{terminal window}{PuTTY window} window, terminal + +\IM{copy and paste} copy and paste +\IM{copy and paste} cut and paste +\IM{copy and paste} paste, copy and + +\IM{three-button mouse} three-button mouse +\IM{three-button mouse} mouse, three-button + +\IM{left mouse button}{left button} left mouse button +\IM{middle mouse button}{middle button} middle mouse button +\IM{right mouse button}{right button} right mouse button + +\IM{selecting words}{word-by-word selection} selecting whole words +\IM{selecting words}{word-by-word selection} words, selecting + +\IM{selecting lines} selecting whole lines +\IM{selecting lines} lines, selecting + +\IM{rectangular selection} rectangular selection +\IM{rectangular selection} selection, rectangular + +\IM{adjusting a selection} adjusting a selection +\IM{adjusting a selection} extending a selection +\IM{adjusting a selection} selection, adjusting + +\IM{right mouse button, with Ctrl} right mouse button, with Ctrl +\IM{right mouse button, with Ctrl} Ctrl, with right mouse button + +\IM{system menu} system menu +\IM{system menu} menu, system +\IM{system menu} window menu + +\IM{context menu} context menu +\IM{context menu} menu, context +\IM{context menu} right mouse button menu + +\IM{Event Log} Event Log +\IM{Event Log} PuTTY Event Log +\IM{Event Log} Log, Event + +\IM{Telnet special commands} Telnet special commands +\IM{Telnet special commands} special commands, in Telnet + +\IM{SSH special commands} SSH special commands +\IM{SSH special commands} special commands, in SSH + +\IM{Repeat key exchange, SSH special command} Repeat key exchange, SSH special command +\IM{Repeat key exchange, SSH special command} key exchange, forcing repeat +\IM{Repeat key exchange, SSH special command} SSH key exchange, forcing repeat + +\IM{accented characters} accented characters +\IM{accented characters} characters, accented + +\IM{line-drawing characters} line-drawing characters +\IM{line-drawing characters} box-drawing characters +\IM{line-drawing characters} characters, line-drawing +\IM{line-drawing characters} ANSI graphics + +\IM{port forwarding}{port forwardings} port forwarding in SSH +\IM{port forwarding}{port forwardings} SSH port forwarding +\IM{port forwarding}{port forwardings} forwarding ports in SSH +\IM{port forwarding}{port forwardings} tunnelling using SSH +\IM{port forwarding}{port forwardings} SSH tunnelling + +\IM{port forwarding, changing mid-session} port forwarding in SSH, changing mid-session +\IM{port forwarding, changing mid-session} SSH port forwarding, changing mid-session +\IM{port forwarding, changing mid-session} forwarding ports in SSH, changing mid-session +\IM{port forwarding, changing mid-session} tunnelling using SSH, changing mid-session +\IM{port forwarding, changing mid-session} SSH tunnelling, changing mid-session + +\IM{local port forwarding} local-to-remote port forwarding +\IM{remote port forwarding} remote-to-local port forwarding + +\IM{dynamic port forwarding} dynamic port forwarding +\IM{dynamic port forwarding} SOCKS port forwarding + +\IM{debugging Internet protocols} debugging Internet protocols +\IM{debugging Internet protocols} Internet protocols, debugging +\IM{debugging Internet protocols} protocols, debugging + +\IM{Internet protocol version} Internet Protocol version +\IM{Internet protocol version} version, of Internet Protocol + +\IM{raw TCP connections} raw TCP connections +\IM{raw TCP connections} TCP connections, raw + +\IM{command-line arguments} command-line arguments +\IM{command-line arguments} arguments, command-line +\IM{command-line arguments} options, command-line +\IM{command-line arguments} switches, command-line + +\IM{Windows shortcut} Windows shortcut +\IM{Windows shortcut} shortcut, Windows + +\IM{telnet URLs} Telnet URLs +\IM{telnet URLs} URLs, Telnet + +\IM{saved sessions, loading from command line} saved sessions, +loading from command line +\IM{saved sessions, loading from command line} loading saved +sessions from command line +\IM{saved sessions, loading from command line} command line, loading +saved sessions from + +\IM{putty @sessionname} \c{putty @sessionname} +\IM{putty @sessionname} \c{@sessionname} command-line argument + +\IM{protocol selection} protocol selection +\IM{protocol selection} selecting a protocol +\IM{protocol selection} choosing a protocol + +\IM{login name}{username} login name +\IM{login name}{username} user name +\IM{login name}{username} account name + +\IM{reading commands from a file} reading commands from a file +\IM{reading commands from a file} commands, reading from a file + +\IM{agent forwarding} agent forwarding +\IM{agent forwarding} authentication agent forwarding +\IM{agent forwarding} SSH agent forwarding +\IM{agent forwarding} forwarding, SSH agent + +\IM{X11 forwarding}{forwarding of X11} X11 forwarding +\IM{X11 forwarding}{forwarding of X11} SSH X11 forwarding +\IM{X11 forwarding}{forwarding of X11} forwarding, of X11 + +\IM{X11 authentication} X11 authentication +\IM{X11 authentication} authentication, X11 + +\IM{pseudo-terminal allocation} pseudo-terminal allocation +\IM{pseudo-terminal allocation} pty allocation +\IM{pseudo-terminal allocation} allocation, of pseudo-terminal + +\IM{ERASE special character} \cw{ERASE}, special character +\IM{ERASE special character} \cw{VERASE}, special character +\IM{QUIT special character} \cw{QUIT}, special character +\IM{QUIT special character} \cw{VQUIT}, special character + +\IM{-telnet} \c{-telnet} command-line option +\IM{-raw} \c{-raw} command-line option +\IM{-rlogin} \c{-rlogin} command-line option +\IM{-ssh} \c{-ssh} command-line option +\IM{-serial} \c{-serial} command-line option +\IM{-cleanup} \c{-cleanup} command-line option +\IM{-load} \c{-load} command-line option +\IM{-v} \c{-v} command-line option +\IM{-l} \c{-l} command-line option +\IM{-L-upper} \c{-L} command-line option +\IM{-R-upper} \c{-R} command-line option +\IM{-D-upper} \c{-D} command-line option +\IM{-m} \c{-m} command-line option +\IM{-P-upper} \c{-P} command-line option +\IM{-pw} \c{-pw} command-line option +\IM{-A-upper} \c{-A} command-line option +\IM{-a} \c{-a} command-line option +\IM{-X-upper} \c{-X} command-line option +\IM{-x} \c{-x} command-line option +\IM{-T-upper} \c{-T} command-line option +\IM{-t} \c{-t} command-line option +\IM{-C-upper} \c{-C} command-line option +\IM{-N-upper} \c{-N} command-line option +\IM{-1} \c{-1} command-line option +\IM{-2} \c{-2} command-line option +\IM{-i} \c{-i} command-line option +\IM{-pgpfp} \c{-pgpfp} command-line option +\IM{-sercfg} \c{-sercfg} command-line option + +\IM{removing registry entries} removing registry entries +\IM{removing registry entries} registry entries, removing + +\IM{random seed file} random seed file +\IM{random seed file} \c{putty.rnd} (random seed file) + +\IM{putty.rnd} \c{putty.rnd} (random seed file) + +\IM{suppressing remote shell} remote shell, suppressing +\IM{suppressing remote shell} shell, remote, suppressing + +\IM{SSH protocol version} SSH protocol version +\IM{SSH protocol version} protocol version, SSH +\IM{SSH protocol version} version, of SSH protocol + +\IM{PPK} \cw{PPK} file +\IM{PPK} private key file, PuTTY + +\IM{PGP key fingerprint} PGP key fingerprint +\IM{PGP key fingerprint} fingerprint, of PGP key + +\IM{verifying new versions} verifying new versions of PuTTY +\IM{verifying new versions} new version, verifying +\IM{verifying new versions} upgraded version, verifying + +\IM{connection}{network connection} network connection +\IM{connection}{network connection} connection, network + +\IM{host name}{hostname} host name +\IM{host name}{hostname} DNS name +\IM{host name}{hostname} server name + +\IM{IP address}{Internet address} IP address +\IM{IP address}{Internet address} address, IP + +\IM{localhost} \c{localhost} + +\IM{loopback IP address}{loopback address} loopback IP address +\IM{loopback IP address}{loopback address} IP address, loopback + +\IM{listen address} listen address +\IM{listen address} bind address + +\IM{DNS} DNS +\IM{DNS} Domain Name System + +\IM{name resolution} name resolution +\IM{name resolution} DNS resolution +\IM{name resolution} host name resolution +\IM{name resolution} server name resolution + +\IM{loading and storing saved sessions} sessions, loading and storing +\IM{loading and storing saved sessions} settings, loading and storing +\IM{loading and storing saved sessions} saving settings +\IM{loading and storing saved sessions} storing settings +\IM{loading and storing saved sessions} loading settings + +\IM{Default Settings} Default Settings +\IM{Default Settings} settings, default + +\IM{Registry} Registry (Windows) +\IM{Registry} Windows Registry + +\IM{inactive window} inactive window +\IM{inactive window} window, inactive +\IM{inactive window} terminal window, inactive + +\IM{SSH packet log} SSH packet log +\IM{SSH packet log} packet log, SSH + +\IM{auto wrap mode}{auto wrap} auto wrap mode +\IM{auto wrap mode}{auto wrap} wrapping, automatic +\IM{auto wrap mode}{auto wrap} line wrapping, automatic + +\IM{control sequence}{control codes} control sequences +\IM{control sequence}{control codes} terminal control sequences +\IM{control sequence}{control codes} escape sequences + +\IM{cursor coordinates} cursor coordinates +\IM{cursor coordinates} coordinates, cursor + +\IM{CR} CR (Carriage Return) +\IM{CR} Carriage Return + +\IM{LF} LF (Line Feed) +\IM{LF} Line Feed + +\IM{clear screen} clear screen +\IM{clear screen} erase screen +\IM{clear screen} screen, clearing + +\IM{blinking text} blinking text +\IM{blinking text} flashing text + +\IM{answerback} answerback string + +\IM{local echo} local echo +\IM{local echo} echo, local + +\IM{remote echo} remote echo +\IM{remote echo} echo, remote + +\IM{local line editing} local line editing +\IM{local line editing} line editing, local + +\IM{remote-controlled printing} ANSI printing +\IM{remote-controlled printing} remote-controlled printing +\IM{remote-controlled printing} printing, remote-controlled + +\IM{Home and End keys} Home key +\IM{Home and End keys} End key + +\IM{keypad} keypad, numeric +\IM{keypad} numeric keypad + +\IM{Application Cursor Keys} Application Cursor Keys +\IM{Application Cursor Keys} cursor keys, \q{Application} mode + +\IM{Application Keypad} Application Keypad +\IM{Application Keypad} keypad, \q{Application} mode +\IM{Application Keypad} numeric keypad, \q{Application} mode + +\IM{Num Lock}{NumLock} Num Lock + +\IM{NetHack keypad mode} NetHack keypad mode +\IM{NetHack keypad mode} keypad, NetHack mode + +\IM{compose key} Compose key +\IM{compose key} DEC Compose key + +\IM{terminal bell} terminal bell +\IM{terminal bell} bell, terminal +\IM{terminal bell} beep, terminal +\IM{terminal bell} feep + +\IM{Windows Default Beep} Windows Default Beep sound +\IM{Windows Default Beep} Default Beep sound, Windows + +\IM{terminal bell, disabling} terminal bell, disabling +\IM{terminal bell, disabling} bell, disabling + +\IM{visual bell} visual bell +\IM{visual bell} bell, visual + +\IM{PC speaker} PC speaker +\IM{PC speaker} beep, with PC speaker + +\IM{sound file} sound file +\IM{sound file} \cw{WAV} file + +\IM{bell overload} bell overload mode +\IM{bell overload} terminal bell overload mode + +\IM{mouse reporting} mouse reporting +\IM{mouse reporting} \c{xterm} mouse reporting + +\IM{links} \c{links} (web browser) + +\IM{mc} \c{mc} +\IM{mc} Midnight Commander + +\IM{terminal resizing}{window resizing} terminal resizing +\IM{terminal resizing}{window resizing} window resizing +\IM{terminal resizing}{window resizing} resizing, terminal + +\IM{destructive backspace} destructive backspace +\IM{destructive backspace} non-destructive backspace +\IM{destructive backspace} backspace, destructive + +\IM{Arabic text shaping} Arabic text shaping +\IM{Arabic text shaping} shaping, of Arabic text + +\IM{Unicode} Unicode +\IM{Unicode} ISO-10646 (Unicode) + +\IM{ASCII} ASCII +\IM{ASCII} US-ASCII + +\IM{bidirectional text} bidirectional text +\IM{bidirectional text} right-to-left text + +\IM{display becomes corrupted} display corruption +\IM{display becomes corrupted} corruption, of display + +\IM{rows} rows, in terminal window +\IM{columns} columns, in terminal window + +\IM{window size} window size +\IM{window size} size, of window + +\IM{font size} font size +\IM{font size} size, of font + +\IM{full screen}{full-screen} full-screen mode + +\IM{cursor blinks} blinking cursor +\IM{cursor blinks} flashing cursor +\IM{cursor blinks} cursor, blinking + +\IM{font} font +\IM{font} typeface + +\IM{minimise} minimise window +\IM{minimise} window, minimising + +\IM{maximise} maximise window +\IM{maximise} window, maximising + +\IM{closing window}{close window} closing window +\IM{closing window}{close window} window, closing + +\IM{Dragon NaturallySpeaking} Dragon NaturallySpeaking +\IM{Dragon NaturallySpeaking} NaturallySpeaking + +\IM{AltGr} \q{AltGr} key +\IM{Alt} \q{Alt} key + +\IM{CJK} CJK +\IM{CJK} Chinese +\IM{CJK} Japanese +\IM{CJK} Korean + +\IM{East Asian Ambiguous characters} East Asian Ambiguous characters +\IM{East Asian Ambiguous characters} CJK ambiguous characters + +\IM{character width} character width +\IM{character width} single-width character +\IM{character width} double-width character + +\IM{Rich Text Format} Rich Text Format +\IM{Rich Text Format} RTF + +\IM{bold}{bold text} bold text + +\IM{colour}{colours} colour + +\IM{8-bit colour} 8-bit colour +\IM{8-bit colour} colour, 8-bit + +\IM{system colours} system colours +\IM{system colours} colours, system + +\IM{ANSI colours} ANSI colours +\IM{ANSI colours} colours, ANSI + +\IM{cursor colour} cursor colour +\IM{cursor colour} colour, of cursor + +\IM{default background} background colour, default +\IM{default background} colour, background, default + +\IM{default foreground} foreground colour, default +\IM{default foreground} colour, foreground, default + +\IM{TERM} \cw{TERM} environment variable + +\IM{logical palettes} logical palettes +\IM{logical palettes} palettes, logical + +\IM{breaks in connectivity} connectivity, breaks in +\IM{breaks in connectivity} intermittent connectivity + +\IM{idle connections} idle connections +\IM{idle connections} timeout, of connections +\IM{idle connections} connections, idle + +\IM{interactive connections}{interactive session} interactive connections +\IM{interactive connections}{interactive session} connections, interactive + +\IM{keepalives} keepalives, application + +\IM{Nagle's algorithm} Nagle's algorithm +\IM{Nagle's algorithm} \cw{TCP_NODELAY} + +\IM{TCP keepalives} TCP keepalives +\IM{TCP keepalives} keepalives, TCP +\IM{TCP keepalives} \cw{SO_KEEPALIVE} + +\IM{half-open connections} half-open connections +\IM{half-open connections} connections, half-open + +\IM{auto-login username} user name, for auto-login +\IM{auto-login username} login name, for auto-login +\IM{auto-login username} account name, for auto-login + +\IM{terminal emulation}{terminal-type} terminal emulation +\IM{terminal emulation}{terminal-type} emulation, terminal + +\IM{terminal speed} terminal speed +\IM{terminal speed} speed, terminal +\IM{terminal speed} baud rate, of terminal + +\IM{environment variables} environment variables +\IM{environment variables} variables, environment + +\IM{proxy} proxy server +\IM{proxy} server, proxy + +\IM{HTTP proxy} HTTP proxy +\IM{HTTP proxy} proxy, HTTP +\IM{HTTP proxy} server, HTTP +\IM{HTTP proxy} \cw{CONNECT} proxy (HTTP) + +\IM{SOCKS server} SOCKS proxy +\IM{SOCKS server} server, SOCKS +\IM{SOCKS server} proxy, SOCKS + +\IM{Telnet proxy} Telnet proxy +\IM{Telnet proxy} TCP proxy +\IM{Telnet proxy} ad-hoc proxy +\IM{Telnet proxy} proxy, Telnet + +\IM{Local proxy} local proxy +\IM{Local proxy} proxy command +\IM{Local proxy} command, proxy + +\IM{proxy DNS} proxy DNS +\IM{proxy DNS} DNS, with proxy +\IM{proxy DNS} name resolution, with proxy +\IM{proxy DNS} host name resolution, with proxy +\IM{proxy DNS} server name resolution, with proxy + +\IM{proxy username} proxy user name +\IM{proxy username} user name, for proxy +\IM{proxy username} login name, for proxy +\IM{proxy username} account name, for proxy + +\IM{proxy password} proxy password +\IM{proxy password} password, for proxy + +\IM{proxy authentication} proxy authentication +\IM{proxy authentication} authentication, to proxy + +\IM{HTTP basic} HTTP \q{basic} authentication +\IM{HTTP basic} \q{basic} authentication (HTTP) + +\IM{plaintext password} plain text password +\IM{plaintext password} password, plain text + +\IM{Telnet negotiation} Telnet option negotiation +\IM{Telnet negotiation} option negotiation, Telnet +\IM{Telnet negotiation} negotiation, of Telnet options + +\IM{firewall}{firewalls} firewalls + +\IM{NAT router}{NAT} NAT routers +\IM{NAT router}{NAT} routers, NAT +\IM{NAT router}{NAT} Network Address Translation +\IM{NAT router}{NAT} IP masquerading + +\IM{Telnet New Line} Telnet New Line +\IM{Telnet New Line} new line, in Telnet + +\IM{.rhosts} \c{.rhosts} file +\IM{.rhosts} \q{rhosts} file + +\IM{passwordless login} passwordless login +\IM{passwordless login} login, passwordless + +\IM{Windows user name} local user name, in Windows +\IM{Windows user name} user name, local, in Windows +\IM{Windows user name} login name, local, in Windows +\IM{Windows user name} account name, local, in Windows + +\IM{local username in Rlogin} local user name, in Rlogin +\IM{local username in Rlogin} user name, local, in Rlogin +\IM{local username in Rlogin} login name, local, in Rlogin +\IM{local username in Rlogin} account name, local, in Rlogin + +\IM{privileged port} privileged port +\IM{privileged port} low-numbered port +\IM{privileged port} port, privileged + +\IM{remote shell} shell, remote +\IM{remote shell} remote shell + +\IM{encryption}{encrypted}{encrypt} encryption + +\IM{encryption algorithm} encryption algorithm +\IM{encryption algorithm} cipher algorithm +\IM{encryption algorithm} symmetric-key algorithm +\IM{encryption algorithm} algorithm, encryption + +\IM{AES} AES +\IM{AES} Advanced Encryption Standard +\IM{AES} Rijndael + +\IM{Arcfour} Arcfour +\IM{Arcfour} RC4 + +\IM{triple-DES} triple-DES + +\IM{single-DES} single-DES +\IM{single-DES} DES + +\IM{key exchange} key exchange +\IM{key exchange} kex + +\IM{shared secret} shared secret +\IM{shared secret} secret, shared + +\IM{key exchange algorithm} key exchange algorithm +\IM{key exchange algorithm} algorithm, key exchange + +\IM{Diffie-Hellman key exchange} Diffie-Hellman key exchange +\IM{Diffie-Hellman key exchange} key exchange, Diffie-Hellman + +\IM{group exchange} Diffie-Hellman group exchange +\IM{group exchange} group exchange, Diffie-Hellman + +\IM{repeat key exchange} repeat key exchange +\IM{repeat key exchange} key exchange, repeat + +\IM{challenge/response authentication} challenge/response authentication +\IM{challenge/response authentication} authentication, challenge/response + +\IM{security token} security token +\IM{security token} token, security + +\IM{one-time passwords} one-time passwords +\IM{one-time passwords} password, one-time + +\IM{keyboard-interactive authentication} keyboard-interactive authentication +\IM{keyboard-interactive authentication} authentication, keyboard-interactive + +\IM{password expiry} password expiry +\IM{password expiry} expiry, of passwords + +\IM{public key authentication}{public-key authentication} public key authentication +\IM{public key authentication}{public-key authentication} RSA authentication +\IM{public key authentication}{public-key authentication} DSA authentication +\IM{public key authentication}{public-key authentication} authentication, public key + +\IM{MIT-MAGIC-COOKIE-1} \cw{MIT-MAGIC-COOKIE-1} +\IM{MIT-MAGIC-COOKIE-1} magic cookie +\IM{MIT-MAGIC-COOKIE-1} cookie, magic + +\IM{SSH server bugs} SSH server bugs +\IM{SSH server bugs} bugs, in SSH servers + +\IM{ignore message} SSH \q{ignore} messages +\IM{ignore message} \q{ignore} messages, in SSH + +\IM{message authentication code}{MAC} message authentication code (MAC) +\IM{message authentication code}{MAC} MAC (message authentication code) + +\IM{signatures} signature +\IM{signatures} digital signature + +\IM{storing configuration in a file} storing settings in a file +\IM{storing configuration in a file} saving settings in a file +\IM{storing configuration in a file} loading settings from a file + +\IM{transferring files} transferring files +\IM{transferring files} files, transferring + +\IM{receiving files}{download a file} receiving files +\IM{receiving files}{download a file} files, receiving +\IM{receiving files}{download a file} downloading files + +\IM{sending files}{upload a file} sending files +\IM{sending files}{upload a file} files, sending +\IM{sending files}{upload a file} uploading files + +\IM{listing files} listing files +\IM{listing files} files, listing + +\IM{wildcard}{wildcards} wildcards +\IM{wildcard}{wildcards} glob (wildcard) + +\IM{PATH} \c{PATH} environment variable + +\IM{SFTP} SFTP +\IM{SFTP} SSH file transfer protocol + +\IM{-unsafe} \c{-unsafe} PSCP command-line option +\IM{-ls-PSCP} \c{-ls} PSCP command-line option +\IM{-p-PSCP} \c{-p} PSCP command-line option +\IM{-q-PSCP} \c{-q} PSCP command-line option +\IM{-r-PSCP} \c{-r} PSCP command-line option +\IM{-batch-PSCP} \c{-batch} PSCP command-line option +\IM{-sftp} \c{-sftp} PSCP command-line option +\IM{-scp} \c{-scp} PSCP command-line option + +\IM{return value} return value +\IM{return value} exit value + +\IM{-b-PSFTP} \c{-b} PSFTP command-line option +\IM{-bc-PSFTP} \c{-bc} PSFTP command-line option +\IM{-be-PSFTP} \c{-be} PSFTP command-line option +\IM{-batch-PSFTP} \c{-batch} PSFTP command-line option + +\IM{spaces in filenames} spaces in filenames +\IM{spaces in filenames} filenames containing spaces + +\IM{working directory} working directory +\IM{working directory} current working directory + +\IM{resuming file transfers} resuming file transfers +\IM{resuming file transfers} files, resuming transfer of + +\IM{changing permissions on files} changing permissions on files +\IM{changing permissions on files} permissions on files, changing +\IM{changing permissions on files} files, changing permissions on +\IM{changing permissions on files} modes of files, changing +\IM{changing permissions on files} access to files, changing + +\IM{deleting files} deleting files +\IM{deleting files} files, deleting +\IM{deleting files} removing files + +\IM{create a directory} creating directories +\IM{create a directory} directories, creating + +\IM{remove a directory} removing directories +\IM{remove a directory} directories, removing +\IM{remove a directory} deleting directories + +\IM{rename remote files} renaming files +\IM{rename remote files} files, renaming and moving +\IM{rename remote files} moving files + +\IM{local Windows command} local Windows command +\IM{local Windows command} Windows command + +\IM{PLINK_PROTOCOL} \c{PLINK_PROTOCOL} environment variable + +\IM{-batch-plink} \c{-batch} Plink command-line option +\IM{-s-plink} \c{-s} Plink command-line option + +\IM{subsystem} subsystem, SSH +\IM{subsystem} SSH subsystem + +\IM{batch file}{batch files} batch files + +\IM{CVS_RSH} \c{CVS_RSH} environment variable + +\IM{DSA} DSA +\IM{DSA} Digital Signature Standard + +\IM{public-key algorithm} public-key algorithm +\IM{public-key algorithm} asymmetric key algorithm +\IM{public-key algorithm} algorithm, public-key + +\IM{generating keys} generating key pairs +\IM{generating keys} creating key pairs +\IM{generating keys} key pairs, generating +\IM{generating keys} public keys, generating +\IM{generating keys} private keys, generating + +\IM{authorized_keys file}{authorized_keys} \cw{authorized_keys} file + +\IM{key fingerprint} fingerprint, of SSH authentication key +\IM{key fingerprint} public key fingerprint (SSH) +\IM{key fingerprint} SSH public key fingerprint + +\IM{SSH-2 public key format} SSH-2 public key file format +\IM{SSH-2 public key format} public key file, SSH-2 + +\IM{OpenSSH private key format} OpenSSH private key file format +\IM{OpenSSH private key format} private key file, OpenSSH + +\IM{ssh.com private key format} \cw{ssh.com} private key file format +\IM{ssh.com private key format} private key file, \cw{ssh.com} + +\IM{importing keys} importing private keys +\IM{importing keys} loading private keys + +\IM{export private keys} exporting private keys +\IM{export private keys} saving private keys + +\IM{.ssh} \c{.ssh} directory + +\IM{.ssh2} \c{.ssh2} directory + +\IM{authentication agent} authentication agent +\IM{authentication agent} agent, authentication + +\IM{-c-pageant} \c{-c} Pageant command-line option + +\IM{FAQ} FAQ +\IM{FAQ} Frequently Asked Questions + +\IM{supported features} supported features +\IM{supported features} features, supported + +\IM{remember my password} storing passwords +\IM{remember my password} password, storing + +\IM{login scripts}{startup scripts} login scripts +\IM{login scripts}{startup scripts} startup scripts + +\IM{WS2_32.DLL} \cw{WS2_32.DLL} +\IM{WS2_32.DLL} WinSock version 2 + +\IM{Red Hat Linux} Red Hat Linux +\IM{Red Hat Linux} Linux, Red Hat + +\IM{SMB} SMB +\IM{SMB} Windows file sharing + +\IM{clean up} clean up after PuTTY +\IM{clean up} uninstalling + +\IM{version of PuTTY} version, of PuTTY + +\IM{PGP signatures} PGP signatures, of PuTTY binaries +\IM{PGP signatures} signatures, of PuTTY binaries + +\IM{logical host name} logical host name +\IM{logical host name} host name, logical +\IM{logical host name} host key, caching policy + +\IM{web browsers} web browser + +\IM{GSSAPI credential delegation} GSSAPI credential delegation +\IM{GSSAPI credential delegation} credential delegation, GSSAPI +\IM{GSSAPI credential delegation} delegation, of GSSAPI credentials diff --git a/putty/DOC/INTRO.BUT b/putty/DOC/INTRO.BUT new file mode 100644 index 0000000..8796662 --- /dev/null +++ b/putty/DOC/INTRO.BUT @@ -0,0 +1,88 @@ +\define{versionidintro} \versionid $Id: intro.but 5593 2005-04-05 18:01:32Z jacob $ + +\C{intro} Introduction to PuTTY + +PuTTY is a free SSH, Telnet and Rlogin client for 32-bit Windows +systems. + +\H{you-what} What are SSH, Telnet and Rlogin? + +If you already know what SSH, Telnet and Rlogin are, you can safely +skip on to the next section. + +SSH, Telnet and Rlogin are three ways of doing the same thing: +logging in to a multi-user computer from another computer, over a +network. + +Multi-user operating systems, such as Unix and VMS, usually present +a \i{command-line interface} to the user, much like the \q{\i{Command +Prompt}} or \q{\i{MS-DOS Prompt}} in Windows. The system prints a +prompt, and you type commands which the system will obey. + +Using this type of interface, there is no need for you to be sitting +at the same machine you are typing commands to. The commands, and +responses, can be sent over a network, so you can sit at one +computer and give commands to another one, or even to more than one. + +SSH, Telnet and Rlogin are \i\e{network protocols} that allow you to +do this. On the computer you sit at, you run a \i\e{client}, which +makes a network connection to the other computer (the \i\e{server}). +The network connection carries your keystrokes and commands from the +client to the server, and carries the server's responses back to +you. + +These protocols can also be used for other types of keyboard-based +interactive session. In particular, there are a lot of bulletin +boards, \i{talker systems} and \i{MUDs} (Multi-User Dungeons) which support +access using Telnet. There are even a few that support SSH. + +You might want to use SSH, Telnet or Rlogin if: + +\b you have an account on a Unix or VMS system which you want to be +able to access from somewhere else + +\b your Internet Service Provider provides you with a login account +on a \i{web server}. (This might also be known as a \i\e{shell account}. +A \e{shell} is the program that runs on the server and interprets +your commands for you.) + +\b you want to use a \i{bulletin board system}, talker or MUD which can +be accessed using Telnet. + +You probably do \e{not} want to use SSH, Telnet or Rlogin if: + +\b you only use Windows. Windows computers have their own +ways of networking between themselves, and unless you are doing +something fairly unusual, you will not need to use any of these +remote login protocols. + +\H{which-one} How do SSH, Telnet and Rlogin differ? + +This list summarises some of the \i{differences between SSH, Telnet +and Rlogin}. + +\b SSH (which stands for \q{\i{secure shell}}) is a recently designed, +high-security protocol. It uses strong cryptography to protect your +connection against eavesdropping, hijacking and other attacks. Telnet +and Rlogin are both older protocols offering minimal security. + +\b SSH and Rlogin both allow you to \I{passwordless login}log in to the +server without having to type a password. (Rlogin's method of doing this is +insecure, and can allow an attacker to access your account on the +server. SSH's method is much more secure, and typically breaking the +security requires the attacker to have gained access to your actual +client machine.) + +\b SSH allows you to connect to the server and automatically send a +command, so that the server will run that command and then +disconnect. So you can use it in automated processing. + +The Internet is a hostile environment and security is everybody's +responsibility. If you are connecting across the open Internet, then +we recommend you use SSH. If the server you want to connect to +doesn't support SSH, it might be worth trying to persuade the +administrator to install it. + +If your client and server are both behind the same (good) firewall, +it is more likely to be safe to use Telnet or Rlogin, but we still +recommend you use SSH. diff --git a/putty/DOC/LICENCE.BUT b/putty/DOC/LICENCE.BUT new file mode 100644 index 0000000..62d8d74 --- /dev/null +++ b/putty/DOC/LICENCE.BUT @@ -0,0 +1,29 @@ +\define{versionidlicence} \versionid $Id: licence.but 9072 2011-01-05 12:01:00Z jacob $ + +\A{licence} PuTTY \ii{Licence} + +PuTTY is \i{copyright} 1997-2011 Simon Tatham. + +Portions copyright Robert de Bath, Joris van Rantwijk, Delian +Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, +Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus +Kuhn, Colin Watson, and CORE SDI S.A. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the \q{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 \q{AS IS}, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/putty/DOC/MAKEFILE b/putty/DOC/MAKEFILE new file mode 100644 index 0000000..cbdb2a8 --- /dev/null +++ b/putty/DOC/MAKEFILE @@ -0,0 +1,75 @@ +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 shows a +# $Id for each individual file. +# +# 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 + echo \\versionid $(VERSION) > vstr.but +FORCE:; +else +VERSIONIDS=vids +endif + +CHAPTERS := $(SITE) blurb intro gs using config pscp psftp plink pubkey +CHAPTERS += pageant errors faq feedback licence udp 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 --winhelp $(INPUTS) + +# During formal builds it's useful to be able to build this one alone. +putty.hlp: $(INPUTS) + $(HALIBUT) --winhelp $(INPUTS) + +putty.info: $(INPUTS) + $(HALIBUT) --info $(INPUTS) + +chm: putty.hhp +putty.hhp: $(INPUTS) chm.but + $(HALIBUT) --html $(INPUTS) chm.but + +MKMAN = $(HALIBUT) --man=$@ mancfg.but $< +MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1 +man: $(MANPAGES) + +putty.1: man-putt.but mancfg.but; $(MKMAN) +puttygen.1: man-pg.but mancfg.but; $(MKMAN) +plink.1: man-pl.but mancfg.but; $(MKMAN) +pscp.1: man-pscp.but mancfg.but; $(MKMAN) +psftp.1: man-psft.but mancfg.but; $(MKMAN) +puttytel.1: man-ptel.but mancfg.but; $(MKMAN) +pterm.1: man-pter.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/putty/DOC/MAN-PG.BUT b/putty/DOC/MAN-PG.BUT new file mode 100644 index 0000000..d71fe57 --- /dev/null +++ b/putty/DOC/MAN-PG.BUT @@ -0,0 +1,211 @@ +\cfg{man-identity}{puttygen}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} + +\H{puttygen-manpage} Man page for PuTTYgen + +\S{puttygen-manpage-name} NAME + +\cw{puttygen} - public-key generator for the PuTTY tools + +\S{puttygen-manpage-synopsis} SYNOPSIS + +\c puttygen ( keyfile | -t keytype [ -b bits ] ) +\e bbbbbbbb iiiiiii bb iiiiiii bb iiii +\c [ -C new-comment ] [ -P ] [ -q ] +\e bb iiiiiiiiiii bb bb +\c [ -O output-type | -l | -L | -p ] +\e bb iiiiiiiiiii bb bb bb +\c [ -o output-file ] +\e bb iiiiiiiiiii + +\S{puttygen-manpage-description} DESCRIPTION + +\c{puttygen} is a tool to generate and manipulate SSH public and +private key pairs. It is part of the PuTTY suite, although it can +also interoperate with the private key formats used by some other +SSH clients. + +When you run \c{puttygen}, it does three things. Firstly, it either +loads an existing key file (if you specified \e{keyfile}), or +generates a new key (if you specified \e{keytype}). Then, it +optionally makes modifications to the key (changing the comment +and/or the passphrase); finally, it outputs the key, or some +information about the key, to a file. + +All three of these phases are controlled by the options described in +the following section. + +\S{puttygen-manpage-options} OPTIONS + +In the first phase, \c{puttygen} either loads or generates a key. +Note that generating a key requires random data (from +\c{/dev/random}), which can cause \c{puttygen} to pause, possibly for +some time if your system does not have much randomness available. + +The options to control this phase are: + +\dt \e{keyfile} + +\dd Specify a private key file to be loaded. This private key file can +be in the (de facto standard) SSH-1 key format, or in PuTTY's SSH-2 +key format, or in either of the SSH-2 private key formats used by +OpenSSH and ssh.com's implementation. + +\dt \cw{\-t} \e{keytype} + +\dd Specify a type of key to generate. The acceptable values here are +\c{rsa} and \c{dsa} (to generate SSH-2 keys), and \c{rsa1} (to +generate SSH-1 keys). + +\dt \cw{\-b} \e{bits} + +\dd Specify the size of the key to generate, in bits. Default is 1024. + +\dt \cw{\-q} + +\dd Suppress the progress display when generating a new key. + +In the second phase, \c{puttygen} optionally alters properties of +the key it has loaded or generated. The options to control this are: + +\dt \cw{\-C} \e{new\-comment} + +\dd Specify a comment string to describe the key. This comment string +will be used by PuTTY to identify the key to you (when asking you to +enter the passphrase, for example, so that you know which passphrase +to type). + +\dt \cw{\-P} + +\dd Indicate that you want to change the key's passphrase. This is +automatic when you are generating a new key, but not when you are +modifying an existing key. + +In the third phase, \c{puttygen} saves the key or information +about it. The options to control this are: + +\dt \cw{\-O} \e{output\-type} + +\dd Specify the type of output you want \c{puttygen} to produce. +Acceptable options are: + +\lcont{ + +\dt \cw{private} + +\dd Save the private key in a format usable by PuTTY. This will either +be the standard SSH-1 key format, or PuTTY's own SSH-2 key format. + +\dt \cw{public} + +\dd Save the public key only. For SSH-1 keys, the standard public key +format will be used (\q{\cw{1024 37 5698745}...}). For SSH-2 keys, the +public key will be output in the format specified by RFC 4716, +which is a multi-line text file beginning with the line +\q{\cw{---- BEGIN SSH2 PUBLIC KEY ----}}. + +\dt \cw{public-openssh} + +\dd Save the public key only, in a format usable by OpenSSH. For SSH-1 +keys, this output format behaves identically to \c{public}. For +SSH-2 keys, the public key will be output in the OpenSSH format, +which is a single line (\q{\cw{ssh-rsa AAAAB3NzaC1yc2}...}). + +\dt \cw{fingerprint} + +\dd Print the fingerprint of the public key. All fingerprinting +algorithms are believed compatible with OpenSSH. + +\dt \cw{private-openssh} + +\dd Save an SSH-2 private key in OpenSSH's format. This option is not +permitted for SSH-1 keys. + +\dt \cw{private-sshcom} + +\dd Save an SSH-2 private key in ssh.com's format. This option is not +permitted for SSH-1 keys. + +If no output type is specified, the default is \c{private}. + +} + +\dt \cw{\-o} \e{output\-file} + +\dd Specify the file where \c{puttygen} should write its output. If +this option is not specified, \c{puttygen} will assume you want to +overwrite the original file if the input and output file types are +the same (changing a comment or passphrase), and will assume you +want to output to stdout if you are asking for a public key or +fingerprint. Otherwise, the \c{\-o} option is required. + +\dt \cw{\-l} + +\dd Synonym for \q{\cw{-O fingerprint}}. + +\dt \cw{\-L} + +\dd Synonym for \q{\cw{-O public-openssh}}. + +\dt \cw{\-p} + +\dd Synonym for \q{\cw{-O public}}. + +The following options do not run PuTTYgen as normal, but print +informational messages and then quit: + +\dt \cw{\-h}, \cw{\-\-help} + +\dd Display a message summarizing the available options. + +\dt \cw{\-V}, \cw{\-\-version} + +\dd Display the version of PuTTYgen. + +\dt \cw{\-\-pgpfp} + +\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid +in verifying new files released by the PuTTY team. + +\S{puttygen-manpage-examples} EXAMPLES + +To generate an SSH-2 RSA key pair and save it in PuTTY's own format +(you will be prompted for the passphrase): + +\c puttygen -t rsa -C "my home key" -o mykey.ppk + +To generate a larger (2048-bit) key: + +\c puttygen -t rsa -b 2048 -C "my home key" -o mykey.ppk + +To change the passphrase on a key (you will be prompted for the old +and new passphrases): + +\c puttygen -P mykey.ppk + +To change the comment on a key: + +\c puttygen -C "new comment" mykey.ppk + +To convert a key into OpenSSH's private key format: + +\c puttygen mykey.ppk -O private-openssh -o my-openssh-key + +To convert a key \e{from} another format (\c{puttygen} will +automatically detect the input key type): + +\c puttygen my-ssh.com-key -o mykey.ppk + +To display the fingerprint of a key (some key types require a +passphrase to extract even this much information): + +\c puttygen -l mykey.ppk + +To add the OpenSSH-format public half of a key to your authorised +keys file: + +\c puttygen -L mykey.ppk >> $HOME/.ssh/authorized_keys + +\S{puttygen-manpage-bugs} BUGS + +There's currently no way to supply passphrases in batch mode, or +even just to specify that you don't want a passphrase at all. diff --git a/putty/DOC/MAN-PL.BUT b/putty/DOC/MAN-PL.BUT new file mode 100644 index 0000000..ecc483f --- /dev/null +++ b/putty/DOC/MAN-PL.BUT @@ -0,0 +1,185 @@ +\cfg{man-identity}{plink}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} + +\H{plink-manpage} Man page for Plink + +\S{plink-manpage-name} NAME + +\cw{plink} \- PuTTY link, command line network connection tool + +\S{plink-manpage-synopsis} SYNOPSIS + +\c plink [options] [user@]host [command] +\e bbbbb iiiiiii iiiib iiii iiiiiii + +\S{plink-manpage-description} DESCRIPTION + +\cw{plink} is a network connection tool supporting several protocols. + +\S{plink-manpage-options} OPTIONS + +The command-line options supported by \cw{plink} are: + +\dt \cw{-V} + +\dd Show version information and exit. + +\dt \cw{-pgpfp} + +\dd Display the fingerprints of the PuTTY PGP Master Keys and exit, +to aid in verifying new files released by the PuTTY team. + +\dt \cw{-v} + +\dd Show verbose messages. + +\dt \cw{-load} \e{session} + +\dd Load settings from saved session. + +\dt \cw{-ssh} + +\dd Force use of SSH protocol (default). + +\dt \cw{-telnet} + +\dd Force use of Telnet protocol. + +\dt \cw{-rlogin} + +\dd Force use of rlogin protocol. + +\dt \cw{-raw} + +\dd Force raw mode. + +\dt \cw{-serial} + +\dd Force serial mode. + +\dt \cw{-P} \e{port} + +\dd Connect to port \e{port}. + +\dt \cw{-l} \e{user} + +\dd Set remote username to \e{user}. + +\dt \cw{-m} \e{path} + +\dd Read remote command(s) from local file \e{path}. + +\dt \cw{-batch} + +\dd Disable interactive prompts. + +\dt \cw{-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}}). + +\dt \cw{\-L} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport} + +\dd Set up a local port forwarding: listen on \e{srcport} (or +\e{srcaddr}:\e{srcport} if specified), and forward any connections +over the SSH connection to the destination address +\e{desthost}:\e{destport}. Only works in SSH. + +\dt \cw{\-R} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport} + +\dd Set up a remote port forwarding: ask the SSH server to listen on +\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and to +forward any connections back over the SSH connection where the +client will pass them on to the destination address +\e{desthost}:\e{destport}. Only works in SSH. + +\dt \cw{\-D} [\e{srcaddr}:]\e{srcport} + +\dd Set up dynamic port forwarding. The client listens on +\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and +implements a SOCKS server. So you can point SOCKS-aware applications +at this port and they will automatically use the SSH connection to +tunnel all their connections. Only works in SSH. + +\dt \cw{-X} + +\dd Enable X11 forwarding. + +\dt \cw{-x} + +\dd Disable X11 forwarding (default). + +\dt \cw{-A} + +\dd Enable agent forwarding. + +\dt \cw{-a} + +\dd Disable agent forwarding (default). + +\dt \cw{-t} + +\dd Enable pty allocation (default if a command is NOT specified). + +\dt \cw{-T} + +\dd Disable pty allocation (default if a command is specified). + +\dt \cw{-1} + +\dd Force use of SSH protocol version 1. + +\dt \cw{-2} + +\dd Force use of SSH protocol version 2. + +\dt \cw{-C} + +\dd Enable SSH compression. + +\dt \cw{-i} \e{path} + +\dd Private key file for authentication. + +\dt \cw{-s} + +\dd Remote command is SSH subsystem (SSH-2 only). + +\dt \cw{-N} + +\dd Don't start a remote command or shell at all (SSH-2 only). + +\dt \cw{\-sercfg} \e{configuration-string} + +\dd Specify the configuration parameters for the serial port, in +\cw{-serial} mode. \e{configuration-string} should be a +comma-separated list of configuration parameters as follows: + +\lcont{ + +\b Any single digit from 5 to 9 sets the number of data bits. + +\b \cq{1}, \cq{1.5} or \cq{2} sets the number of stop bits. + +\b Any other numeric string is interpreted as a baud rate. + +\b A single lower-case letter specifies the parity: \cq{n} for none, +\cq{o} for odd, \cq{e} for even, \cq{m} for mark and \cq{s} for space. + +\b A single upper-case letter specifies the flow control: \cq{N} for +none, \cq{X} for XON/XOFF, \cq{R} for RTS/CTS and \cq{D} for +DSR/DTR. + +} + +\S{plink-manpage-more-information} MORE INFORMATION + +For more information on plink, it's probably best to go and look at +the manual on the PuTTY web page: + +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} + +\S{plink-manpage-bugs} BUGS + +This man page isn't terribly complete. See the above web link for +better documentation. diff --git a/putty/DOC/MAN-PSCP.BUT b/putty/DOC/MAN-PSCP.BUT new file mode 100644 index 0000000..c56db97 --- /dev/null +++ b/putty/DOC/MAN-PSCP.BUT @@ -0,0 +1,116 @@ +\cfg{man-identity}{pscp}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} + +\H{pscp-manpage} Man page for PSCP + +\S{pscp-manpage-name} NAME + +\cw{pscp} \- command-line SCP (secure copy) / SFTP client + +\S{pscp-manpage-synopsis} SYNOPSIS + +\c pscp [options] [user@]host:source target +\e bbbb iiiiiii iiiib iiiibiiiiii iiiiii +\c pscp [options] source [source...] [user@]host:target +\e bbbb iiiiiii iiiiii iiiiii iiiib iiiibiiiiii +\c pscp [options] -ls [user@]host:filespec +\e bbbb iiiiiii bbb iiiib iiiibiiiiiiii + +\S{pscp-manpage-description} DESCRIPTION + +\cw{pscp} is a command-line client for the SSH-based SCP (secure +copy) and SFTP (secure file transfer protocol) protocols. + +\S{pscp-manpage-options} OPTIONS + +The command-line options supported by \e{pscp} are: + +\dt \cw{-V} + +\dd Show version information and exit. + +\dt \cw{-pgpfp} + +\dd Display the fingerprints of the PuTTY PGP Master Keys and exit, +to aid in verifying new files released by the PuTTY team. + +\dt \cw{-ls} + +\dd Remote directory listing. + +\dt \cw{-p} + +\dd Preserve file attributes. + +\dt \cw{-q} + +\dd Quiet, don't show statistics. + +\dt \cw{-r} + +\dd Copy directories recursively. + +\dt \cw{-unsafe} + +\dd Allow server-side wildcards (DANGEROUS). + +\dt \cw{-v} + +\dd Show verbose messages. + +\dt \cw{-load} \e{session} + +\dd Load settings from saved session. + +\dt \cw{-P} \e{port} + +\dd Connect to port \e{port}. + +\dt \cw{-l} \e{user} + +\dd Set remote username to \e{user}. + +\dt \cw{-batch} + +\dd Disable interactive prompts. + +\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}}). + +\dt \cw{-1} + +\dd Force use of SSH protocol version 1. + +\dt \cw{-2} + +\dd Force use of SSH protocol version 2. + +\dt \cw{-C} + +\dd Enable SSH compression. + +\dt \cw{-i} \e{path} + +\dd Private key file for authentication. + +\dt \cw{-scp} + +\dd Force use of SCP protocol. + +\dt \cw{-sftp} + +\dd Force use of SFTP protocol. + +\S{pscp-manpage-more-information} MORE INFORMATION + +For more information on \cw{pscp} it's probably best to go and look at +the manual on the PuTTY web page: + +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} + +\S{pscp-manpage-bugs} BUGS + +This man page isn't terribly complete. See the above web link for +better documentation. diff --git a/putty/DOC/MAN-PSFT.BUT b/putty/DOC/MAN-PSFT.BUT new file mode 100644 index 0000000..adaf640 --- /dev/null +++ b/putty/DOC/MAN-PSFT.BUT @@ -0,0 +1,101 @@ +\cfg{man-identity}{psftp}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} + +\H{psftp-manpage} Man page for PSFTP + +\S{psftp-manpage-name} NAME + +\cw{psftp} \- interactive SFTP (secure file transfer protocol) client + +\S{psftp-manpage-synopsis} SYNOPSIS + +\c psftp [options] [user@]host +\e bbbbb iiiiiii iiiib iiii + +\S{psftp-manpage-description} DESCRIPTION + +\cw{psftp} is an interactive text-based client for the SSH-based SFTP +(secure file transfer) protocol. + +\S{psftp-manpage-options} OPTIONS + +The command-line options supported by \cw{psftp} are: + +\dt \cw{-V} + +\dd Show version information and exit. + +\dt \cw{-pgpfp} + +\dd Display the fingerprints of the PuTTY PGP Master Keys and exit, +to aid in verifying new files released by the PuTTY team. + +\dt \cw{-b} \e{batchfile} + +\dd Use specified batchfile. + +\dt \cw{-bc} + +\dd Output batchfile commands. + +\dt \cw{-be} + +\dd Don't stop batchfile processing on errors. + +\dt \cw{-v} + +\dd Show verbose messages. + +\dt \cw{-load} \e{session} + +\dd Load settings from saved session. + +\dt \cw{-P} \e{port} + +\dd Connect to port \e{port}. + +\dt \cw{-l} \e{user} + +\dd Set remote username to \e{user}. + +\dt \cw{-batch} + +\dd Disable interactive prompts. + +\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}}). + +\dt \cw{-1} + +\dd Force use of SSH protocol version 1. + +\dt \cw{-2} + +\dd Force use of SSH protocol version 2. + +\dt \cw{-C} + +\dd Enable SSH compression. + +\dt \cw{-i} \e{path} + +\dd Private key file for authentication. + +\S{psftp-manpage-commands} COMMANDS + +For a list of commands available inside \cw{psftp}, type \cw{help} +at the \cw{psftp>} prompt. + +\S{psftp-manpage-more-information} MORE INFORMATION + +For more information on \cw{psftp} it's probably best to go and look at +the manual on the PuTTY web page: + +\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} + +\S{psftp-manpage-bugs} BUGS + +This man page isn't terribly complete. See the above web link for +better documentation. diff --git a/putty/DOC/MAN-PTEL.BUT b/putty/DOC/MAN-PTEL.BUT new file mode 100644 index 0000000..1bdd334 --- /dev/null +++ b/putty/DOC/MAN-PTEL.BUT @@ -0,0 +1,189 @@ +\cfg{man-identity}{puttytel}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} + +\H{puttytel-manpage} Man page for PuTTYtel + +\S{puttytel-manpage-name} NAME + +\cw{puttytel} \- GUI Telnet and Rlogin client for X + +\S{puttytel-manpage-synopsis} SYNOPSIS + +\c puttytel [ options ] [ host ] +\e bbbbbbbb iiiiiii iiii + +\S{puttytel-manpage-description} DESCRIPTION + +\cw{puttytel} is a graphical Telnet and Rlogin client for X. It +is a direct port of the Windows Telnet and Rlogin client of the same +name, and a cut-down cryptography-free version of PuTTY. + +\S{puttytel-manpage-options} OPTIONS + +The command-line options supported by \cw{puttytel} are: + +\dt \cw{\-\-display} \e{display\-name} + +\dd Specify the X display on which to open \cw{puttytel}. (Note this +option has a double minus sign, even though none of the others do. +This is because this option is supplied automatically by GTK. +Sorry.) + +\dt \cw{\-fn} \e{font-name} + +\dd Specify the font to use for normal text displayed in the terminal. + +\dt \cw{\-fb} \e{font-name} + +\dd Specify the font to use for bold text displayed in the terminal. If +the \cw{BoldAsColour} resource is set to 1 (the default), bold text +will be displayed in different colours instead of a different font, +so this option will be ignored. If \cw{BoldAsColour} is set to 0 +and you do not specify a bold font, \cw{puttytel} will overprint the +normal font to make it look bolder. + +\dt \cw{\-fw} \e{font-name} + +\dd Specify the font to use for double-width characters (typically +Chinese, Japanese and Korean text) displayed in the terminal. + +\dt \cw{\-fwb} \e{font-name} + +\dd Specify the font to use for bold double-width characters +(typically Chinese, Japanese and Korean text). Like \cw{-fb}, this +will be ignored unless the \cw{BoldAsColour} resource is set to 0. + +\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 +specifications. + +\dt \cw{\-sl} \e{lines} + +\dd Specify the number of lines of scrollback to save off the top of the +terminal. + +\dt \cw{\-fg} \e{colour} + +\dd Specify the foreground colour to use for normal text. + +\dt \cw{\-bg} \e{colour} + +\dd Specify the background colour to use for normal text. + +\dt \cw{\-bfg} \e{colour} + +\dd Specify the foreground colour to use for bold text, if the +\cw{BoldAsColour} resource is set to 1 (the default). + +\dt \cw{\-bbg} \e{colour} + +\dd Specify the foreground colour to use for bold reverse-video text, if +the \cw{BoldAsColour} resource is set to 1 (the default). (This +colour is best thought of as the bold version of the background +colour; so it only appears when text is displayed \e{in} the +background colour.) + +\dt \cw{\-cfg} \e{colour} + +\dd Specify the foreground colour to use for text covered by the cursor. + +\dt \cw{\-cbg} \e{colour} + +\dd Specify the background colour to use for text covered by the cursor. +In other words, this is the main colour of the cursor. + +\dt \cw{\-title} \e{title} + +\dd Specify the initial title of the terminal window. (This can be +changed under control of the server.) + +\dt \cw{\-sb\-} or \cw{+sb} + +\dd Tells \cw{puttytel} not to display a scroll bar. + +\dt \cw{\-sb} + +\dd Tells \cw{puttytel} to display a scroll bar: this is the opposite of +\cw{\-sb\-}. This is the default option: you will probably only need +to specify it explicitly if you have changed the default using the +\cw{ScrollBar} resource. + +\dt \cw{\-log} \e{filename} + +\dd This option makes \cw{puttytel} log all the terminal output to a file +as well as displaying it in the terminal. + +\dt \cw{\-cs} \e{charset} + +\dd This option specifies the character set in which \cw{puttytel} +should assume the session is operating. This character set will be +used to interpret all the data received from the session, and all +input you type or paste into \cw{puttytel} will be converted into +this character set before being sent to the session. + +\lcont{ Any character set name which is valid in a MIME header (and +supported by \cw{puttytel}) should be valid here (examples are +\q{\cw{ISO-8859-1}}, \q{\cw{windows-1252}} or \q{\cw{UTF-8}}). Also, +any character encoding which is valid in an X logical font +description should be valid (\q{\cw{ibm-cp437}}, for example). + +\cw{puttytel}'s default behaviour is to use the same character +encoding as its primary font. If you supply a Unicode +(\cw{iso10646-1}) font, it will default to the UTF-8 character set. + +Character set names are case-insensitive. +} + +\dt \cw{\-nethack} + +\dd Tells \cw{puttytel} to enable NetHack keypad mode, in which the +numeric keypad generates the NetHack \c{hjklyubn} direction keys. +This enables you to play NetHack with the numeric keypad without +having to use the NetHack \c{number_pad} option (which requires you +to press \q{\cw{n}} before any repeat count). So you can move with +the numeric keypad, and enter repeat counts with the normal number +keys. + +\dt \cw{\-help}, \cw{\-\-help} + +\dd Display a message summarizing the available options. + +\dt \cw{\-pgpfp} + +\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid +in verifying new files released by the PuTTY team. + +\dt \cw{\-load} \e{session} + +\dd Load a saved session by name. This allows you to run a saved session +straight from the command line without having to go through the +configuration box first. + +\dt \cw{\-telnet}, \cw{\-rlogin}, \cw{\-raw} + +\dd Select the protocol \cw{puttytel} will use to make the connection. + +\dt \cw{\-l} \e{username} + +\dd Specify the username to use when logging in to the server. + +\dt \cw{\-P} \e{port} + +\dd Specify the port to connect to the server on. + +\S{puttytel-manpage-saved-sessions} SAVED SESSIONS + +Saved sessions are stored in a \cw{.putty/sessions} subdirectory in +your home directory. + +\S{puttytel-manpage-more-information} MORE INFORMATION + +For more information on PuTTY and PuTTYtel, it's probably best to go +and look at the manual on the web page: + +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} + +\S{puttytel-manpage-bugs} BUGS + +This man page isn't terribly complete. diff --git a/putty/DOC/MAN-PTER.BUT b/putty/DOC/MAN-PTER.BUT new file mode 100644 index 0000000..9e96f77 --- /dev/null +++ b/putty/DOC/MAN-PTER.BUT @@ -0,0 +1,676 @@ +\cfg{man-identity}{pterm}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} + +\H{pterm-manpage} Man page for pterm + +\S{pterm-manpage-name} NAME + +pterm \- yet another X terminal emulator + +\S{pterm-manpage-synopsis} SYNOPSIS + +\c pterm [ options ] +\e bbbbb iiiiiii + +\S{pterm-manpage-description} DESCRIPTION + +\cw{pterm} is a terminal emulator for X. It is based on a port of +the terminal emulation engine in the Windows SSH client PuTTY. + +\S{pterm-manpage-options} OPTIONS + +The command-line options supported by \cw{pterm} are: + +\dt \cw{\-e} \e{command} [ \e{arguments} ] + +\dd Specify a command to be executed in the new terminal. Everything on +the command line after this option will be passed straight to the +\cw{execvp} system call; so if you need the command to redirect its +input or output, you will have to use \cw{sh}: + +\lcont{ + +\c pterm -e sh -c 'mycommand < inputfile' + +} + +\dt \cw{\-\-display} \e{display\-name} + +\dd Specify the X display on which to open \cw{pterm}. (Note this +option has a double minus sign, even though none of the others do. +This is because this option is supplied automatically by GTK. +Sorry.) + +\dt \cw{\-name} \e{name} + +\dd Specify the name under which \cw{pterm} looks up X resources. +Normally it will look them up as (for example) \cw{pterm.Font}. If +you specify \q{\cw{\-name xyz}}, it will look them up as +\cw{xyz.Font} instead. This allows you to set up several different +sets of defaults and choose between them. + +\dt \cw{\-fn} \e{font-name} + +\dd Specify the font to use for normal text displayed in the terminal. + +\dt \cw{\-fb} \e{font-name} + +\dd Specify the font to use for bold text displayed in the terminal. If +the \cw{BoldAsColour} resource is set to 1 (the default), bold text +will be displayed in different colours instead of a different font, +so this option will be ignored. If \cw{BoldAsColour} is set to 0 +and you do not specify a bold font, \cw{pterm} will overprint the +normal font to make it look bolder. + +\dt \cw{\-fw} \e{font-name} + +\dd Specify the font to use for double-width characters (typically +Chinese, Japanese and Korean text) displayed in the terminal. + +\dt \cw{\-fwb} \e{font-name} + +\dd Specify the font to use for bold double-width characters +(typically Chinese, Japanese and Korean text). Like \cw{-fb}, this +will be ignored unless the \cw{BoldAsColour} resource is set to 0. + +\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 +specifications. + +\dt \cw{\-sl} \e{lines} + +\dd Specify the number of lines of scrollback to save off the top of the +terminal. + +\dt \cw{\-fg} \e{colour} + +\dd Specify the foreground colour to use for normal text. + +\dt \cw{\-bg} \e{colour} + +\dd Specify the background colour to use for normal text. + +\dt \cw{\-bfg} \e{colour} + +\dd Specify the foreground colour to use for bold text, if the +\cw{BoldAsColour} resource is set to 1 (the default). + +\dt \cw{\-bbg} \e{colour} + +\dd Specify the foreground colour to use for bold reverse-video text, if +the \cw{BoldAsColour} resource is set to 1 (the default). (This +colour is best thought of as the bold version of the background +colour; so it only appears when text is displayed \e{in} the +background colour.) + +\dt \cw{\-cfg} \e{colour} + +\dd Specify the foreground colour to use for text covered by the cursor. + +\dt \cw{\-cbg} \e{colour} + +\dd Specify the background colour to use for text covered by the cursor. +In other words, this is the main colour of the cursor. + +\dt \cw{\-title} \e{title} + +\dd Specify the initial title of the terminal window. (This can be +changed under control of the server.) + +\dt \cw{\-ut\-} or \cw{+ut} + +\dd Tells \cw{pterm} not to record your login in the \cw{utmp}, +\cw{wtmp} and \cw{lastlog} system log files; so you will not show +up on \cw{finger} or \cw{who} listings, for example. + +\dt \cw{\-ut} + +\dd Tells \cw{pterm} to record your login in \cw{utmp}, \cw{wtmp} and +\cw{lastlog}: this is the opposite of \cw{\-ut\-}. This is the +default option: you will probably only need to specify it explicitly +if you have changed the default using the \cw{StampUtmp} resource. + +\dt \cw{\-ls\-} or \cw{+ls} + +\dd Tells \cw{pterm} not to execute your shell as a login shell. + +\dt \cw{\-ls} + +\dd Tells \cw{pterm} to execute your shell as a login shell: this is +the opposite of \cw{\-ls\-}. This is the default option: you will +probably only need to specify it explicitly if you have changed the +default using the \cw{LoginShell} resource. + +\dt \cw{\-sb\-} or \cw{+sb} + +\dd Tells \cw{pterm} not to display a scroll bar. + +\dt \cw{\-sb} + +\dd Tells \cw{pterm} to display a scroll bar: this is the opposite of +\cw{\-sb\-}. This is the default option: you will probably only need +to specify it explicitly if you have changed the default using the +\cw{ScrollBar} resource. + +\dt \cw{\-log} \e{filename} + +\dd This option makes \cw{pterm} log all the terminal output to a file +as well as displaying it in the terminal. + +\dt \cw{\-cs} \e{charset} + +\dd This option specifies the character set in which \cw{pterm} should +assume the session is operating. This character set will be used to +interpret all the data received from the session, and all input you +type or paste into \cw{pterm} will be converted into this character +set before being sent to the session. + +\lcont{ Any character set name which is valid in a MIME header (and +supported by \cw{pterm}) should be valid here (examples are +\q{\cw{ISO-8859-1}}, \q{\cw{windows-1252}} or \q{\cw{UTF-8}}). Also, +any character encoding which is valid in an X logical font +description should be valid (\q{\cw{ibm-cp437}}, for example). + +\cw{pterm}'s default behaviour is to use the same character encoding +as its primary font. If you supply a Unicode (\cw{iso10646-1}) font, +it will default to the UTF-8 character set. + +Character set names are case-insensitive. +} + +\dt \cw{\-nethack} + +\dd Tells \cw{pterm} to enable NetHack keypad mode, in which the +numeric keypad generates the NetHack \c{hjklyubn} direction keys. +This enables you to play NetHack with the numeric keypad without +having to use the NetHack \c{number_pad} option (which requires you +to press \q{\cw{n}} before any repeat count). So you can move with +the numeric keypad, and enter repeat counts with the normal number +keys. + +\dt \cw{\-xrm} \e{resource-string} + +\dd This option specifies an X resource string. Useful for setting +resources which do not have their own command-line options. For +example: + +\lcont{ + +\c pterm -xrm 'ScrollbarOnLeft: 1' + +} + +\dt \cw{\-help}, \cw{\-\-help} + +\dd Display a message summarizing the available options. + +\dt \cw{\-pgpfp} + +\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid +in verifying new files released by the PuTTY team. + +\S{pterm-manpage-x-resources} X RESOURCES + +\cw{pterm} can be more completely configured by means of X +resources. All of these resources are of the form \cw{pterm.FOO} for +some \cw{FOO}; you can make \cw{pterm} look them up under another +name, such as \cw{xyz.FOO}, by specifying the command-line option +\q{\cw{\-name xyz}}. + +\dt \cw{pterm.CloseOnExit} + +\dd This option should be set to 0, 1 or 2; the default is 2. It +controls what \cw{pterm} does when the process running inside it +terminates. When set to 2 (the default), \cw{pterm} will close its +window as soon as the process inside it terminates. When set to 0, +\cw{pterm} will print the process's exit status, and the window +will remain present until a key is pressed (allowing you to inspect +the scrollback, and copy and paste text out of it). + +\lcont{ + +When this setting is set to 1, \cw{pterm} will close +immediately if the process exits cleanly (with an exit status of +zero), but the window will stay around if the process exits with a +non-zero code or on a signal. This enables you to see what went +wrong if the process suffers an error, but not to have to bother +closing the window in normal circumstances. + +} + +\dt \cw{pterm.WarnOnClose} + +\dd This option should be set to either 0 or 1; the default is 1. +When set to 1, \cw{pterm} will ask for confirmation before closing +its window when you press the close button. + +\dt \cw{pterm.TerminalType} + +\dd This controls the value set in the \cw{TERM} environment +variable inside the new terminal. The default is \q{\cw{xterm}}. + +\dt \cw{pterm.BackspaceIsDelete} + +\dd This option should be set to either 0 or 1; the default is 1. +When set to 0, the ordinary Backspace key generates the Backspace +character (\cw{^H}); when set to 1, it generates the Delete +character (\cw{^?}). Whichever one you set, the terminal device +inside \cw{pterm} will be set up to expect it. + +\dt \cw{pterm.RXVTHomeEnd} + +\dd This option should be set to either 0 or 1; the default is 0. When +it is set to 1, the Home and End keys generate the control sequences +they would generate in the \cw{rxvt} terminal emulator, instead of +the more usual ones generated by other emulators. + +\dt \cw{pterm.LinuxFunctionKeys} + +\dd This option can be set to any number between 0 and 5 inclusive; +the default is 0. The modes vary the control sequences sent by the +function keys; for more complete documentation, it is probably +simplest to try each option in \q{\cw{pterm \-e cat}}, and press the +keys to see what they generate. + +\dt \cw{pterm.NoApplicationKeys} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, it stops the server from ever switching the numeric keypad +into application mode (where the keys send function-key-like +sequences instead of numbers or arrow keys). You probably only need +this if some application is making a nuisance of itself. + +\dt \cw{pterm.NoApplicationCursors} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, it stops the server from ever switching the cursor keys +into application mode (where the keys send slightly different +sequences). You probably only need this if some application is +making a nuisance of itself. + +\dt \cw{pterm.NoMouseReporting} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, it stops the server from ever enabling mouse reporting +mode (where mouse clicks are sent to the application instead of +controlling cut and paste). + +\dt \cw{pterm.NoRemoteResize} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, it stops the server from being able to remotely control +the size of the \cw{pterm} window. + +\dt \cw{pterm.NoAltScreen} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, it stops the server from using the \q{alternate screen} +terminal feature, which lets full-screen applications leave the +screen exactly the way they found it. + +\dt \cw{pterm.NoRemoteWinTitle} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, it stops the server from remotely controlling the title of +the \cw{pterm} window. + +\dt \cw{pterm.NoRemoteQTitle} + +\dd This option should be set to either 0 or 1; the default is 1. When +set to 1, it stops the server from remotely requesting the title of +the \cw{pterm} window. + +\lcont{ +This feature is a \e{POTENTIAL SECURITY HAZARD}. If a malicious +application can write data to your terminal (for example, if you +merely \cw{cat} a file owned by someone else on the server +machine), it can change your window title (unless you have disabled +this using the \cw{NoRemoteWinTitle} resource) and then use this +service to have the new window title sent back to the server as if +typed at the keyboard. This allows an attacker to fake keypresses +and potentially cause your server-side applications to do things you +didn't want. Therefore this feature is disabled by default, and we +recommend you do not turn it on unless you \e{really} know what +you are doing. +} + +\dt \cw{pterm.NoDBackspace} + +\dd This option should be set to either 0 or 1; the default is 0. +When set to 1, it disables the normal action of the Delete (\cw{^?}) +character when sent from the server to the terminal, which is to +move the cursor left by one space and erase the character now under +it. + +\dt \cw{pterm.ApplicationCursorKeys} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, the default initial state of the cursor keys are +application mode (where the keys send function-key-like sequences +instead of numbers or arrow keys). When set to 0, the default state +is the normal one. + +\dt \cw{pterm.ApplicationKeypad} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, the default initial state of the numeric keypad is +application mode (where the keys send function-key-like sequences +instead of numbers or arrow keys). When set to 0, the default state +is the normal one. + +\dt \cw{pterm.NetHackKeypad} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, the numeric keypad operates in NetHack mode. This is +equivalent to the \cw{\-nethack} command-line option. + +\dt \cw{pterm.Answerback} + +\dd This option controls the string which the terminal sends in +response to receiving the \cw{^E} character (\q{tell me about +yourself}). By default this string is \q{\cw{PuTTY}}. + +\dt \cw{pterm.HideMousePtr} + +\dd This option should be set to either 0 or 1; the default is 0. When +it is set to 1, the mouse pointer will disappear if it is over the +\cw{pterm} window and you press a key. It will reappear as soon as +you move it. + +\dt \cw{pterm.WindowBorder} + +\dd This option controls the number of pixels of space between the text +in the \cw{pterm} window and the window frame. The default is 1. +You can increase this value, but decreasing it to 0 is not +recommended because it can cause the window manager's size hints to +work incorrectly. + +\dt \cw{pterm.CurType} + +\dd This option should be set to either 0, 1 or 2; the default is 0. +When set to 0, the text cursor displayed in the window is a +rectangular block. When set to 1, the cursor is an underline; when +set to 2, it is a vertical line. + +\dt \cw{pterm.BlinkCur} + +\dd This option should be set to either 0 or 1; the default is 0. When +it is set to 1, the text cursor will blink when the window is active. + +\dt \cw{pterm.Beep} + +\dd This option should be set to either 0 or 2 (yes, 2); the default +is 0. When it is set to 2, \cw{pterm} will respond to a bell +character (\cw{^G}) by flashing the window instead of beeping. + +\dt \cw{pterm.BellOverload} + +\dd This option should be set to either 0 or 1; the default is 0. When +it is set to 1, \cw{pterm} will watch out for large numbers of +bells arriving in a short time and will temporarily disable the bell +until they stop. The idea is that if you \cw{cat} a binary file, +the frantic beeping will mostly be silenced by this feature and will +not drive you crazy. + +\lcont{ +The bell overload mode is activated by receiving N bells in time T; +after a further time S without any bells, overload mode will turn +itself off again. + +Bell overload mode is always deactivated by any keypress in the +terminal. This means it can respond to large unexpected streams of +data, but does not interfere with ordinary command-line activities +that generate beeps (such as filename completion). +} + +\dt \cw{pterm.BellOverloadN} + +\dd This option counts the number of bell characters which will activate +bell overload if they are received within a length of time T. The +default is 5. + +\dt \cw{pterm.BellOverloadT} + +\dd This option specifies the time period in which receiving N or more +bells will activate bell overload mode. It is measured in +microseconds, so (for example) set it to 1000000 for one second. The +default is 2000000 (two seconds). + +\dt \cw{pterm.BellOverloadS} + +\dd This option specifies the time period of silence required to turn +off bell overload mode. It is measured in microseconds, so (for +example) set it to 1000000 for one second. The default is 5000000 +(five seconds of silence). + +\dt \cw{pterm.ScrollbackLines} + +\dd This option specifies how many lines of scrollback to save above the +visible terminal screen. The default is 200. This resource is +equivalent to the \cw{\-sl} command-line option. + +\dt \cw{pterm.DECOriginMode} + +\dd This option should be set to either 0 or 1; the default is 0. It +specifies the default state of DEC Origin Mode. (If you don't know +what that means, you probably don't need to mess with it.) + +\dt \cw{pterm.AutoWrapMode} + +\dd This option should be set to either 0 or 1; the default is 1. It +specifies the default state of auto wrap mode. When set to 1, very +long lines will wrap over to the next line on the terminal; when set +to 0, long lines will be squashed against the right-hand edge of the +screen. + +\dt \cw{pterm.LFImpliesCR} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, the terminal will return the cursor to the left side of +the screen when it receives a line feed character. + +\dt \cw{pterm.WinTitle} + +\dd This resource is the same as the \cw{\-T} command-line option: +it controls the initial title of the window. The default is +\q{\cw{pterm}}. + +\dt \cw{pterm.TermWidth} + +\dd This resource is the same as the width part of the \cw{\-geometry} +command-line option: it controls the number of columns of text in +the window. The default is 80. + +\dt \cw{pterm.TermHeight} + +\dd This resource is the same as the width part of the \cw{\-geometry} +command-line option: it controls the number of columns of text in +the window. The defaults is 24. + +\dt \cw{pterm.Font} + +\dd This resource is the same as the \cw{\-fn} command-line option: it +controls the font used to display normal text. The default is +\q{\cw{fixed}}. + +\dt \cw{pterm.BoldFont} + +\dd This resource is the same as the \cw{\-fb} command-line option: it +controls the font used to display bold text when \cw{BoldAsColour} +is turned off. The default is unset (the font will be bolded by +printing it twice at a one-pixel offset). + +\dt \cw{pterm.WideFont} + +\dd This resource is the same as the \cw{\-fw} command-line option: it +controls the font used to display double-width characters. The +default is unset (double-width characters cannot be displayed). + +\dt \cw{pterm.WideBoldFont} + +\dd This resource is the same as the \cw{\-fwb} command-line option: it +controls the font used to display double-width characters in bold, +when \cw{BoldAsColour} is turned off. The default is unset +(double-width characters are displayed in bold by printing them +twice at a one-pixel offset). + +\dt \cw{pterm.ShadowBoldOffset} + +\dd This resource can be set to an integer; the default is \-1. It +specifies the offset at which text is overprinted when using +\q{shadow bold} mode. The default (1) means that the text will be +printed in the normal place, and also one character to the right; +this seems to work well for most X bitmap fonts, which have a blank +line of pixels down the right-hand side. For some fonts, you may +need to set this to \-1, so that the text is overprinted one pixel +to the left; for really large fonts, you may want to set it higher +than 1 (in one direction or the other). + +\dt \cw{pterm.BoldAsColour} + +\dd This option should be set to either 0 or 1; the default is 1. It +specifies the default state of auto wrap mode. When set to 1, bold +text is shown by displaying it in a brighter colour; when set to 0, +bold text is shown by displaying it in a heavier font. + +\dt \cw{pterm.Colour0}, \cw{pterm.Colour1}, ..., \cw{pterm.Colour21} + +\dd These options control the various colours used to display text +in the \cw{pterm} window. Each one should be specified as a triple +of decimal numbers giving red, green and blue values: so that black +is \q{\cw{0,0,0}}, white is \q{\cw{255,255,255}}, red is +\q{\cw{255,0,0}} and so on. + +\lcont{ + +Colours 0 and 1 specify the foreground colour and its bold +equivalent (the \cw{\-fg} and \cw{\-bfg} command-line options). +Colours 2 and 3 specify the background colour and its bold +equivalent (the \cw{\-bg} and \cw{\-bbg} command-line options). +Colours 4 and 5 specify the text and block colours used for the +cursor (the \cw{\-cfg} and \cw{\-cbg} command-line options). Each +even number from 6 to 20 inclusive specifies the colour to be used +for one of the ANSI primary colour specifications (black, red, +green, yellow, blue, magenta, cyan, white, in that order); the odd +numbers from 7 to 21 inclusive specify the bold version of each +colour, in the same order. The defaults are: + +\c pterm.Colour0: 187,187,187 +\c pterm.Colour1: 255,255,255 +\c pterm.Colour2: 0,0,0 +\c pterm.Colour3: 85,85,85 +\c pterm.Colour4: 0,0,0 +\c pterm.Colour5: 0,255,0 +\c pterm.Colour6: 0,0,0 +\c pterm.Colour7: 85,85,85 +\c pterm.Colour8: 187,0,0 +\c pterm.Colour9: 255,85,85 +\c pterm.Colour10: 0,187,0 +\c pterm.Colour11: 85,255,85 +\c pterm.Colour12: 187,187,0 +\c pterm.Colour13: 255,255,85 +\c pterm.Colour14: 0,0,187 +\c pterm.Colour15: 85,85,255 +\c pterm.Colour16: 187,0,187 +\c pterm.Colour17: 255,85,255 +\c pterm.Colour18: 0,187,187 +\c pterm.Colour19: 85,255,255 +\c pterm.Colour20: 187,187,187 +\c pterm.Colour21: 255,255,255 + +} + +\dt \cw{pterm.RectSelect} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 0, dragging the mouse over several lines selects to the end +of each line and from the beginning of the next; when set to 1, +dragging the mouse over several lines selects a rectangular region. +In each case, holding down Alt while dragging gives the other +behaviour. + +\dt \cw{pterm.MouseOverride} + +\dd This option should be set to either 0 or 1; the default is 1. When +set to 1, if the application requests mouse tracking (so that mouse +clicks are sent to it instead of doing selection), holding down +Shift will revert the mouse to normal selection. When set to 0, +mouse tracking completely disables selection. + +\dt \cw{pterm.Printer} + +\dd This option is unset by default. If you set it, then +server-controlled printing is enabled: the server can send control +sequences to request data to be sent to a printer. That data will be +piped into the command you specify here; so you might want to set it +to \q{\cw{lpr}}, for example, or \q{\cw{lpr \-Pmyprinter}}. + +\dt \cw{pterm.ScrollBar} + +\dd This option should be set to either 0 or 1; the default is 1. When +set to 0, the scrollbar is hidden (although Shift-PageUp and +Shift-PageDown still work). This is the same as the \cw{\-sb} +command-line option. + +\dt \cw{pterm.ScrollbarOnLeft} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, the scrollbar will be displayed on the left of the +terminal instead of on the right. + +\dt \cw{pterm.ScrollOnKey} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, any keypress causes the position of the scrollback to be +reset to the very bottom. + +\dt \cw{pterm.ScrollOnDisp} + +\dd This option should be set to either 0 or 1; the default is 1. When +set to 1, any activity in the display causes the position of the +scrollback to be reset to the very bottom. + +\dt \cw{pterm.LineCodePage} + +\dd This option specifies the character set to be used for the session. +This is the same as the \cw{\-cs} command-line option. + +\dt \cw{pterm.NoRemoteCharset} + +\dd This option disables the terminal's ability to change its character +set when it receives escape sequences telling it to. You might need +to do this to interoperate with programs which incorrectly change +the character set to something they think is sensible. + +\dt \cw{pterm.BCE} + +\dd This option should be set to either 0 or 1; the default is 1. When +set to 1, the various control sequences that erase parts of the +terminal display will erase in whatever the current background +colour is; when set to 0, they will erase in black always. + +\dt \cw{pterm.BlinkText} + +\dd This option should be set to either 0 or 1; the default is 0. When +set to 1, text specified as blinking by the server will actually +blink on and off; when set to 0, \cw{pterm} will use the less +distracting approach of making the text's background colour bold. + +\dt \cw{pterm.StampUtmp} + +\dd This option should be set to either 0 or 1; the default is 1. When +set to 1, \cw{pterm} will log the login in the various system log +files. This resource is equivalent to the \cw{\-ut} command-line +option. + +\dt \cw{pterm.LoginShell} + +\dd This option should be set to either 0 or 1; the default is 1. When +set to 1, \cw{pterm} will execute your shell as a login shell. This +resource is equivalent to the \cw{\-ls} command-line option. + +\S{pterm-manpage-bugs} BUGS + +Most of the X resources have silly names. (Historical reasons from +PuTTY, mostly.) diff --git a/putty/DOC/MAN-PUTT.BUT b/putty/DOC/MAN-PUTT.BUT new file mode 100644 index 0000000..004c88e --- /dev/null +++ b/putty/DOC/MAN-PUTT.BUT @@ -0,0 +1,263 @@ +\cfg{man-identity}{putty}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} + +\H{putty-manpage} Man page for PuTTY + +\S{putty-manpage-name} NAME + +\cw{putty} - GUI SSH, Telnet and Rlogin client for X + +\S{putty-manpage-synopsis} SYNOPSIS + +\c putty [ options ] [ host ] +\e bbbbb iiiiiii iiii + +\S{putty-manpage-description} DESCRIPTION + +\cw{putty} is a graphical SSH, Telnet and Rlogin client for X. It is +a direct port of the Windows SSH client of the same name. + +\S{putty-manpage-options} OPTIONS + +The command-line options supported by \cw{putty} are: + +\dt \cw{\-\-display} \e{display\-name} + +\dd Specify the X display on which to open \cw{putty}. (Note this +option has a double minus sign, even though none of the others do. +This is because this option is supplied automatically by GTK. +Sorry.) + +\dt \cw{\-fn} \e{font-name} + +\dd Specify the font to use for normal text displayed in the terminal. + +\dt \cw{\-fb} \e{font-name} + +\dd Specify the font to use for bold text displayed in the terminal. +If the \cw{BoldAsColour} resource is set to 1 (the default), bold +text will be displayed in different colours instead of a different +font, so this option will be ignored. If \cw{BoldAsColour} is set to +0 and you do not specify a bold font, \cw{putty} will overprint the +normal font to make it look bolder. + +\dt \cw{\-fw} \e{font-name} + +\dd Specify the font to use for double-width characters (typically +Chinese, Japanese and Korean text) displayed in the terminal. + +\dt \cw{\-fwb} \e{font-name} + +\dd Specify the font to use for bold double-width characters +(typically Chinese, Japanese and Korean text). Like \cw{-fb}, this +will be ignored unless the \cw{BoldAsColour} resource is set to 0. + +\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 +specifications. + +\dt \cw{\-sl} \e{lines} + +\dd Specify the number of lines of scrollback to save off the top of the +terminal. + +\dt \cw{\-fg} \e{colour} + +\dd Specify the foreground colour to use for normal text. + +\dt \cw{\-bg} \e{colour} + +\dd Specify the background colour to use for normal text. + +\dt \cw{\-bfg} \e{colour} + +\dd Specify the foreground colour to use for bold text, if the +\cw{BoldAsColour} resource is set to 1 (the default). + +\dt \cw{\-bbg} \e{colour} + +\dd Specify the foreground colour to use for bold reverse-video +text, if the \cw{BoldAsColour} resource is set to 1 (the default). +(This colour is best thought of as the bold version of the +background colour; so it only appears when text is displayed \e{in} +the background colour.) + +\dt \cw{\-cfg} \e{colour} + +\dd Specify the foreground colour to use for text covered by the cursor. + +\dt \cw{\-cbg} \e{colour} + +\dd Specify the background colour to use for text covered by the cursor. +In other words, this is the main colour of the cursor. + +\dt \cw{\-title} \e{title} + +\dd Specify the initial title of the terminal window. (This can be +changed under control of the server.) + +\dt \cw{\-sb\-} or \cw{+sb} + +\dd Tells \cw{putty} not to display a scroll bar. + +\dt \cw{\-sb} + +\dd Tells \cw{putty} to display a scroll bar: this is the opposite of +\cw{\-sb\-}. This is the default option: you will probably only need +to specify it explicitly if you have changed the default using the +\cw{ScrollBar} resource. + +\dt \cw{\-log} \e{filename} + +\dd This option makes \cw{putty} log all the terminal output to a file +as well as displaying it in the terminal. + + +\dt \cw{\-cs} \e{charset} + +\dd This option specifies the character set in which \cw{putty} +should assume the session is operating. This character set will be +used to interpret all the data received from the session, and all +input you type or paste into \cw{putty} will be converted into +this character set before being sent to the session. + +\lcont{ Any character set name which is valid in a MIME header (and +supported by \cw{putty}) should be valid here (examples are +\q{\cw{ISO-8859-1}}, \q{\cw{windows-1252}} or \q{\cw{UTF-8}}). Also, +any character encoding which is valid in an X logical font +description should be valid (\q{\cw{ibm-cp437}}, for example). + +\cw{putty}'s default behaviour is to use the same character +encoding as its primary font. If you supply a Unicode +(\cw{iso10646-1}) font, it will default to the UTF-8 character set. + +Character set names are case-insensitive. +} + +\dt \cw{\-nethack} + +\dd Tells \cw{putty} to enable NetHack keypad mode, in which the +numeric keypad generates the NetHack \c{hjklyubn} direction keys. +This enables you to play NetHack with the numeric keypad without +having to use the NetHack \c{number_pad} option (which requires you +to press \q{\cw{n}} before any repeat count). So you can move with +the numeric keypad, and enter repeat counts with the normal number +keys. + +\dt \cw{\-help}, \cw{\-\-help} + +\dd Display a message summarizing the available options. + +\dt \cw{\-pgpfp} + +\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid +in verifying new files released by the PuTTY team. + +\dt \cw{\-load} \e{session} + +\dd Load a saved session by name. This allows you to run a saved session +straight from the command line without having to go through the +configuration box first. + +\dt \cw{\-ssh}, \cw{\-telnet}, \cw{\-rlogin}, \cw{\-raw}, \cw{\-serial} + +\dd Select the protocol \cw{putty} will use to make the connection. + +\dt \cw{\-l} \e{username} + +\dd Specify the username to use when logging in to the server. + +\dt \cw{\-L} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport} + +\dd Set up a local port forwarding: listen on \e{srcport} (or +\e{srcaddr}:\e{srcport} if specified), and forward any connections +over the SSH connection to the destination address +\e{desthost}:\e{destport}. Only works in SSH. + +\dt \cw{\-R} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport} + +\dd Set up a remote port forwarding: ask the SSH server to listen on +\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and to +forward any connections back over the SSH connection where the +client will pass them on to the destination address +\e{desthost}:\e{destport}. Only works in SSH. + +\dt \cw{\-D} [\e{srcaddr}:]\e{srcport} + +\dd Set up dynamic port forwarding. The client listens on +\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and +implements a SOCKS server. So you can point SOCKS-aware applications +at this port and they will automatically use the SSH connection to +tunnel all their connections. Only works in SSH. + +\dt \cw{\-P} \e{port} + +\dd Specify the port to connect to the server on. + +\dt \cw{\-A}, \cw{\-a} + +\dd Enable (\cw{\-A}) or disable (\cw{\-a}) SSH agent forwarding. +Currently this only works with OpenSSH and SSH-1. + +\dt \cw{\-X}, \cw{\-x} + +\dd Enable (\cw{\-X}) or disable (\cw{\-x}) X11 forwarding. + +\dt \cw{\-T}, \cw{\-t} + +\dd Enable (\cw{\-t}) or disable (\cw{\-T}) the allocation of a +pseudo-terminal at the server end. + +\dt \cw{\-C} + +\dd Enable zlib-style compression on the connection. + +\dt \cw{\-1}, \cw{\-2} + +\dd Select SSH protocol version 1 or 2. + +\dt \cw{\-i} \e{keyfile} + +\dd Specify a private key file to use for authentication. For SSH-2 +keys, this key file must be in PuTTY's format, not OpenSSH's or +anyone else's. + +\dt \cw{\-sercfg} \e{configuration-string} + +\dd Specify the configuration parameters for the serial port, in +\cw{-serial} mode. \e{configuration-string} should be a +comma-separated list of configuration parameters as follows: + +\lcont{ + +\b Any single digit from 5 to 9 sets the number of data bits. + +\b \cq{1}, \cq{1.5} or \cq{2} sets the number of stop bits. + +\b Any other numeric string is interpreted as a baud rate. + +\b A single lower-case letter specifies the parity: \cq{n} for none, +\cq{o} for odd, \cq{e} for even, \cq{m} for mark and \cq{s} for space. + +\b A single upper-case letter specifies the flow control: \cq{N} for +none, \cq{X} for XON/XOFF, \cq{R} for RTS/CTS and \cq{D} for +DSR/DTR. + +} + +\S{putty-manpage-saved-sessions} SAVED SESSIONS + +Saved sessions are stored in a \cw{.putty/sessions} subdirectory in +your home directory. + +\S{putty-manpage-more-information} MORE INFORMATION + +For more information on PuTTY, it's probably best to go and look at +the manual on the web page: + +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} + +\S{putty-manpage-bugs} BUGS + +This man page isn't terribly complete. diff --git a/putty/DOC/MANCFG.BUT b/putty/DOC/MANCFG.BUT new file mode 100644 index 0000000..37723bd --- /dev/null +++ b/putty/DOC/MANCFG.BUT @@ -0,0 +1,3 @@ +\cfg{man-mindepth}{2} + +\C{not-shown} Chapter title which is not shown diff --git a/putty/DOC/MANPAGES.BUT b/putty/DOC/MANPAGES.BUT new file mode 100644 index 0000000..5fe4a76 --- /dev/null +++ b/putty/DOC/MANPAGES.BUT @@ -0,0 +1,3 @@ +\A{man-pages} Man pages for Unix PuTTY + +This appendix contains all the man pages for Unix PuTTY. diff --git a/putty/DOC/PAGEANT.BUT b/putty/DOC/PAGEANT.BUT new file mode 100644 index 0000000..04715ed --- /dev/null +++ b/putty/DOC/PAGEANT.BUT @@ -0,0 +1,273 @@ +\define{versionidpageant} \versionid $Id: pageant.but 6610 2006-03-14 11:21:59Z jacob $ + +\C{pageant} Using \i{Pageant} for authentication + +\cfg{winhelp-topic}{pageant.general} + +Pageant is an SSH \i{authentication agent}. It holds your \i{private key}s +in memory, already decoded, so that you can use them often +\I{passwordless login}without needing to type a \i{passphrase}. + +\H{pageant-start} Getting started with Pageant + +Before you run Pageant, you need to have a private key in \c{*.\i{PPK}} +format. See \k{pubkey} to find out how to generate and use one. + +When you run Pageant, it will put an icon of a computer wearing a +hat into the \ii{System tray}. It will then sit and do nothing, until you +load a private key into it. + +If you click the Pageant icon with the right mouse button, you will +see a menu. Select \q{View Keys} from this menu. The Pageant main +window will appear. (You can also bring this window up by +double-clicking on the Pageant icon.) + +The Pageant window contains a list box. This shows the private keys +Pageant is holding. When you start Pageant, it has no keys, so the +list box will be empty. After you add one or more keys, they will +show up in the list box. + +To add a key to Pageant, press the \q{Add Key} button. Pageant will +bring up a file dialog, labelled \q{Select Private Key File}. Find +your private key file in this dialog, and press \q{Open}. + +Pageant will now load the private key. If the key is protected by a +passphrase, Pageant will ask you to type the passphrase. When the +key has been loaded, it will appear in the list in the Pageant +window. + +Now start PuTTY and open an SSH session to a site that accepts your +key. PuTTY will notice that Pageant is running, retrieve the key +automatically from Pageant, and use it to authenticate. You can now +open as many PuTTY sessions as you like without having to type your +passphrase again. + +(PuTTY can be configured not to try to use Pageant, but it will try +by default. See \k{config-ssh-tryagent} and +\k{using-cmdline-agentauth} for more information.) + +When you want to shut down Pageant, click the right button on the +Pageant icon in the System tray, and select \q{Exit} from the menu. +Closing the Pageant main window does \e{not} shut down Pageant. + +\H{pageant-mainwin} The Pageant main window + +The Pageant main window appears when you left-click on the Pageant +system tray icon, or alternatively right-click and select \q{View +Keys} from the menu. You can use it to keep track of what keys are +currently loaded into Pageant, and to add new ones or remove the +existing keys. + +\S{pageant-mainwin-keylist} The key list box + +\cfg{winhelp-topic}{pageant.keylist} + +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 ssh1 1024 22:c3:68:3b:09:41:36:c3:39:83:91:ae:71:b2:0f:04 k1 +\c ssh-rsa 1023 74:63:08:82:95:75:e1:7c:33:31:bb:cb:00:c0:89:8b k2 + +For each key, the list box will tell you: + +\b The type of the key. Currently, this can be \c{ssh1} (an RSA key +for use with the SSH-1 protocol), \c{ssh-rsa} (an RSA key for use +with the SSH-2 protocol), or \c{ssh-dss} (a DSA key for use with +the SSH-2 protocol). + +\b The size (in bits) of the key. + +\b The \I{key fingerprint}fingerprint for the public key. This should be +the same fingerprint given by PuTTYgen, and (hopefully) also the same +fingerprint shown by remote utilities such as \i\c{ssh-keygen} when +applied to your \c{authorized_keys} file. + +\b The comment attached to the key. + +\S{pageant-mainwin-addkey} The \q{Add Key} button + +\cfg{winhelp-topic}{pageant.addkey} + +To add a key to Pageant by reading it out of a local disk file, +press the \q{Add Key} button in the Pageant main window, or +alternatively right-click on the Pageant icon in the system tray and +select \q{Add Key} from there. + +Pageant will bring up a file dialog, labelled \q{Select Private Key +File}. Find your private key file in this dialog, and press +\q{Open}. If you want to add more than one key at once, you can +select multiple files using Shift-click (to select several adjacent +files) or Ctrl-click (to select non-adjacent files). + +Pageant will now load the private key(s). If a key is protected by a +passphrase, Pageant will ask you to type the passphrase. + +(This is not the only way to add a private key to Pageant. You can +also add one from a remote system by using agent forwarding; see +\k{pageant-forward} for details.) + +\S{pageant-mainwin-remkey} The \q{Remove Key} button + +\cfg{winhelp-topic}{pageant.remkey} + +If you need to remove a key from Pageant, select that key in the +list box, and press the \q{Remove Key} button. Pageant will remove +the key from its memory. + +You can apply this to keys you added using the \q{Add Key} button, +or to keys you added remotely using agent forwarding (see +\k{pageant-forward}); it makes no difference. + +\H{pageant-cmdline} The Pageant command line + +Pageant can be made to do things automatically when it starts up, by +\I{command-line arguments}specifying instructions on its command line. +If you're starting Pageant from the Windows GUI, you can arrange this +by editing the properties of the \i{Windows shortcut} that it was +started from. + +If Pageant is already running, invoking it again with the options +below causes actions to be performed with the existing instance, not a +new one. + +\S{pageant-cmdline-loadkey} Making Pageant automatically load keys +on startup + +Pageant can automatically load one or more private keys when it +starts up, if you provide them on the Pageant command line. Your +command line might then look like: + +\c C:\PuTTY\pageant.exe d:\main.ppk d:\secondary.ppk + +If the keys are stored encrypted, Pageant will request the +passphrases on startup. + +If Pageant is already running, this syntax loads keys into the +existing Pageant. + +\S{pageant-cmdline-command} Making Pageant run another program + +You can arrange for Pageant to start another program once it has +initialised itself and loaded any keys specified on its command +line. This program (perhaps a PuTTY, or a WinCVS making use of +Plink, or whatever) will then be able to use the keys Pageant has +loaded. + +You do this by specifying the \I{-c-pageant}\c{-c} option followed +by the command, like this: + +\c C:\PuTTY\pageant.exe d:\main.ppk -c C:\PuTTY\putty.exe + +\H{pageant-forward} Using \i{agent forwarding} + +Agent forwarding is a mechanism that allows applications on your SSH +server machine to talk to the agent on your client machine. + +Note that at present, agent forwarding in SSH-2 is only available +when your SSH server is \i{OpenSSH}. The \i\cw{ssh.com} server uses a +different agent protocol, which PuTTY does not yet support. + +To enable agent forwarding, first start Pageant. Then set up a PuTTY +SSH session in which \q{Allow agent forwarding} is enabled (see +\k{config-ssh-agentfwd}). Open the session as normal. (Alternatively, +you can use the \c{-A} command line option; see +\k{using-cmdline-agent} for details.) + +If this has worked, your applications on the server should now have +access to a Unix domain socket which the SSH server will forward +back to PuTTY, and PuTTY will forward on to the agent. To check that +this has actually happened, you can try this command on Unix server +machines: + +\c unixbox:~$ echo $SSH_AUTH_SOCK +\c /tmp/ssh-XXNP18Jz/agent.28794 +\c unixbox:~$ + +If the result line comes up blank, agent forwarding has not been +enabled at all. + +Now if you run \c{ssh} on the server and use it to connect through +to another server that accepts one of the keys in Pageant, you +should be able to log in without a password: + +\c unixbox:~$ ssh -v otherunixbox +\c [...] +\c debug: next auth method to try is publickey +\c debug: userauth_pubkey_agent: trying agent key my-putty-key +\c debug: ssh-userauth2 successful: method publickey +\c [...] + +If you enable agent forwarding on \e{that} SSH connection as well +(see the manual for your server-side SSH client to find out how to +do this), your authentication keys will still be available on the +next machine you connect to - two SSH connections away from where +they're actually stored. + +In addition, if you have a private key on one of the SSH servers, +you can send it all the way back to Pageant using the local +\i\c{ssh-add} command: + +\c unixbox:~$ ssh-add ~/.ssh/id_rsa +\c Need passphrase for /home/fred/.ssh/id_rsa +\c Enter passphrase for /home/fred/.ssh/id_rsa: +\c Identity added: /home/fred/.ssh/id_rsa (/home/simon/.ssh/id_rsa) +\c unixbox:~$ + +and then it's available to every machine that has agent forwarding +available (not just the ones downstream of the place you added it). + +\H{pageant-security} Security considerations + +\I{security risk}Using Pageant for public-key authentication gives you the +convenience of being able to open multiple SSH sessions without +having to type a passphrase every time, but also gives you the +security benefit of never storing a decrypted private key on disk. +Many people feel this is a good compromise between security and +convenience. + +It \e{is} a compromise, however. Holding your decrypted private keys +in Pageant is better than storing them in easy-to-find disk files, +but still less secure than not storing them anywhere at all. This is +for two reasons: + +\b Windows unfortunately provides no way to protect pieces of memory +from being written to the system \i{swap file}. So if Pageant is holding +your private keys for a long period of time, it's possible that +decrypted private key data may be written to the system swap file, +and an attacker who gained access to your hard disk later on might +be able to recover that data. (However, if you stored an unencrypted +key in a disk file they would \e{certainly} be able to recover it.) + +\b Although, like most modern operating systems, Windows prevents +programs from accidentally accessing one another's memory space, it +does allow programs to access one another's memory space +deliberately, for special purposes such as debugging. This means +that if you allow a virus, trojan, or other malicious program on to +your Windows system while Pageant is running, it could access the +memory of the Pageant process, extract your decrypted authentication +keys, and send them back to its master. + +Similarly, use of agent \e{forwarding} is a security improvement on +other methods of one-touch authentication, but not perfect. Holding +your keys in Pageant on your Windows box has a security advantage +over holding them on the remote server machine itself (either in an +agent or just unencrypted on disk), because if the server machine +ever sees your unencrypted private key then the sysadmin or anyone +who cracks the machine can steal the keys and pretend to be you for +as long as they want. + +However, the sysadmin of the server machine can always pretend to be +you \e{on that machine}. So if you forward your agent to a server +machine, then the sysadmin of that machine can access the forwarded +agent connection and request signatures from your private keys, and +can therefore log in to other machines as you. They can only do this +to a limited extent - when the agent forwarding disappears they lose +the ability - but using Pageant doesn't actually \e{prevent} the +sysadmin (or hackers) on the server from doing this. + +Therefore, if you don't trust the sysadmin of a server machine, you +should \e{never} use agent forwarding to that machine. (Of course +you also shouldn't store private keys on that machine, type +passphrases into it, or log into other machines from it in any way +at all; Pageant is hardly unique in this respect.) diff --git a/putty/DOC/PGPKEYS.BUT b/putty/DOC/PGPKEYS.BUT new file mode 100644 index 0000000..87f80db --- /dev/null +++ b/putty/DOC/PGPKEYS.BUT @@ -0,0 +1,141 @@ +\define{versionidpgpkeys} \versionid $Id: pgpkeys.but 5598 2005-04-05 19:36:25Z simon $ + +\A{pgpkeys} PuTTY download keys and signatures + +\cfg{winhelp-topic}{pgpfingerprints} + +\I{verifying new versions}We create \i{PGP signatures} for all the PuTTY +files distributed from our web site, so that users can be confident +that the files have not been tampered with. Here we identify +our public keys, and explain our signature policy so you can have an +accurate idea of what each signature guarantees. +This description is provided as both a web page on the PuTTY site, and +an appendix in the PuTTY manual. + +As of release 0.58, all of the PuTTY executables contain fingerprint +material (usually accessed via the \i\c{-pgpfp} command-line +option), such that if you have an executable you trust, you can use +it to establish a trust path, for instance to a newer version +downloaded from the Internet. + +(Note that none of the keys, signatures, etc mentioned here have +anything to do with keys used with SSH - they are purely for verifying +the origin of files distributed by the PuTTY team.) + +\H{pgpkeys-pubkey} Public keys + +We supply two complete sets of keys. We supply a set of RSA keys, +compatible with both \W{http://www.gnupg.org/}{GnuPG} and PGP2, +and also a set of DSA keys compatible with GnuPG. + +In each format, we have three keys: + +\b A Development Snapshots key, used to sign the nightly builds. + +\b A Releases key, used to sign actual releases. + +\b A Master Key. The Master Key is used to sign the other two keys, and +they sign it in return. + +Therefore, we have six public keys in total: + +\b RSA: +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{Master Key}, +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-rsa.asc}{Release key}, +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-rsa.asc}{Snapshot key} + +\lcont{ +Master Key: 1024-bit; \I{PGP key fingerprint}fingerprint: +\cw{8F\_15\_97\_DA\_25\_30\_AB\_0D\_\_88\_D1\_92\_54\_11\_CF\_0C\_4C} +} + +\b DSA: +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-dsa.asc}{Master Key}, +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-dsa.asc}{Release key}, +\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-dsa.asc}{Snapshot key} + +\lcont{ +Master Key: 1024-bit; fingerprint: +\cw{313C\_3E76\_4B74\_C2C5\_F2AE\_\_83A8\_4F5E\_6DF5\_6A93\_B34E} +} + +\H{pgpkeys-security} Security details + +The various keys have various different security levels. This +section explains what those security levels are, and how far you can +expect to trust each key. + +\S{pgpkeys-snapshot} The Development Snapshots keys + +These keys are stored \e{without passphrases}. This is +necessary, because the snapshots are generated every night without +human intervention, so nobody would be able to type a passphrase. + +The actual snapshots are built on a team member's home Windows box. +The keys themselves are stored on an independently run Unix box +(the same one that hosts our Subversion repository). After +being built, the binaries are uploaded to this Unix box and then +signed automatically. + +Therefore, a signature from one of the Development Snapshots keys +\e{DOES} protect you against: + +\b People tampering with the PuTTY binaries between the PuTTY web site +and you. + +But it \e{DOES NOT} protect you against: + +\b People tampering with the binaries before they are uploaded to the +independent Unix box. + +\b The sysadmin of the independent Unix box using his root privilege to +steal the private keys and abuse them, or tampering with the +binaries before they are signed. + +\b Somebody getting root on the Unix box. + +Of course, we don't believe any of those things is very likely. We +know our sysadmin personally and trust him (both to be competent and +to be non-malicious), and we take all reasonable precautions to +guard the build machine. But when you see a signature, you should +always be certain of precisely what it guarantees and precisely what +it does not. + +\S{pgpkeys-release} The Releases keys + +The Release keys have passphrases and we can be more careful about +how we use them. + +The Release keys are kept safe on the developers' own local +machines, and only used to sign releases that have been built by +hand. A signature from a Release key protects you from almost any +plausible attack. + +(Some of the developers' machines have cable modem connections and +might in theory be crackable, but of course the private keys are +still encrypted, so the crack would have to go unnoticed for long +enough to steal a passphrase.) + +\S{pgpkeys-master} The Master Keys + +The Master Keys sign almost nothing. Their purpose is to bind the +other keys together and certify that they are all owned by the same +people and part of the same integrated setup. The only signatures +produced by the Master Keys, \e{ever}, should be the signatures +on the other keys. + +We intend to arrange for the Master Keys to sign each other, to +certify that the DSA keys and RSA keys are part of the same setup. +We have not yet got round to this at the time of writing. + +We have collected a few third-party signatures on the Master Keys, +in order to increase the chances that you can find a suitable trust +path to them. We intend to collect more. (Note that the keys on the +keyservers appear to have also collected some signatures from people +who haven't performed any verification of the Master Keys.) + +We have uploaded our various keys to public keyservers, so that +even if you don't know any of the people who have signed our +keys, you can still be reasonably confident that an attacker would +find it hard to substitute fake keys on all the public keyservers at +once. diff --git a/putty/DOC/PLINK.BUT b/putty/DOC/PLINK.BUT new file mode 100644 index 0000000..bb293c2 --- /dev/null +++ b/putty/DOC/PLINK.BUT @@ -0,0 +1,296 @@ +\define{versionidplink} \versionid $Id: plink.but 9202 2011-07-12 18:26:18Z simon $ + +\C{plink} Using the command-line connection tool \i{Plink} + +\i{Plink} (PuTTY Link) is a command-line connection tool similar to +UNIX \c{ssh}. It is mostly used for \i{automated operations}, such as +making CVS access a repository on a remote server. + +Plink is probably not what you want if you want to run an +\i{interactive session} in a console window. + +\H{plink-starting} Starting Plink + +Plink is a command line application. This means that you cannot just +double-click on its icon to run it and instead you have to bring up +a \i{console window}. In Windows 95, 98, and ME, this is called an +\q{MS-DOS Prompt}, and in Windows NT, 2000, and XP, it is called a +\q{Command Prompt}. It should be available from the Programs section +of your Start Menu. + +In order to use Plink, the file \c{plink.exe} will need either to be +on your \i{\c{PATH}} or in your current directory. To add the +directory containing Plink to your \c{PATH} environment variable, +type into the console window: + +\c set PATH=C:\path\to\putty\directory;%PATH% + +This will only work for the lifetime of that particular console +window. To set your \c{PATH} more permanently on Windows NT, 2000, +and XP, use the Environment tab of the System Control Panel. On +Windows 95, 98, and ME, you will need to edit your \i\c{AUTOEXEC.BAT} +to include a \c{set} command like the one above. + +\H{plink-usage} Using Plink + +This section describes the basics of how to use Plink for +interactive logins and for automated processes. + +Once you've got a console window to type into, you can just type +\c{plink} on its own to bring up a usage message. This tells you the +version of Plink you're using, and gives you a brief summary of how to +use Plink: + +\c Z:\sysosd>plink +\c PuTTY Link: command-line connection utility +\c Release 0.61 +\c Usage: plink [options] [user@]host [command] +\c ("host" can also be a PuTTY saved session name) +\c Options: +\c -V print version information and exit +\c -pgpfp print PGP key fingerprints and exit +\c -v show verbose messages +\c -load sessname Load settings from saved session +\c -ssh -telnet -rlogin -raw -serial +\c force use of a particular protocol +\c -P port connect to specified port +\c -l user connect with specified username +\c -batch disable all interactive prompts +\c The following options only apply to SSH connections: +\c -pw passw login with specified password +\c -D [listen-IP:]listen-port +\c Dynamic SOCKS-based port forwarding +\c -L [listen-IP:]listen-port:host:port +\c Forward local port to remote address +\c -R [listen-IP:]listen-port:host:port +\c Forward remote port to local address +\c -X -x enable / disable X11 forwarding +\c -A -a enable / disable agent forwarding +\c -t -T enable / disable pty allocation +\c -1 -2 force use of particular protocol version +\c -4 -6 force use of IPv4 or IPv6 +\c -C enable compression +\c -i key private key file for authentication +\c -noagent disable use of Pageant +\c -agent enable use of Pageant +\c -m file read remote command(s) from file +\c -s remote command is an SSH subsystem (SSH-2 only) +\c -N don't start a shell/command (SSH-2 only) +\c -nc host:port +\c open tunnel in place of session (SSH-2 only) +\c -sercfg configuration-string (e.g. 19200,8,n,1,X) +\c Specify the serial configuration (serial only) + +Once this works, you are ready to use Plink. + +\S{plink-usage-interactive} Using Plink for interactive logins + +To make a simple interactive connection to a remote server, just +type \c{plink} and then the host name: + +\c Z:\sysosd>plink login.example.com +\c +\c Debian GNU/Linux 2.2 flunky.example.com +\c flunky login: + +You should then be able to log in as normal and run a session. The +output sent by the server will be written straight to your command +prompt window, which will most likely not interpret terminal \i{control +codes} in the way the server expects it to. So if you run any +full-screen applications, for example, you can expect to see strange +characters appearing in your window. Interactive connections like +this are not the main point of Plink. + +In order to connect with a different protocol, you can give the +command line options \c{-ssh}, \c{-telnet}, \c{-rlogin} or \c{-raw}. +To make an SSH connection, for example: + +\c Z:\sysosd>plink -ssh login.example.com +\c login as: + +If you have already set up a PuTTY saved session, then instead of +supplying a host name, you can give the saved session name. This +allows you to use public-key authentication, specify a user name, +and use most of the other features of PuTTY: + +\c Z:\sysosd>plink my-ssh-session +\c Sent username "fred" +\c Authenticating with public key "fred@winbox" +\c Last login: Thu Dec 6 19:25:33 2001 from :0.0 +\c fred@flunky:~$ + +(You can also use the \c{-load} command-line option to load a saved +session; see \k{using-cmdline-load}. If you use \c{-load}, the saved +session exists, and it specifies a hostname, you cannot also specify a +\c{host} or \c{user@host} argument - it will be treated as part of the +remote command.) + +\S{plink-usage-batch} Using Plink for automated connections + +More typically Plink is used with the SSH protocol, to enable you to +talk directly to a program running on the server. To do this you +have to ensure Plink is \e{using} the SSH protocol. You can do this +in several ways: + +\b Use the \c{-ssh} option as described in +\k{plink-usage-interactive}. + +\b Set up a PuTTY saved session that describes the server you are +connecting to, and that also specifies the protocol as SSH. + +\b Set the Windows environment variable \i\c{PLINK_PROTOCOL} to the +word \c{ssh}. + +Usually Plink is not invoked directly by a user, but run +automatically by another process. Therefore you typically do not +want Plink to prompt you for a user name or a password. + +Next, you are likely to need to avoid the various interactive +prompts Plink can produce. You might be prompted to verify the host +key of the server you're connecting to, to enter a user name, or to +enter a password. + +To avoid being prompted for the server host key when using Plink for +an automated connection, you should first make a \e{manual} +connection (using either of PuTTY or Plink) to the same server, +verify the host key (see \k{gs-hostkey} for more information), and +select Yes to add the host key to the Registry. After that, Plink +commands connecting to that server should not give a host key prompt +unless the host key changes. + +To avoid being prompted for a user name, you can: + +\b Use the \c{-l} option to specify a user name on the command line. +For example, \c{plink login.example.com -l fred}. + +\b Set up a PuTTY saved session that describes the server you are +connecting to, and that also specifies the username to log in as +(see \k{config-username}). + +To avoid being prompted for a password, you should almost certainly +set up \i{public-key authentication}. (See \k{pubkey} for a general +introduction to public-key authentication.) Again, you can do this +in two ways: + +\b Set up a PuTTY saved session that describes the server you are +connecting to, and that also specifies a private key file (see +\k{config-ssh-privkey}). For this to work without prompting, your +private key will need to have no passphrase. + +\b Store the private key in Pageant. See \k{pageant} for further +information. + +Once you have done all this, you should be able to run a remote +command on the SSH server machine and have it execute automatically +with no prompting: + +\c Z:\sysosd>plink login.example.com -l fred echo hello, world +\c hello, world +\c +\c Z:\sysosd> + +Or, if you have set up a saved session with all the connection +details: + +\c Z:\sysosd>plink mysession echo hello, world +\c hello, world +\c +\c Z:\sysosd> + +Then you can set up other programs to run this Plink command and +talk to it as if it were a process on the server machine. + +\S{plink-options} Plink command line options + +Plink accepts all the general command line options supported by the +PuTTY tools. See \k{using-general-opts} for a description of these +options. + +Plink also supports some of its own options. The following sections +describe Plink's specific command-line options. + +\S2{plink-option-batch} \I{-batch-plink}\c{-batch}: disable all +interactive prompts + +If you use the \c{-batch} option, Plink will never give an +interactive prompt while establishing the connection. If the +server's host key is invalid, for example (see \k{gs-hostkey}), then +the connection will simply be abandoned instead of asking you what +to do next. + +This may help Plink's behaviour when it is used in automated +scripts: using \c{-batch}, if something goes wrong at connection +time, the batch job will fail rather than hang. + +\S2{plink-option-s} \I{-s-plink}\c{-s}: remote command is SSH subsystem + +If you specify the \c{-s} option, Plink passes the specified command +as the name of an SSH \q{\i{subsystem}} rather than an ordinary command +line. + +(This option is only meaningful with the SSH-2 protocol.) + +\H{plink-batch} Using Plink in \i{batch files} and \i{scripts} + +Once you have set up Plink to be able to log in to a remote server +without any interactive prompting (see \k{plink-usage-batch}), you +can use it for lots of scripting and batch purposes. For example, to +start a backup on a remote machine, you might use a command like: + +\c plink root@myserver /etc/backups/do-backup.sh + +Or perhaps you want to fetch all system log lines relating to a +particular web area: + +\c plink mysession grep /~fred/ /var/log/httpd/access.log > fredlog + +Any non-interactive command you could usefully run on the server +command line, you can run in a batch file using Plink in this way. + +\H{plink-cvs} Using Plink with \i{CVS} + +To use Plink with CVS, you need to set the environment variable +\i\c{CVS_RSH} to point to Plink: + +\c set CVS_RSH=\path\to\plink.exe + +You also need to arrange to be able to connect to a remote host +without any interactive prompts, as described in +\k{plink-usage-batch}. + +You should then be able to run CVS as follows: + +\c cvs -d :ext:user@sessionname:/path/to/repository co module + +If you specified a username in your saved session, you don't even +need to specify the \q{user} part of this, and you can just say: + +\c cvs -d :ext:sessionname:/path/to/repository co module + +\H{plink-wincvs} Using Plink with \i{WinCVS} + +Plink can also be used with WinCVS. Firstly, arrange for Plink to be +able to connect to a remote host non-interactively, as described in +\k{plink-usage-batch}. + +Then, in WinCVS, bring up the \q{Preferences} dialogue box from the +\e{Admin} menu, and switch to the \q{Ports} tab. Tick the box there +labelled \q{Check for an alternate \cw{rsh} name} and in the text +entry field to the right enter the full path to \c{plink.exe}. +Select \q{OK} on the \q{Preferences} dialogue box. + +Next, select \q{Command Line} from the WinCVS \q{Admin} menu, and type +a CVS command as in \k{plink-cvs}, for example: + +\c cvs -d :ext:user@hostname:/path/to/repository co module + +or (if you're using a saved session): + +\c cvs -d :ext:user@sessionname:/path/to/repository co module + +Select the folder you want to check out to with the \q{Change Folder} +button, and click \q{OK} to check out your module. Once you've got +modules checked out, WinCVS will happily invoke plink from the GUI for +CVS operations. + +\# \H{plink-whatelse} Using Plink with... ? diff --git a/putty/DOC/PSCP.BUT b/putty/DOC/PSCP.BUT new file mode 100644 index 0000000..14064a9 --- /dev/null +++ b/putty/DOC/PSCP.BUT @@ -0,0 +1,318 @@ +\define{versionidpscp} \versionid $Id: pscp.but 9202 2011-07-12 18:26:18Z simon $ + +\#FIXME: Need examples + +\C{pscp} Using \i{PSCP} to transfer files securely + +\i{PSCP}, the PuTTY Secure Copy client, is a tool for \i{transferring files} +securely between computers using an SSH connection. + +If you have an SSH-2 server, you might prefer PSFTP (see \k{psftp}) +for interactive use. PSFTP does not in general work with SSH-1 +servers, however. + +\H{pscp-starting} Starting PSCP + +PSCP is a command line application. This means that you cannot just +double-click on its icon to run it and instead you have to bring up a +\i{console window}. With Windows 95, 98, and ME, this is called an +\q{MS-DOS Prompt} and with Windows NT, 2000, and XP, it is called a +\q{Command Prompt}. It should be available from the Programs section +of your \i{Start Menu}. + +To start PSCP it will need either to be on your \i{\c{PATH}} or in your +current directory. To add the directory containing PSCP to your +\c{PATH} environment variable, type into the console window: + +\c set PATH=C:\path\to\putty\directory;%PATH% + +This will only work for the lifetime of that particular console +window. To set your \c{PATH} more permanently on Windows NT, 2000, +and XP, use the Environment tab of the System Control Panel. On +Windows 95, 98, and ME, you will need to edit your \i\c{AUTOEXEC.BAT} +to include a \c{set} command like the one above. + +\H{pscp-usage} PSCP Usage + +Once you've got a console window to type into, you can just type +\c{pscp} on its own to bring up a usage message. This tells you the +version of PSCP you're using, and gives you a brief summary of how to +use PSCP: + +\c Z:\owendadmin>pscp +\c PuTTY Secure Copy client +\c Release 0.61 +\c Usage: pscp [options] [user@]host:source target +\c pscp [options] source [source...] [user@]host:target +\c pscp [options] -ls [user@]host:filespec +\c Options: +\c -V print version information and exit +\c -pgpfp print PGP key fingerprints and exit +\c -p preserve file attributes +\c -q quiet, don't show statistics +\c -r copy directories recursively +\c -v show verbose messages +\c -load sessname Load settings from saved session +\c -P port connect to specified port +\c -l user connect with specified username +\c -pw passw login with specified password +\c -1 -2 force use of particular SSH protocol version +\c -4 -6 force use of IPv4 or IPv6 +\c -C enable compression +\c -i key private key file for authentication +\c -noagent disable use of Pageant +\c -agent enable use of Pageant +\c -batch disable all interactive prompts +\c -unsafe allow server-side wildcards (DANGEROUS) +\c -sftp force use of SFTP protocol +\c -scp force use of SCP protocol + +(PSCP's interface is much like the Unix \c{scp} command, if you're +familiar with that.) + +\S{pscp-usage-basics} The basics + +To \I{receiving files}receive (a) file(s) from a remote server: + +\c pscp [options] [user@]host:source target + +So to copy the file \c{/etc/hosts} from the server \c{example.com} as +user \c{fred} to the file \c{c:\\temp\\example-hosts.txt}, you would type: + +\c pscp fred@example.com:/etc/hosts c:\temp\example-hosts.txt + +To \I{sending files}send (a) file(s) to a remote server: + +\c pscp [options] source [source...] [user@]host:target + +So to copy the local file \c{c:\\documents\\foo.txt} to the server +\c{example.com} as user \c{fred} to the file \c{/tmp/foo} you would +type: + +\c pscp c:\documents\foo.txt fred@example.com:/tmp/foo + +You can use \i{wildcards} to transfer multiple files in either +direction, like this: + +\c pscp c:\documents\*.doc fred@example.com:docfiles +\c pscp fred@example.com:source/*.c c:\source + +However, in the second case (using a wildcard for multiple remote +files) you may see a warning saying something like \q{warning: +remote host tried to write to a file called \cq{terminal.c} when we +requested a file called \cq{*.c}. If this is a wildcard, consider +upgrading to SSH-2 or using the \cq{-unsafe} option. Renaming of +this file has been disallowed}. + +This is due to a \I{security risk}fundamental insecurity in the old-style +\i{SCP protocol}: the client sends the wildcard string (\c{*.c}) to the +server, and the server sends back a sequence of file names that +match the wildcard pattern. However, there is nothing to stop the +server sending back a \e{different} pattern and writing over one of +your other files: if you request \c{*.c}, the server might send back +the file name \c{AUTOEXEC.BAT} and install a virus for you. Since +the wildcard matching rules are decided by the server, the client +cannot reliably verify that the filenames sent back match the +pattern. + +PSCP will attempt to use the newer \i{SFTP} protocol (part of SSH-2) +where possible, which does not suffer from this security flaw. If +you are talking to an SSH-2 server which supports SFTP, you will +never see this warning. (You can force use of the SFTP protocol, +if available, with \c{-sftp} - see \k{pscp-usage-options-backend}.) + +If you really need to use a server-side wildcard with an SSH-1 +server, you can use the \i\c{-unsafe} command line option with PSCP: + +\c pscp -unsafe fred@example.com:source/*.c c:\source + +This will suppress the warning message and the file transfer will +happen. However, you should be aware that by using this option you +are giving the server the ability to write to \e{any} file in the +target directory, so you should only use this option if you trust +the server administrator not to be malicious (and not to let the +server machine be cracked by malicious people). Alternatively, do +any such download in a newly created empty directory. (Even in +\q{unsafe} mode, PSCP will still protect you against the server +trying to get out of that directory using pathnames including +\cq{..}.) + +\S2{pscp-usage-basics-user} \c{user} + +The \i{login name} on the remote server. If this is omitted, and \c{host} +is a PuTTY saved session, PSCP will use any username specified by that +saved session. Otherwise, PSCP will attempt to use the local Windows +username. + +\S2{pscp-usage-basics-host} \I{hostname}\c{host} + +The name of the remote server, or the name of an existing PuTTY saved +session. In the latter case, the session's settings for hostname, port +number, cipher type and username will be used. + +\S2{pscp-usage-basics-source} \c{source} + +One or more source files. \ii{Wildcards} are allowed. The syntax of +wildcards depends on the system to which they apply, so if you are +copying \e{from} a Windows system \e{to} a UNIX system, you should use +Windows wildcard syntax (e.g. \c{*.*}), but if you are copying \e{from} +a UNIX system \e{to} a Windows system, you would use the wildcard +syntax allowed by your UNIX shell (e.g. \c{*}). + +If the source is a remote server and you do not specify a full +pathname (in UNIX, a pathname beginning with a \c{/} (slash) +character), what you specify as a source will be interpreted relative +to your \i{home directory} on the remote server. + +\S2{pscp-usage-basics-target} \c{target} + +The filename or directory to put the file(s). When copying from a +remote server to a local host, you may wish simply to place the +file(s) in the current directory. To do this, you should specify a +target of \c{.}. For example: + +\c pscp fred@example.com:/home/tom/.emacs . + +...would copy \c{/home/tom/.emacs} on the remote server to the current +directory. + +As with the \c{source} parameter, if the target is on a remote server +and is not a full path name, it is interpreted relative to your home +directory on the remote server. + +\S{pscp-usage-options} Options + +PSCP accepts all the general command line options supported by the +PuTTY tools, except the ones which make no sense in a file transfer +utility. See \k{using-general-opts} for a description of these +options. (The ones not supported by PSCP are clearly marked.) + +PSCP also supports some of its own options. The following sections +describe PSCP's specific command-line options. + +\S2{pscp-usage-options-ls}\I{-ls-PSCP}\c{-ls} \I{listing files}list remote files + +If the \c{-ls} option is given, no files are transferred; instead, +remote files are listed. Only a hostname specification and +optional remote file specification need be given. For example: + +\c pscp -ls fred@example.com:dir1 + +The SCP protocol does not contain within itself a means of listing +files. If SCP is in use, this option therefore assumes that the +server responds appropriately to the command \c{ls\_-la}; +this may not work with all servers. + +If SFTP is in use, this option should work with all servers. + +\S2{pscp-usage-options-p}\I{-p-PSCP}\c{-p} \i{preserve file attributes} + +By default, files copied with PSCP are \i{timestamp}ed with the date and +time they were copied. The \c{-p} option preserves the original +timestamp on copied files. + +\S2{pscp-usage-options-q}\I{-q-PSCP}\c{-q} quiet, don't show \i{statistics} + +By default, PSCP displays a meter displaying the progress of the +current transfer: + +\c mibs.tar | 168 kB | 84.0 kB/s | ETA: 00:00:13 | 13% + +The fields in this display are (from left to right), filename, size +(in kilobytes) of file transferred so far, estimate of how fast the +file is being transferred (in kilobytes per second), estimated time +that the transfer will be complete, and percentage of the file so far +transferred. The \c{-q} option to PSCP suppresses the printing of +these statistics. + +\S2{pscp-usage-options-r}\I{-r-PSCP}\c{-r} copies directories \i{recursive}ly + +By default, PSCP will only copy files. Any directories you specify to +copy will be skipped, as will their contents. The \c{-r} option tells +PSCP to descend into any directories you specify, and to copy them and +their contents. This allows you to use PSCP to transfer whole +directory structures between machines. + +\S2{pscp-usage-options-batch}\I{-batch-PSCP}\c{-batch} avoid interactive prompts + +If you use the \c{-batch} option, PSCP will never give an +interactive prompt while establishing the connection. If the +server's host key is invalid, for example (see \k{gs-hostkey}), then +the connection will simply be abandoned instead of asking you what +to do next. + +This may help PSCP's behaviour when it is used in automated +scripts: using \c{-batch}, if something goes wrong at connection +time, the batch job will fail rather than hang. + +\S2{pscp-usage-options-backend}\i\c{-sftp}, \i\c{-scp} force use of +particular protocol + +As mentioned in \k{pscp-usage-basics}, there are two different file +transfer protocols in use with SSH. Despite its name, PSCP (like many +other ostensible \cw{scp} clients) can use either of these protocols. + +The older \i{SCP protocol} does not have a written specification and +leaves a lot of detail to the server platform. \ii{Wildcards} are expanded +on the server. The simple design means that any wildcard specification +supported by the server platform (such as brace expansion) can be +used, but also leads to interoperability issues such as with filename +quoting (for instance, where filenames contain spaces), and also the +security issue described in \k{pscp-usage-basics}. + +The newer \i{SFTP} protocol, which is usually associated with SSH-2 +servers, is specified in a more platform independent way, and leaves +issues such as wildcard syntax up to the client. (PuTTY's SFTP +wildcard syntax is described in \k{psftp-wildcards}.) This makes it +more consistent across platforms, more suitable for scripting and +automation, and avoids security issues with wildcard matching. + +Normally PSCP will attempt to use the SFTP protocol, and only fall +back to the SCP protocol if SFTP is not available on the server. + +The \c{-scp} option forces PSCP to use the SCP protocol or quit. + +The \c{-sftp} option forces PSCP to use the SFTP protocol or quit. +When this option is specified, PSCP looks harder for an SFTP server, +which may allow use of SFTP with SSH-1 depending on server setup. + +\S{pscp-retval} \ii{Return value} + +PSCP returns an \i\cw{ERRORLEVEL} of zero (success) only if the files +were correctly transferred. You can test for this in a \i{batch file}, +using code such as this: + +\c pscp file*.* user@hostname: +\c if errorlevel 1 echo There was an error + +\S{pscp-pubkey} Using \i{public key authentication} with PSCP + +Like PuTTY, PSCP can authenticate using a public key instead of a +password. There are three ways you can do this. + +Firstly, PSCP can use PuTTY saved sessions in place of hostnames +(see \k{pscp-usage-basics-host}). So you would do this: + +\b Run PuTTY, and create a PuTTY saved session (see +\k{config-saving}) which specifies your private key file (see +\k{config-ssh-privkey}). You will probably also want to specify a +username to log in as (see \k{config-username}). + +\b In PSCP, you can now use the name of the session instead of a +hostname: type \c{pscp sessionname:file localfile}, where +\c{sessionname} is replaced by the name of your saved session. + +Secondly, you can supply the name of a private key file on the command +line, with the \c{-i} option. See \k{using-cmdline-identity} for more +information. + +Thirdly, PSCP will attempt to authenticate using Pageant if Pageant +is running (see \k{pageant}). So you would do this: + +\b Ensure Pageant is running, and has your private key stored in it. + +\b Specify a user and host name to PSCP as normal. PSCP will +automatically detect Pageant and try to use the keys within it. + +For more general information on public-key authentication, see +\k{pubkey}. diff --git a/putty/DOC/PSFTP.BUT b/putty/DOC/PSFTP.BUT new file mode 100644 index 0000000..b7730fb --- /dev/null +++ b/putty/DOC/PSFTP.BUT @@ -0,0 +1,593 @@ +\define{versionidpsftp} \versionid $Id: psftp.but 8325 2008-11-24 18:19:55Z jacob $ + +\C{psftp} Using \i{PSFTP} to transfer files securely + +\i{PSFTP}, the PuTTY SFTP client, is a tool for \i{transferring files} +securely between computers using an SSH connection. + +PSFTP differs from PSCP in the following ways: + +\b PSCP should work on virtually every SSH server. PSFTP uses the +new \i{SFTP} protocol, which is a feature of SSH-2 only. (PSCP will also +use this protocol if it can, but there is an SSH-1 equivalent it can +fall back to if it cannot.) + +\b PSFTP allows you to run an interactive file transfer session, +much like the Windows \i\c{ftp} program. You can list the contents of +directories, browse around the file system, issue multiple \c{get} +and \c{put} commands, and eventually log out. By contrast, PSCP is +designed to do a single file transfer operation and immediately +terminate. + +\H{psftp-starting} Starting PSFTP + +The usual way to start PSFTP is from a command prompt, much like +PSCP. To do this, it will need either to be on your \i{\c{PATH}} or +in your current directory. To add the directory containing PSFTP to +your \c{PATH} environment variable, type into the console window: + +\c set PATH=C:\path\to\putty\directory;%PATH% + +Unlike PSCP, however, PSFTP has no complex command-line syntax; you +just specify a host name and perhaps a user name: + +\c psftp server.example.com + +or perhaps + +\c psftp fred@server.example.com + +Alternatively, if you just type \c{psftp} on its own (or +double-click the PSFTP icon in the Windows GUI), you will see the +PSFTP prompt, and a message telling you PSFTP has not connected to +any server: + +\c C:\>psftp +\c psftp: no hostname specified; use "open host.name" to connect +\c psftp> + +At this point you can type \c{open server.example.com} or \c{open +fred@server.example.com} to start a session. + +PSFTP accepts all the general command line options supported by the +PuTTY tools, except the ones which make no sense in a file transfer +utility. See \k{using-general-opts} for a description of these +options. (The ones not supported by PSFTP are clearly marked.) + +PSFTP also supports some of its own options. The following sections +describe PSFTP's specific command-line options. + +\S{psftp-option-b} \I{-b-PSFTP}\c{-b}: specify a file containing batch commands + +In normal operation, PSFTP is an interactive program which displays +a command line and accepts commands from the keyboard. + +If you need to do automated tasks with PSFTP, you would probably +prefer to \I{batch scripts in PSFTP}specify a set of commands in +advance and have them executed automatically. The \c{-b} option +allows you to do this. You use it with a file name containing batch +commands. For example, you might create a file called \c{myscript.scr} +containing lines like this: + +\c cd /home/ftp/users/jeff +\c del jam-old.tar.gz +\c ren jam.tar.gz jam-old.tar.gz +\c put jam.tar.gz +\c chmod a+r jam.tar.gz + +and then you could run the script by typing + +\c psftp user@hostname -b myscript.scr + +When you run a batch script in this way, PSFTP will abort the script +if any command fails to complete successfully. To change this +behaviour, you can add the \c{-be} option (\k{psftp-option-be}). + +PSFTP will terminate after it finishes executing the batch script. + +\S{psftp-option-bc} \I{-bc-PSFTP}\c{-bc}: display batch commands as they are run + +The \c{-bc} option alters what PSFTP displays while processing a +batch script specified with \c{-b}. With the \c{-bc} option, PSFTP +will display prompts and commands just as if the commands had been +typed at the keyboard. So instead of seeing this: + +\c C:\>psftp fred@hostname -b batchfile +\c Sent username "fred" +\c Remote working directory is /home/fred +\c Listing directory /home/fred/lib +\c drwxrwsr-x 4 fred fred 1024 Sep 6 10:42 . +\c drwxr-sr-x 25 fred fred 2048 Dec 14 09:36 .. +\c drwxrwsr-x 3 fred fred 1024 Apr 17 2000 jed +\c lrwxrwxrwx 1 fred fred 24 Apr 17 2000 timber +\c drwxrwsr-x 2 fred fred 1024 Mar 13 2000 trn + +you might see this: + +\c C:\>psftp fred@hostname -bc -b batchfile +\c Sent username "fred" +\c Remote working directory is /home/fred +\c psftp> dir lib +\c Listing directory /home/fred/lib +\c drwxrwsr-x 4 fred fred 1024 Sep 6 10:42 . +\c drwxr-sr-x 25 fred fred 2048 Dec 14 09:36 .. +\c drwxrwsr-x 3 fred fred 1024 Apr 17 2000 jed +\c lrwxrwxrwx 1 fred fred 24 Apr 17 2000 timber +\c drwxrwsr-x 2 fred fred 1024 Mar 13 2000 trn +\c psftp> quit + +\S{psftp-option-be} \I{-be-PSFTP}\c{-be}: continue batch processing on errors + +When running a batch file, this additional option causes PSFTP to +continue processing even if a command fails to complete successfully. + +You might want this to happen if you wanted to delete a file and +didn't care if it was already not present, for example. + +\S{psftp-usage-options-batch} \I{-batch-PSFTP}\c{-batch}: avoid +interactive prompts + +If you use the \c{-batch} option, PSFTP will never give an +interactive prompt while establishing the connection. If the +server's host key is invalid, for example (see \k{gs-hostkey}), then +the connection will simply be abandoned instead of asking you what +to do next. + +This may help PSFTP's behaviour when it is used in automated +scripts: using \c{-batch}, if something goes wrong at connection +time, the batch job will fail rather than hang. + +\H{psftp-commands} Running PSFTP + +Once you have started your PSFTP session, you will see a \c{psftp>} +prompt. You can now type commands to perform file-transfer +functions. This section lists all the available commands. + +Any line starting with a \cw{#} will be treated as a \i{comment} +and ignored. + +\S{psftp-quoting} \I{quoting, in PSFTP}General quoting rules for PSFTP commands + +Most PSFTP commands are considered by the PSFTP command interpreter +as a sequence of words, separated by spaces. For example, the +command \c{ren oldfilename newfilename} splits up into three words: +\c{ren} (the command name), \c{oldfilename} (the name of the file to +be renamed), and \c{newfilename} (the new name to give the file). + +Sometimes you will need to specify \I{spaces in filenames}file names +that \e{contain} spaces. In order to do this, you can surround +the file name with double quotes. This works equally well for +local file names and remote file names: + +\c psftp> get "spacey file name.txt" "save it under this name.txt" + +The double quotes themselves will not appear as part of the file +names; they are removed by PSFTP and their only effect is to stop +the spaces inside them from acting as word separators. + +If you need to \e{use} a double quote (on some types of remote +system, such as Unix, you are allowed to use double quotes in file +names), you can do this by doubling it. This works both inside and +outside double quotes. For example, this command + +\c psftp> ren ""this"" "a file with ""quotes"" in it" + +will take a file whose current name is \c{"this"} (with a double +quote character at the beginning and the end) and rename it to a +file whose name is \c{a file with "quotes" in it}. + +(The one exception to the PSFTP quoting rules is the \c{!} command, +which passes its command line straight to Windows without splitting +it up into words at all. See \k{psftp-cmd-pling}.) + +\S{psftp-wildcards} Wildcards in PSFTP + +Several commands in PSFTP support \q{\i{wildcards}} to select multiple +files. + +For \e{local} file specifications (such as the first argument to +\c{put}), wildcard rules for the local operating system are used. For +instance, PSFTP running on Windows might require the use of \c{*.*} +where PSFTP on Unix would need \c{*}. + +For \e{remote} file specifications (such as the first argument to +\c{get}), PSFTP uses a standard wildcard syntax (similar to \i{POSIX} +wildcards): + +\b \c{*} matches any sequence of characters (including a zero-length +sequence). + +\b \c{?} matches exactly one character. + +\b \c{[abc]} matches exactly one character which can be \cw{a}, +\cw{b}, or \cw{c}. + +\lcont{ + +\c{[a-z]} matches any character in the range \cw{a} to \cw{z}. + +\c{[^abc]} matches a single character that is \e{not} \cw{a}, \cw{b}, +or \cw{c}. + +Special cases: \c{[-a]} matches a literal hyphen (\cw{-}) or \cw{a}; +\c{[^-a]} matches all other characters. \c{[a^]} matches a literal +caret (\cw{^}) or \cw{a}. + +} + +\b \c{\\} (backslash) before any of the above characters (or itself) +removes that character's special meaning. + +A leading period (\cw{.}) on a filename is not treated specially, +unlike in some Unix contexts; \c{get *} will fetch all files, whether +or not they start with a leading period. + +\S{psftp-cmd-open} The \c{open} command: start a session + +If you started PSFTP by double-clicking in the GUI, or just by +typing \c{psftp} at the command line, you will need to open a +connection to an SFTP server before you can issue any other +commands (except \c{help} and \c{quit}). + +To create a connection, type \c{open host.name}, or if you need to +specify a user name as well you can type \c{open user@host.name}. +You can optionally specify a port as well: +\c{open user@host.name 22}. + +Once you have issued this command, you will not be able to issue it +again, \e{even} if the command fails (for example, if you mistype +the host name or the connection times out). So if the connection is +not opened successfully, PSFTP will terminate immediately. + +\S{psftp-cmd-quit} The \c{quit} command: end your session + +When you have finished your session, type the command \c{quit} to +close the connection, terminate PSFTP and return to the command line +(or just close the PSFTP console window if you started it from the +GUI). + +You can also use the \c{bye} and \c{exit} commands, which have +exactly the same effect. + +\S{psftp-cmd-close} The \c{close} command: close your connection + +If you just want to close the network connection but keep PSFTP +running, you can use the \c{close} command. You can then use the +\c{open} command to open a new connection. + +\S{psftp-cmd-help} The \c{help} command: get quick online help + +If you type \c{help}, PSFTP will give a short list of the available +commands. + +If you type \c{help} with a command name - for example, \c{help get} +- then PSFTP will give a short piece of help on that particular +command. + +\S{psftp-cmd-cd} The \c{cd} and \c{pwd} commands: changing the +remote \i{working directory} + +PSFTP maintains a notion of your \q{working directory} on the +server. This is the default directory that other commands will +operate on. For example, if you type \c{get filename.dat} then PSFTP +will look for \c{filename.dat} in your remote working directory on +the server. + +To change your remote working directory, use the \c{cd} command. If +you don't provide an argument, \c{cd} will return you to your home +directory on the server (more precisely, the remote directory you were +in at the start of the connection). + +To display your current remote working directory, type \c{pwd}. + +\S{psftp-cmd-lcd} The \c{lcd} and \c{lpwd} commands: changing the +local \i{working directory} + +As well as having a working directory on the remote server, PSFTP +also has a working directory on your local machine (just like any +other Windows process). This is the default local directory that +other commands will operate on. For example, if you type \c{get +filename.dat} then PSFTP will save the resulting file as +\c{filename.dat} in your local working directory. + +To change your local working directory, use the \c{lcd} command. To +display your current local working directory, type \c{lpwd}. + +\S{psftp-cmd-get} The \c{get} command: fetch a file from the server + +To \i{download a file} from the server and store it on your local PC, +you use the \c{get} command. + +In its simplest form, you just use this with a file name: + +\c get myfile.dat + +If you want to store the file locally under a different name, +specify the local file name after the remote one: + +\c get myfile.dat newname.dat + +This will fetch the file on the server called \c{myfile.dat}, but +will save it to your local machine under the name \c{newname.dat}. + +To fetch an entire directory \i{recursive}ly, you can use the \c{-r} +option: + +\c get -r mydir +\c get -r mydir newname + +(If you want to fetch a file whose name starts with a hyphen, you +may have to use the \c{--} special argument, which stops \c{get} +from interpreting anything as a switch after it. For example, +\cq{get -- -silly-name-}.) + +\S{psftp-cmd-put} The \c{put} command: send a file to the server + +To \i{upload a file} to the server from your local PC, you use the +\c{put} command. + +In its simplest form, you just use this with a file name: + +\c put myfile.dat + +If you want to store the file remotely under a different name, +specify the remote file name after the local one: + +\c put myfile.dat newname.dat + +This will send the local file called \c{myfile.dat}, but will store +it on the server under the name \c{newname.dat}. + +To send an entire directory \i{recursive}ly, you can use the \c{-r} +option: + +\c put -r mydir +\c put -r mydir newname + +(If you want to send a file whose name starts with a hyphen, you may +have to use the \c{--} special argument, which stops \c{put} from +interpreting anything as a switch after it. For example, \cq{put -- +-silly-name-}.) + +\S{psftp-cmd-mgetput} The \c{mget} and \c{mput} commands: fetch or +send multiple files + +\c{mget} works almost exactly like \c{get}, except that it allows +you to specify more than one file to fetch at once. You can do this +in two ways: + +\b by giving two or more explicit file names (\cq{mget file1.txt +file2.txt}) + +\b by using a wildcard (\cq{mget *.txt}). + +Every argument to \c{mget} is treated as the name of a file to fetch +(unlike \c{get}, which will interpret at most one argument like +that, and a second argument will be treated as an alternative name +under which to store the retrieved file), or a \i{wildcard} expression +matching more than one file. + +The \c{-r} and \c{--} options from \c{get} are also available with +\c{mget}. + +\c{mput} is similar to \c{put}, with the same differences. + +\S{psftp-cmd-regetput} The \c{reget} and \c{reput} commands: +\i{resuming file transfers} + +If a file transfer fails half way through, and you end up with half +the file stored on your disk, you can resume the file transfer using +the \c{reget} and \c{reput} commands. These work exactly like the +\c{get} and \c{put} commands, but they check for the presence of the +half-written destination file and start transferring from where the +last attempt left off. + +The syntax of \c{reget} and \c{reput} is exactly the same as the +syntax of \c{get} and \c{put}: + +\c reget myfile.dat +\c reget myfile.dat newname.dat +\c reget -r mydir + +These commands are intended mainly for resuming interrupted transfers. +They assume that the remote file or directory structure has not +changed in any way; if there have been changes, you may end up with +corrupted files. In particular, the \c{-r} option will not pick up +changes to files or directories already transferred in full. + +\S{psftp-cmd-dir} The \c{dir} command: \I{listing files}list remote files + +To list the files in your remote working directory, just type +\c{dir}. + +You can also list the contents of a different directory by typing +\c{dir} followed by the directory name: + +\c dir /home/fred +\c dir sources + +And you can list a subset of the contents of a directory by +providing a wildcard: + +\c dir /home/fred/*.txt +\c dir sources/*.c + +The \c{ls} command works exactly the same way as \c{dir}. + +\S{psftp-cmd-chmod} The \c{chmod} command: change permissions on +remote files + +\I{changing permissions on files}PSFTP +allows you to modify the file permissions on files and +directories on the server. You do this using the \c{chmod} command, +which works very much like the Unix \c{chmod} command. + +The basic syntax is \c{chmod modes file}, where \c{modes} represents +a modification to the file permissions, and \c{file} is the filename +to modify. You can specify multiple files or wildcards. For example: + +\c chmod go-rwx,u+w privatefile +\c chmod a+r public* +\c chmod 640 groupfile1 groupfile2 + +The \c{modes} parameter can be a set of octal digits in the Unix +style. (If you don't know what this means, you probably don't want +to be using it!) Alternatively, it can be a list of permission +modifications, separated by commas. Each modification consists of: + +\b The people affected by the modification. This can be \c{u} (the +owning user), \c{g} (members of the owning group), or \c{o} +(everybody else - \q{others}), or some combination of those. It can +also be \c{a} (\q{all}) to affect everybody at once. + +\b A \c{+} or \c{-} sign, indicating whether permissions are to be +added or removed. + +\b The actual permissions being added or removed. These can be +\I{read permission}\c{r} (permission to read the file), +\I{write permission}\c{w} (permission to write to the file), and +\I{execute permission}\c{x} (permission to execute the file, or in +the case of a directory, permission to access files within the +directory). + +So the above examples would do: + +\b The first example: \c{go-rwx} removes read, write and execute +permissions for members of the owning group and everybody else (so +the only permissions left are the ones for the file owner). \c{u+w} +adds write permission for the file owner. + +\b The second example: \c{a+r} adds read permission for everybody to +all files and directories starting with \q{public}. + +In addition to all this, there are a few extra special cases for +\i{Unix} systems. On non-Unix systems these are unlikely to be useful: + +\b You can specify \c{u+s} and \c{u-s} to add or remove the Unix +\i{set-user-ID bit}. This is typically only useful for special purposes; +refer to your Unix documentation if you're not sure about it. + +\b You can specify \c{g+s} and \c{g-s} to add or remove the Unix +\i{set-group-ID bit}. On a file, this works similarly to the set-user-ID +bit (see your Unix documentation again); on a directory it ensures +that files created in the directory are accessible by members of the +group that owns the directory. + +\b You can specify \c{+t} and \c{-t} to add or remove the Unix +\q{\i{sticky bit}}. When applied to a directory, this means that the +owner of a file in that directory can delete the file (whereas +normally only the owner of the \e{directory} would be allowed to). + +\S{psftp-cmd-del} The \c{del} command: delete remote files + +To \I{deleting files}delete a file on the server, type \c{del} and +then the filename or filenames: + +\c del oldfile.dat +\c del file1.txt file2.txt +\c del *.o + +Files will be deleted without further prompting, even if multiple files +are specified. + +\c{del} will only delete files. You cannot use it to delete +directories; use \c{rmdir} for that. + +The \c{rm} command works exactly the same way as \c{del}. + +\S{psftp-cmd-mkdir} The \c{mkdir} command: create remote directories + +To \i{create a directory} on the server, type \c{mkdir} and then the +directory name: + +\c mkdir newstuff + +You can specify multiple directories to create at once: + +\c mkdir dir1 dir2 dir3 + +\S{psftp-cmd-rmdir} The \c{rmdir} command: remove remote directories + +To \i{remove a directory} on the server, type \c{rmdir} and then the +directory name or names: + +\c rmdir oldstuff +\c rmdir *.old ancient + +Directories will be deleted without further prompting, even if +multiple directories are specified. + +Most SFTP servers will probably refuse to remove a directory if the +directory has anything in it, so you will need to delete the +contents first. + +\S{psftp-cmd-mv} The \c{mv} command: move and \i{rename remote files} + +To rename a single file on the server, type \c{mv}, then the current +file name, and then the new file name: + +\c mv oldfile newname + +You can also move the file into a different directory and change the +name: + +\c mv oldfile dir/newname + +To move one or more files into an existing subdirectory, specify the +files (using wildcards if desired), and then the destination +directory: + +\c mv file dir +\c mv file1 dir1/file2 dir2 +\c mv *.c *.h .. + +The \c{rename} and \c{ren} commands work exactly the same way as +\c{mv}. + +\S{psftp-cmd-pling} The \c{!} command: run a \i{local Windows command} + +You can run local Windows commands using the \c{!} command. This is +the only PSFTP command that is not subject to the command quoting +rules given in \k{psftp-quoting}. If any command line begins with +the \c{!} character, then the rest of the line will be passed +straight to Windows without further translation. + +For example, if you want to move an existing copy of a file out of +the way before downloading an updated version, you might type: + +\c psftp> !ren myfile.dat myfile.bak +\c psftp> get myfile.dat + +using the Windows \c{ren} command to rename files on your local PC. + +\H{psftp-pubkey} Using \i{public key authentication} with PSFTP + +Like PuTTY, PSFTP can authenticate using a public key instead of a +password. There are three ways you can do this. + +Firstly, PSFTP can use PuTTY saved sessions in place of hostnames. +So you might do this: + +\b Run PuTTY, and create a PuTTY saved session (see +\k{config-saving}) which specifies your private key file (see +\k{config-ssh-privkey}). You will probably also want to specify a +username to log in as (see \k{config-username}). + +\b In PSFTP, you can now use the name of the session instead of a +hostname: type \c{psftp sessionname}, where \c{sessionname} is +replaced by the name of your saved session. + +Secondly, you can supply the name of a private key file on the command +line, with the \c{-i} option. See \k{using-cmdline-identity} for more +information. + +Thirdly, PSFTP will attempt to authenticate using Pageant if Pageant +is running (see \k{pageant}). So you would do this: + +\b Ensure Pageant is running, and has your private key stored in it. + +\b Specify a user and host name to PSFTP as normal. PSFTP will +automatically detect Pageant and try to use the keys within it. + +For more general information on public-key authentication, see +\k{pubkey}. diff --git a/putty/DOC/PUBKEY.BUT b/putty/DOC/PUBKEY.BUT new file mode 100644 index 0000000..580c6ee --- /dev/null +++ b/putty/DOC/PUBKEY.BUT @@ -0,0 +1,442 @@ +\define{versionidpubkey} \versionid $Id: pubkey.but 8607 2009-07-12 12:02:58Z simon $ + +\C{pubkey} Using public keys for SSH authentication + +\H{pubkey-intro} \ii{Public key authentication} - an introduction + +Public key authentication is an alternative means of identifying +yourself to a login server, instead of typing a password. It is more +secure and more flexible, but more difficult to set up. + +In conventional password authentication, you prove you are who you +claim to be by proving that you know the correct password. The only +way to prove you know the password is to tell the server what you +think the password is. This means that if the server has been +hacked, or \i\e{spoofed} (see \k{gs-hostkey}), an attacker can learn +your password. + +Public key authentication solves this problem. You generate a \i\e{key +pair}, consisting of a \i{public key} (which everybody is allowed to +know) and a \i{private key} (which you keep secret and do not give to +anybody). The private key is able to generate \i\e{signatures}. +A signature created using your private key cannot be forged by +anybody who does not have that key; but anybody who has your public +key can verify that a particular signature is genuine. + +So you generate a key pair on your own computer, and you copy the +public key to the server. Then, when the server asks you to prove +who you are, PuTTY can generate a signature using your private key. +The server can verify that signature (since it has your public key) +and allow you to log in. Now if the server is hacked or spoofed, the +attacker does not gain your private key or password; they only gain +one signature. And signatures cannot be re-used, so they have gained +nothing. + +There is a problem with this: if your private key is stored +unprotected on your own computer, then anybody who gains access to +\e{that} will be able to generate signatures as if they were you. So +they will be able to log in to your server under your account. For +this reason, your private key is usually \i\e{encrypted} when it is +stored on your local machine, using a \i{passphrase} of your choice. In +order to generate a signature, PuTTY must decrypt the key, so you +have to type your passphrase. + +This can make public-key authentication less convenient than +password authentication: every time you log in to the server, +instead of typing a short password, you have to type a longer +passphrase. One solution to this is to use an \i\e{authentication +agent}, a separate program which holds decrypted private keys and +generates signatures on request. PuTTY's authentication agent is +called \i{Pageant}. When you begin a Windows session, you start Pageant +and load your private key into it (typing your passphrase once). For +the rest of your session, you can start PuTTY any number of times +and Pageant will automatically generate signatures without you +having to do anything. When you close your Windows session, Pageant +shuts down, without ever having stored your decrypted private key on +disk. Many people feel this is a good compromise between security +and convenience. See \k{pageant} for further details. + +There is more than one \i{public-key algorithm} available. The most +common is \i{RSA}, but others exist, notably \i{DSA} (otherwise known as +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 + +\cfg{winhelp-topic}{puttygen.general} + +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 and DSA keys. + +When you run PuTTYgen you will see a window where you have two +choices: \q{Generate}, to generate a new public/private key pair, or +\q{Load} to load in an existing private key. + +\S{puttygen-generating} Generating a new key + +This is a general outline of the procedure for generating a new key +pair. The following sections describe the process in more detail. + +\b First, you need to select which type of key you want to generate, +and also select the strength of the key. This is described in more +detail in \k{puttygen-keytype} and +\k{puttygen-strength}. + +\b Then press the \q{Generate} button, to actually generate the key. +\K{puttygen-generate} describes this step. + +\b Once you have generated the key, select a comment field +(\k{puttygen-comment}) and a passphrase (\k{puttygen-passphrase}). + +\b Now you're ready to save the private key to disk; press the +\q{Save private key} button. (See \k{puttygen-savepriv}). + +Your key pair is now ready for use. You may also want to copy the +public key to your server, either by copying it out of the \q{Public +key for pasting into authorized_keys file} box (see +\k{puttygen-pastekey}), or by using the \q{Save public key} button +(\k{puttygen-savepub}). However, you don't need to do this +immediately; if you want, you can load the private key back into +PuTTYgen later (see \k{puttygen-load}) and the public key will be +available for copying and pasting again. + +\K{pubkey-gettingready} describes the typical process of configuring +PuTTY to attempt public-key authentication, and configuring your SSH +server to accept it. + +\S{puttygen-keytype} Selecting the type of key + +\cfg{winhelp-topic}{puttygen.keytype} + +Before generating a key pair using PuTTYgen, you need to select +which type of key you need. PuTTYgen currently supports three types +of key: + +\b An \i{RSA} key for use with the SSH-1 protocol. + +\b An RSA key for use with the SSH-2 protocol. + +\b A \i{DSA} key for use with the SSH-2 protocol. + +The SSH-1 protocol only supports RSA keys; if you will be connecting +using the SSH-1 protocol, you must select the first key type or your +key will be completely useless. + +The SSH-2 protocol supports more than one key type. The two types +supported by PuTTY are RSA and DSA. + +The PuTTY developers \e{strongly} recommend you use RSA. +\I{security risk}\i{DSA} has an intrinsic weakness which makes it very +easy to create a signature which contains enough information to give +away the \e{private} key! +This would allow an attacker to pretend to be you for any number of +future sessions. PuTTY's implementation has taken very careful +precautions to avoid this weakness, but we cannot be 100% certain we +have managed it, and if you have the choice we strongly recommend +using RSA keys instead. + +If you really need to connect to an SSH server which only supports +DSA, then you probably have no choice but to use DSA. If you do use +DSA, we recommend you do not use the same key to authenticate with +more than one server. + +\S{puttygen-strength} Selecting the size (strength) of the key + +\cfg{winhelp-topic}{puttygen.bits} + +The \q{Number of bits} input box allows you to choose the strength +of the key PuTTYgen will generate. + +Currently 1024 bits should be sufficient for most purposes. + +Note that an RSA key is generated by finding two primes of half the +length requested, and then multiplying them together. For example, +if you ask PuTTYgen for a 1024-bit RSA key, it will create two +512-bit primes and multiply them. The result of this multiplication +might be 1024 bits long, or it might be only 1023; so you may not +get the exact length of key you asked for. This is perfectly normal, +and you do not need to worry. The lengths should only ever differ by +one, and there is no perceptible drop in security as a result. + +DSA keys are not created by multiplying primes together, so they +should always be exactly the length you asked for. + +\S{puttygen-generate} The \q{Generate} button + +\cfg{winhelp-topic}{puttygen.generate} + +Once you have chosen the type of key you want, and the strength of +the key, press the \q{Generate} button and PuTTYgen will begin the +process of actually generating the key. + +First, a progress bar will appear and PuTTYgen will ask you to move +the mouse around to generate randomness. Wave the mouse in circles +over the blank area in the PuTTYgen window, and the progress bar +will gradually fill up as PuTTYgen collects enough randomness. You +don't need to wave the mouse in particularly imaginative patterns +(although it can't hurt); PuTTYgen will collect enough randomness +just from the fine detail of \e{exactly} how far the mouse has moved +each time Windows samples its position. + +When the progress bar reaches the end, PuTTYgen will begin creating +the key. The progress bar will reset to the start, and gradually +move up again to track the progress of the key generation. It will +not move evenly, and may occasionally slow down to a stop; this is +unfortunately unavoidable, because key generation is a random +process and it is impossible to reliably predict how long it will +take. + +When the key generation is complete, a new set of controls will +appear in the window to indicate this. + +\S{puttygen-fingerprint} The \q{\ii{Key fingerprint}} box + +\cfg{winhelp-topic}{puttygen.fingerprint} + +The \q{Key fingerprint} box shows you a fingerprint value for the +generated key. This is derived cryptographically from the \e{public} +key value, so it doesn't need to be kept secret. + +The fingerprint value is intended to be cryptographically secure, in +the sense that it is computationally infeasible for someone to +invent a second key with the same fingerprint, or to find a key with +a particular fingerprint. So some utilities, such as the Pageant key +list box (see \k{pageant-mainwin-keylist}) and the Unix \c{ssh-add} +utility, will list key fingerprints rather than the whole public key. + +\S{puttygen-comment} Setting a comment for your key + +\cfg{winhelp-topic}{puttygen.comment} + +If you have more than one key and use them for different purposes, +you don't need to memorise the key fingerprints in order to tell +them apart. PuTTYgen allows you to enter a \e{comment} for your key, +which will be displayed whenever PuTTY or Pageant asks you for the +passphrase. + +The default comment format, if you don't specify one, contains the +key type and the date of generation, such as \c{rsa-key-20011212}. +Another commonly used approach is to use your name and the name of +the computer the key will be used on, such as \c{simon@simons-pc}. + +To alter the key comment, just type your comment text into the +\q{Key comment} box before saving the private key. If you want to +change the comment later, you can load the private key back into +PuTTYgen, change the comment, and save it again. + +\S{puttygen-passphrase} Setting a \i{passphrase} for your key + +\cfg{winhelp-topic}{puttygen.passphrase} + +The \q{Key passphrase} and \q{Confirm passphrase} boxes allow you to +choose a passphrase for your key. The passphrase will be used to +\i{encrypt} the key on disk, so you will not be able to use the key +without first entering the passphrase. + +When you save the key, PuTTYgen will check that the \q{Key passphrase} +and \q{Confirm passphrase} boxes both contain exactly the same +passphrase, and will refuse to save the key otherwise. + +If you leave the passphrase fields blank, the key will be saved +unencrypted. You should \e{not} do this without good reason; if you +do, your private key file on disk will be all an attacker needs to +gain access to any machine configured to accept that key. If you +want to be able to \I{passwordless login}log in without having to +type a passphrase every time, you should consider using Pageant +(\k{pageant}) so that your decrypted key is only held in memory +rather than on disk. + +Under special circumstances you may genuinely \e{need} to use a key +with no passphrase; for example, if you need to run an automated +batch script that needs to make an SSH connection, you can't be +there to type the passphrase. In this case we recommend you generate +a special key for each specific batch script (or whatever) that +needs one, and on the server side you should arrange that each key +is \e{restricted} so that it can only be used for that specific +purpose. The documentation for your SSH server should explain how to +do this (it will probably vary between servers). + +Choosing a good passphrase is difficult. Just as you shouldn't use a +dictionary word as a password because it's easy for an attacker to +run through a whole dictionary, you should not use a song lyric, +quotation or other well-known sentence as a passphrase. \i{DiceWare} +(\W{http://www.diceware.com/}\cw{www.diceware.com}) recommends using +at least five words each generated randomly by rolling five dice, +which gives over 2^64 possible passphrases and is probably not a bad +scheme. If you want your passphrase to make grammatical sense, this +cuts down the possibilities a lot and you should use a longer one as +a result. + +\e{Do not forget your passphrase}. There is no way to recover it. + +\S{puttygen-savepriv} Saving your private key to a disk file + +\cfg{winhelp-topic}{puttygen.savepriv} + +Once you have generated a key, set a comment field and set a +passphrase, you are ready to save your private key to disk. + +Press the \q{Save private key} button. PuTTYgen will put up a dialog +box asking you where to save the file. Select a directory, type in a +file name, and press \q{Save}. + +This file is in PuTTY's native format (\c{*.\i{PPK}}); it is the one you +will need to tell PuTTY to use for authentication (see +\k{config-ssh-privkey}) or tell Pageant to load (see +\k{pageant-mainwin-addkey}). + +\S{puttygen-savepub} Saving your public key to a disk file + +\cfg{winhelp-topic}{puttygen.savepub} + +RFC 4716 specifies a \I{SSH-2 public key format}standard format for +storing SSH-2 public keys on disk. Some SSH servers (such as +\i\cw{ssh.com}'s) require a public key in this format in order to accept +authentication with the corresponding private key. (Others, such as +OpenSSH, use a different format; see \k{puttygen-pastekey}.) + +To save your public key in the SSH-2 standard format, press the +\q{Save public key} button in PuTTYgen. PuTTYgen will put up a +dialog box asking you where to save the file. Select a directory, +type in a file name, and press \q{Save}. + +You will then probably want to copy the public key file to your SSH +server machine. See \k{pubkey-gettingready} for general instructions +on configuring public-key authentication once you have generated a +key. + +If you use this option with an SSH-1 key, the file PuTTYgen saves +will contain exactly the same text that appears in the \q{Public key +for pasting} box. This is the only existing standard for SSH-1 +public keys. + +\S{puttygen-pastekey} \q{Public key for pasting into \i{authorized_keys +file}} + +\cfg{winhelp-topic}{puttygen.pastekey} + +All SSH-1 servers require your public key to be given to it in a +one-line format before it will accept authentication with your +private key. The \i{OpenSSH} server also requires this for SSH-2. + +The \q{Public key for pasting into authorized_keys file} gives the +public-key data in the correct one-line format. Typically you will +want to select the entire contents of the box using the mouse, press +Ctrl+C to copy it to the clipboard, and then paste the data into a +PuTTY session which is already connected to the server. + +See \k{pubkey-gettingready} for general instructions on configuring +public-key authentication once you have generated a key. + +\S{puttygen-load} Reloading a private key + +\cfg{winhelp-topic}{puttygen.load} + +PuTTYgen allows you to load an existing private key file into +memory. If you do this, you can then change the passphrase and +comment before saving it again; you can also make extra copies of +the public key. + +To load an existing key, press the \q{Load} button. PuTTYgen will +put up a dialog box where you can browse around the file system and +find your key file. Once you select the file, PuTTYgen will ask you +for a passphrase (if necessary) and will then display the key +details in the same way as if it had just generated the key. + +If you use the Load command to load a foreign key format, it will +work, but you will see a message box warning you that the key you +have loaded is not a PuTTY native key. See \k{puttygen-conversions} +for information about importing foreign key formats. + +\S{puttygen-conversions} Dealing with private keys in other formats + +\cfg{winhelp-topic}{puttygen.conversions} + +Most SSH-1 clients use a standard format for storing private keys on +disk. PuTTY uses this format as well; so if you have generated an +SSH-1 private key using OpenSSH or \cw{ssh.com}'s client, you can use +it with PuTTY, and vice versa. + +However, SSH-2 private keys have no standard format. \I{OpenSSH private +key format}OpenSSH and \I{ssh.com private key format}\cw{ssh.com} have +different formats, and PuTTY's is different again. +So a key generated with one client cannot immediately be used with +another. + +Using the \I{importing keys}\q{Import} command from the \q{Conversions} +menu, PuTTYgen can load SSH-2 private keys in OpenSSH's format and +\cw{ssh.com}'s format. Once you have loaded one of these key types, you +can then save it back out as a PuTTY-format key (\c{*.\i{PPK}}) so that +you can use it with the PuTTY suite. The passphrase will be unchanged by this +process (unless you deliberately change it). You may want to change +the key comment before you save the key, since OpenSSH's SSH-2 key +format contains no space for a comment and \cw{ssh.com}'s default +comment format is long and verbose. + +PuTTYgen can also \i{export private keys} in OpenSSH format and in +\cw{ssh.com} format. To do so, select one of the \q{Export} options +from the \q{Conversions} menu. Exporting a key works exactly like +saving it (see \k{puttygen-savepriv}) - you need to have typed your +passphrase in beforehand, and you will be warned if you are about to +save a key without a passphrase. + +Note that since only SSH-2 keys come in different formats, the export +options are not available if you have generated an SSH-1 key. + +\H{pubkey-gettingready} Getting ready for public key authentication + +Connect to your SSH server using PuTTY with the SSH protocol. When the +connection succeeds you will be prompted for your user name and +password to login. Once logged in, you must configure the server to +accept your public key for authentication: + +\b If your server is using the SSH-1 protocol, you should change +into the \i\c{.ssh} directory and open the file \i\c{authorized_keys} +with your favourite editor. (You may have to create this file if +this is the first key you have put in it). Then switch to the +PuTTYgen window, select all of the text in the \q{Public key for +pasting into authorized_keys file} box (see \k{puttygen-pastekey}), +and copy it to the clipboard (\c{Ctrl+C}). Then, switch back to the +PuTTY window and insert the data into the open file, making sure it +ends up all on one line. Save the file. + +\b If your server is \i{OpenSSH} and is using the SSH-2 protocol, you +should follow the same instructions, except that in earlier versions +of OpenSSH 2 the file might be called \c{authorized_keys2}. (In +modern versions the same \c{authorized_keys} file is used for both +SSH-1 and SSH-2 keys.) + +\b If your server is \i\cw{ssh.com}'s product and is using SSH-2, you +need to save a \e{public} key file from PuTTYgen (see +\k{puttygen-savepub}), and copy that into the \i\c{.ssh2} directory on +the server. Then you should go into that \c{.ssh2} directory, and edit +(or create) a file called \c{authorization}. In this file you should +put a line like \c{Key mykey.pub}, with \c{mykey.pub} replaced by the +name of your key file. + +\b For other SSH server software, you should refer to the manual for +that server. + +You may also need to ensure that your home directory, your \c{.ssh} +directory, and any other files involved (such as +\c{authorized_keys}, \c{authorized_keys2} or \c{authorization}) are +not group-writable or world-writable. You can typically do this by +using a command such as + +\c chmod go-w $HOME $HOME/.ssh $HOME/.ssh/authorized_keys + +Your server should now be configured to accept authentication using +your private key. Now you need to configure PuTTY to \e{attempt} +authentication using your private key. You can do this in any of +three ways: + +\b Select the private key in PuTTY's configuration. See +\k{config-ssh-privkey} for details. + +\b Specify the key file on the command line with the \c{-i} option. +See \k{using-cmdline-identity} for details. + +\b Load the private key into Pageant (see \k{pageant}). In this case +PuTTY will automatically try to use it for authentication if it can. diff --git a/putty/DOC/SITE.BUT b/putty/DOC/SITE.BUT new file mode 100644 index 0000000..dc8c32e --- /dev/null +++ b/putty/DOC/SITE.BUT @@ -0,0 +1,4 @@ +\# Additional configuration for the version of the PuTTY docs +\# actually published as HTML on the website. + +\cfg{xhtml-head-end}{} diff --git a/putty/DOC/SSHNAMES.BUT b/putty/DOC/SSHNAMES.BUT new file mode 100644 index 0000000..f8bb4f7 --- /dev/null +++ b/putty/DOC/SSHNAMES.BUT @@ -0,0 +1,64 @@ +\define{versionidsshnames} \versionid $Id$ + +\A{sshnames} SSH-2 names specified for PuTTY + +There are various parts of the SSH-2 protocol where things are specified +using a textual name. Names ending in \cw{@putty.projects.tartarus.org} +are reserved for allocation by the PuTTY team. Allocated names are +documented here. + +\H{sshnames-channel} Connection protocol channel request names + +These names can be sent in a \cw{SSH_MSG_CHANNEL_REQUEST} message. + +\dt \cw{simple@putty.projects.tartarus.org} + +\dd This is sent by a client to announce that it will not have more than +one channel open at a time in the current connection (that one being +the one the request is sent on). The intention is that the server, +knowing this, can set the window on that one channel to something very +large, and leave flow control to TCP. There is no message-specific data. + +\dt \cw{winadj@putty.projects.tartarus.org} + +\dd PuTTY sends this request along with some +\cw{SSH_MSG_CHANNEL_WINDOW_ADJUST} messages as part of its window-size +tuning. It can be sent on any type of channel. There is no +message-specific data. Servers MUST treat it as an unrecognised request +and respond with \cw{SSH_MSG_CHANNEL_FAILURE}. + +\H{sshnames-kex} Key exchange method names + +\dt \cw{rsa-sha1-draft-00@putty.projects.tartarus.org} + +\dt \cw{rsa-sha256-draft-00@putty.projects.tartarus.org} + +\dt \cw{rsa1024-sha1-draft-01@putty.projects.tartarus.org} + +\dt \cw{rsa1024-sha256-draft-01@putty.projects.tartarus.org} + +\dt \cw{rsa2048-sha256-draft-01@putty.projects.tartarus.org} + +\dt \cw{rsa1024-sha1-draft-02@putty.projects.tartarus.org} + +\dt \cw{rsa2048-sha512-draft-02@putty.projects.tartarus.org} + +\dt \cw{rsa1024-sha1-draft-03@putty.projects.tartarus.org} + +\dt \cw{rsa2048-sha256-draft-03@putty.projects.tartarus.org} + +\dt \cw{rsa1024-sha1-draft-04@putty.projects.tartarus.org} + +\dt \cw{rsa2048-sha256-draft-04@putty.projects.tartarus.org} + +\dd These appeared in various drafts of what eventually became RFC\_4432. +They have been superseded by \cw{rsa1024-sha1} and \cw{rsa2048-sha256}. + +\H{sshnames-encrypt} Encryption algorithm names + +\dt \cw{arcfour128-draft-00@putty.projects.tartarus.org} + +\dt \cw{arcfour256-draft-00@putty.projects.tartarus.org} + +\dd These were used in drafts of what eventually became RFC\_4345. +They have been superseded by \cw{arcfour128} and \cw{arcfour256}. diff --git a/putty/DOC/UDP.BUT b/putty/DOC/UDP.BUT new file mode 100644 index 0000000..6635654 --- /dev/null +++ b/putty/DOC/UDP.BUT @@ -0,0 +1,389 @@ +\# This file is so named for tradition's sake: it contains what we +\# always used to refer to, before they were written down, as +\# PuTTY's `unwritten design principles'. It has nothing to do with +\# the User Datagram Protocol. + +\define{versionidudp} \versionid $Id: udp.but 5525 2005-03-19 02:29:57Z jacob $ + +\A{udp} PuTTY hacking guide + +This appendix lists a selection of the design principles applying to +the PuTTY source code. If you are planning to send code +contributions, you should read this first. + +\H{udp-portability} Cross-OS portability + +Despite Windows being its main area of fame, PuTTY is no longer a +Windows-only application suite. It has a working Unix port; a Mac +port is in progress; more ports may or may not happen at a later +date. + +Therefore, embedding Windows-specific code in core modules such as +\cw{ssh.c} is not acceptable. We went to great lengths to \e{remove} +all the Windows-specific stuff from our core modules, and to shift +it out into Windows-specific modules. Adding large amounts of +Windows-specific stuff in parts of the code that should be portable +is almost guaranteed to make us reject a contribution. + +The PuTTY source base is divided into platform-specific modules and +platform-generic modules. The Unix-specific modules are all in the +\c{unix} subdirectory; the Mac-specific modules are in the \c{mac} +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. + +This also means you should stick to what you are guaranteed by +ANSI/ISO C (that is, the original C89/C90 standard, not C99). Try +not to make assumptions about the precise size of basic types such +as \c{int} and \c{long int}; don't use pointer casts to do +endianness-dependent operations, and so on. + +(There are one or two aspects of ANSI C portability which we +\e{don't} care about. In particular, we expect PuTTY to be compiled +on 32-bit architectures \e{or bigger}; so it's safe to assume that +\c{int} is at least 32 bits wide, not just the 16 you are guaranteed +by ANSI C. Similarly, we assume that the execution character +encoding is a superset of the printable characters of ASCII, though +we don't assume the numeric values of control characters, +particularly \cw{'\\n'} and \cw{'\\r'}.) + +\H{udp-multi-backend} Multiple backends treated equally + +PuTTY is not an SSH client with some other stuff tacked on the side. +PuTTY is a generic, multiple-backend, remote VT-terminal client +which happens to support one backend which is larger, more popular +and more useful than the rest. Any extra feature which can possibly +be general across all backends should be so: localising features +unnecessarily into the SSH back end is a design error. (For example, +we had several code submissions for proxy support which worked by +hacking \cw{ssh.c}. Clearly this is completely wrong: the +\cw{network.h} abstraction is the place to put it, so that it will +apply to all back ends equally, and indeed we eventually put it +there after another contributor sent a better patch.) + +The rest of PuTTY should try to avoid knowing anything about +specific back ends if at all possible. To support a feature which is +only available in one network protocol, for example, the back end +interface should be extended in a general manner such that \e{any} +back end which is able to provide that feature can do so. If it so +happens that only one back end actually does, that's just the way it +is, but it shouldn't be relied upon by any code. + +\H{udp-globals} Multiple sessions per process on some platforms + +Some ports of PuTTY - notably the in-progress Mac port - are +constrained by the operating system to run as a single process +potentially managing multiple sessions. + +Therefore, the platform-independent parts of PuTTY never use global +variables to store per-session data. The global variables that do +exist are tolerated because they are not specific to a particular +login session: \c{flags} defines properties that are expected to +apply equally to \e{all} the sessions run by a single PuTTY process, +the random number state in \cw{sshrand.c} and the timer list in +\cw{timing.c} serve all sessions equally, and so on. But most data +is specific to a particular network session, and is therefore stored +in dynamically allocated data structures, and pointers to these +structures are passed around between functions. + +Platform-specific code can reverse this decision if it likes. The +Windows code, for historical reasons, stores most of its data as +global variables. That's OK, because \e{on Windows} we know there is +only one session per PuTTY process, so it's safe to do that. But +changes to the platform-independent code should avoid introducing +global variables, unless they are genuinely cross-session. + +\H{udp-pure-c} C, not C++ + +PuTTY is written entirely in C, not in C++. + +We have made \e{some} effort to make it easy to compile our code +using a C++ compiler: notably, our \c{snew}, \c{snewn} and +\c{sresize} macros explicitly cast the return values of \cw{malloc} +and \cw{realloc} to the target type. (This has type checking +advantages even in C: it means you never accidentally allocate the +wrong size piece of memory for the pointer type you're assigning it +to. C++ friendliness is really a side benefit.) + +We want PuTTY to continue being pure C, at least in the +platform-independent parts and the currently existing ports. Patches +which switch the Makefiles to compile it as C++ and start using +classes will not be accepted. Also, in particular, we disapprove of +\cw{//} comments, at least for the moment. (Perhaps once C99 becomes +genuinely widespread we might be more lenient.) + +The one exception: a port to a new platform may use languages other +than C if they are necessary to code on that platform. If your +favourite PDA has a GUI with a C++ API, then there's no way you can +do a port of PuTTY without using C++, so go ahead and use it. But +keep the C++ restricted to that platform's subdirectory; if your +changes force the Unix or Windows ports to be compiled as C++, they +will be unacceptable to us. + +\H{udp-security} Security-conscious coding + +PuTTY is a network application and a security application. Assume +your code will end up being fed deliberately malicious data by +attackers, and try to code in a way that makes it unlikely to be a +security risk. + +In particular, try not to use fixed-size buffers for variable-size +data such as strings received from the network (or even the user). +We provide functions such as \cw{dupcat} and \cw{dupprintf}, which +dynamically allocate buffers of the right size for the string they +construct. Use these wherever possible. + +\H{udp-multi-compiler} Independence of specific compiler + +Windows PuTTY can currently be compiled with any of four Windows +compilers: MS Visual C, Borland's freely downloadable C compiler, +the Cygwin / \cw{mingw32} GNU tools, and \cw{lcc-win32}. + +This is a really useful property of PuTTY, because it means people +who want to contribute to the coding don't depend on having a +specific compiler; so they don't have to fork out money for MSVC if +they don't already have it, but on the other hand if they \e{do} +have it they also don't have to spend effort installing \cw{gcc} +alongside it. They can use whichever compiler they happen to have +available, or install whichever is cheapest and easiest if they +don't have one. + +Therefore, we don't want PuTTY to start depending on which compiler +you're using. Using GNU extensions to the C language, for example, +would ruin this useful property (not that anyone's ever tried it!); +and more realistically, depending on an MS-specific library function +supplied by the MSVC C library (\cw{_snprintf}, for example) is a +mistake, because that function won't be available under the other +compilers. Any function supplied in an official Windows DLL as part +of the Windows API is fine, and anything defined in the C library +standard is also fine, because those should be available +irrespective of compilation environment. But things in between, +available as non-standard library and language extensions in only +one compiler, are disallowed. + +(\cw{_snprintf} in particular should be unnecessary, since we +provide \cw{dupprintf}; see \k{udp-security}.) + +Compiler independence should apply on all platforms, of course, not +just on Windows. + +\H{udp-small} Small code size + +PuTTY is tiny, compared to many other Windows applications. And it's +easy to install: it depends on no DLLs, no other applications, no +service packs or system upgrades. It's just one executable. You +install that executable wherever you want to, and run it. + +We want to keep both these properties - the small size, and the ease +of installation - if at all possible. So code contributions that +depend critically on external DLLs, or that add a huge amount to the +code size for a feature which is only useful to a small minority of +users, are likely to be thrown out immediately. + +We do vaguely intend to introduce a DLL plugin interface for PuTTY, +whereby seriously large extra features can be implemented in plugin +modules. The important thing, though, is that those DLLs will be +\e{optional}; if PuTTY can't find them on startup, it should run +perfectly happily and just won't provide those particular features. +A full installation of PuTTY might one day contain ten or twenty +little DLL plugins, which would cut down a little on the ease of +installation - but if you really needed ease of installation you +\e{could} still just install the one PuTTY binary, or just the DLLs +you really needed, and it would still work fine. + +Depending on \e{external} DLLs is something we'd like to avoid if at +all possible (though for some purposes, such as complex SSH +authentication mechanisms, it may be unavoidable). If it can't be +avoided, the important thing is to follow the same principle of +graceful degradation: if a DLL can't be found, then PuTTY should run +happily and just not supply the feature that depended on it. + +\H{udp-single-threaded} Single-threaded code + +PuTTY and its supporting tools, or at least the vast majority of +them, run in only one OS thread. + +This means that if you're devising some piece of internal mechanism, +there's no need to use locks to make sure it doesn't get called by +two threads at once. The only way code can be called re-entrantly is +by recursion. + +That said, most of Windows PuTTY's network handling is triggered off +Windows messages requested by \cw{WSAAsyncSelect()}, so if you call +\cw{MessageBox()} deep within some network event handling code you +should be aware that you might be re-entered if a network event +comes in and is passed on to our window procedure by the +\cw{MessageBox()} message loop. + +Also, the front ends (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. + +One important consequence of this: PuTTY has only one thread in +which to do everything. That \q{everything} may include managing +more than one login session (\k{udp-globals}), managing multiple +data channels within an SSH session, responding to GUI events even +when nothing is happening on the network, and responding to network +requests from the server (such as repeat key exchange) even when the +program is dealing with complex user interaction such as the +re-configuration dialog box. This means that \e{almost none} of the +PuTTY code can safely block. + +\H{udp-keystrokes} Keystrokes sent to the server wherever possible + +In almost all cases, PuTTY sends keystrokes to the server. Even +weird keystrokes that you think should be hot keys controlling +PuTTY. Even Alt-F4 or Alt-Space, for example. If a keystroke has a +well-defined escape sequence that it could usefully be sending to +the server, then it should do so, or at the very least it should be +configurably able to do so. + +To unconditionally turn a key combination into a hot key to control +PuTTY is almost always a design error. If a hot key is really truly +required, then try to find a key combination for it which \e{isn't} +already used in existing PuTTYs (either it sends nothing to the +server, or it sends the same thing as some other combination). Even +then, be prepared for the possibility that one day that key +combination might end up being needed to send something to the +server - so make sure that there's an alternative way to invoke +whatever PuTTY feature it controls. + +\H{udp-640x480} 640\u00D7{x}480 friendliness in configuration panels + +There's a reason we have lots of tiny configuration panels instead +of a few huge ones, and that reason is that not everyone has a +1600\u00D7{x}1200 desktop. 640\u00D7{x}480 is still a viable +resolution for running Windows (and indeed it's still the default if +you start up in safe mode), so it's still a resolution we care +about. + +Accordingly, the PuTTY configuration box, and the PuTTYgen control +window, are deliberately kept just small enough to fit comfortably +on a 640\u00D7{x}480 display. If you're adding controls to either of +these boxes and you find yourself wanting to increase the size of +the whole box, \e{don't}. Split it into more panels instead. + +\H{udp-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 \cw{ssh.c} + +Large parts of the code in \cw{ssh.c} are structured using a set of +macros that implement (something close to) Donald Knuth's +\q{coroutines} concept in C. + +Essentially, the purpose of these macros are to arrange that a +function can call \cw{crReturn()} to return to its caller, and the +next time it is called control will resume from just after that +\cw{crReturn} statement. + +This means that any local (automatic) variables declared in such a +function will be corrupted every time you call \cw{crReturn}. If you +need a variable to persist for longer than that, you \e{must} make +it a field in one of the persistent state structures: either the +local state structures \c{s} or \c{st} in each function, or the +backend-wide structure \c{ssh}. + +See +\W{http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html}\c{http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html} +for a more in-depth discussion of what these macros are for and how +they work. + +\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} +of the principles listed above. There may be the occasional +SSH-specific piece of code in what should be a backend-independent +module, or the occasional dependence on a non-standard X library +function under Unix. + +This should not be taken as a licence to go ahead and violate the +rules. Where we violate them ourselves, we're not happy about it, +and we would welcome patches that fix any existing problems. Please +try to help us make our code better, not worse! diff --git a/putty/DOC/USING.BUT b/putty/DOC/USING.BUT new file mode 100644 index 0000000..fa55ddd --- /dev/null +++ b/putty/DOC/USING.BUT @@ -0,0 +1,944 @@ +\define{versionidusing} \versionid $Id: using.but 9149 2011-04-08 15:52:02Z jacob $ + +\C{using} Using PuTTY + +This chapter provides a general introduction to some more advanced +features of PuTTY. For extreme detail and reference purposes, +\k{config} is likely to contain more information. + +\H{using-session} During your session + +A lot of PuTTY's complexity and features are in the configuration +panel. Once you have worked your way through that and started +a session, things should be reasonably simple after that. +Nevertheless, there are a few more useful features available. + +\S{using-selection} Copying and pasting text + +\I{copy and paste}Often in a PuTTY session you will find text on +your terminal screen which you want to type in again. Like most +other terminal emulators, PuTTY allows you to copy and paste the +text rather than having to type it again. Also, copy and paste uses +the \I{Windows clipboard}Windows \i{clipboard}, so that you can +paste (for example) URLs into a web browser, or paste from a word +processor or spreadsheet into your terminal session. + +PuTTY's copy and paste works entirely with the \i{mouse}. In order +to copy text to the clipboard, you just click the \i{left mouse +button} in the \i{terminal window}, and drag to \I{selecting text}select +text. When you let go of the button, the text is \e{automatically} +copied to the clipboard. You do not need to press Ctrl-C or +Ctrl-Ins; in fact, if you do press Ctrl-C, PuTTY will send a Ctrl-C +character down your session to the server where it will probably +cause a process to be interrupted. + +Pasting is done using the right button (or the middle mouse button, +if you have a \i{three-button mouse} and have set it up; see +\k{config-mouse}). (Pressing \i{Shift-Ins}, or selecting \q{Paste} +from the \I{right mouse button, with Ctrl}Ctrl+right-click +\i{context menu}, have the same effect.) When +you click the \i{right mouse button}, PuTTY will read whatever is in +the Windows clipboard and paste it into your session, \e{exactly} as +if it had been typed at the keyboard. (Therefore, be careful of +pasting formatted text into an editor that does automatic indenting; +you may find that the spaces pasted from the clipboard plus the +spaces added by the editor add up to too many spaces and ruin the +formatting. There is nothing PuTTY can do about this.) + +If you \i{double-click} the left mouse button, PuTTY will +\I{selecting words}select a whole word. If you double-click, hold +down the second click, and drag the mouse, PuTTY will select a +sequence of whole words. (You can adjust precisely what PuTTY +considers to be part of a word; see \k{config-charclasses}.) +If you \e{triple}-click, or \i{triple-click} and drag, then +PuTTY will \I{selecting lines}select a whole line or sequence of lines. + +If you want to select a \I{rectangular selection}rectangular region +instead of selecting to the end of each line, you can do this by +holding down Alt when you make your selection. You can also +configure rectangular selection to be the default, and then holding +down Alt gives the normal behaviour instead: see +\k{config-rectselect} for details. + +(In some Unix environments, Alt+drag is intercepted by the window +manager. Shift+Alt+drag should work for rectangular selection as +well, so you could try that instead.) + +If you have a \i{middle mouse button}, then you can use it to +\I{adjusting a selection}adjust an existing selection if you +selected something slightly wrong. (If you have configured the +middle mouse button to paste, then the right mouse button does this +instead.) Click the button on the screen, and you can pick up the +nearest end of the selection and drag it to somewhere else. + +It's possible for the server to ask to \I{mouse reporting}handle mouse +clicks in the PuTTY window itself. If this happens, the \i{mouse pointer} +will turn into an arrow, and using the mouse to copy and paste will only +work if you hold down Shift. See \k{config-features-mouse} and +\k{config-mouseshift} for details of this feature and how to configure +it. + +\S{using-scrollback} \I{scrollback}Scrolling the screen back + +PuTTY keeps track of text that has scrolled up off the top of the +terminal. So if something appears on the screen that you want to +read, but it scrolls too fast and it's gone by the time you try to +look for it, you can use the \i{scrollbar} on the right side of the +window to look back up the session \i{history} and find it again. + +As well as using the scrollbar, you can also page the scrollback up +and down by pressing \i{Shift-PgUp} and \i{Shift-PgDn}. You can +scroll a line at a time using \i{Ctrl-PgUp} and \i{Ctrl-PgDn}. These +are still available if you configure the scrollbar to be invisible. + +By default the last 200 lines scrolled off the top are +preserved for you to look at. You can increase (or decrease) this +value using the configuration box; see \k{config-scrollback}. + +\S{using-sysmenu} The \ii{System menu} + +If you click the left mouse button on the icon in the top left +corner of PuTTY's terminal window, or click the right mouse button +on the title bar, you will see the standard Windows system menu +containing items like Minimise, Move, Size and Close. + +PuTTY's system menu contains extra program features in addition to +the Windows standard options. These extra menu commands are +described below. + +(These options are also available in a \i{context menu} brought up +by holding Ctrl and clicking with the right mouse button anywhere +in the \i{PuTTY window}.) + +\S2{using-eventlog} The PuTTY \i{Event Log} + +If you choose \q{Event Log} from the system menu, a small window +will pop up in which PuTTY logs significant events during the +connection. Most of the events in the log will probably take place +during session startup, but a few can occur at any point in the +session, and one or two occur right at the end. + +You can use the mouse to select one or more lines of the Event Log, +and hit the Copy button to copy them to the \i{clipboard}. If you +are reporting a bug, it's often useful to paste the contents of the +Event Log into your bug report. + +\S2{using-specials} \ii{Special commands} + +Depending on the protocol used for the current session, there may be +a submenu of \q{special commands}. These are protocol-specific +tokens, such as a \q{break} signal, that can be sent down a +connection in addition to normal data. Their precise effect is usually +up to the server. Currently only Telnet, SSH, and serial connections +have special commands. + +The \q{break} signal can also be invoked from the keyboard with +\i{Ctrl-Break}. + +The following \I{Telnet special commands}special commands are +available in Telnet: + +\b \I{Are You There, Telnet special command}Are You There + +\b \I{Break, Telnet special command}Break + +\b \I{Synch, Telnet special command}Synch + +\b \I{Erase Character, Telnet special command}Erase Character + +\lcont{ +PuTTY can also be configured to send this when the Backspace key is +pressed; see \k{config-telnetkey}. +} + +\b \I{Erase Line, Telnet special command}Erase Line + +\b \I{Go Ahead, Telnet special command}Go Ahead + +\b \I{No Operation, Telnet special command}No Operation + +\lcont{ +Should have no effect. +} + +\b \I{Abort Process, Telnet special command}Abort Process + +\b \I{Abort Output, Telnet special command}Abort Output + +\b \I{Interrupt Process, Telnet special command}Interrupt Process + +\lcont{ +PuTTY can also be configured to send this when Ctrl-C is typed; see +\k{config-telnetkey}. +} + +\b \I{Suspend Process, Telnet special command}Suspend Process + +\lcont{ +PuTTY can also be configured to send this when Ctrl-Z is typed; see +\k{config-telnetkey}. +} + +\b \I{End Of Record, Telnet special command}End Of Record + +\b \I{End Of File, Telnet special command}End Of File + +In an SSH connection, the following \I{SSH special commands}special +commands are available: + +\b \I{IGNORE message, SSH special command}\I{No-op, in SSH}\ii{IGNORE message} + +\lcont{ +Should have no effect. +} + +\b \I{Repeat key exchange, SSH special command}Repeat key exchange + +\lcont{ +Only available in SSH-2. Forces a \i{repeat key exchange} immediately (and +resets associated timers and counters). For more information about +repeat key exchanges, see \k{config-ssh-kex-rekey}. +} + +\b \I{Break, SSH special command}Break + +\lcont{ +Only available in SSH-2, and only during a session. Optional +extension; may not be supported by server. PuTTY requests the server's +default break length. +} + +\b \I{Signal, SSH special command}Signals (SIGINT, SIGTERM etc) + +\lcont{ +Only available in SSH-2, and only during a session. Sends various +POSIX signals. Not honoured by all servers. +} + +With a serial connection, the only available special command is +\I{Break, serial special command}\q{Break}. + +\S2{using-newsession} Starting new sessions + +PuTTY's system menu provides some shortcut ways to start new +sessions: + +\b Selecting \i{\q{New Session}} will start a completely new +instance of PuTTY, and bring up the configuration box as normal. + +\b Selecting \i{\q{Duplicate Session}} will start a session in a +new window with precisely the same options as your current one - +connecting to the same host using the same protocol, with all the +same terminal settings and everything. + +\b In an inactive window, selecting \i{\q{Restart Session}} will +do the same as \q{Duplicate Session}, but in the current window. + +\b The \i{\q{Saved Sessions} submenu} gives you quick access to any +sets of stored session details you have previously saved. See +\k{config-saving} for details of how to create saved sessions. + +\S2{using-changesettings} \I{settings, changing}Changing your +session settings + +If you select \i{\q{Change Settings}} from the system menu, PuTTY will +display a cut-down version of its initial configuration box. This +allows you to adjust most properties of your current session. You +can change the terminal size, the font, the actions of various +keypresses, the colours, and so on. + +Some of the options that are available in the main configuration box +are not shown in the cut-down Change Settings box. These are usually +options which don't make sense to change in the middle of a session +(for example, you can't switch from SSH to Telnet in mid-session). + +You can save the current settings to a saved session for future use +from this dialog box. See \k{config-saving} for more on saved +sessions. + +\S2{using-copyall} \i{Copy All to Clipboard} + +This system menu option provides a convenient way to copy the whole +contents of the terminal screen (up to the last nonempty line) and +scrollback to the \i{clipboard} in one go. + +\S2{reset-terminal} \I{scrollback, clearing}Clearing and +\I{terminal, resetting}resetting the terminal + +The \i{\q{Clear Scrollback}} option on the system menu tells PuTTY +to discard all the lines of text that have been kept after they +scrolled off the top of the screen. This might be useful, for +example, if you displayed sensitive information and wanted to make +sure nobody could look over your shoulder and see it. (Note that +this only prevents a casual user from using the scrollbar to view +the information; the text is not guaranteed not to still be in +PuTTY's memory.) + +The \i{\q{Reset Terminal}} option causes a full reset of the +\i{terminal emulation}. A VT-series terminal is a complex piece of +software and can easily get into a state where all the text printed +becomes unreadable. (This can happen, for example, if you +accidentally output a binary file to your terminal.) If this +happens, selecting Reset Terminal should sort it out. + +\S2{using-fullscreen} \ii{Full screen} mode + +If you find the title bar on a maximised window to be ugly or +distracting, you can select Full Screen mode to maximise PuTTY +\q{even more}. When you select this, PuTTY will expand to fill the +whole screen and its borders, title bar and scrollbar will +disappear. (You can configure the scrollbar not to disappear in +full-screen mode if you want to keep it; see \k{config-scrollback}.) + +When you are in full-screen mode, you can still access the \i{system +menu} if you click the left mouse button in the \e{extreme} top left +corner of the screen. + +\H{using-logging} Creating a \i{log file} of your \I{session +log}session + +For some purposes you may find you want to log everything that +appears on your screen. You can do this using the \q{Logging} +panel in the configuration box. + +To begin a session log, select \q{Change Settings} from the system +menu and go to the Logging panel. Enter a log file name, and select +a logging mode. (You can log all session output including the +terminal \i{control sequence}s, or you can just log the printable text. +It depends what you want the log for.) Click \q{Apply} and your log +will be started. Later on, you can go back to the Logging panel and +select \q{Logging turned off completely} to stop logging; then PuTTY +will close the log file and you can safely read it. + +See \k{config-logging} for more details and options. + +\H{using-translation} Altering your \i{character set} configuration + +If you find that special characters (\i{accented characters}, for +example, or \i{line-drawing characters}) are not being displayed +correctly in your PuTTY session, it may be that PuTTY is interpreting +the characters sent by the server according to the wrong \e{character +set}. There are a lot of different character sets available, so it's +entirely possible for this to happen. + +If you click \q{Change Settings} and look at the \q{Translation} +panel, you should see a large number of character sets which you can +select, and other related options. Now all you need is to find out +which of them you want! (See \k{config-translation} for more +information.) + +\H{using-x-forwarding} Using \i{X11 forwarding} in SSH + +The SSH protocol has the ability to securely forward X Window System +applications over your encrypted SSH connection, so that you can run +an application on the SSH server machine and have it put its windows +up on your local machine without sending any X network traffic in +the clear. + +In order to use this feature, you will need an X display server for +your Windows machine, such as Cygwin/X, X-Win32, or Exceed. This will probably +install itself as display number 0 on your local machine; if it +doesn't, the manual for the \i{X server} should tell you what it +does do. + +You should then tick the \q{Enable X11 forwarding} box in the +X11 panel (see \k{config-ssh-x11}) before starting your SSH +session. The \i{\q{X display location}} box is blank by default, which +means that PuTTY will try to use a sensible default such as \c{:0}, +which is the usual display location where your X server will be +installed. If that needs changing, then change it. + +Now you should be able to log in to the SSH server as normal. To +check that X forwarding has been successfully negotiated during +connection startup, you can check the PuTTY Event Log (see +\k{using-eventlog}). It should say something like this: + +\c 2001-12-05 17:22:01 Requesting X11 forwarding +\c 2001-12-05 17:22:02 X11 forwarding enabled + +If the remote system is Unix or Unix-like, you should also be able +to see that the \i{\c{DISPLAY} environment variable} has been set to +point at display 10 or above on the SSH server machine itself: + +\c fred@unixbox:~$ echo $DISPLAY +\c unixbox:10.0 + +If this works, you should then be able to run X applications in the +remote session and have them display their windows on your PC. + +For more options relating to X11 forwarding, see \k{config-ssh-x11}. + +\H{using-port-forwarding} Using \i{port forwarding} in SSH + +The SSH protocol has the ability to forward arbitrary \i{network +connection}s over your encrypted SSH connection, to avoid the network +traffic being sent in clear. For example, you could use this to +connect from your home computer to a \i{POP-3} server on a remote +machine without your POP-3 password being visible to network +sniffers. + +In order to use port forwarding to \I{local port forwarding}connect +from your local machine to a port on a remote server, you need to: + +\b Choose a \i{port number} on your local machine where PuTTY should +listen for incoming connections. There are likely to be plenty of +unused port numbers above 3000. (You can also use a local loopback +address here; see below for more details.) + +\b Now, before you start your SSH connection, go to the Tunnels +panel (see \k{config-ssh-portfwd}). Make sure the \q{Local} radio +button is set. Enter the local port number into the \q{Source port} +box. Enter the destination host name and port number into the +\q{Destination} box, separated by a colon (for example, +\c{popserver.example.com:110} to connect to a POP-3 server). + +\b Now click the \q{Add} button. The details of your port forwarding +should appear in the list box. + +Now start your session and log in. (Port forwarding will not be +enabled until after you have logged in; otherwise it would be easy +to perform completely anonymous network attacks, and gain access to +anyone's virtual private network.) To check that PuTTY has set up +the port forwarding correctly, you can look at the PuTTY Event Log +(see \k{using-eventlog}). It should say something like this: + +\c 2001-12-05 17:22:10 Local port 3110 forwarding to +\c popserver.example.com:110 + +Now if you connect to the source port number on your local PC, you +should find that it answers you exactly as if it were the service +running on the destination machine. So in this example, you could +then configure an e-mail client to use \c{localhost:3110} as a POP-3 +server instead of \c{popserver.example.com:110}. (Of course, the +forwarding will stop happening when your PuTTY session closes down.) + +You can also forward ports in the other direction: arrange for a +particular port number on the \e{server} machine to be \I{remote +port forwarding}forwarded back to your PC as a connection to a +service on your PC or near it. +To do this, just select the \q{Remote} radio button instead of the +\q{Local} one. The \q{Source port} box will now specify a port +number on the \e{server} (note that most servers will not allow you +to use \I{privileged port}port numbers under 1024 for this purpose). + +An alternative way to forward local connections to remote hosts is +to use \I{dynamic port forwarding}dynamic SOCKS proxying. In this +mode, PuTTY acts as a SOCKS server, which SOCKS-aware programs can +connect to and open forwarded connections to the destination of their +choice, so this can be an alternative to long lists of static +forwardings. To use this mode, you will need to select the \q{Dynamic} +radio button instead of \q{Local}, and then you should not enter +anything into the \q{Destination} box (it will be ignored). PuTTY will +then listen for SOCKS connections on the port you have specified. +Most \i{web browsers} can be configured to connect to this SOCKS proxy +service; also, you can forward other PuTTY connections through it by +setting up the Proxy control panel (see \k{config-proxy} for details). + +The source port for a forwarded connection usually does not accept +connections from any machine except the \I{localhost}SSH client or +server machine itself (for local and remote forwardings respectively). +There are controls in the Tunnels panel to change this: + +\b The \q{Local ports accept connections from other hosts} option +allows you to set up local-to-remote port forwardings (including +dynamic port forwardings) in such a way that machines other than +your client PC can connect to the forwarded port. + +\b The \q{Remote ports do the same} option does the same thing for +remote-to-local port forwardings (so that machines other than the +SSH server machine can connect to the forwarded port.) Note that +this feature is only available in the SSH-2 protocol, and not all +SSH-2 servers honour it (in \i{OpenSSH}, for example, it's usually +disabled by default). + +You can also specify an \i{IP address} to \I{listen address}listen +on. Typically a Windows machine can be asked to listen on any single +IP address in the \cw{127.*.*.*} range, and all of these are +\i{loopback address}es available only to the local machine. So if +you forward (for example) \c{127.0.0.5:79} to a remote machine's +\i\cw{finger} port, then you should be able to run commands such as +\c{finger fred@127.0.0.5}. +This can be useful if the program connecting to the forwarded port +doesn't allow you to change the port number it uses. This feature is +available for local-to-remote forwarded ports; SSH-1 is unable to +support it for remote-to-local ports, while SSH-2 can support it in +theory but servers will not necessarily cooperate. + +(Note that if you're using Windows XP Service Pack 2, you may need +to obtain a fix from Microsoft in order to use addresses like +\cw{127.0.0.5} - see \k{faq-alternate-localhost}.) + +For more options relating to port forwarding, see +\k{config-ssh-portfwd}. + +If the connection you are forwarding over SSH is itself a second SSH +connection made by another copy of PuTTY, you might find the +\q{logical host name} configuration option useful to warn PuTTY of +which host key it should be expecting. See \k{config-loghost} for +details of this. + +\H{using-rawprot} Making \i{raw TCP connections} + +A lot of \I{debugging Internet protocols}Internet protocols are +composed of commands and responses in plain text. For example, +\i{SMTP} (the protocol used to transfer e-mail), \i{NNTP} (the +protocol used to transfer Usenet news), and \i{HTTP} (the protocol +used to serve Web pages) all consist of commands in readable plain +text. + +Sometimes it can be useful to connect directly to one of these +services and speak the protocol \q{by hand}, by typing protocol +commands and watching the responses. On Unix machines, you can do +this using the system's \c{telnet} command to connect to the right +port number. For example, \c{telnet mailserver.example.com 25} might +enable you to talk directly to the SMTP service running on a mail +server. + +Although the Unix \c{telnet} program provides this functionality, +the protocol being used is not really Telnet. Really there is no +actual protocol at all; the bytes sent down the connection are +exactly the ones you type, and the bytes shown on the screen are +exactly the ones sent by the server. Unix \c{telnet} will attempt to +detect or guess whether the service it is talking to is a real +Telnet service or not; PuTTY prefers to be told for certain. + +In order to make a debugging connection to a service of this type, +you simply select the fourth protocol name, \I{\q{Raw} +protocol}\q{Raw}, from the \q{Protocol} buttons in the \q{Session} +configuration panel. (See \k{config-hostname}.) You can then enter a +host name and a port number, and make the connection. + +\H{using-serial} Connecting to a local serial line + +PuTTY can connect directly to a local serial line as an alternative +to making a network connection. In this mode, text typed into the +PuTTY window will be sent straight out of your computer's serial +port, and data received through that port will be displayed in the +PuTTY window. You might use this mode, for example, if your serial +port is connected to another computer which has a serial connection. + +To make a connection of this type, simply select \q{Serial} from the +\q{Connection type} radio buttons on the \q{Session} configuration +panel (see \k{config-hostname}). The \q{Host Name} and \q{Port} +boxes will transform into \q{Serial line} and \q{Speed}, allowing +you to specify which serial line to use (if your computer has more +than one) and what speed (baud rate) to use when transferring data. +For further configuration options (data bits, stop bits, parity, +flow control), you can use the \q{Serial} configuration panel (see +\k{config-serial}). + +After you start up PuTTY in serial mode, you might find that you +have to make the first move, by sending some data out of the serial +line in order to notify the device at the other end that someone is +there for it to talk to. This probably depends on the device. If you +start up a PuTTY serial session and nothing appears in the window, +try pressing Return a few times and see if that helps. + +A serial line provides no well defined means for one end of the +connection to notify the other that the connection is finished. +Therefore, PuTTY in serial mode will remain connected until you +close the window using the close button. + +\H{using-cmdline} The PuTTY command line + +PuTTY can be made to do various things without user intervention by +supplying \i{command-line arguments} (e.g., from a \i{command prompt +window}, or a \i{Windows shortcut}). + +\S{using-cmdline-session} Starting a session from the command line + +\I\c{-ssh}\I\c{-telnet}\I\c{-rlogin}\I\c{-raw}\I\c{-serial}These +options allow you to bypass the configuration window and launch +straight into a session. + +To start a connection to a server called \c{host}: + +\c putty.exe [-ssh | -telnet | -rlogin | -raw] [user@]host + +If this syntax is used, settings are taken from the \i{Default Settings} +(see \k{config-saving}); \c{user} overrides these settings if +supplied. Also, you can specify a protocol, which will override the +default protocol (see \k{using-cmdline-protocol}). + +For telnet sessions, the following alternative syntax is supported +(this makes PuTTY suitable for use as a URL handler for \i{telnet +URLs} in \i{web browsers}): + +\c putty.exe telnet://host[:port]/ + +To start a connection to a serial port, e.g. COM1: + +\c putty.exe -serial com1 + +In order to start an existing saved session called \c{sessionname}, +use the \c{-load} option (described in \k{using-cmdline-load}). + +\c putty.exe -load "session name" + +\S{using-cleanup} \i\c{-cleanup} + +\cfg{winhelp-topic}{options.cleanup} + +If invoked with the \c{-cleanup} option, rather than running as +normal, PuTTY will remove its \I{removing registry entries}registry +entries and \i{random seed file} from the local machine (after +confirming with the user). + +Note that on \i{multi-user systems}, \c{-cleanup} only removes +registry entries and files associated with the currently logged-in +user. + +\S{using-general-opts} Standard command-line options + +PuTTY and its associated tools support a range of command-line +options, most of which are consistent across all the tools. This +section lists the available options in all tools. Options which are +specific to a particular tool are covered in the chapter about that +tool. + +\S2{using-cmdline-load} \i\c{-load}: load a saved session + +\I{saved sessions, loading from command line}The \c{-load} option +causes PuTTY to load configuration details out of a saved session. +If these details include a host name, then this option is all you +need to make PuTTY start a session. + +You need double quotes around the session name if it contains spaces. + +If you want to create a \i{Windows shortcut} to start a PuTTY saved +session, this is the option you should use: your shortcut should +call something like + +\c d:\path\to\putty.exe -load "my session" + +(Note that PuTTY itself supports an alternative form of this option, +for backwards compatibility. If you execute \i\c{putty @sessionname} +it will have the same effect as \c{putty -load "sessionname"}. With +the \c{@} form, no double quotes are required, and the \c{@} sign +must be the very first thing on the command line. This form of the +option is deprecated.) + +\S2{using-cmdline-protocol} Selecting a protocol: \c{-ssh}, +\c{-telnet}, \c{-rlogin}, \c{-raw} \c{-serial} + +To choose which protocol you want to connect with, you can use one +of these options: + +\b \i\c{-ssh} selects the SSH protocol. + +\b \i\c{-telnet} selects the Telnet protocol. + +\b \i\c{-rlogin} selects the Rlogin protocol. + +\b \i\c{-raw} selects the raw protocol. + +\b \i\c{-serial} selects a serial connection. + +These options are not available in the file transfer tools PSCP and +PSFTP (which only work with the SSH protocol). + +These options are equivalent to the \i{protocol selection} buttons +in the Session panel of the PuTTY configuration box (see +\k{config-hostname}). + +\S2{using-cmdline-v} \i\c{-v}: increase verbosity + +\I{verbose mode}Most of the PuTTY tools can be made to tell you more +about what they are doing by supplying the \c{-v} option. If you are +having trouble when making a connection, or you're simply curious, +you can turn this switch on and hope to find out more about what is +happening. + +\S2{using-cmdline-l} \i\c{-l}: specify a \i{login name} + +You can specify the user name to log in as on the remote server +using the \c{-l} option. For example, \c{plink login.example.com -l +fred}. + +These options are equivalent to the username selection box in the +Connection panel of the PuTTY configuration box (see +\k{config-username}). + +\S2{using-cmdline-portfwd} \I{-L-upper}\c{-L}, \I{-R-upper}\c{-R} +and \I{-D-upper}\c{-D}: set up \i{port forwardings} + +As well as setting up port forwardings in the PuTTY configuration +(see \k{config-ssh-portfwd}), you can also set up forwardings on the +command line. The command-line options work just like the ones in +Unix \c{ssh} programs. + +To \I{local port forwarding}forward a local port (say 5110) to a +remote destination (say \cw{popserver.example.com} port 110), you +can write something like one of these: + +\c putty -L 5110:popserver.example.com:110 -load mysession +\c plink mysession -L 5110:popserver.example.com:110 + +To forward a \I{remote port forwarding}remote port to a local +destination, just use the \c{-R} option instead of \c{-L}: + +\c putty -R 5023:mytelnetserver.myhouse.org:23 -load mysession +\c plink mysession -R 5023:mytelnetserver.myhouse.org:23 + +To \I{listen address}specify an IP address for the listening end of the +tunnel, prepend it to the argument: + +\c plink -L 127.0.0.5:23:localhost:23 myhost + +To set up \I{dynamic port forwarding}SOCKS-based dynamic port +forwarding on a local port, use the \c{-D} option. For this one you +only have to pass the port number: + +\c putty -D 4096 -load mysession + +For general information on port forwarding, see +\k{using-port-forwarding}. + +These options are not available in the file transfer tools PSCP and +PSFTP. + +\S2{using-cmdline-m} \i\c{-m}: \I{reading commands from a file}read +a remote command or script from a file + +The \i\c{-m} option performs a similar function to the \q{\ii{Remote +command}} box in the SSH panel of the PuTTY configuration box (see +\k{config-command}). However, the \c{-m} option expects to be given +a local file name, and it will read a command from that file. + +With some servers (particularly Unix systems), you can even put +multiple lines in this file and execute more than one command in +sequence, or a whole shell script; but this is arguably an abuse, and +cannot be expected to work on all servers. In particular, it is known +\e{not} to work with certain \q{embedded} servers, such as \i{Cisco} +routers. + +This option is not available in the file transfer tools PSCP and +PSFTP. + +\S2{using-cmdline-p} \I{-P-upper}\c{-P}: specify a \i{port number} + +The \c{-P} option is used to specify the port number to connect to. If +you have a Telnet server running on port 9696 of a machine instead of +port 23, for example: + +\c putty -telnet -P 9696 host.name +\c plink -telnet -P 9696 host.name + +(Note that this option is more useful in Plink than in PuTTY, +because in PuTTY you can write \c{putty -telnet host.name 9696} in +any case.) + +This option is equivalent to the port number control in the Session +panel of the PuTTY configuration box (see \k{config-hostname}). + +\S2{using-cmdline-pw} \i\c{-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. + +Note that the \c{-pw} option only works when you are using the SSH +protocol. Due to fundamental limitations of Telnet and Rlogin, these +protocols do not support automated password authentication. + +\S2{using-cmdline-agentauth} \i\c{-agent} and \i\c{-noagent}: +control use of Pageant for authentication + +The \c{-agent} option turns on SSH authentication using Pageant, and +\c{-noagent} turns it off. These options are only meaningful if you +are using SSH. + +See \k{pageant} for general information on \i{Pageant}. + +These options are equivalent to the agent authentication checkbox in +the Auth panel of the PuTTY configuration box (see +\k{config-ssh-tryagent}). + +\S2{using-cmdline-agent} \I{-A-upper}\c{-A} and \i\c{-a}: control \i{agent +forwarding} + +The \c{-A} option turns on SSH agent forwarding, and \c{-a} turns it +off. These options are only meaningful if you are using SSH. + +See \k{pageant} for general information on \i{Pageant}, and +\k{pageant-forward} for information on agent forwarding. Note that +there is a security risk involved with enabling this option; see +\k{pageant-security} for details. + +These options are equivalent to the agent forwarding checkbox in the +Auth panel of the PuTTY configuration box (see \k{config-ssh-agentfwd}). + +These options are not available in the file transfer tools PSCP and +PSFTP. + +\S2{using-cmdline-x11} \I{-X-upper}\c{-X} and \i\c{-x}: control \i{X11 +forwarding} + +The \c{-X} option turns on X11 forwarding in SSH, and \c{-x} turns +it off. These options are only meaningful if you are using SSH. + +For information on X11 forwarding, see \k{using-x-forwarding}. + +These options are equivalent to the X11 forwarding checkbox in the +X11 panel of the PuTTY configuration box (see \k{config-ssh-x11}). + +These options are not available in the file transfer tools PSCP and +PSFTP. + +\S2{using-cmdline-pty} \i\c{-t} and \I{-T-upper}\c{-T}: control +\i{pseudo-terminal allocation} + +The \c{-t} option ensures PuTTY attempts to allocate a +pseudo-terminal at the server, and \c{-T} stops it from allocating +one. These options are only meaningful if you are using SSH. + +These options are equivalent to the \q{Don't allocate a +pseudo-terminal} checkbox in the SSH panel of the PuTTY +configuration box (see \k{config-ssh-pty}). + +These options are not available in the file transfer tools PSCP and +PSFTP. + +\S2{using-cmdline-noshell} \I{-N-upper}\c{-N}: suppress starting a +\I{suppressing remote shell}shell or command + +The \c{-N} option prevents PuTTY from attempting to start a shell or +command on the remote server. You might want to use this option if +you are only using the SSH connection for port forwarding, and your +user account on the server does not have the ability to run a shell. + +This feature is only available in SSH protocol version 2 (since the +version 1 protocol assumes you will always want to run a shell). + +This option is equivalent to the \q{Don't start a shell or command +at all} checkbox in the SSH panel of the PuTTY configuration box +(see \k{config-ssh-noshell}). + +This option is not available in the file transfer tools PSCP and +PSFTP. + +\S2{using-cmdline-ncmode} \I{-nc}\c{-nc}: make a \i{remote network +connection} in place of a remote shell or command + +The \c{-nc} option prevents Plink (or PuTTY) from attempting to +start a shell or command on the remote server. Instead, it will +instruct the remote server to open a network connection to a host +name and port number specified by you, and treat that network +connection as if it were the main session. + +You specify a host and port as an argument to the \c{-nc} option, +with a colon separating the host name from the port number, like +this: + +\c plink host1.example.com -nc host2.example.com:1234 + +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 feature is only available in SSH protocol version 2 (since the +version 1 protocol assumes you will always want to run a shell). It +is not available in the file transfer tools PSCP and PSFTP. It is +available in PuTTY itself, although it is unlikely to be very useful +in any tool other than Plink. Also, \c{-nc} uses the same server +functionality as port forwarding, so it will not work if your server +administrator has disabled port forwarding. + +(The option is named \c{-nc} after the Unix program +\W{http://www.vulnwatch.org/netcat/}\c{nc}, short for \q{netcat}. +The command \cq{plink host1 -nc host2:port} is very similar in +functionality to \cq{plink host1 nc host2 port}, which invokes +\c{nc} on the server and tells it to connect to the specified +destination. However, Plink's built-in \c{-nc} option does not +depend on the \c{nc} program being installed on the server.) + +\S2{using-cmdline-compress} \I{-C-upper}\c{-C}: enable \i{compression} + +The \c{-C} option enables compression of the data sent across the +network. This option is only meaningful if you are using SSH. + +This option is equivalent to the \q{Enable compression} checkbox in +the SSH panel of the PuTTY configuration box (see +\k{config-ssh-comp}). + +\S2{using-cmdline-sshprot} \i\c{-1} and \i\c{-2}: specify an \i{SSH +protocol version} + +The \c{-1} and \c{-2} options force PuTTY to use version \I{SSH-1}1 +or version \I{SSH-2}2 of the SSH protocol. These options are only +meaningful if you are using SSH. + +These options are equivalent to selecting your preferred SSH +protocol version as \q{1 only} or \q{2 only} in the SSH panel of the +PuTTY configuration box (see \k{config-ssh-prot}). + +\S2{using-cmdline-ipversion} \i\c{-4} and \i\c{-6}: specify an +\i{Internet protocol version} + +The \c{-4} and \c{-6} options force PuTTY to use the older Internet +protocol \i{IPv4} or the newer \i{IPv6} for most outgoing +connections. + +These options are equivalent to selecting your preferred Internet +protocol version as \q{IPv4} or \q{IPv6} in the Connection panel of +the PuTTY configuration box (see \k{config-address-family}). + +\S2{using-cmdline-identity} \i\c{-i}: specify an SSH \i{private key} + +The \c{-i} option allows you to specify the name of a private key +file in \c{*.\i{PPK}} format which PuTTY will use to authenticate with the +server. This option is only meaningful if you are using SSH. + +For general information on \i{public-key authentication}, see +\k{pubkey}. + +This option is equivalent to the \q{Private key file for +authentication} box in the Auth panel of the PuTTY configuration box +(see \k{config-ssh-privkey}). + +\S2{using-cmdline-loghost} \i\c{-loghost}: specify a \i{logical host +name} + +This option overrides PuTTY's normal SSH host key caching policy by +telling it the name of the host you expect your connection to end up +at (in cases where this differs from the location PuTTY thinks it's +connecting to). It can be a plain host name, or a host name followed +by a colon and a port number. See \k{config-loghost} for more detail +on this. + +\S2{using-cmdline-pgpfp} \i\c{-pgpfp}: display \i{PGP key fingerprint}s + +This option causes the PuTTY tools not to run as normal, but instead +to display the fingerprints of the PuTTY PGP Master Keys, in order to +aid with \i{verifying new versions}. See \k{pgpkeys} for more information. + +\S2{using-cmdline-sercfg} \i\c{-sercfg}: specify serial port +\i{configuration} + +This option specifies the configuration parameters for the serial +port (baud rate, stop bits etc). Its argument is interpreted as a +comma-separated list of configuration options, which can be as +follows: + +\b Any single digit from 5 to 9 sets the number of data bits. + +\b \cq{1}, \cq{1.5} or \cq{2} sets the number of stop bits. + +\b Any other numeric string is interpreted as a baud rate. + +\b A single lower-case letter specifies the parity: \cq{n} for none, +\cq{o} for odd, \cq{e} for even, \cq{m} for mark and \cq{s} for space. + +\b A single upper-case letter specifies the flow control: \cq{N} for +none, \cq{X} for XON/XOFF, \cq{R} for RTS/CTS and \cq{D} for +DSR/DTR. + +For example, \cq{-sercfg 19200,8,n,1,N} denotes a baud rate of +19200, 8 data bits, no parity, 1 stop bit and no flow control. diff --git a/putty/DOC/VIDS.BUT b/putty/DOC/VIDS.BUT new file mode 100644 index 0000000..7f35118 --- /dev/null +++ b/putty/DOC/VIDS.BUT @@ -0,0 +1,36 @@ +\# Invoke the versionid macros defined in all the other manual +\# chapter files. + +\versionidblurb + +\versionidintro + +\versionidgs + +\versionidusing + +\versionidconfig + +\versionidpscp + +\versionidpsftp + +\versionidplink + +\versionidpubkey + +\versionidpageant + +\versioniderrors + +\versionidfaq + +\versionidfeedback + +\versionidlicence + +\versionidudp + +\versionidpgpkeys + +\versionidindex diff --git a/putty/ICONS/CICON.PL b/putty/ICONS/CICON.PL new file mode 100644 index 0000000..7bce7d3 --- /dev/null +++ b/putty/ICONS/CICON.PL @@ -0,0 +1,28 @@ +#!/usr/bin/perl + +# Given a list of input PNGs, create a C source file containing a +# const array of XPMs, named by a given C identifier. + +$id = shift @ARGV; +$k = 0; +@xpms = (); +foreach $f (@ARGV) { + # XPM format is generated directly by ImageMagick, so that's easy + # enough. We just have to adjust the declaration line so that it + # has the right name, linkage and storage class. + @lines = (); + open XPM, "convert $f xpm:- |"; + push @lines, $_ while ; + close XPM; + die "XPM from $f in unexpected format\n" unless $lines[1] =~ /^static.*\{$/; + $lines[1] = "static const char *const ${id}_$k"."[] = {\n"; + $k++; + push @xpms, @lines, "\n"; +} + +# Now output. +foreach $line (@xpms) { print $line; } +print "const char *const *const ${id}[] = {\n"; +for ($i = 0; $i < $k; $i++) { print " ${id}_$i,\n"; } +print "};\n"; +print "const int n_${id} = $k;\n"; diff --git a/putty/ICONS/ICON.PL b/putty/ICONS/ICON.PL new file mode 100644 index 0000000..aefd3fa --- /dev/null +++ b/putty/ICONS/ICON.PL @@ -0,0 +1,270 @@ +#!/usr/bin/perl + +# Take a collection of input image files and convert them into a +# multi-resolution Windows .ICO icon file. +# +# The input images can be treated as having four different colour +# depths: +# +# - 24-bit true colour +# - 8-bit with custom palette +# - 4-bit using the Windows 16-colour palette (see comment below +# for details) +# - 1-bit using black and white only. +# +# The images can be supplied in any input format acceptable to +# ImageMagick, but their actual colour usage must already be +# appropriate for the specified mode; this script will not do any +# substantive conversion. So if an image intended to be used in 4- +# or 1-bit mode contains any colour not in the appropriate fixed +# palette, that's a fatal error; if an image to be used in 8-bit +# mode contains more than 256 distinct colours, that's also a fatal +# error. +# +# Command-line syntax is: +# +# icon.pl -depth imagefile [imagefile...] [-depth imagefile [imagefile...]] +# +# where `-depth' is one of `-24', `-8', `-4' or `-1', and tells the +# script how to treat all the image files given after that option +# until the next depth option. For example, you might execute +# +# icon.pl -24 48x48x24.png 32x32x24.png -8 32x32x8.png -1 monochrome.png +# +# to build an icon file containing two differently sized 24-bit +# images, one 8-bit image and one black and white image. +# +# Windows .ICO files support a 1-bit alpha channel on all these +# image types. That is, any pixel can be either opaque or fully +# transparent, but not partially transparent. The alpha channel is +# separate from the main image data, meaning that `transparent' is +# not required to take up a palette entry. (So an 8-bit image can +# have 256 distinct _opaque_ colours, plus transparent pixels as +# well.) If the input images have alpha channels, they will be used +# to determine which pixels of the icon are transparent, by simple +# quantisation half way up (e.g. in a PNG image with an 8-bit alpha +# channel, alpha values of 00-7F will be mapped to transparent +# pixels, and 80-FF will become opaque). + +# The Windows 16-colour palette consists of: +# - the eight corners of the colour cube (000000, 0000FF, 00FF00, +# 00FFFF, FF0000, FF00FF, FFFF00, FFFFFF) +# - dim versions of the seven non-black corners, at 128/255 of the +# brightness (000080, 008000, 008080, 800000, 800080, 808000, +# 808080) +# - light grey at 192/255 of full brightness (C0C0C0). +%win16pal = ( + "\x00\x00\x00\x00" => 0, + "\x00\x00\x80\x00" => 1, + "\x00\x80\x00\x00" => 2, + "\x00\x80\x80\x00" => 3, + "\x80\x00\x00\x00" => 4, + "\x80\x00\x80\x00" => 5, + "\x80\x80\x00\x00" => 6, + "\xC0\xC0\xC0\x00" => 7, + "\x80\x80\x80\x00" => 8, + "\x00\x00\xFF\x00" => 9, + "\x00\xFF\x00\x00" => 10, + "\x00\xFF\xFF\x00" => 11, + "\xFF\x00\x00\x00" => 12, + "\xFF\x00\xFF\x00" => 13, + "\xFF\xFF\x00\x00" => 14, + "\xFF\xFF\xFF\x00" => 15, +); +@win16pal = sort { $win16pal{$a} <=> $win16pal{$b} } keys %win16pal; + +# The black and white palette consists of black (000000) and white +# (FFFFFF), obviously. +%win2pal = ( + "\x00\x00\x00\x00" => 0, + "\xFF\xFF\xFF\x00" => 1, +); +@win2pal = sort { $win16pal{$a} <=> $win2pal{$b} } keys %win2pal; + +@hdr = (); +@dat = (); + +$depth = undef; +foreach $_ (@ARGV) { + if (/^-(24|8|4|1)$/) { + $depth = $1; + } elsif (defined $depth) { + &readicon($_, $depth); + } else { + $usage = 1; + } +} +if ($usage || length @hdr == 0) { + print "usage: icon.pl ( -24 | -8 | -4 | -1 ) image [image...]\n"; + print " [ ( -24 | -8 | -4 | -1 ) image [image...] ...]\n"; + exit 0; +} + +# Now write out the output icon file. +print pack "vvv", 0, 1, scalar @hdr; # file-level header +$filepos = 6 + 16 * scalar @hdr; +for ($i = 0; $i < scalar @hdr; $i++) { + print $hdr[$i]; + print pack "V", $filepos; + $filepos += length($dat[$i]); +} +for ($i = 0; $i < scalar @hdr; $i++) { + print $dat[$i]; +} + +sub readicon { + my $filename = shift @_; + my $depth = shift @_; + my $pix; + my $i; + my %pal; + + # Determine the icon's width and height. + my $w = `identify -format %w $filename`; + my $h = `identify -format %h $filename`; + + # Read the file in as RGBA data. We flip vertically at this + # point, to avoid having to do it ourselves (.BMP and hence + # .ICO are bottom-up). + my $data = []; + open IDATA, "convert -flip -depth 8 $filename rgba:- |"; + push @$data, $rgb while (read IDATA,$rgb,4,0) == 4; + close IDATA; + # Check we have the right amount of data. + $xl = $w * $h; + $al = scalar @$data; + die "wrong amount of image data ($al, expected $xl) from $filename\n" + unless $al == $xl; + + # Build the alpha channel now, so we can exclude transparent + # pixels from the palette analysis. We replace transparent + # pixels with undef in the data array. + # + # We quantise the alpha channel half way up, so that alpha of + # 0x80 or more is taken to be fully opaque and 0x7F or less is + # fully transparent. Nasty, but the best we can do without + # dithering (and don't even suggest we do that!). + my $x; + my $y; + my $alpha = ""; + + for ($y = 0; $y < $h; $y++) { + my $currbyte = 0, $currbits = 0; + for ($x = 0; $x < (($w+31)|31)-31; $x++) { + $pix = ($x < $w ? $data->[$y*$w+$x] : "\x00\x00\x00\xFF"); + my @rgba = unpack "CCCC", $pix; + $currbyte <<= 1; + $currbits++; + if ($rgba[3] < 0x80) { + if ($x < $w) { + $data->[$y*$w+$x] = undef; + } + $currbyte |= 1; # MS has the alpha channel inverted :-) + } else { + # Might as well flip RGBA into BGR0 while we're here. + if ($x < $w) { + $data->[$y*$w+$x] = pack "CCCC", + $rgba[2], $rgba[1], $rgba[0], 0; + } + } + if ($currbits >= 8) { + $alpha .= pack "C", $currbyte; + $currbits -= 8; + } + } + } + + # For an 8-bit image, check we have at most 256 distinct + # colours, and build the palette. + %pal = (); + if ($depth == 8) { + my $palindex = 0; + foreach $pix (@$data) { + next unless defined $pix; + $pal{$pix} = $palindex++ unless defined $pal{$pix}; + } + die "too many colours in 8-bit image $filename\n" unless $palindex <= 256; + } elsif ($depth == 4) { + %pal = %win16pal; + } elsif ($depth == 1) { + %pal = %win2pal; + } + + my $raster = ""; + if ($depth < 24) { + # For a non-24-bit image, flatten the image into one palette + # index per pixel. + $pad = 32 / $depth; # number of pixels to pad scanline to 4-byte align + $pmask = $pad-1; + for ($y = 0; $y < $h; $y++) { + my $currbyte = 0, $currbits = 0; + for ($x = 0; $x < (($w+$pmask)|$pmask)-$pmask; $x++) { + $currbyte <<= $depth; + $currbits += $depth; + if ($x < $w && defined ($pix = $data->[$y*$w+$x])) { + if (!defined $pal{$pix}) { + $pixhex = sprintf "%02x%02x%02x", unpack "CCC", $pix; + die "illegal colour value $pixhex at pixel ($x,$y) in $filename\n"; + } + $currbyte |= $pal{$pix}; + } + if ($currbits >= 8) { + $raster .= pack "C", $currbyte; + $currbits -= 8; + } + } + } + } else { + # For a 24-bit image, reverse the order of the R,G,B values + # and stick a padding zero on the end. + # + # (In this loop we don't need to bother padding the + # scanline out to a multiple of four bytes, because every + # pixel takes four whole bytes anyway.) + for ($i = 0; $i < scalar @$data; $i++) { + if (defined $data->[$i]) { + $raster .= $data->[$i]; + } else { + $raster .= "\x00\x00\x00\x00"; + } + } + $depth = 32; # and adjust this + } + + # Prepare the icon data. First the header... + my $data = pack "VVVvvVVVVVV", + 40, # size of bitmap info header + $w, # icon width + $h*2, # icon height (x2 to indicate the subsequent alpha channel) + 1, # 1 plane (common to all MS image formats) + $depth, # bits per pixel + 0, # no compression + length $raster, # image size + 0, 0, 0, 0; # resolution, colours used, colours important (ignored) + # ... then the palette ... + if ($depth <= 8) { + my $ncols = (1 << $depth); + my $palette = "\x00\x00\x00\x00" x $ncols; + foreach $i (keys %pal) { + substr($palette, $pal{$i}*4, 4) = $i; + } + $data .= $palette; + } + # ... the raster data we already had ready ... + $data .= $raster; + # ... and the alpha channel we already had as well. + $data .= $alpha; + + # Prepare the header which will represent this image in the + # icon file. + my $header = pack "CCCCvvV", + $w, $h, # width and height (this time the real height) + 1 << $depth, # number of colours, if less than 256 + 0, # reserved + 1, # planes + $depth, # bits per pixel + length $data; # size of real icon data + + push @hdr, $header; + push @dat, $data; +} diff --git a/putty/ICONS/MAKEFILE b/putty/ICONS/MAKEFILE new file mode 100644 index 0000000..620fab4 --- /dev/null +++ b/putty/ICONS/MAKEFILE @@ -0,0 +1,92 @@ +# Makefile for the PuTTY icon suite. + +ICONS = putty puttycfg puttygen pscp pageant pterm ptermcfg puttyins +SIZES = 16 32 48 + +MODE = # override to -it on command line for opaque testing + +PNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S).png)) +MONOPNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-mono.png)) +TRUEPNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-true.png)) + +ICOS = putty.ico puttygen.ico pscp.ico pageant.ico pageants.ico puttycfg.ico \ + puttyins.ico +CICONS = xpmputty.c xpmpucfg.c xpmpterm.c xpmptcfg.c + +base: icos cicons + +all: pngs monopngs base # truepngs currently disabled by default + +pngs: $(PNGS) +monopngs: $(MONOPNGS) +truepngs: $(TRUEPNGS) + +icos: $(ICOS) +cicons: $(CICONS) + +install: icos cicons + cp $(ICOS) ../windows + cp $(CICONS) ../unix + +$(PNGS): %.png: mkicon.py + ./mkicon.py $(MODE) $(join $(subst -, ,$(basename $@)),_icon) $@ + +$(MONOPNGS): %.png: mkicon.py + ./mkicon.py -2 $(MODE) $(join $(subst -, ,$(subst -mono,,$(basename $@))),_icon) $@ + +$(TRUEPNGS): %.png: mkicon.py + ./mkicon.py -T $(MODE) $(join $(subst -, ,$(subst -true,,$(basename $@))),_icon) $@ + +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, $^) > $@ + +puttycfg.ico: puttycfg-16.png puttycfg-32.png puttycfg-48.png \ + puttycfg-16-mono.png puttycfg-32-mono.png puttycfg-48-mono.png + ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@ + +puttygen.ico: puttygen-16.png puttygen-32.png puttygen-48.png \ + puttygen-16-mono.png puttygen-32-mono.png puttygen-48-mono.png + ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@ + +pageant.ico: pageant-16.png pageant-32.png pageant-48.png \ + pageant-16-mono.png pageant-32-mono.png pageant-48-mono.png + ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@ + +pageants.ico: pageant-16.png pageant-16-mono.png + ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@ + +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, $^) > $@ + +# 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. +puttyins.ico: puttyins-16.png puttyins-32.png puttyins-48.png \ + puttyins-16-mono.png puttyins-32-mono.png \ + puttyins-48-mono.png \ + puttyins-16-true.png puttyins-32-true.png \ + puttyins-48-true.png + ./icon.pl -8 $(filter %-true.png, $^) \ + -4 $(filter-out %-true.png, $(filter-out %-mono.png, $^)) \ + -1 $(filter %-mono.png, $^) > $@ + +# Icon for the website. (This isn't linked into "make all".) +website.ico: putty-16.png + ./icon.pl -4 $^ >$@ + +xpmputty.c: putty-16.png putty-32.png putty-48.png + ./cicon.pl main_icon $^ > $@ + +xpmpucfg.c: puttycfg-16.png puttycfg-32.png puttycfg-48.png + ./cicon.pl cfg_icon $^ > $@ + +xpmpterm.c: pterm-16.png pterm-32.png pterm-48.png + ./cicon.pl main_icon $^ > $@ + +xpmptcfg.c: ptermcfg-16.png ptermcfg-32.png ptermcfg-48.png + ./cicon.pl cfg_icon $^ > $@ + +clean: + rm -f *.png *.ico *.c diff --git a/putty/ICONS/MKICON.PY b/putty/ICONS/MKICON.PY new file mode 100644 index 0000000..122eaaa --- /dev/null +++ b/putty/ICONS/MKICON.PY @@ -0,0 +1,1093 @@ +#!/usr/bin/env python + +import math + +# Python code which draws the PuTTY icon components at a range of +# sizes. + +# TODO +# ---- +# +# - use of alpha blending +# + try for variable-transparency borders +# +# - can we integrate the Mac icons into all this? Do we want to? + +def pixel(x, y, colour, canvas): + canvas[(int(x),int(y))] = colour + +def overlay(src, x, y, dst): + x = int(x) + y = int(y) + for (sx, sy), colour in src.items(): + dst[sx+x, sy+y] = blend(colour, dst.get((sx+x, sy+y), cT)) + +def finalise(canvas): + for k in canvas.keys(): + canvas[k] = finalisepix(canvas[k]) + +def bbox(canvas): + minx, miny, maxx, maxy = None, None, None, None + for (x, y) in canvas.keys(): + if minx == None: + minx, miny, maxx, maxy = x, y, x+1, y+1 + else: + minx = min(minx, x) + miny = min(miny, y) + maxx = max(maxx, x+1) + maxy = max(maxy, y+1) + return (minx, miny, maxx, maxy) + +def topy(canvas): + miny = {} + for (x, y) in canvas.keys(): + miny[x] = min(miny.get(x, y), y) + return miny + +def render(canvas, minx, miny, maxx, maxy): + w = maxx - minx + h = maxy - miny + ret = [] + for y in range(h): + ret.append([outpix(cT)] * w) + for (x, y), colour in canvas.items(): + if x >= minx and x < maxx and y >= miny and y < maxy: + ret[y-miny][x-minx] = outpix(colour) + return ret + +# Code to actually draw pieces of icon. These don't generally worry +# about positioning within a canvas; they just draw at a standard +# location, return some useful coordinates, and leave composition +# to other pieces of code. + +sqrthash = {} +def memoisedsqrt(x): + if not sqrthash.has_key(x): + sqrthash[x] = math.sqrt(x) + return sqrthash[x] + +BR, TR, BL, TL = range(4) # enumeration of quadrants for border() + +def border(canvas, thickness, squarecorners, out={}): + # I haven't yet worked out exactly how to do borders in a + # properly alpha-blended fashion. + # + # When you have two shades of dark available (half-dark H and + # full-dark F), the right sequence of circular border sections + # around a pixel x starts off with these two layouts: + # + # H F + # HxH FxF + # H F + # + # Where it goes after that I'm not entirely sure, but I'm + # absolutely sure those are the right places to start. However, + # every automated algorithm I've tried has always started off + # with the two layouts + # + # H HHH + # HxH HxH + # H HHH + # + # which looks much worse. This is true whether you do + # pixel-centre sampling (define an inner circle and an outer + # circle with radii differing by 1, set any pixel whose centre + # is inside the inner circle to F, any pixel whose centre is + # outside the outer one to nothing, interpolate between the two + # and round sensibly), _or_ whether you plot a notional circle + # of a given radius and measure the actual _proportion_ of each + # pixel square taken up by it. + # + # It's not clear what I should be doing to prevent this. One + # option is to attempt error-diffusion: Ian Jackson proved on + # paper that if you round each pixel's ideal value to the + # nearest of the available output values, then measure the + # error at each pixel, propagate that error outwards into the + # original values of the surrounding pixels, and re-round + # everything, you do get the correct second stage. However, I + # haven't tried it at a proper range of radii. + # + # Another option is that the automated mechanisms described + # above would be entirely adequate if it weren't for the fact + # that the human visual centres are adapted to detect + # horizontal and vertical lines in particular, so the only + # place you have to behave a bit differently is at the ends of + # the top and bottom row of pixels in the circle, and the top + # and bottom of the extreme columns. + # + # For the moment, what I have below is a very simple mechanism + # which always uses only one alpha level for any given border + # thickness, and which seems to work well enough for Windows + # 16-colour icons. Everything else will have to wait. + + thickness = memoisedsqrt(thickness) + + if thickness < 0.9: + darkness = 0.5 + else: + darkness = 1 + if thickness < 1: thickness = 1 + thickness = round(thickness - 0.5) + 0.3 + + out["borderthickness"] = thickness + + dmax = int(round(thickness)) + if dmax < thickness: dmax = dmax + 1 + + cquadrant = [[0] * (dmax+1) for x in range(dmax+1)] + squadrant = [[0] * (dmax+1) for x in range(dmax+1)] + + for x in range(dmax+1): + for y in range(dmax+1): + if max(x, y) < thickness: + squadrant[x][y] = darkness + if memoisedsqrt(x*x+y*y) < thickness: + cquadrant[x][y] = darkness + + bvalues = {} + for (x, y), colour in canvas.items(): + for dx in range(-dmax, dmax+1): + for dy in range(-dmax, dmax+1): + quadrant = 2 * (dx < 0) + (dy < 0) + if (x, y, quadrant) in squarecorners: + bval = squadrant[abs(dx)][abs(dy)] + else: + bval = cquadrant[abs(dx)][abs(dy)] + if bvalues.get((x+dx,y+dy),0) < bval: + bvalues[(x+dx,y+dy)] = bval + + for (x, y), value in bvalues.items(): + if not canvas.has_key((x,y)): + canvas[(x,y)] = dark(value) + +def sysbox(size, out={}): + canvas = {} + + # The system box of the computer. + + height = int(round(3.6*size)) + width = int(round(16.51*size)) + depth = int(round(2*size)) + highlight = int(round(1*size)) + bothighlight = int(round(1*size)) + + out["sysboxheight"] = height + + floppystart = int(round(19*size)) # measured in half-pixels + floppyend = int(round(29*size)) # measured in half-pixels + floppybottom = height - bothighlight + floppyrheight = 0.7 * size + floppyheight = int(round(floppyrheight)) + if floppyheight < 1: + floppyheight = 1 + floppytop = floppybottom - floppyheight + + # The front panel is rectangular. + for x in range(width): + for y in range(height): + grey = 3 + if x < highlight or y < highlight: + grey = grey + 1 + if x >= width-highlight or y >= height-bothighlight: + grey = grey - 1 + if y < highlight and x >= width-highlight: + v = (highlight-1-y) - (x-(width-highlight)) + if v < 0: + grey = grey - 1 + elif v > 0: + grey = grey + 1 + if y >= floppytop and y < floppybottom and \ + 2*x+2 > floppystart and 2*x < floppyend: + if 2*x >= floppystart and 2*x+2 <= floppyend and \ + floppyrheight >= 0.7: + grey = 0 + else: + grey = 2 + pixel(x, y, greypix(grey/4.0), canvas) + + # The side panel is a parallelogram. + for x in range(depth): + for y in range(height): + pixel(x+width, y-(x+1), greypix(0.5), canvas) + + # The top panel is another parallelogram. + for x in range(width-1): + for y in range(depth): + grey = 3 + if x >= width-1 - highlight: + grey = grey + 1 + pixel(x+(y+1), -(y+1), greypix(grey/4.0), canvas) + + # And draw a border. + border(canvas, size, [], out) + + return canvas + +def monitor(size): + canvas = {} + + # The computer's monitor. + + height = int(round(9.55*size)) + width = int(round(11.49*size)) + surround = int(round(1*size)) + botsurround = int(round(2*size)) + sheight = height - surround - botsurround + swidth = width - 2*surround + depth = int(round(2*size)) + highlight = int(round(math.sqrt(size))) + shadow = int(round(0.55*size)) + + # The front panel is rectangular. + for x in range(width): + for y in range(height): + if x >= surround and y >= surround and \ + x < surround+swidth and y < surround+sheight: + # Screen. + sx = (float(x-surround) - swidth/3) / swidth + sy = (float(y-surround) - sheight/3) / sheight + shighlight = 1.0 - (sx*sx+sy*sy)*0.27 + pix = bluepix(shighlight) + if x < surround+shadow or y < surround+shadow: + pix = blend(cD, pix) # sharp-edged shadow on top and left + else: + # Complicated double bevel on the screen surround. + + # First, the outer bevel. We compute the distance + # from this pixel to each edge of the front + # rectangle. + list = [ + (x, +1), + (y, +1), + (width-1-x, -1), + (height-1-y, -1) + ] + # Now sort the list to find the distance to the + # _nearest_ edge, or the two joint nearest. + list.sort() + # If there's one nearest edge, that determines our + # bevel colour. If there are two joint nearest, our + # bevel colour is their shared one if they agree, + # and neutral otherwise. + outerbevel = 0 + if list[0][0] < list[1][0] or list[0][1] == list[1][1]: + if list[0][0] < highlight: + outerbevel = list[0][1] + + # Now, the inner bevel. We compute the distance + # from this pixel to each edge of the screen + # itself. + list = [ + (surround-1-x, -1), + (surround-1-y, -1), + (x-(surround+swidth), +1), + (y-(surround+sheight), +1) + ] + # Now we sort to find the _maximum_ distance, which + # conveniently ignores any less than zero. + list.sort() + # And now the strategy is pretty much the same as + # above, only we're working from the opposite end + # of the list. + innerbevel = 0 + if list[-1][0] > list[-2][0] or list[-1][1] == list[-2][1]: + if list[-1][0] >= 0 and list[-1][0] < highlight: + innerbevel = list[-1][1] + + # Now we know the adjustment we want to make to the + # pixel's overall grey shade due to the outer + # bevel, and due to the inner one. We break a tie + # in favour of a light outer bevel, but otherwise + # add. + grey = 3 + if outerbevel > 0 or outerbevel == innerbevel: + innerbevel = 0 + grey = grey + outerbevel + innerbevel + + pix = greypix(grey / 4.0) + + pixel(x, y, pix, canvas) + + # The side panel is a parallelogram. + for x in range(depth): + for y in range(height): + pixel(x+width, y-x, greypix(0.5), canvas) + + # The top panel is another parallelogram. + for x in range(width): + for y in range(depth-1): + pixel(x+(y+1), -(y+1), greypix(0.75), canvas) + + # And draw a border. + border(canvas, size, [(0,int(height-1),BL)]) + + return canvas + +def computer(size): + # Monitor plus sysbox. + out = {} + m = monitor(size) + s = sysbox(size, out) + x = int(round((2+size/(size+1))*size)) + y = int(out["sysboxheight"] + out["borderthickness"]) + mb = bbox(m) + sb = bbox(s) + xoff = sb[0] - mb[0] + x + yoff = sb[3] - mb[3] - y + overlay(m, xoff, yoff, s) + return s + +def lightning(size): + canvas = {} + + # The lightning bolt motif. + + # We always want this to be an even number of pixels in height, + # and an odd number in width. + width = round(7*size) * 2 - 1 + height = round(8*size) * 2 + + # The outer edge of each side of the bolt goes to this point. + outery = round(8.4*size) + outerx = round(11*size) + + # And the inner edge goes to this point. + innery = height - 1 - outery + innerx = round(7*size) + + for y in range(int(height)): + list = [] + if y <= outery: + list.append(width-1-int(outerx * float(y) / outery + 0.3)) + if y <= innery: + list.append(width-1-int(innerx * float(y) / innery + 0.3)) + y0 = height-1-y + if y0 <= outery: + list.append(int(outerx * float(y0) / outery + 0.3)) + if y0 <= innery: + list.append(int(innerx * float(y0) / innery + 0.3)) + list.sort() + for x in range(int(list[0]), int(list[-1]+1)): + pixel(x, y, cY, canvas) + + # And draw a border. + border(canvas, size, [(int(width-1),0,TR), (0,int(height-1),BL)]) + + return canvas + +def document(size): + canvas = {} + + # The document used in the PSCP/PSFTP icon. + + width = round(13*size) + height = round(16*size) + + lineht = round(1*size) + if lineht < 1: lineht = 1 + linespc = round(0.7*size) + if linespc < 1: linespc = 1 + nlines = int((height-linespc)/(lineht+linespc)) + height = nlines*(lineht+linespc)+linespc # round this so it fits better + + # Start by drawing a big white rectangle. + for y in range(int(height)): + for x in range(int(width)): + pixel(x, y, cW, canvas) + + # Now draw lines of text. + for line in range(nlines): + # Decide where this line of text begins. + if line == 0: + start = round(4*size) + elif line < 5*nlines/7: + start = round((line - (nlines/7)) * size) + else: + start = round(1*size) + if start < round(1*size): + start = round(1*size) + # Decide where it ends. + endpoints = [10, 8, 11, 6, 5, 7, 5] + ey = line * 6.0 / (nlines-1) + eyf = math.floor(ey) + eyc = math.ceil(ey) + exf = endpoints[int(eyf)] + exc = endpoints[int(eyc)] + if eyf == eyc: + end = exf + else: + end = exf * (eyc-ey) + exc * (ey-eyf) + end = round(end * size) + + liney = height - (lineht+linespc) * (line+1) + for x in range(int(start), int(end)): + for y in range(int(lineht)): + pixel(x, y+liney, cK, canvas) + + # And draw a border. + border(canvas, size, \ + [(0,0,TL),(int(width-1),0,TR),(0,int(height-1),BL), \ + (int(width-1),int(height-1),BR)]) + + return canvas + +def hat(size): + canvas = {} + + # The secret-agent hat in the Pageant icon. + + topa = [6]*9+[5,3,1,0,0,1,2,2,1,1,1,9,9,10,10,11,11,12,12] + topa = [round(x*size) for x in topa] + botl = round(topa[0]+2.4*math.sqrt(size)) + botr = round(topa[-1]+2.4*math.sqrt(size)) + width = round(len(topa)*size) + + # Line equations for the top and bottom of the hat brim, in the + # form y=mx+c. c, of course, needs scaling by size, but m is + # independent of size. + brimm = 1.0 / 3.75 + brimtopc = round(4*size/3) + brimbotc = round(10*size/3) + + for x in range(int(width)): + xs = float(x) * (len(topa)-1) / (width-1) + xf = math.floor(xs) + xc = math.ceil(xs) + topf = topa[int(xf)] + topc = topa[int(xc)] + if xf == xc: + top = topf + else: + top = topf * (xc-xs) + topc * (xs-xf) + top = math.floor(top) + bot = round(botl + (botr-botl) * x/(width-1)) + + for y in range(int(top), int(bot)): + pixel(x, y, cK, canvas) + + # Now draw the brim. + for x in range(int(width)): + brimtop = brimtopc + brimm * x + brimbot = brimbotc + brimm * x + for y in range(int(math.floor(brimtop)), int(math.ceil(brimbot))): + tophere = max(min(brimtop - y, 1), 0) + bothere = max(min(brimbot - y, 1), 0) + grey = bothere - tophere + # Only draw brim pixels over pixels which are (a) part + # of the main hat, and (b) not right on its edge. + if canvas.has_key((x,y)) and \ + canvas.has_key((x,y-1)) and \ + canvas.has_key((x,y+1)) and \ + canvas.has_key((x-1,y)) and \ + canvas.has_key((x+1,y)): + pixel(x, y, greypix(grey), canvas) + + return canvas + +def key(size): + canvas = {} + + # The key in the PuTTYgen icon. + + keyheadw = round(9.5*size) + keyheadh = round(12*size) + keyholed = round(4*size) + keyholeoff = round(2*size) + # Ensure keyheadh and keyshafth have the same parity. + keyshafth = round((2*size - (int(keyheadh)&1)) / 2) * 2 + (int(keyheadh)&1) + keyshaftw = round(18.5*size) + keyhead = [round(x*size) for x in [12,11,8,10,9,8,11,12]] + + squarepix = [] + + # Ellipse for the key head, minus an off-centre circular hole. + for y in range(int(keyheadh)): + dy = (y-(keyheadh-1)/2.0) / (keyheadh/2.0) + dyh = (y-(keyheadh-1)/2.0) / (keyholed/2.0) + for x in range(int(keyheadw)): + dx = (x-(keyheadw-1)/2.0) / (keyheadw/2.0) + dxh = (x-(keyheadw-1)/2.0-keyholeoff) / (keyholed/2.0) + if dy*dy+dx*dx <= 1 and dyh*dyh+dxh*dxh > 1: + pixel(x + keyshaftw, y, cy, canvas) + + # Rectangle for the key shaft, extended at the bottom for the + # key head detail. + for x in range(int(keyshaftw)): + top = round((keyheadh - keyshafth) / 2) + bot = round((keyheadh + keyshafth) / 2) + xs = float(x) * (len(keyhead)-1) / round((len(keyhead)-1)*size) + xf = math.floor(xs) + xc = math.ceil(xs) + in_head = 0 + if xc < len(keyhead): + in_head = 1 + yf = keyhead[int(xf)] + yc = keyhead[int(xc)] + if xf == xc: + bot = yf + else: + bot = yf * (xc-xs) + yc * (xs-xf) + for y in range(int(top),int(bot)): + pixel(x, y, cy, canvas) + if in_head: + last = (x, y) + if x == 0: + squarepix.append((x, int(top), TL)) + if x == 0: + squarepix.append(last + (BL,)) + if last != None and not in_head: + squarepix.append(last + (BR,)) + last = None + + # And draw a border. + border(canvas, size, squarepix) + + return canvas + +def linedist(x1,y1, x2,y2, x,y): + # Compute the distance from the point x,y to the line segment + # joining x1,y1 to x2,y2. Returns the distance vector, measured + # with x,y at the origin. + + vectors = [] + + # Special case: if x1,y1 and x2,y2 are the same point, we + # don't attempt to extrapolate it into a line at all. + if x1 != x2 or y1 != y2: + # First, find the nearest point to x,y on the infinite + # projection of the line segment. So we construct a vector + # n perpendicular to that segment... + nx = y2-y1 + ny = x1-x2 + # ... compute the dot product of (x1,y1)-(x,y) with that + # vector... + nd = (x1-x)*nx + (y1-y)*ny + # ... multiply by the vector we first thought of... + ndx = nd * nx + ndy = nd * ny + # ... and divide twice by the length of n. + ndx = ndx / (nx*nx+ny*ny) + ndy = ndy / (nx*nx+ny*ny) + # That gives us a displacement vector from x,y to the + # nearest point. See if it's within the range of the line + # segment. + cx = x + ndx + cy = y + ndy + if cx >= min(x1,x2) and cx <= max(x1,x2) and \ + cy >= min(y1,y2) and cy <= max(y1,y2): + vectors.append((ndx,ndy)) + + # Now we have up to three candidate result vectors: (ndx,ndy) + # as computed just above, and the two vectors to the ends of + # the line segment, (x1-x,y1-y) and (x2-x,y2-y). Pick the + # shortest. + vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)] + bestlen, best = None, None + for v in vectors: + vlen = v[0]*v[0]+v[1]*v[1] + if bestlen == None or bestlen > vlen: + bestlen = vlen + best = v + return best + +def spanner(size): + canvas = {} + + # The spanner in the config box icon. + + headcentre = 0.5 + round(4*size) + headradius = headcentre + 0.1 + headhighlight = round(1.5*size) + holecentre = 0.5 + round(3*size) + holeradius = round(2*size) + holehighlight = round(1.5*size) + shaftend = 0.5 + round(25*size) + shaftwidth = round(2*size) + shafthighlight = round(1.5*size) + cmax = shaftend + shaftwidth + + # Define three line segments, such that the shortest distance + # vectors from any point to each of these segments determines + # everything we need to know about where it is on the spanner + # shape. + segments = [ + ((0,0), (holecentre, holecentre)), + ((headcentre, headcentre), (headcentre, headcentre)), + ((headcentre+headradius/math.sqrt(2), headcentre+headradius/math.sqrt(2)), + (cmax, cmax)) + ] + + for y in range(int(cmax)): + for x in range(int(cmax)): + vectors = [linedist(a,b,c,d,x,y) for ((a,b),(c,d)) in segments] + dists = [memoisedsqrt(vx*vx+vy*vy) for (vx,vy) in vectors] + + # If the distance to the hole line is less than + # holeradius, we're not part of the spanner. + if dists[0] < holeradius: + continue + # If the distance to the head `line' is less than + # headradius, we are part of the spanner; likewise if + # the distance to the shaft line is less than + # shaftwidth _and_ the resulting shaft point isn't + # beyond the shaft end. + if dists[1] > headradius and \ + (dists[2] > shaftwidth or x+vectors[2][0] >= shaftend): + continue + + # We're part of the spanner. Now compute the highlight + # on this pixel. We do this by computing a `slope + # vector', which points from this pixel in the + # direction of its nearest edge. We store an array of + # slope vectors, in polar coordinates. + angles = [math.atan2(vy,vx) for (vx,vy) in vectors] + slopes = [] + if dists[0] < holeradius + holehighlight: + slopes.append(((dists[0]-holeradius)/holehighlight,angles[0])) + if dists[1]/headradius < dists[2]/shaftwidth: + if dists[1] > headradius - headhighlight and dists[1] < headradius: + slopes.append(((headradius-dists[1])/headhighlight,math.pi+angles[1])) + else: + if dists[2] > shaftwidth - shafthighlight and dists[2] < shaftwidth: + slopes.append(((shaftwidth-dists[2])/shafthighlight,math.pi+angles[2])) + # Now we find the smallest distance in that array, if + # any, and that gives us a notional position on a + # sphere which we can use to compute the final + # highlight level. + bestdist = None + bestangle = 0 + for dist, angle in slopes: + if bestdist == None or bestdist > dist: + bestdist = dist + bestangle = angle + if bestdist == None: + bestdist = 1.0 + sx = (1.0-bestdist) * math.cos(bestangle) + sy = (1.0-bestdist) * math.sin(bestangle) + sz = math.sqrt(1.0 - sx*sx - sy*sy) + shade = sx-sy+sz / math.sqrt(3) # can range from -1 to +1 + shade = 1.0 - (1-shade)/3 + + pixel(x, y, yellowpix(shade), canvas) + + # And draw a border. + border(canvas, size, []) + + return canvas + +def box(size, back): + canvas = {} + + # The back side of the cardboard box in the installer icon. + + boxwidth = round(15 * size) + boxheight = round(12 * size) + boxdepth = round(4 * size) + boxfrontflapheight = round(5 * size) + boxrightflapheight = round(3 * size) + + # Three shades of basically acceptable brown, all achieved by + # halftoning between two of the Windows-16 colours. I'm quite + # pleased that was feasible at all! + dark = halftone(cr, cK) + med = halftone(cr, cy) + light = halftone(cr, cY) + # We define our halftoning parity in such a way that the black + # pixels along the RHS of the visible part of the box back + # match up with the one-pixel black outline around the + # right-hand side of the box. In other words, we want the pixel + # at (-1, boxwidth-1) to be black, and hence the one at (0, + # boxwidth) too. + parityadjust = int(boxwidth) % 2 + + # The entire back of the box. + if back: + for x in range(int(boxwidth + boxdepth)): + ytop = max(-x-1, -boxdepth-1) + ybot = min(boxheight, boxheight+boxwidth-1-x) + for y in range(int(ytop), int(ybot)): + pixel(x, y, dark[(x+y+parityadjust) % 2], canvas) + + # Even when drawing the back of the box, we still draw the + # whole shape, because that means we get the right overall size + # (the flaps make the box front larger than the box back) and + # it'll all be overwritten anyway. + + # The front face of the box. + for x in range(int(boxwidth)): + for y in range(int(boxheight)): + pixel(x, y, med[(x+y+parityadjust) % 2], canvas) + # The right face of the box. + for x in range(int(boxwidth), int(boxwidth+boxdepth)): + ybot = boxheight + boxwidth-x + ytop = ybot - boxheight + for y in range(int(ytop), int(ybot)): + pixel(x, y, dark[(x+y+parityadjust) % 2], canvas) + # The front flap of the box. + for y in range(int(boxfrontflapheight)): + xadj = int(round(-0.5*y)) + for x in range(int(xadj), int(xadj+boxwidth)): + pixel(x, y, light[(x+y+parityadjust) % 2], canvas) + # The right flap of the box. + for x in range(int(boxwidth), int(boxwidth + boxdepth + boxrightflapheight + 1)): + ytop = max(boxwidth - 1 - x, x - boxwidth - 2*boxdepth - 1) + ybot = min(x - boxwidth - 1, boxwidth + 2*boxrightflapheight - 1 - x) + for y in range(int(ytop), int(ybot+1)): + pixel(x, y, med[(x+y+parityadjust) % 2], canvas) + + # And draw a border. + border(canvas, size, [(0, int(boxheight)-1, BL)]) + + return canvas + +def boxback(size): + return box(size, 1) +def boxfront(size): + return box(size, 0) + +# Functions to draw entire icons by composing the above components. + +def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, aux={}): + # Two unspecified objects and a lightning bolt. + + canvas = {} + w = h = round(32 * size) + + bolt = lightning(size) + + # Position c2 against the top right of the icon. + bb = bbox(c2) + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + overlay(c2, w-bb[2], 0-bb[1], canvas) + aux["c2pos"] = (w-bb[2], 0-bb[1]) + # Position c1 against the bottom left of the icon. + bb = bbox(c1) + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + overlay(c1, 0-bb[0], h-bb[3], canvas) + aux["c1pos"] = (0-bb[0], h-bb[3]) + # Place the lightning bolt artistically off-centre. (The + # rationale for this positioning is that it's centred on the + # midpoint between the centres of the two monitors in the PuTTY + # icon proper, but it's not really feasible to _base_ the + # calculation here on that.) + bb = bbox(bolt) + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + overlay(bolt, (w-bb[0]-bb[2])/2 + round(boltoffx*size), \ + (h-bb[1]-bb[3])/2 + round((boltoffy-2)*size), canvas) + + return canvas + +def putty_icon(size): + return xybolt(computer(size), computer(size), size) + +def puttycfg_icon(size): + w = h = round(32 * size) + s = spanner(size) + canvas = putty_icon(size) + # Centre the spanner. + bb = bbox(s) + overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) + return canvas + +def puttygen_icon(size): + return xybolt(computer(size), key(size), size, boltoffx=2) + +def pscp_icon(size): + return xybolt(document(size), computer(size), size) + +def puttyins_icon(size): + aret = {} + # The box back goes behind the lightning bolt. + canvas = xybolt(boxback(size), computer(size), size, boltoffx=-2, boltoffy=+1, aux=aret) + # But the box front goes over the top, so that the lightning + # bolt appears to come _out_ of the box. Here it's useful to + # know the exact coordinates where xybolt placed the box back, + # so we can overlay the box front exactly on top of it. + c1x, c1y = aret["c1pos"] + overlay(boxfront(size), c1x, c1y, canvas) + return canvas + +def pterm_icon(size): + # Just a really big computer. + + canvas = {} + w = h = round(32 * size) + + c = computer(size * 1.4) + + # Centre c in the return canvas. + bb = bbox(c) + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) + + return canvas + +def ptermcfg_icon(size): + w = h = round(32 * size) + s = spanner(size) + canvas = pterm_icon(size) + # Centre the spanner. + bb = bbox(s) + overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) + return canvas + +def pageant_icon(size): + # A biggish computer, in a hat. + + canvas = {} + w = h = round(32 * size) + + c = computer(size * 1.2) + ht = hat(size) + + cbb = bbox(c) + hbb = bbox(ht) + + # Determine the relative y-coordinates of the computer and hat. + # We just centre the one on the other. + xrel = (cbb[0]+cbb[2]-hbb[0]-hbb[2])/2 + + # Determine the relative y-coordinates of the computer and hat. + # We do this by sitting the hat as low down on the computer as + # possible without any computer showing over the top. To do + # this we first have to find the minimum x coordinate at each + # y-coordinate of both components. + cty = topy(c) + hty = topy(ht) + yrelmin = None + for cx in cty.keys(): + hx = cx - xrel + assert hty.has_key(hx) + yrel = cty[cx] - hty[hx] + if yrelmin == None: + yrelmin = yrel + else: + yrelmin = min(yrelmin, yrel) + + # Overlay the hat on the computer. + overlay(ht, xrel, yrelmin, c) + + # And centre the result in the main icon canvas. + bb = bbox(c) + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) + + return canvas + +# Test and output functions. + +import os +import sys + +def testrun(func, fname): + canvases = [] + for size in [0.5, 0.6, 1.0, 1.2, 1.5, 4.0]: + canvases.append(func(size)) + wid = 0 + ht = 0 + for canvas in canvases: + minx, miny, maxx, maxy = bbox(canvas) + wid = max(wid, maxx-minx+4) + ht = ht + maxy-miny+4 + block = [] + for canvas in canvases: + minx, miny, maxx, maxy = bbox(canvas) + block.extend(render(canvas, minx-2, miny-2, minx-2+wid, maxy+2)) + p = os.popen("convert -depth 8 -size %dx%d rgb:- %s" % (wid,ht,fname), "w") + assert len(block) == ht + for line in block: + assert len(line) == wid + for r, g, b, a in line: + # Composite on to orange. + r = int(round((r * a + 255 * (255-a)) / 255.0)) + g = int(round((g * a + 128 * (255-a)) / 255.0)) + b = int(round((b * a + 0 * (255-a)) / 255.0)) + p.write("%c%c%c" % (r,g,b)) + p.close() + +def drawicon(func, width, fname, orangebackground = 0): + canvas = func(width / 32.0) + finalise(canvas) + minx, miny, maxx, maxy = bbox(canvas) + assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width + + block = render(canvas, 0, 0, width, width) + p = os.popen("convert -depth 8 -size %dx%d rgba:- %s" % (width,width,fname), "w") + assert len(block) == width + for line in block: + assert len(line) == width + for r, g, b, a in line: + if orangebackground: + # Composite on to orange. + r = int(round((r * a + 255 * (255-a)) / 255.0)) + g = int(round((g * a + 128 * (255-a)) / 255.0)) + b = int(round((b * a + 0 * (255-a)) / 255.0)) + a = 255 + p.write("%c%c%c%c" % (r,g,b,a)) + p.close() + +args = sys.argv[1:] + +orangebackground = test = 0 +colours = 1 # 0=mono, 1=16col, 2=truecol +doingargs = 1 + +realargs = [] +for arg in args: + if doingargs and arg[0] == "-": + if arg == "-t": + test = 1 + elif arg == "-it": + orangebackground = 1 + elif arg == "-2": + colours = 0 + elif arg == "-T": + colours = 2 + elif arg == "--": + doingargs = 0 + else: + sys.stderr.write("unrecognised option '%s'\n" % arg) + sys.exit(1) + else: + realargs.append(arg) + +if colours == 0: + # Monochrome. + cK=cr=cg=cb=cm=cc=cP=cw=cR=cG=cB=cM=cC=cD = 0 + cY=cy=cW = 1 + cT = -1 + def greypix(value): + return [cK,cW][int(round(value))] + def yellowpix(value): + return [cK,cW][int(round(value))] + def bluepix(value): + return cK + def dark(value): + return [cT,cK][int(round(value))] + def blend(col1, col2): + if col1 == cT: + return col2 + else: + return col1 + pixvals = [ + (0x00, 0x00, 0x00, 0xFF), # cK + (0xFF, 0xFF, 0xFF, 0xFF), # cW + (0x00, 0x00, 0x00, 0x00), # cT + ] + def outpix(colour): + return pixvals[colour] + def finalisepix(colour): + return colour + def halftone(col1, col2): + return (col1, col2) +elif colours == 1: + # Windows 16-colour palette. + cK,cr,cg,cy,cb,cm,cc,cP,cw,cR,cG,cY,cB,cM,cC,cW = range(16) + cT = -1 + cD = -2 # special translucent half-darkening value used internally + def greypix(value): + return [cK,cw,cw,cP,cW][int(round(4*value))] + def yellowpix(value): + return [cK,cy,cY][int(round(2*value))] + def bluepix(value): + return [cK,cb,cB][int(round(2*value))] + def dark(value): + return [cT,cD,cK][int(round(2*value))] + def blend(col1, col2): + if col1 == cT: + return col2 + elif col1 == cD: + return [cK,cK,cK,cK,cK,cK,cK,cw,cK,cr,cg,cy,cb,cm,cc,cw,cD,cD][col2] + else: + return col1 + pixvals = [ + (0x00, 0x00, 0x00, 0xFF), # cK + (0x80, 0x00, 0x00, 0xFF), # cr + (0x00, 0x80, 0x00, 0xFF), # cg + (0x80, 0x80, 0x00, 0xFF), # cy + (0x00, 0x00, 0x80, 0xFF), # cb + (0x80, 0x00, 0x80, 0xFF), # cm + (0x00, 0x80, 0x80, 0xFF), # cc + (0xC0, 0xC0, 0xC0, 0xFF), # cP + (0x80, 0x80, 0x80, 0xFF), # cw + (0xFF, 0x00, 0x00, 0xFF), # cR + (0x00, 0xFF, 0x00, 0xFF), # cG + (0xFF, 0xFF, 0x00, 0xFF), # cY + (0x00, 0x00, 0xFF, 0xFF), # cB + (0xFF, 0x00, 0xFF, 0xFF), # cM + (0x00, 0xFF, 0xFF, 0xFF), # cC + (0xFF, 0xFF, 0xFF, 0xFF), # cW + (0x00, 0x00, 0x00, 0x80), # cD + (0x00, 0x00, 0x00, 0x00), # cT + ] + def outpix(colour): + return pixvals[colour] + def finalisepix(colour): + # cD is used internally, but can't be output. Convert to cK. + if colour == cD: + return cK + return colour + def halftone(col1, col2): + return (col1, col2) +else: + # True colour. + cK = (0x00, 0x00, 0x00, 0xFF) + cr = (0x80, 0x00, 0x00, 0xFF) + cg = (0x00, 0x80, 0x00, 0xFF) + cy = (0x80, 0x80, 0x00, 0xFF) + cb = (0x00, 0x00, 0x80, 0xFF) + cm = (0x80, 0x00, 0x80, 0xFF) + cc = (0x00, 0x80, 0x80, 0xFF) + cP = (0xC0, 0xC0, 0xC0, 0xFF) + cw = (0x80, 0x80, 0x80, 0xFF) + cR = (0xFF, 0x00, 0x00, 0xFF) + cG = (0x00, 0xFF, 0x00, 0xFF) + cY = (0xFF, 0xFF, 0x00, 0xFF) + cB = (0x00, 0x00, 0xFF, 0xFF) + cM = (0xFF, 0x00, 0xFF, 0xFF) + cC = (0x00, 0xFF, 0xFF, 0xFF) + cW = (0xFF, 0xFF, 0xFF, 0xFF) + cD = (0x00, 0x00, 0x00, 0x80) + cT = (0x00, 0x00, 0x00, 0x00) + def greypix(value): + value = max(min(value, 1), 0) + return (int(round(0xFF*value)),) * 3 + (0xFF,) + def yellowpix(value): + value = max(min(value, 1), 0) + return (int(round(0xFF*value)),) * 2 + (0, 0xFF) + def bluepix(value): + value = max(min(value, 1), 0) + return (0, 0, int(round(0xFF*value)), 0xFF) + def dark(value): + value = max(min(value, 1), 0) + return (0, 0, 0, int(round(0xFF*value))) + def blend(col1, col2): + r1,g1,b1,a1 = col1 + r2,g2,b2,a2 = col2 + r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0)) + g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0)) + b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0)) + a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0)) + return r, g, b, a + def outpix(colour): + return colour + if colours == 2: + # True colour with no alpha blending: we still have to + # finalise half-dark pixels to black. + def finalisepix(colour): + if colour[3] > 0: + return colour[:3] + (0xFF,) + return colour + else: + def finalisepix(colour): + return colour + def halftone(col1, col2): + r1,g1,b1,a1 = col1 + r2,g2,b2,a2 = col2 + colret = (int(r1+r2)/2, int(g1+g2)/2, int(b1+b2)/2, int(a1+a2)/2) + return (colret, colret) + +if test: + testrun(eval(realargs[0]), realargs[1]) +else: + drawicon(eval(realargs[0]), int(realargs[1]), realargs[2], orangebackground) diff --git a/putty/IMPORT.C b/putty/IMPORT.C new file mode 100644 index 0000000..1490c64 --- /dev/null +++ b/putty/IMPORT.C @@ -0,0 +1,1715 @@ +/* + * Code for PuTTY to import and export private key files in other + * SSH clients' formats. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "misc.h" + +int openssh_encrypted(const Filename *filename); +struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase, + const char **errmsg_p); +int openssh_write(const Filename *filename, struct ssh2_userkey *key, + char *passphrase); + +int sshcom_encrypted(const Filename *filename, char **comment); +struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase, + const char **errmsg_p); +int sshcom_write(const Filename *filename, struct ssh2_userkey *key, + char *passphrase); + +/* + * Given a key type, determine whether we know how to import it. + */ +int import_possible(int type) +{ + if (type == SSH_KEYTYPE_OPENSSH) + return 1; + if (type == SSH_KEYTYPE_SSHCOM) + return 1; + return 0; +} + +/* + * Given a key type, determine what native key type + * (SSH_KEYTYPE_SSH1 or SSH_KEYTYPE_SSH2) it will come out as once + * we've imported it. + */ +int import_target_type(int type) +{ + /* + * There are no known foreign SSH-1 key formats. + */ + return SSH_KEYTYPE_SSH2; +} + +/* + * Determine whether a foreign key is encrypted. + */ +int import_encrypted(const Filename *filename, int type, char **comment) +{ + if (type == SSH_KEYTYPE_OPENSSH) { + /* OpenSSH doesn't do key comments */ + *comment = dupstr(filename_to_str(filename)); + return openssh_encrypted(filename); + } + if (type == SSH_KEYTYPE_SSHCOM) { + return sshcom_encrypted(filename, comment); + } + return 0; +} + +/* + * Import an SSH-1 key. + */ +int import_ssh1(const Filename *filename, int type, + struct RSAKey *key, char *passphrase, const char **errmsg_p) +{ + return 0; +} + +/* + * Import an SSH-2 key. + */ +struct ssh2_userkey *import_ssh2(const Filename *filename, int type, + char *passphrase, const char **errmsg_p) +{ + if (type == SSH_KEYTYPE_OPENSSH) + return openssh_read(filename, passphrase, errmsg_p); + if (type == SSH_KEYTYPE_SSHCOM) + return sshcom_read(filename, passphrase, errmsg_p); + return NULL; +} + +/* + * Export an SSH-1 key. + */ +int export_ssh1(const Filename *filename, int type, struct RSAKey *key, + char *passphrase) +{ + return 0; +} + +/* + * Export an SSH-2 key. + */ +int export_ssh2(const Filename *filename, int type, + struct ssh2_userkey *key, char *passphrase) +{ + if (type == SSH_KEYTYPE_OPENSSH) + return openssh_write(filename, key, passphrase); + if (type == SSH_KEYTYPE_SSHCOM) + return sshcom_write(filename, key, passphrase); + return 0; +} + +/* + * Strip trailing CRs and LFs at the end of a line of text. + */ +void strip_crlf(char *str) +{ + char *p = str + strlen(str); + + while (p > str && (p[-1] == '\r' || p[-1] == '\n')) + *--p = '\0'; +} + +/* ---------------------------------------------------------------------- + * Helper routines. (The base64 ones are defined in sshpubk.c.) + */ + +#define isbase64(c) ( ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + ((c) >= '0' && (c) <= '9') || \ + (c) == '+' || (c) == '/' || (c) == '=' \ + ) + +/* + * Read an ASN.1/BER identifier and length pair. + * + * Flags are a combination of the #defines listed below. + * + * Returns -1 if unsuccessful; otherwise returns the number of + * bytes used out of the source data. + */ + +/* ASN.1 tag classes. */ +#define ASN1_CLASS_UNIVERSAL (0 << 6) +#define ASN1_CLASS_APPLICATION (1 << 6) +#define ASN1_CLASS_CONTEXT_SPECIFIC (2 << 6) +#define ASN1_CLASS_PRIVATE (3 << 6) +#define ASN1_CLASS_MASK (3 << 6) + +/* Primitive versus constructed bit. */ +#define ASN1_CONSTRUCTED (1 << 5) + +static int ber_read_id_len(void *source, int sourcelen, + int *id, int *length, int *flags) +{ + unsigned char *p = (unsigned char *) source; + + if (sourcelen == 0) + return -1; + + *flags = (*p & 0xE0); + if ((*p & 0x1F) == 0x1F) { + *id = 0; + while (*p & 0x80) { + p++, sourcelen--; + if (sourcelen == 0) + return -1; + *id = (*id << 7) | (*p & 0x7F); + } + p++, sourcelen--; + } else { + *id = *p & 0x1F; + p++, sourcelen--; + } + + if (sourcelen == 0) + return -1; + + if (*p & 0x80) { + int n = *p & 0x7F; + p++, sourcelen--; + if (sourcelen < n) + return -1; + *length = 0; + while (n--) + *length = (*length << 8) | (*p++); + sourcelen -= n; + } else { + *length = *p; + p++, sourcelen--; + } + + return p - (unsigned char *) source; +} + +/* + * Write an ASN.1/BER identifier and length pair. Returns the + * number of bytes consumed. Assumes dest contains enough space. + * Will avoid writing anything if dest is NULL, but still return + * amount of space required. + */ +static int ber_write_id_len(void *dest, int id, int length, int flags) +{ + unsigned char *d = (unsigned char *)dest; + int len = 0; + + if (id <= 30) { + /* + * Identifier is one byte. + */ + len++; + if (d) *d++ = id | flags; + } else { + int n; + /* + * Identifier is multiple bytes: the first byte is 11111 + * plus the flags, and subsequent bytes encode the value of + * the identifier, 7 bits at a time, with the top bit of + * each byte 1 except the last one which is 0. + */ + len++; + if (d) *d++ = 0x1F | flags; + for (n = 1; (id >> (7*n)) > 0; n++) + continue; /* count the bytes */ + while (n--) { + len++; + if (d) *d++ = (n ? 0x80 : 0) | ((id >> (7*n)) & 0x7F); + } + } + + if (length < 128) { + /* + * Length is one byte. + */ + len++; + if (d) *d++ = length; + } else { + int n; + /* + * Length is multiple bytes. The first is 0x80 plus the + * number of subsequent bytes, and the subsequent bytes + * encode the actual length. + */ + for (n = 1; (length >> (8*n)) > 0; n++) + continue; /* count the bytes */ + len++; + if (d) *d++ = 0x80 | n; + while (n--) { + len++; + if (d) *d++ = (length >> (8*n)) & 0xFF; + } + } + + return len; +} + +static int put_string(void *target, void *data, int len) +{ + unsigned char *d = (unsigned char *)target; + + PUT_32BIT(d, len); + memcpy(d+4, data, len); + return len+4; +} + +static int put_mp(void *target, void *data, int len) +{ + unsigned char *d = (unsigned char *)target; + unsigned char *i = (unsigned char *)data; + + if (*i & 0x80) { + PUT_32BIT(d, len+1); + d[4] = 0; + memcpy(d+5, data, len); + return len+5; + } else { + PUT_32BIT(d, len); + memcpy(d+4, data, len); + return len+4; + } +} + +/* Simple structure to point to an mp-int within a blob. */ +struct mpint_pos { void *start; int bytes; }; + +static int ssh2_read_mpint(void *data, int len, struct mpint_pos *ret) +{ + int bytes; + unsigned char *d = (unsigned char *) data; + + if (len < 4) + goto error; + bytes = GET_32BIT(d); + if (len < 4+bytes) + goto error; + + ret->start = d + 4; + ret->bytes = bytes; + return bytes+4; + + error: + ret->start = NULL; + ret->bytes = -1; + return len; /* ensure further calls fail as well */ +} + +/* ---------------------------------------------------------------------- + * Code to read and write OpenSSH private keys. + */ + +enum { OSSH_DSA, OSSH_RSA }; +enum { OSSH_ENC_3DES, OSSH_ENC_AES }; +struct openssh_key { + int type; + int encrypted, encryption; + char iv[32]; + unsigned char *keyblob; + int keyblob_len, keyblob_size; +}; + +static struct openssh_key *load_openssh_key(const Filename *filename, + const char **errmsg_p) +{ + struct openssh_key *ret; + FILE *fp; + char *line = NULL; + char *errmsg, *p; + int headers_done; + char base64_bit[4]; + int base64_chars = 0; + + ret = snew(struct openssh_key); + ret->keyblob = NULL; + ret->keyblob_len = ret->keyblob_size = 0; + ret->encrypted = 0; + memset(ret->iv, 0, sizeof(ret->iv)); + + fp = f_open(*filename, "r", FALSE); + if (!fp) { + errmsg = "unable to open key file"; + goto error; + } + + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (0 != strncmp(line, "-----BEGIN ", 11) || + 0 != strcmp(line+strlen(line)-16, "PRIVATE KEY-----")) { + errmsg = "file does not begin with OpenSSH key header"; + goto error; + } + if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----")) + ret->type = OSSH_RSA; + else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----")) + ret->type = OSSH_DSA; + else { + errmsg = "unrecognised key type"; + goto error; + } + memset(line, 0, strlen(line)); + sfree(line); + line = NULL; + + headers_done = 0; + while (1) { + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (0 == strncmp(line, "-----END ", 9) && + 0 == strcmp(line+strlen(line)-16, "PRIVATE KEY-----")) + break; /* done */ + if ((p = strchr(line, ':')) != NULL) { + if (headers_done) { + errmsg = "header found in body of key data"; + goto error; + } + *p++ = '\0'; + while (*p && isspace((unsigned char)*p)) p++; + if (!strcmp(line, "Proc-Type")) { + if (p[0] != '4' || p[1] != ',') { + errmsg = "Proc-Type is not 4 (only 4 is supported)"; + goto error; + } + p += 2; + if (!strcmp(p, "ENCRYPTED")) + ret->encrypted = 1; + } else if (!strcmp(line, "DEK-Info")) { + int i, j, ivlen; + + if (!strncmp(p, "DES-EDE3-CBC,", 13)) { + ret->encryption = OSSH_ENC_3DES; + ivlen = 8; + } else if (!strncmp(p, "AES-128-CBC,", 12)) { + ret->encryption = OSSH_ENC_AES; + ivlen = 16; + } else { + errmsg = "unsupported cipher"; + goto error; + } + p = strchr(p, ',') + 1;/* always non-NULL, by above checks */ + for (i = 0; i < ivlen; i++) { + if (1 != sscanf(p, "%2x", &j)) { + errmsg = "expected more iv data in DEK-Info"; + goto error; + } + ret->iv[i] = j; + p += 2; + } + if (*p) { + errmsg = "more iv data than expected in DEK-Info"; + goto error; + } + } + } else { + headers_done = 1; + + p = line; + while (isbase64(*p)) { + base64_bit[base64_chars++] = *p; + if (base64_chars == 4) { + unsigned char out[3]; + int len; + + base64_chars = 0; + + len = base64_decode_atom(base64_bit, out); + + if (len <= 0) { + errmsg = "invalid base64 encoding"; + goto error; + } + + if (ret->keyblob_len + len > ret->keyblob_size) { + ret->keyblob_size = ret->keyblob_len + len + 256; + ret->keyblob = sresize(ret->keyblob, ret->keyblob_size, + unsigned char); + } + + memcpy(ret->keyblob + ret->keyblob_len, out, len); + ret->keyblob_len += len; + + memset(out, 0, sizeof(out)); + } + + p++; + } + } + memset(line, 0, strlen(line)); + sfree(line); + line = NULL; + } + + if (ret->keyblob_len == 0 || !ret->keyblob) { + errmsg = "key body not present"; + goto error; + } + + if (ret->encrypted && ret->keyblob_len % 8 != 0) { + errmsg = "encrypted key blob is not a multiple of cipher block size"; + goto error; + } + + memset(base64_bit, 0, sizeof(base64_bit)); + if (errmsg_p) *errmsg_p = NULL; + return ret; + + error: + if (line) { + memset(line, 0, strlen(line)); + sfree(line); + line = NULL; + } + memset(base64_bit, 0, sizeof(base64_bit)); + if (ret) { + if (ret->keyblob) { + memset(ret->keyblob, 0, ret->keyblob_size); + sfree(ret->keyblob); + } + memset(ret, 0, sizeof(*ret)); + sfree(ret); + } + if (errmsg_p) *errmsg_p = errmsg; + return NULL; +} + +int openssh_encrypted(const Filename *filename) +{ + struct openssh_key *key = load_openssh_key(filename, NULL); + int ret; + + if (!key) + return 0; + ret = key->encrypted; + memset(key->keyblob, 0, key->keyblob_size); + sfree(key->keyblob); + memset(key, 0, sizeof(*key)); + sfree(key); + return ret; +} + +struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase, + const char **errmsg_p) +{ + struct openssh_key *key = load_openssh_key(filename, errmsg_p); + struct ssh2_userkey *retkey; + unsigned char *p; + int ret, id, len, flags; + int i, num_integers; + struct ssh2_userkey *retval = NULL; + char *errmsg; + unsigned char *blob; + int blobsize = 0, blobptr, privptr; + char *modptr = NULL; + int modlen = 0; + + blob = NULL; + + if (!key) + return NULL; + + if (key->encrypted) { + /* + * Derive encryption key from passphrase and iv/salt: + * + * - let block A equal MD5(passphrase || iv) + * - let block B equal MD5(A || passphrase || iv) + * - block C would be MD5(B || passphrase || iv) and so on + * - encryption key is the first N bytes of A || B + * + * (Note that only 8 bytes of the iv are used for key + * derivation, even when the key is encrypted with AES and + * hence there are 16 bytes available.) + */ + struct MD5Context md5c; + unsigned char keybuf[32]; + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, (unsigned char *)key->iv, 8); + MD5Final(keybuf, &md5c); + + MD5Init(&md5c); + MD5Update(&md5c, keybuf, 16); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, (unsigned char *)key->iv, 8); + MD5Final(keybuf+16, &md5c); + + /* + * Now decrypt the key blob. + */ + if (key->encryption == OSSH_ENC_3DES) + des3_decrypt_pubkey_ossh(keybuf, (unsigned char *)key->iv, + key->keyblob, key->keyblob_len); + else { + void *ctx; + assert(key->encryption == OSSH_ENC_AES); + ctx = aes_make_context(); + aes128_key(ctx, keybuf); + aes_iv(ctx, (unsigned char *)key->iv); + aes_ssh2_decrypt_blk(ctx, key->keyblob, key->keyblob_len); + aes_free_context(ctx); + } + + memset(&md5c, 0, sizeof(md5c)); + memset(keybuf, 0, sizeof(keybuf)); + } + + /* + * Now we have a decrypted key blob, which contains an ASN.1 + * encoded private key. We must now untangle the ASN.1. + * + * We expect the whole key blob to be formatted as a SEQUENCE + * (0x30 followed by a length code indicating that the rest of + * the blob is part of the sequence). Within that SEQUENCE we + * expect to see a bunch of INTEGERs. What those integers mean + * depends on the key type: + * + * - For RSA, we expect the integers to be 0, n, e, d, p, q, + * dmp1, dmq1, iqmp in that order. (The last three are d mod + * (p-1), d mod (q-1), inverse of q mod p respectively.) + * + * - For DSA, we expect them to be 0, p, q, g, y, x in that + * order. + */ + + p = key->keyblob; + + /* Expect the SEQUENCE header. Take its absence as a failure to decrypt. */ + ret = ber_read_id_len(p, key->keyblob_len, &id, &len, &flags); + p += ret; + if (ret < 0 || id != 16) { + errmsg = "ASN.1 decoding failure"; + retval = SSH2_WRONG_PASSPHRASE; + goto error; + } + + /* Expect a load of INTEGERs. */ + if (key->type == OSSH_RSA) + num_integers = 9; + else if (key->type == OSSH_DSA) + num_integers = 6; + else + num_integers = 0; /* placate compiler warnings */ + + /* + * Space to create key blob in. + */ + blobsize = 256+key->keyblob_len; + blob = snewn(blobsize, unsigned char); + PUT_32BIT(blob, 7); + if (key->type == OSSH_DSA) + memcpy(blob+4, "ssh-dss", 7); + else if (key->type == OSSH_RSA) + memcpy(blob+4, "ssh-rsa", 7); + blobptr = 4+7; + privptr = -1; + + for (i = 0; i < num_integers; i++) { + ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, + &id, &len, &flags); + p += ret; + if (ret < 0 || id != 2 || + key->keyblob+key->keyblob_len-p < len) { + errmsg = "ASN.1 decoding failure"; + retval = SSH2_WRONG_PASSPHRASE; + goto error; + } + + if (i == 0) { + /* + * The first integer should be zero always (I think + * this is some sort of version indication). + */ + if (len != 1 || p[0] != 0) { + errmsg = "version number mismatch"; + goto error; + } + } else if (key->type == OSSH_RSA) { + /* + * Integers 1 and 2 go into the public blob but in the + * opposite order; integers 3, 4, 5 and 8 go into the + * private blob. The other two (6 and 7) are ignored. + */ + if (i == 1) { + /* Save the details for after we deal with number 2. */ + modptr = (char *)p; + modlen = len; + } else if (i != 6 && i != 7) { + PUT_32BIT(blob+blobptr, len); + memcpy(blob+blobptr+4, p, len); + blobptr += 4+len; + if (i == 2) { + PUT_32BIT(blob+blobptr, modlen); + memcpy(blob+blobptr+4, modptr, modlen); + blobptr += 4+modlen; + privptr = blobptr; + } + } + } else if (key->type == OSSH_DSA) { + /* + * Integers 1-4 go into the public blob; integer 5 goes + * into the private blob. + */ + PUT_32BIT(blob+blobptr, len); + memcpy(blob+blobptr+4, p, len); + blobptr += 4+len; + if (i == 4) + privptr = blobptr; + } + + /* Skip past the number. */ + p += len; + } + + /* + * Now put together the actual key. Simplest way to do this is + * to assemble our own key blobs and feed them to the createkey + * functions; this is a bit faffy but it does mean we get all + * the sanity checks for free. + */ + assert(privptr > 0); /* should have bombed by now if not */ + retkey = snew(struct ssh2_userkey); + retkey->alg = (key->type == OSSH_RSA ? &ssh_rsa : &ssh_dss); + retkey->data = retkey->alg->createkey(blob, privptr, + blob+privptr, blobptr-privptr); + if (!retkey->data) { + sfree(retkey); + errmsg = "unable to create key data structure"; + goto error; + } + + retkey->comment = dupstr("imported-openssh-key"); + errmsg = NULL; /* no error */ + retval = retkey; + + error: + if (blob) { + memset(blob, 0, blobsize); + sfree(blob); + } + memset(key->keyblob, 0, key->keyblob_size); + sfree(key->keyblob); + memset(key, 0, sizeof(*key)); + sfree(key); + if (errmsg_p) *errmsg_p = errmsg; + return retval; +} + +int openssh_write(const Filename *filename, struct ssh2_userkey *key, + char *passphrase) +{ + unsigned char *pubblob, *privblob, *spareblob; + int publen, privlen, sparelen = 0; + unsigned char *outblob; + int outlen; + struct mpint_pos numbers[9]; + int nnumbers, pos, len, seqlen, i; + char *header, *footer; + char zero[1]; + unsigned char iv[8]; + int ret = 0; + FILE *fp; + + /* + * Fetch the key blobs. + */ + pubblob = key->alg->public_blob(key->data, &publen); + privblob = key->alg->private_blob(key->data, &privlen); + spareblob = outblob = NULL; + + /* + * Find the sequence of integers to be encoded into the OpenSSH + * key blob, and also decide on the header line. + */ + if (key->alg == &ssh_rsa) { + int pos; + struct mpint_pos n, e, d, p, q, iqmp, dmp1, dmq1; + Bignum bd, bp, bq, bdmp1, bdmq1; + + pos = 4 + GET_32BIT(pubblob); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &e); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &n); + pos = 0; + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &d); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &p); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &q); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &iqmp); + + assert(e.start && iqmp.start); /* can't go wrong */ + + /* We also need d mod (p-1) and d mod (q-1). */ + bd = bignum_from_bytes(d.start, d.bytes); + bp = bignum_from_bytes(p.start, p.bytes); + bq = bignum_from_bytes(q.start, q.bytes); + decbn(bp); + decbn(bq); + bdmp1 = bigmod(bd, bp); + bdmq1 = bigmod(bd, bq); + freebn(bd); + freebn(bp); + freebn(bq); + + dmp1.bytes = (bignum_bitcount(bdmp1)+8)/8; + dmq1.bytes = (bignum_bitcount(bdmq1)+8)/8; + sparelen = dmp1.bytes + dmq1.bytes; + spareblob = snewn(sparelen, unsigned char); + dmp1.start = spareblob; + dmq1.start = spareblob + dmp1.bytes; + for (i = 0; i < dmp1.bytes; i++) + spareblob[i] = bignum_byte(bdmp1, dmp1.bytes-1 - i); + for (i = 0; i < dmq1.bytes; i++) + spareblob[i+dmp1.bytes] = bignum_byte(bdmq1, dmq1.bytes-1 - i); + freebn(bdmp1); + freebn(bdmq1); + + numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0'; + numbers[1] = n; + numbers[2] = e; + numbers[3] = d; + numbers[4] = p; + numbers[5] = q; + numbers[6] = dmp1; + numbers[7] = dmq1; + numbers[8] = iqmp; + + nnumbers = 9; + header = "-----BEGIN RSA PRIVATE KEY-----\n"; + footer = "-----END RSA PRIVATE KEY-----\n"; + } else if (key->alg == &ssh_dss) { + int pos; + struct mpint_pos p, q, g, y, x; + + pos = 4 + GET_32BIT(pubblob); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &p); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &q); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &g); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &y); + pos = 0; + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &x); + + assert(y.start && x.start); /* can't go wrong */ + + numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0'; + numbers[1] = p; + numbers[2] = q; + numbers[3] = g; + numbers[4] = y; + numbers[5] = x; + + nnumbers = 6; + header = "-----BEGIN DSA PRIVATE KEY-----\n"; + footer = "-----END DSA PRIVATE KEY-----\n"; + } else { + assert(0); /* zoinks! */ + exit(1); /* XXX: GCC doesn't understand assert() on some systems. */ + } + + /* + * Now count up the total size of the ASN.1 encoded integers, + * so as to determine the length of the containing SEQUENCE. + */ + len = 0; + for (i = 0; i < nnumbers; i++) { + len += ber_write_id_len(NULL, 2, numbers[i].bytes, 0); + len += numbers[i].bytes; + } + seqlen = len; + /* Now add on the SEQUENCE header. */ + len += ber_write_id_len(NULL, 16, seqlen, ASN1_CONSTRUCTED); + /* Round up to the cipher block size, ensuring we have at least one + * byte of padding (see below). */ + outlen = len; + if (passphrase) + outlen = (outlen+8) &~ 7; + + /* + * Now we know how big outblob needs to be. Allocate it. + */ + outblob = snewn(outlen, unsigned char); + + /* + * And write the data into it. + */ + pos = 0; + pos += ber_write_id_len(outblob+pos, 16, seqlen, ASN1_CONSTRUCTED); + for (i = 0; i < nnumbers; i++) { + pos += ber_write_id_len(outblob+pos, 2, numbers[i].bytes, 0); + memcpy(outblob+pos, numbers[i].start, numbers[i].bytes); + pos += numbers[i].bytes; + } + + /* + * Padding on OpenSSH keys is deterministic. The number of + * padding bytes is always more than zero, and always at most + * the cipher block length. The value of each padding byte is + * equal to the number of padding bytes. So a plaintext that's + * an exact multiple of the block size will be padded with 08 + * 08 08 08 08 08 08 08 (assuming a 64-bit block cipher); a + * plaintext one byte less than a multiple of the block size + * will be padded with just 01. + * + * This enables the OpenSSL key decryption function to strip + * off the padding algorithmically and return the unpadded + * plaintext to the next layer: it looks at the final byte, and + * then expects to find that many bytes at the end of the data + * with the same value. Those are all removed and the rest is + * returned. + */ + assert(pos == len); + while (pos < outlen) { + outblob[pos++] = outlen - len; + } + + /* + * Encrypt the key. + * + * For the moment, we still encrypt our OpenSSH keys using + * old-style 3DES. + */ + if (passphrase) { + /* + * Invent an iv. Then derive encryption key from passphrase + * and iv/salt: + * + * - let block A equal MD5(passphrase || iv) + * - let block B equal MD5(A || passphrase || iv) + * - block C would be MD5(B || passphrase || iv) and so on + * - encryption key is the first N bytes of A || B + */ + struct MD5Context md5c; + unsigned char keybuf[32]; + + for (i = 0; i < 8; i++) iv[i] = random_byte(); + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, iv, 8); + MD5Final(keybuf, &md5c); + + MD5Init(&md5c); + MD5Update(&md5c, keybuf, 16); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, iv, 8); + MD5Final(keybuf+16, &md5c); + + /* + * Now encrypt the key blob. + */ + des3_encrypt_pubkey_ossh(keybuf, iv, outblob, outlen); + + memset(&md5c, 0, sizeof(md5c)); + memset(keybuf, 0, sizeof(keybuf)); + } + + /* + * And save it. We'll use Unix line endings just in case it's + * subsequently transferred in binary mode. + */ + fp = f_open(*filename, "wb", TRUE); /* ensure Unix line endings */ + if (!fp) + goto error; + fputs(header, fp); + if (passphrase) { + fprintf(fp, "Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,"); + for (i = 0; i < 8; i++) + fprintf(fp, "%02X", iv[i]); + fprintf(fp, "\n\n"); + } + base64_encode(fp, outblob, outlen, 64); + fputs(footer, fp); + fclose(fp); + ret = 1; + + error: + if (outblob) { + memset(outblob, 0, outlen); + sfree(outblob); + } + if (spareblob) { + memset(spareblob, 0, sparelen); + sfree(spareblob); + } + if (privblob) { + memset(privblob, 0, privlen); + sfree(privblob); + } + if (pubblob) { + memset(pubblob, 0, publen); + sfree(pubblob); + } + return ret; +} + +/* ---------------------------------------------------------------------- + * Code to read ssh.com private keys. + */ + +/* + * The format of the base64 blob is largely SSH-2-packet-formatted, + * except that mpints are a bit different: they're more like the + * old SSH-1 mpint. You have a 32-bit bit count N, followed by + * (N+7)/8 bytes of data. + * + * So. The blob contains: + * + * - uint32 0x3f6ff9eb (magic number) + * - uint32 size (total blob size) + * - string key-type (see below) + * - string cipher-type (tells you if key is encrypted) + * - string encrypted-blob + * + * (The first size field includes the size field itself and the + * magic number before it. All other size fields are ordinary SSH-2 + * strings, so the size field indicates how much data is to + * _follow_.) + * + * The encrypted blob, once decrypted, contains a single string + * which in turn contains the payload. (This allows padding to be + * added after that string while still making it clear where the + * real payload ends. Also it probably makes for a reasonable + * decryption check.) + * + * The payload blob, for an RSA key, contains: + * - mpint e + * - mpint d + * - mpint n (yes, the public and private stuff is intermixed) + * - mpint u (presumably inverse of p mod q) + * - mpint p (p is the smaller prime) + * - mpint q (q is the larger) + * + * For a DSA key, the payload blob contains: + * - uint32 0 + * - mpint p + * - mpint g + * - mpint q + * - mpint y + * - mpint x + * + * Alternatively, if the parameters are `predefined', that + * (0,p,g,q) sequence can be replaced by a uint32 1 and a string + * containing some predefined parameter specification. *shudder*, + * but I doubt we'll encounter this in real life. + * + * The key type strings are ghastly. The RSA key I looked at had a + * type string of + * + * `if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}' + * + * and the DSA key wasn't much better: + * + * `dl-modp{sign{dsa-nist-sha1},dh{plain}}' + * + * It isn't clear that these will always be the same. I think it + * might be wise just to look at the `if-modn{sign{rsa' and + * `dl-modp{sign{dsa' prefixes. + * + * Finally, the encryption. The cipher-type string appears to be + * either `none' or `3des-cbc'. Looks as if this is SSH-2-style + * 3des-cbc (i.e. outer cbc rather than inner). The key is created + * from the passphrase by means of yet another hashing faff: + * + * - first 16 bytes are MD5(passphrase) + * - next 16 bytes are MD5(passphrase || first 16 bytes) + * - if there were more, they'd be MD5(passphrase || first 32), + * and so on. + */ + +#define SSHCOM_MAGIC_NUMBER 0x3f6ff9eb + +struct sshcom_key { + char comment[256]; /* allowing any length is overkill */ + unsigned char *keyblob; + int keyblob_len, keyblob_size; +}; + +static struct sshcom_key *load_sshcom_key(const Filename *filename, + const char **errmsg_p) +{ + struct sshcom_key *ret; + FILE *fp; + char *line = NULL; + int hdrstart, len; + char *errmsg, *p; + int headers_done; + char base64_bit[4]; + int base64_chars = 0; + + ret = snew(struct sshcom_key); + ret->comment[0] = '\0'; + ret->keyblob = NULL; + ret->keyblob_len = ret->keyblob_size = 0; + + fp = f_open(*filename, "r", FALSE); + if (!fp) { + errmsg = "unable to open key file"; + goto error; + } + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (0 != strcmp(line, "---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----")) { + errmsg = "file does not begin with ssh.com key header"; + goto error; + } + memset(line, 0, strlen(line)); + sfree(line); + line = NULL; + + headers_done = 0; + while (1) { + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (!strcmp(line, "---- END SSH2 ENCRYPTED PRIVATE KEY ----")) + break; /* done */ + if ((p = strchr(line, ':')) != NULL) { + if (headers_done) { + errmsg = "header found in body of key data"; + goto error; + } + *p++ = '\0'; + while (*p && isspace((unsigned char)*p)) p++; + hdrstart = p - line; + + /* + * Header lines can end in a trailing backslash for + * continuation. + */ + len = hdrstart + strlen(line+hdrstart); + assert(!line[len]); + while (line[len-1] == '\\') { + char *line2; + int line2len; + + line2 = fgetline(fp); + if (!line2) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line2); + + line2len = strlen(line2); + line = sresize(line, len + line2len + 1, char); + strcpy(line + len - 1, line2); + len += line2len - 1; + assert(!line[len]); + + memset(line2, 0, strlen(line2)); + sfree(line2); + line2 = NULL; + } + p = line + hdrstart; + strip_crlf(p); + if (!strcmp(line, "Comment")) { + /* Strip quotes in comment if present. */ + if (p[0] == '"' && p[strlen(p)-1] == '"') { + p++; + p[strlen(p)-1] = '\0'; + } + strncpy(ret->comment, p, sizeof(ret->comment)); + ret->comment[sizeof(ret->comment)-1] = '\0'; + } + } else { + headers_done = 1; + + p = line; + while (isbase64(*p)) { + base64_bit[base64_chars++] = *p; + if (base64_chars == 4) { + unsigned char out[3]; + + base64_chars = 0; + + len = base64_decode_atom(base64_bit, out); + + if (len <= 0) { + errmsg = "invalid base64 encoding"; + goto error; + } + + if (ret->keyblob_len + len > ret->keyblob_size) { + ret->keyblob_size = ret->keyblob_len + len + 256; + ret->keyblob = sresize(ret->keyblob, ret->keyblob_size, + unsigned char); + } + + memcpy(ret->keyblob + ret->keyblob_len, out, len); + ret->keyblob_len += len; + } + + p++; + } + } + memset(line, 0, strlen(line)); + sfree(line); + line = NULL; + } + + if (ret->keyblob_len == 0 || !ret->keyblob) { + errmsg = "key body not present"; + goto error; + } + + if (errmsg_p) *errmsg_p = NULL; + return ret; + + error: + if (line) { + memset(line, 0, strlen(line)); + sfree(line); + line = NULL; + } + if (ret) { + if (ret->keyblob) { + memset(ret->keyblob, 0, ret->keyblob_size); + sfree(ret->keyblob); + } + memset(ret, 0, sizeof(*ret)); + sfree(ret); + } + if (errmsg_p) *errmsg_p = errmsg; + return NULL; +} + +int sshcom_encrypted(const Filename *filename, char **comment) +{ + struct sshcom_key *key = load_sshcom_key(filename, NULL); + int pos, len, answer; + + *comment = NULL; + if (!key) + return 0; + + /* + * Check magic number. + */ + if (GET_32BIT(key->keyblob) != 0x3f6ff9eb) + return 0; /* key is invalid */ + + /* + * Find the cipher-type string. + */ + answer = 0; + pos = 8; + if (key->keyblob_len < pos+4) + goto done; /* key is far too short */ + pos += 4 + GET_32BIT(key->keyblob + pos); /* skip key type */ + if (key->keyblob_len < pos+4) + goto done; /* key is far too short */ + len = GET_32BIT(key->keyblob + pos); /* find cipher-type length */ + if (key->keyblob_len < pos+4+len) + goto done; /* cipher type string is incomplete */ + if (len != 4 || 0 != memcmp(key->keyblob + pos + 4, "none", 4)) + answer = 1; + + done: + *comment = dupstr(key->comment); + memset(key->keyblob, 0, key->keyblob_size); + sfree(key->keyblob); + memset(key, 0, sizeof(*key)); + sfree(key); + return answer; +} + +static int sshcom_read_mpint(void *data, int len, struct mpint_pos *ret) +{ + int bits; + int bytes; + unsigned char *d = (unsigned char *) data; + + if (len < 4) + goto error; + bits = GET_32BIT(d); + + bytes = (bits + 7) / 8; + if (len < 4+bytes) + goto error; + + ret->start = d + 4; + ret->bytes = bytes; + return bytes+4; + + error: + ret->start = NULL; + ret->bytes = -1; + return len; /* ensure further calls fail as well */ +} + +static int sshcom_put_mpint(void *target, void *data, int len) +{ + unsigned char *d = (unsigned char *)target; + unsigned char *i = (unsigned char *)data; + int bits = len * 8 - 1; + + while (bits > 0) { + if (*i & (1 << (bits & 7))) + break; + if (!(bits-- & 7)) + i++, len--; + } + + PUT_32BIT(d, bits+1); + memcpy(d+4, i, len); + return len+4; +} + +struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase, + const char **errmsg_p) +{ + struct sshcom_key *key = load_sshcom_key(filename, errmsg_p); + char *errmsg; + int pos, len; + const char prefix_rsa[] = "if-modn{sign{rsa"; + const char prefix_dsa[] = "dl-modp{sign{dsa"; + enum { RSA, DSA } type; + int encrypted; + char *ciphertext; + int cipherlen; + struct ssh2_userkey *ret = NULL, *retkey; + const struct ssh_signkey *alg; + unsigned char *blob = NULL; + int blobsize = 0, publen, privlen; + + if (!key) + return NULL; + + /* + * Check magic number. + */ + if (GET_32BIT(key->keyblob) != SSHCOM_MAGIC_NUMBER) { + errmsg = "key does not begin with magic number"; + goto error; + } + + /* + * Determine the key type. + */ + pos = 8; + if (key->keyblob_len < pos+4 || + (len = GET_32BIT(key->keyblob + pos)) > key->keyblob_len - pos - 4) { + errmsg = "key blob does not contain a key type string"; + goto error; + } + if (len > sizeof(prefix_rsa) - 1 && + !memcmp(key->keyblob+pos+4, prefix_rsa, sizeof(prefix_rsa) - 1)) { + type = RSA; + } else if (len > sizeof(prefix_dsa) - 1 && + !memcmp(key->keyblob+pos+4, prefix_dsa, sizeof(prefix_dsa) - 1)) { + type = DSA; + } else { + errmsg = "key is of unknown type"; + goto error; + } + pos += 4+len; + + /* + * Determine the cipher type. + */ + if (key->keyblob_len < pos+4 || + (len = GET_32BIT(key->keyblob + pos)) > key->keyblob_len - pos - 4) { + errmsg = "key blob does not contain a cipher type string"; + goto error; + } + if (len == 4 && !memcmp(key->keyblob+pos+4, "none", 4)) + encrypted = 0; + else if (len == 8 && !memcmp(key->keyblob+pos+4, "3des-cbc", 8)) + encrypted = 1; + else { + errmsg = "key encryption is of unknown type"; + goto error; + } + pos += 4+len; + + /* + * Get hold of the encrypted part of the key. + */ + if (key->keyblob_len < pos+4 || + (len = GET_32BIT(key->keyblob + pos)) > key->keyblob_len - pos - 4) { + errmsg = "key blob does not contain actual key data"; + goto error; + } + ciphertext = (char *)key->keyblob + pos + 4; + cipherlen = len; + if (cipherlen == 0) { + errmsg = "length of key data is zero"; + goto error; + } + + /* + * Decrypt it if necessary. + */ + if (encrypted) { + /* + * Derive encryption key from passphrase and iv/salt: + * + * - let block A equal MD5(passphrase) + * - let block B equal MD5(passphrase || A) + * - block C would be MD5(passphrase || A || B) and so on + * - encryption key is the first N bytes of A || B + */ + struct MD5Context md5c; + unsigned char keybuf[32], iv[8]; + + if (cipherlen % 8 != 0) { + errmsg = "encrypted part of key is not a multiple of cipher block" + " size"; + goto error; + } + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Final(keybuf, &md5c); + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, keybuf, 16); + MD5Final(keybuf+16, &md5c); + + /* + * Now decrypt the key blob. + */ + memset(iv, 0, sizeof(iv)); + des3_decrypt_pubkey_ossh(keybuf, iv, (unsigned char *)ciphertext, + cipherlen); + + memset(&md5c, 0, sizeof(md5c)); + memset(keybuf, 0, sizeof(keybuf)); + + /* + * Hereafter we return WRONG_PASSPHRASE for any parsing + * error. (But only if we've just tried to decrypt it! + * Returning WRONG_PASSPHRASE for an unencrypted key is + * automatic doom.) + */ + if (encrypted) + ret = SSH2_WRONG_PASSPHRASE; + } + + /* + * Strip away the containing string to get to the real meat. + */ + len = GET_32BIT(ciphertext); + if (len < 0 || len > cipherlen-4) { + errmsg = "containing string was ill-formed"; + goto error; + } + ciphertext += 4; + cipherlen = len; + + /* + * Now we break down into RSA versus DSA. In either case we'll + * construct public and private blobs in our own format, and + * end up feeding them to alg->createkey(). + */ + blobsize = cipherlen + 256; + blob = snewn(blobsize, unsigned char); + privlen = 0; + if (type == RSA) { + struct mpint_pos n, e, d, u, p, q; + int pos = 0; + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &e); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &d); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &n); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &u); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &p); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &q); + if (!q.start) { + errmsg = "key data did not contain six integers"; + goto error; + } + + alg = &ssh_rsa; + pos = 0; + pos += put_string(blob+pos, "ssh-rsa", 7); + pos += put_mp(blob+pos, e.start, e.bytes); + pos += put_mp(blob+pos, n.start, n.bytes); + publen = pos; + pos += put_string(blob+pos, d.start, d.bytes); + pos += put_mp(blob+pos, q.start, q.bytes); + pos += put_mp(blob+pos, p.start, p.bytes); + pos += put_mp(blob+pos, u.start, u.bytes); + privlen = pos - publen; + } else if (type == DSA) { + struct mpint_pos p, q, g, x, y; + int pos = 4; + if (GET_32BIT(ciphertext) != 0) { + errmsg = "predefined DSA parameters not supported"; + goto error; + } + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &p); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &g); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &q); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &y); + pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &x); + if (!x.start) { + errmsg = "key data did not contain five integers"; + goto error; + } + + alg = &ssh_dss; + pos = 0; + pos += put_string(blob+pos, "ssh-dss", 7); + pos += put_mp(blob+pos, p.start, p.bytes); + pos += put_mp(blob+pos, q.start, q.bytes); + pos += put_mp(blob+pos, g.start, g.bytes); + pos += put_mp(blob+pos, y.start, y.bytes); + publen = pos; + pos += put_mp(blob+pos, x.start, x.bytes); + privlen = pos - publen; + } else + return NULL; + + assert(privlen > 0); /* should have bombed by now if not */ + + retkey = snew(struct ssh2_userkey); + retkey->alg = alg; + retkey->data = alg->createkey(blob, publen, blob+publen, privlen); + if (!retkey->data) { + sfree(retkey); + errmsg = "unable to create key data structure"; + goto error; + } + retkey->comment = dupstr(key->comment); + + errmsg = NULL; /* no error */ + ret = retkey; + + error: + if (blob) { + memset(blob, 0, blobsize); + sfree(blob); + } + memset(key->keyblob, 0, key->keyblob_size); + sfree(key->keyblob); + memset(key, 0, sizeof(*key)); + sfree(key); + if (errmsg_p) *errmsg_p = errmsg; + return ret; +} + +int sshcom_write(const Filename *filename, struct ssh2_userkey *key, + char *passphrase) +{ + unsigned char *pubblob, *privblob; + int publen, privlen; + unsigned char *outblob; + int outlen; + struct mpint_pos numbers[6]; + int nnumbers, initial_zero, pos, lenpos, i; + char *type; + char *ciphertext; + int cipherlen; + int ret = 0; + FILE *fp; + + /* + * Fetch the key blobs. + */ + pubblob = key->alg->public_blob(key->data, &publen); + privblob = key->alg->private_blob(key->data, &privlen); + outblob = NULL; + + /* + * Find the sequence of integers to be encoded into the OpenSSH + * key blob, and also decide on the header line. + */ + if (key->alg == &ssh_rsa) { + int pos; + struct mpint_pos n, e, d, p, q, iqmp; + + pos = 4 + GET_32BIT(pubblob); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &e); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &n); + pos = 0; + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &d); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &p); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &q); + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &iqmp); + + assert(e.start && iqmp.start); /* can't go wrong */ + + numbers[0] = e; + numbers[1] = d; + numbers[2] = n; + numbers[3] = iqmp; + numbers[4] = q; + numbers[5] = p; + + nnumbers = 6; + initial_zero = 0; + type = "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}"; + } else if (key->alg == &ssh_dss) { + int pos; + struct mpint_pos p, q, g, y, x; + + pos = 4 + GET_32BIT(pubblob); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &p); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &q); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &g); + pos += ssh2_read_mpint(pubblob+pos, publen-pos, &y); + pos = 0; + pos += ssh2_read_mpint(privblob+pos, privlen-pos, &x); + + assert(y.start && x.start); /* can't go wrong */ + + numbers[0] = p; + numbers[1] = g; + numbers[2] = q; + numbers[3] = y; + numbers[4] = x; + + nnumbers = 5; + initial_zero = 1; + type = "dl-modp{sign{dsa-nist-sha1},dh{plain}}"; + } else { + assert(0); /* zoinks! */ + exit(1); /* XXX: GCC doesn't understand assert() on some systems. */ + } + + /* + * Total size of key blob will be somewhere under 512 plus + * combined length of integers. We'll calculate the more + * precise size as we construct the blob. + */ + outlen = 512; + for (i = 0; i < nnumbers; i++) + outlen += 4 + numbers[i].bytes; + outblob = snewn(outlen, unsigned char); + + /* + * Create the unencrypted key blob. + */ + pos = 0; + PUT_32BIT(outblob+pos, SSHCOM_MAGIC_NUMBER); pos += 4; + pos += 4; /* length field, fill in later */ + pos += put_string(outblob+pos, type, strlen(type)); + { + char *ciphertype = passphrase ? "3des-cbc" : "none"; + pos += put_string(outblob+pos, ciphertype, strlen(ciphertype)); + } + lenpos = pos; /* remember this position */ + pos += 4; /* encrypted-blob size */ + pos += 4; /* encrypted-payload size */ + if (initial_zero) { + PUT_32BIT(outblob+pos, 0); + pos += 4; + } + for (i = 0; i < nnumbers; i++) + pos += sshcom_put_mpint(outblob+pos, + numbers[i].start, numbers[i].bytes); + /* Now wrap up the encrypted payload. */ + PUT_32BIT(outblob+lenpos+4, pos - (lenpos+8)); + /* Pad encrypted blob to a multiple of cipher block size. */ + if (passphrase) { + int padding = -(pos - (lenpos+4)) & 7; + while (padding--) + outblob[pos++] = random_byte(); + } + ciphertext = (char *)outblob+lenpos+4; + cipherlen = pos - (lenpos+4); + assert(!passphrase || cipherlen % 8 == 0); + /* Wrap up the encrypted blob string. */ + PUT_32BIT(outblob+lenpos, cipherlen); + /* And finally fill in the total length field. */ + PUT_32BIT(outblob+4, pos); + + assert(pos < outlen); + + /* + * Encrypt the key. + */ + if (passphrase) { + /* + * Derive encryption key from passphrase and iv/salt: + * + * - let block A equal MD5(passphrase) + * - let block B equal MD5(passphrase || A) + * - block C would be MD5(passphrase || A || B) and so on + * - encryption key is the first N bytes of A || B + */ + struct MD5Context md5c; + unsigned char keybuf[32], iv[8]; + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Final(keybuf, &md5c); + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Update(&md5c, keybuf, 16); + MD5Final(keybuf+16, &md5c); + + /* + * Now decrypt the key blob. + */ + memset(iv, 0, sizeof(iv)); + des3_encrypt_pubkey_ossh(keybuf, iv, (unsigned char *)ciphertext, + cipherlen); + + memset(&md5c, 0, sizeof(md5c)); + memset(keybuf, 0, sizeof(keybuf)); + } + + /* + * And save it. We'll use Unix line endings just in case it's + * subsequently transferred in binary mode. + */ + fp = f_open(*filename, "wb", TRUE); /* ensure Unix line endings */ + if (!fp) + goto error; + fputs("---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); + fprintf(fp, "Comment: \""); + /* + * Comment header is broken with backslash-newline if it goes + * over 70 chars. Although it's surrounded by quotes, it + * _doesn't_ escape backslashes or quotes within the string. + * Don't ask me, I didn't design it. + */ + { + int slen = 60; /* starts at 60 due to "Comment: " */ + char *c = key->comment; + while ((int)strlen(c) > slen) { + fprintf(fp, "%.*s\\\n", slen, c); + c += slen; + slen = 70; /* allow 70 chars on subsequent lines */ + } + fprintf(fp, "%s\"\n", c); + } + base64_encode(fp, outblob, pos, 70); + fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); + fclose(fp); + ret = 1; + + error: + if (outblob) { + memset(outblob, 0, outlen); + sfree(outblob); + } + if (privblob) { + memset(privblob, 0, privlen); + sfree(privblob); + } + if (pubblob) { + memset(pubblob, 0, publen); + sfree(pubblob); + } + return ret; +} diff --git a/putty/INT64.C b/putty/INT64.C new file mode 100644 index 0000000..6f24bce --- /dev/null +++ b/putty/INT64.C @@ -0,0 +1,175 @@ +/* + * Handling of the int64 and uint64 types. Done in 32-bit integers, + * for (pre-C99) portability. Hopefully once C99 becomes widespread + * we can kiss this lot goodbye... + */ + +#include +#include + +#include "int64.h" + +uint64 uint64_div10(uint64 x, int *remainder) +{ + uint64 y; + unsigned int rem, r2; + y.hi = x.hi / 10; + y.lo = x.lo / 10; + rem = x.lo % 10; + /* + * Now we have to add in the remainder left over from x.hi. + */ + r2 = x.hi % 10; + y.lo += r2 * 429496729; + rem += r2 * 6; + y.lo += rem / 10; + rem %= 10; + + if (remainder) + *remainder = rem; + return y; +} + +void uint64_decimal(uint64 x, char *buffer) +{ + char buf[20]; + int start = 20; + int d; + + do { + x = uint64_div10(x, &d); + assert(start > 0); + buf[--start] = d + '0'; + } while (x.hi || x.lo); + + memcpy(buffer, buf + start, sizeof(buf) - start); + buffer[sizeof(buf) - start] = '\0'; +} + +uint64 uint64_make(unsigned long hi, unsigned long lo) +{ + uint64 y; + y.hi = hi & 0xFFFFFFFFU; + y.lo = lo & 0xFFFFFFFFU; + return y; +} + +uint64 uint64_add(uint64 x, uint64 y) +{ + x.lo = (x.lo + y.lo) & 0xFFFFFFFFU; + x.hi += y.hi + (x.lo < y.lo ? 1 : 0); + return x; +} + +uint64 uint64_add32(uint64 x, unsigned long y) +{ + uint64 yy; + yy.hi = 0; + yy.lo = y; + return uint64_add(x, yy); +} + +int uint64_compare(uint64 x, uint64 y) +{ + if (x.hi != y.hi) + return x.hi < y.hi ? -1 : +1; + if (x.lo != y.lo) + return x.lo < y.lo ? -1 : +1; + return 0; +} + +uint64 uint64_subtract(uint64 x, uint64 y) +{ + x.lo = (x.lo - y.lo) & 0xFFFFFFFFU; + x.hi = (x.hi - y.hi - (x.lo > (y.lo ^ 0xFFFFFFFFU) ? 1 : 0)) & 0xFFFFFFFFU; + return x; +} + +double uint64_to_double(uint64 x) +{ + return (4294967296.0 * x.hi) + (double)x.lo; +} + +uint64 uint64_shift_right(uint64 x, int shift) +{ + if (shift < 32) { + x.lo >>= shift; + x.lo |= (x.hi << (32-shift)) & 0xFFFFFFFFU; + x.hi >>= shift; + } else { + x.lo = x.hi >> (shift-32); + x.hi = 0; + } + return x; +} + +uint64 uint64_shift_left(uint64 x, int shift) +{ + if (shift < 32) { + x.hi = (x.hi << shift) & 0xFFFFFFFFU; + x.hi |= (x.lo >> (32-shift)); + x.lo = (x.lo << shift) & 0xFFFFFFFFU; + } else { + x.hi = (x.lo << (shift-32)) & 0xFFFFFFFFU; + x.lo = 0; + } + return x; +} + +uint64 uint64_from_decimal(char *str) +{ + uint64 ret; + ret.hi = ret.lo = 0; + while (*str >= '0' && *str <= '9') { + ret = uint64_add(uint64_shift_left(ret, 3), + uint64_shift_left(ret, 1)); + ret = uint64_add32(ret, *str - '0'); + str++; + } + return ret; +} + +#ifdef TESTMODE + +#include + +int main(void) +{ + uint64 x, y, z; + char buf[80]; + + x = uint64_make(0x3456789AUL, 0xDEF01234UL); + printf("%08lx.%08lx\n", x.hi, x.lo); + uint64_decimal(x, buf); + printf("%s\n", buf); + + y = uint64_add32(x, 0xFFFFFFFFU); + printf("%08lx.%08lx\n", y.hi, y.lo); + uint64_decimal(y, buf); + printf("%s\n", buf); + + z = uint64_subtract(y, x); + printf("%08lx.%08lx\n", z.hi, z.lo); + uint64_decimal(z, buf); + printf("%s\n", buf); + + z = uint64_subtract(x, y); + printf("%08lx.%08lx\n", z.hi, z.lo); + uint64_decimal(z, buf); + printf("%s\n", buf); + + y = uint64_shift_right(x, 4); + printf("%08lx.%08lx\n", y.hi, y.lo); + + y = uint64_shift_right(x, 36); + printf("%08lx.%08lx\n", y.hi, y.lo); + + y = uint64_shift_left(x, 4); + printf("%08lx.%08lx\n", x.hi, x.lo); + + y = uint64_shift_left(x, 36); + printf("%08lx.%08lx\n", x.hi, x.lo); + + return 0; +} +#endif diff --git a/putty/INT64.H b/putty/INT64.H new file mode 100644 index 0000000..63df3a9 --- /dev/null +++ b/putty/INT64.H @@ -0,0 +1,24 @@ +/* + * Header for int64.c. + */ + +#ifndef PUTTY_INT64_H +#define PUTTY_INT64_H + +typedef struct { + unsigned long hi, lo; +} uint64; + +uint64 uint64_div10(uint64 x, int *remainder); +void uint64_decimal(uint64 x, char *buffer); +uint64 uint64_make(unsigned long hi, unsigned long lo); +uint64 uint64_add(uint64 x, uint64 y); +uint64 uint64_add32(uint64 x, unsigned long y); +int uint64_compare(uint64 x, uint64 y); +uint64 uint64_subtract(uint64 x, uint64 y); +double uint64_to_double(uint64 x); +uint64 uint64_shift_right(uint64 x, int shift); +uint64 uint64_shift_left(uint64 x, int shift); +uint64 uint64_from_decimal(char *str); + +#endif diff --git a/putty/LDISC.C b/putty/LDISC.C new file mode 100644 index 0000000..119a02a --- /dev/null +++ b/putty/LDISC.C @@ -0,0 +1,336 @@ +/* + * ldisc.c: PuTTY line discipline. Sits between the input coming + * from keypresses in the window, and the output channel leading to + * the back end. Implements echo and/or local line editing, + * depending on what's currently configured. + */ + +#include +#include + +#include "putty.h" +#include "terminal.h" +#include "ldisc.h" + +#define ECHOING (ldisc->cfg->localecho == FORCE_ON || \ + (ldisc->cfg->localecho == AUTO && \ + (ldisc->back->ldisc(ldisc->backhandle, LD_ECHO) || \ + term_ldisc(ldisc->term, LD_ECHO)))) +#define EDITING (ldisc->cfg->localedit == FORCE_ON || \ + (ldisc->cfg->localedit == AUTO && \ + (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \ + term_ldisc(ldisc->term, LD_EDIT)))) + +static void c_write(Ldisc ldisc, char *buf, int len) +{ + from_backend(ldisc->frontend, 0, buf, len); +} + +static int plen(Ldisc ldisc, unsigned char c) +{ + if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term))) + return 1; + else if (c < 128) + return 2; /* ^x for some x */ + else if (in_utf(ldisc->term) && c >= 0xC0) + return 1; /* UTF-8 introducer character + * (FIXME: combining / wide chars) */ + else if (in_utf(ldisc->term) && c >= 0x80 && c < 0xC0) + return 0; /* UTF-8 followup character */ + else + return 4; /* hex representation */ +} + +static void pwrite(Ldisc ldisc, unsigned char c) +{ + if ((c >= 32 && c <= 126) || + (!in_utf(ldisc->term) && c >= 0xA0) || + (in_utf(ldisc->term) && c >= 0x80)) { + c_write(ldisc, (char *)&c, 1); + } else if (c < 128) { + char cc[2]; + cc[1] = (c == 127 ? '?' : c + 0x40); + cc[0] = '^'; + c_write(ldisc, cc, 2); + } else { + char cc[5]; + sprintf(cc, "<%02X>", c); + c_write(ldisc, cc, 4); + } +} + +static int char_start(Ldisc ldisc, unsigned char c) +{ + if (in_utf(ldisc->term)) + return (c < 0x80 || c >= 0xC0); + else + return 1; +} + +static void bsb(Ldisc ldisc, int n) +{ + while (n--) + c_write(ldisc, "\010 \010", 3); +} + +#define CTRL(x) (x^'@') +#define KCTRL(x) ((x^'@') | 0x100) + +void *ldisc_create(Config *mycfg, Terminal *term, + Backend *back, void *backhandle, + void *frontend) +{ + Ldisc ldisc = snew(struct ldisc_tag); + + ldisc->buf = NULL; + ldisc->buflen = 0; + ldisc->bufsiz = 0; + ldisc->quotenext = 0; + + ldisc->cfg = mycfg; + ldisc->back = back; + ldisc->backhandle = backhandle; + ldisc->term = term; + ldisc->frontend = frontend; + + /* Link ourselves into the backend and the terminal */ + if (term) + term->ldisc = ldisc; + if (back) + back->provide_ldisc(backhandle, ldisc); + + return ldisc; +} + +void ldisc_free(void *handle) +{ + Ldisc ldisc = (Ldisc) handle; + + if (ldisc->term) + ldisc->term->ldisc = NULL; + if (ldisc->back) + ldisc->back->provide_ldisc(ldisc->backhandle, NULL); + if (ldisc->buf) + sfree(ldisc->buf); + sfree(ldisc); +} + +void ldisc_send(void *handle, char *buf, int len, int interactive) +{ + Ldisc ldisc = (Ldisc) handle; + int keyflag = 0; + /* + * Called with len=0 when the options change. We must inform + * the front end in case it needs to know. + */ + if (len == 0) { + ldisc_update(ldisc->frontend, ECHOING, EDITING); + return; + } + /* + * Notify the front end that something was pressed, in case + * it's depending on finding out (e.g. keypress termination for + * Close On Exit). + */ + frontend_keypress(ldisc->frontend); + + /* + * Less than zero means null terminated special string. + */ + if (len < 0) { + len = strlen(buf); + keyflag = KCTRL('@'); + } + /* + * Either perform local editing, or just send characters. + */ + if (EDITING) { + while (len--) { + int c; + c = (unsigned char)(*buf++) + keyflag; + if (!interactive && c == '\r') + c += KCTRL('@'); + switch (ldisc->quotenext ? ' ' : c) { + /* + * ^h/^?: delete, and output BSBs, to return to + * last character boundary (in UTF-8 mode this may + * be more than one byte) + * ^w: delete, and output BSBs, to return to last + * space/nonspace boundary + * ^u: delete, and output BSBs, to return to BOL + * ^c: Do a ^u then send a telnet IP + * ^z: Do a ^u then send a telnet SUSP + * ^\: Do a ^u then send a telnet ABORT + * ^r: echo "^R\n" and redraw line + * ^v: quote next char + * ^d: if at BOL, end of file and close connection, + * else send line and reset to BOL + * ^m: send line-plus-\r\n and reset to BOL + */ + case KCTRL('H'): + case KCTRL('?'): /* backspace/delete */ + if (ldisc->buflen > 0) { + do { + if (ECHOING) + bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); + ldisc->buflen--; + } while (!char_start(ldisc, ldisc->buf[ldisc->buflen])); + } + break; + case CTRL('W'): /* delete word */ + while (ldisc->buflen > 0) { + if (ECHOING) + bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); + ldisc->buflen--; + if (ldisc->buflen > 0 && + isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) && + !isspace((unsigned char)ldisc->buf[ldisc->buflen])) + break; + } + break; + case CTRL('U'): /* delete line */ + case CTRL('C'): /* Send IP */ + case CTRL('\\'): /* Quit */ + case CTRL('Z'): /* Suspend */ + while (ldisc->buflen > 0) { + if (ECHOING) + bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); + ldisc->buflen--; + } + ldisc->back->special(ldisc->backhandle, TS_EL); + /* + * We don't send IP, SUSP or ABORT if the user has + * configured telnet specials off! This breaks + * talkers otherwise. + */ + if (!ldisc->cfg->telnet_keyboard) + goto default_case; + if (c == CTRL('C')) + ldisc->back->special(ldisc->backhandle, TS_IP); + if (c == CTRL('Z')) + ldisc->back->special(ldisc->backhandle, TS_SUSP); + if (c == CTRL('\\')) + ldisc->back->special(ldisc->backhandle, TS_ABORT); + break; + case CTRL('R'): /* redraw line */ + if (ECHOING) { + int i; + c_write(ldisc, "^R\r\n", 4); + for (i = 0; i < ldisc->buflen; i++) + pwrite(ldisc, ldisc->buf[i]); + } + break; + case CTRL('V'): /* quote next char */ + ldisc->quotenext = TRUE; + break; + case CTRL('D'): /* logout or send */ + if (ldisc->buflen == 0) { + ldisc->back->special(ldisc->backhandle, TS_EOF); + } else { + ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); + ldisc->buflen = 0; + } + break; + /* + * This particularly hideous bit of code from RDB + * allows ordinary ^M^J to do the same thing as + * magic-^M when in Raw protocol. The line `case + * KCTRL('M'):' is _inside_ the if block. Thus: + * + * - receiving regular ^M goes straight to the + * default clause and inserts as a literal ^M. + * - receiving regular ^J _not_ directly after a + * literal ^M (or not in Raw protocol) fails the + * if condition, leaps to the bottom of the if, + * and falls through into the default clause + * again. + * - receiving regular ^J just after a literal ^M + * in Raw protocol passes the if condition, + * deletes the literal ^M, and falls through + * into the magic-^M code + * - receiving a magic-^M empties the line buffer, + * signals end-of-line in one of the various + * entertaining ways, and _doesn't_ fall out of + * the bottom of the if and through to the + * default clause because of the break. + */ + case CTRL('J'): + if (ldisc->cfg->protocol == PROT_RAW && + ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') { + if (ECHOING) + bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); + ldisc->buflen--; + /* FALLTHROUGH */ + case KCTRL('M'): /* send with newline */ + if (ldisc->buflen > 0) + ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); + if (ldisc->cfg->protocol == PROT_RAW) + ldisc->back->send(ldisc->backhandle, "\r\n", 2); + else if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->telnet_newline) + ldisc->back->special(ldisc->backhandle, TS_EOL); + else + ldisc->back->send(ldisc->backhandle, "\r", 1); + if (ECHOING) + c_write(ldisc, "\r\n", 2); + ldisc->buflen = 0; + break; + } + /* FALLTHROUGH */ + default: /* get to this label from ^V handler */ + default_case: + if (ldisc->buflen >= ldisc->bufsiz) { + ldisc->bufsiz = ldisc->buflen + 256; + ldisc->buf = sresize(ldisc->buf, ldisc->bufsiz, char); + } + ldisc->buf[ldisc->buflen++] = c; + if (ECHOING) + pwrite(ldisc, (unsigned char) c); + ldisc->quotenext = FALSE; + break; + } + } + } else { + if (ldisc->buflen != 0) { + ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); + while (ldisc->buflen > 0) { + bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); + ldisc->buflen--; + } + } + if (len > 0) { + if (ECHOING) + c_write(ldisc, buf, len); + if (keyflag && ldisc->cfg->protocol == PROT_TELNET && len == 1) { + switch (buf[0]) { + case CTRL('M'): + if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->telnet_newline) + ldisc->back->special(ldisc->backhandle, TS_EOL); + else + ldisc->back->send(ldisc->backhandle, "\r", 1); + break; + case CTRL('?'): + case CTRL('H'): + if (ldisc->cfg->telnet_keyboard) { + ldisc->back->special(ldisc->backhandle, TS_EC); + break; + } + case CTRL('C'): + if (ldisc->cfg->telnet_keyboard) { + ldisc->back->special(ldisc->backhandle, TS_IP); + break; + } + case CTRL('Z'): + if (ldisc->cfg->telnet_keyboard) { + ldisc->back->special(ldisc->backhandle, TS_SUSP); + break; + } + + default: + ldisc->back->send(ldisc->backhandle, buf, len); + break; + } + } else + ldisc->back->send(ldisc->backhandle, buf, len); + } + } +} diff --git a/putty/LDISC.H b/putty/LDISC.H new file mode 100644 index 0000000..ef84f6d --- /dev/null +++ b/putty/LDISC.H @@ -0,0 +1,22 @@ +/* + * ldisc.h: defines the Ldisc data structure used by ldisc.c and + * ldiscucs.c. (Unfortunately it was necessary to split the ldisc + * module in two, to avoid unnecessarily linking in the Unicode + * stuff in tools that don't require it.) + */ + +#ifndef PUTTY_LDISC_H +#define PUTTY_LDISC_H + +typedef struct ldisc_tag { + Terminal *term; + Backend *back; + Config *cfg; + void *backhandle; + void *frontend; + + char *buf; + int buflen, bufsiz, quotenext; +} *Ldisc; + +#endif /* PUTTY_LDISC_H */ diff --git a/putty/LDISCUCS.C b/putty/LDISCUCS.C new file mode 100644 index 0000000..eda383b --- /dev/null +++ b/putty/LDISCUCS.C @@ -0,0 +1,100 @@ +/* + * ldisc.c: PuTTY line discipline. Sits between the input coming + * from keypresses in the window, and the output channel leading to + * the back end. Implements echo and/or local line editing, + * depending on what's currently configured. + */ + +#include +#include + +#include "putty.h" +#include "terminal.h" +#include "ldisc.h" + +void lpage_send(void *handle, + int codepage, char *buf, int len, int interactive) +{ + Ldisc ldisc = (Ldisc)handle; + wchar_t *widebuffer = 0; + int widesize = 0; + int wclen; + + if (codepage < 0) { + ldisc_send(ldisc, buf, len, interactive); + return; + } + + widesize = len * 2; + widebuffer = snewn(widesize, wchar_t); + + wclen = mb_to_wc(codepage, 0, buf, len, widebuffer, widesize); + luni_send(ldisc, widebuffer, wclen, interactive); + + sfree(widebuffer); +} + +void luni_send(void *handle, wchar_t * widebuf, int len, int interactive) +{ + Ldisc ldisc = (Ldisc)handle; + int ratio = (in_utf(ldisc->term))?3:1; + char *linebuffer; + int linesize; + int i; + char *p; + + linesize = len * ratio * 2; + linebuffer = snewn(linesize, char); + + if (in_utf(ldisc->term)) { + /* UTF is a simple algorithm */ + for (p = linebuffer, i = 0; i < len; i++) { + unsigned long ch = widebuf[i]; + + if ((ch & 0xF800) == 0xD800) { +#ifdef PLATFORM_IS_UTF16 + if (i+1 < len) { + unsigned long ch2 = widebuf[i+1]; + if ((ch & 0xFC00) == 0xD800 && + (ch2 & 0xFC00) == 0xDC00) { + ch = 0x10000 + ((ch & 0x3FF) << 10) + (ch2 & 0x3FF); + i++; + } + } else +#endif + { + /* Unrecognised UTF-16 sequence */ + ch = '.'; + } + } + + if (ch < 0x80) { + *p++ = (char) (ch); + } else if (ch < 0x800) { + *p++ = (char) (0xC0 | (ch >> 6)); + *p++ = (char) (0x80 | (ch & 0x3F)); + } else if (ch < 0x10000) { + *p++ = (char) (0xE0 | (ch >> 12)); + *p++ = (char) (0x80 | ((ch >> 6) & 0x3F)); + *p++ = (char) (0x80 | (ch & 0x3F)); + } else { + *p++ = (char) (0xF0 | (ch >> 18)); + *p++ = (char) (0x80 | ((ch >> 12) & 0x3F)); + *p++ = (char) (0x80 | ((ch >> 6) & 0x3F)); + *p++ = (char) (0x80 | (ch & 0x3F)); + } + } + } else { + int rv; + rv = wc_to_mb(ldisc->term->ucsdata->line_codepage, 0, widebuf, len, + linebuffer, linesize, NULL, NULL, ldisc->term->ucsdata); + if (rv >= 0) + p = linebuffer + rv; + else + p = linebuffer; + } + if (p > linebuffer) + ldisc_send(ldisc, linebuffer, p - linebuffer, interactive); + + sfree(linebuffer); +} diff --git a/putty/LICENCE b/putty/LICENCE new file mode 100644 index 0000000..d404fee --- /dev/null +++ b/putty/LICENCE @@ -0,0 +1,25 @@ +PuTTY is copyright 1997-2011 Simon Tatham. + +Portions copyright Robert de Bath, Joris van Rantwijk, Delian +Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, +Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus +Kuhn, Colin Watson, and CORE SDI S.A. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/putty/LOGGING.C b/putty/LOGGING.C new file mode 100644 index 0000000..8fc5819 --- /dev/null +++ b/putty/LOGGING.C @@ -0,0 +1,430 @@ +/* + * Session logging. + */ + +#include +#include +#include + +#include +#include + +#include "putty.h" + +/* log session to file stuff ... */ +struct LogContext { + FILE *lgfp; + enum { L_CLOSED, L_OPENING, L_OPEN, L_ERROR } state; + bufchain queue; + Filename currlogfilename; + void *frontend; + Config cfg; +}; + +static void xlatlognam(Filename *d, Filename s, char *hostname, struct tm *tm); + +/* + * Internal wrapper function which must be called for _all_ output + * to the log file. It takes care of opening the log file if it + * isn't open, buffering data if it's in the process of being + * opened asynchronously, etc. + */ +static void logwrite(struct LogContext *ctx, void *data, int len) +{ + /* + * In state L_CLOSED, we call logfopen, which will set the state + * to one of L_OPENING, L_OPEN or L_ERROR. Hence we process all of + * those three _after_ processing L_CLOSED. + */ + if (ctx->state == L_CLOSED) + logfopen(ctx); + + if (ctx->state == L_OPENING) { + bufchain_add(&ctx->queue, data, len); + } else if (ctx->state == L_OPEN) { + assert(ctx->lgfp); + if (fwrite(data, 1, len, ctx->lgfp) < (size_t)len) { + logfclose(ctx); + ctx->state = L_ERROR; + /* Log state is L_ERROR so this won't cause a loop */ + logevent(ctx->frontend, + "Disabled writing session log due to error while writing"); + } + } /* else L_ERROR, so ignore the write */ +} + +/* + * Convenience wrapper on logwrite() which printf-formats the + * string. + */ +static void logprintf(struct LogContext *ctx, const char *fmt, ...) +{ + va_list ap; + char *data; + + va_start(ap, fmt); + data = dupvprintf(fmt, ap); + va_end(ap); + + logwrite(ctx, data, strlen(data)); + sfree(data); +} + +/* + * Flush any open log file. + */ +void logflush(void *handle) { + struct LogContext *ctx = (struct LogContext *)handle; + if (ctx->cfg.logtype > 0) + if (ctx->state == L_OPEN) + fflush(ctx->lgfp); +} + +static void logfopen_callback(void *handle, int mode) +{ + struct LogContext *ctx = (struct LogContext *)handle; + char buf[256], *event; + struct tm tm; + const char *fmode; + + if (mode == 0) { + ctx->state = L_ERROR; /* disable logging */ + } else { + fmode = (mode == 1 ? "ab" : "wb"); + ctx->lgfp = f_open(ctx->currlogfilename, fmode, FALSE); + if (ctx->lgfp) + ctx->state = L_OPEN; + else + ctx->state = L_ERROR; + } + + if (ctx->state == L_OPEN) { + /* Write header line into log file. */ + tm = ltime(); + strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm); + logprintf(ctx, "=~=~=~=~=~=~=~=~=~=~=~= PuTTY log %s" + " =~=~=~=~=~=~=~=~=~=~=~=\r\n", buf); + } + + event = dupprintf("%s session log (%s mode) to file: %s", + ctx->state == L_ERROR ? + (mode == 0 ? "Disabled writing" : "Error writing") : + (mode == 1 ? "Appending" : "Writing new"), + (ctx->cfg.logtype == LGTYP_ASCII ? "ASCII" : + ctx->cfg.logtype == LGTYP_DEBUG ? "raw" : + ctx->cfg.logtype == LGTYP_PACKETS ? "SSH packets" : + ctx->cfg.logtype == LGTYP_SSHRAW ? "SSH raw data" : + "unknown"), + filename_to_str(&ctx->currlogfilename)); + logevent(ctx->frontend, event); + sfree(event); + + /* + * Having either succeeded or failed in opening the log file, + * we should write any queued data out. + */ + assert(ctx->state != L_OPENING); /* make _sure_ it won't be requeued */ + while (bufchain_size(&ctx->queue)) { + void *data; + int len; + bufchain_prefix(&ctx->queue, &data, &len); + logwrite(ctx, data, len); + bufchain_consume(&ctx->queue, len); + } +} + +/* + * Open the log file. Takes care of detecting an already-existing + * file and asking the user whether they want to append, overwrite + * or cancel logging. + */ +void logfopen(void *handle) +{ + struct LogContext *ctx = (struct LogContext *)handle; + struct tm tm; + int mode; + + /* Prevent repeat calls */ + if (ctx->state != L_CLOSED) + return; + + if (!ctx->cfg.logtype) + return; + + tm = ltime(); + + /* substitute special codes in file name */ + xlatlognam(&ctx->currlogfilename, ctx->cfg.logfilename,ctx->cfg.host, &tm); + + ctx->lgfp = f_open(ctx->currlogfilename, "r", FALSE); /* file already present? */ + if (ctx->lgfp) { + fclose(ctx->lgfp); + if (ctx->cfg.logxfovr != LGXF_ASK) { + mode = ((ctx->cfg.logxfovr == LGXF_OVR) ? 2 : 1); + } else + mode = askappend(ctx->frontend, ctx->currlogfilename, + logfopen_callback, ctx); + } else + mode = 2; /* create == overwrite */ + + if (mode < 0) + ctx->state = L_OPENING; + else + logfopen_callback(ctx, mode); /* open the file */ +} + +void logfclose(void *handle) +{ + struct LogContext *ctx = (struct LogContext *)handle; + if (ctx->lgfp) { + fclose(ctx->lgfp); + ctx->lgfp = NULL; + } + ctx->state = L_CLOSED; +} + +/* + * Log session traffic. + */ +void logtraffic(void *handle, unsigned char c, int logmode) +{ + struct LogContext *ctx = (struct LogContext *)handle; + if (ctx->cfg.logtype > 0) { + if (ctx->cfg.logtype == logmode) + logwrite(ctx, &c, 1); + } +} + +/* + * Log an Event Log entry. Used in SSH packet logging mode; this is + * also as convenient a place as any to put the output of Event Log + * entries to stderr when a command-line tool is in verbose mode. + * (In particular, this is a better place to put it than in the + * front ends, because it only has to be done once for all + * platforms. Platforms which don't have a meaningful stderr can + * just avoid defining FLAG_STDERR. + */ +void log_eventlog(void *handle, const char *event) +{ + struct LogContext *ctx = (struct LogContext *)handle; + if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) { + fprintf(stderr, "%s\n", event); + fflush(stderr); + } + /* If we don't have a context yet (eg winnet.c init) then skip entirely */ + if (!ctx) + return; + if (ctx->cfg.logtype != LGTYP_PACKETS && + ctx->cfg.logtype != LGTYP_SSHRAW) + return; + logprintf(ctx, "Event Log: %s\r\n", event); + logflush(ctx); +} + +/* + * Log an SSH packet. + * If n_blanks != 0, blank or omit some parts. + * Set of blanking areas must be in increasing order. + */ +void log_packet(void *handle, int direction, int type, + char *texttype, const void *data, int len, + int n_blanks, const struct logblank_t *blanks, + const unsigned long *seq) +{ + struct LogContext *ctx = (struct LogContext *)handle; + char dumpdata[80], smalldata[5]; + int p = 0, b = 0, omitted = 0; + int output_pos = 0; /* NZ if pending output in dumpdata */ + + if (!(ctx->cfg.logtype == LGTYP_SSHRAW || + (ctx->cfg.logtype == LGTYP_PACKETS && texttype))) + return; + + /* Packet header. */ + if (texttype) { + if (seq) { + logprintf(ctx, "%s packet #0x%lx, type %d / 0x%02x (%s)\r\n", + direction == PKT_INCOMING ? "Incoming" : "Outgoing", + *seq, type, type, texttype); + } else { + logprintf(ctx, "%s packet type %d / 0x%02x (%s)\r\n", + direction == PKT_INCOMING ? "Incoming" : "Outgoing", + type, type, texttype); + } + } else { + logprintf(ctx, "%s raw data\r\n", + direction == PKT_INCOMING ? "Incoming" : "Outgoing"); + } + + /* + * Output a hex/ASCII dump of the packet body, blanking/omitting + * parts as specified. + */ + while (p < len) { + int blktype; + + /* Move to a current entry in the blanking array. */ + while ((b < n_blanks) && + (p >= blanks[b].offset + blanks[b].len)) + b++; + /* Work out what type of blanking to apply to + * this byte. */ + blktype = PKTLOG_EMIT; /* default */ + if ((b < n_blanks) && + (p >= blanks[b].offset) && + (p < blanks[b].offset + blanks[b].len)) + blktype = blanks[b].type; + + /* If we're about to stop omitting, it's time to say how + * much we omitted. */ + if ((blktype != PKTLOG_OMIT) && omitted) { + logprintf(ctx, " (%d byte%s omitted)\r\n", + omitted, (omitted==1?"":"s")); + omitted = 0; + } + + /* (Re-)initialise dumpdata as necessary + * (start of row, or if we've just stopped omitting) */ + if (!output_pos && !omitted) + sprintf(dumpdata, " %08x%*s\r\n", p-(p%16), 1+3*16+2+16, ""); + + /* Deal with the current byte. */ + if (blktype == PKTLOG_OMIT) { + omitted++; + } else { + int c; + if (blktype == PKTLOG_BLANK) { + c = 'X'; + sprintf(smalldata, "XX"); + } else { /* PKTLOG_EMIT */ + c = ((unsigned char *)data)[p]; + sprintf(smalldata, "%02x", c); + } + dumpdata[10+2+3*(p%16)] = smalldata[0]; + dumpdata[10+2+3*(p%16)+1] = smalldata[1]; + dumpdata[10+1+3*16+2+(p%16)] = (isprint(c) ? c : '.'); + output_pos = (p%16) + 1; + } + + p++; + + /* Flush row if necessary */ + if (((p % 16) == 0) || (p == len) || omitted) { + if (output_pos) { + strcpy(dumpdata + 10+1+3*16+2+output_pos, "\r\n"); + logwrite(ctx, dumpdata, strlen(dumpdata)); + output_pos = 0; + } + } + + } + + /* Tidy up */ + if (omitted) + logprintf(ctx, " (%d byte%s omitted)\r\n", + omitted, (omitted==1?"":"s")); + logflush(ctx); +} + +void *log_init(void *frontend, Config *cfg) +{ + struct LogContext *ctx = snew(struct LogContext); + ctx->lgfp = NULL; + ctx->state = L_CLOSED; + ctx->frontend = frontend; + ctx->cfg = *cfg; /* STRUCTURE COPY */ + bufchain_init(&ctx->queue); + return ctx; +} + +void log_free(void *handle) +{ + struct LogContext *ctx = (struct LogContext *)handle; + + logfclose(ctx); + bufchain_clear(&ctx->queue); + sfree(ctx); +} + +void log_reconfig(void *handle, Config *cfg) +{ + struct LogContext *ctx = (struct LogContext *)handle; + int reset_logging; + + if (!filename_equal(ctx->cfg.logfilename, cfg->logfilename) || + ctx->cfg.logtype != cfg->logtype) + reset_logging = TRUE; + else + reset_logging = FALSE; + + if (reset_logging) + logfclose(ctx); + + ctx->cfg = *cfg; /* STRUCTURE COPY */ + + if (reset_logging) + logfopen(ctx); +} + +/* + * translate format codes into time/date strings + * and insert them into log file name + * + * "&Y":YYYY "&m":MM "&d":DD "&T":hhmmss "&h": "&&":& + */ +static void xlatlognam(Filename *dest, Filename src, + char *hostname, struct tm *tm) { + char buf[10], *bufp; + int size; + char buffer[FILENAME_MAX]; + int len = sizeof(buffer)-1; + char *d; + const char *s; + + d = buffer; + s = filename_to_str(&src); + + while (*s) { + /* Let (bufp, len) be the string to append. */ + bufp = buf; /* don't usually override this */ + if (*s == '&') { + char c; + s++; + size = 0; + if (*s) switch (c = *s++, tolower((unsigned char)c)) { + case 'y': + size = strftime(buf, sizeof(buf), "%Y", tm); + break; + case 'm': + size = strftime(buf, sizeof(buf), "%m", tm); + break; + case 'd': + size = strftime(buf, sizeof(buf), "%d", tm); + break; + case 't': + size = strftime(buf, sizeof(buf), "%H%M%S", tm); + break; + case 'h': + bufp = hostname; + size = strlen(bufp); + break; + default: + buf[0] = '&'; + size = 1; + if (c != '&') + buf[size++] = c; + } + } else { + buf[0] = *s++; + size = 1; + } + if (size > len) + size = len; + memcpy(d, bufp, size); + d += size; + len -= size; + } + *d = '\0'; + + *dest = filename_from_str(buffer); +} diff --git a/putty/MACOSX/INFO.PLI b/putty/MACOSX/INFO.PLI new file mode 100644 index 0000000..3d53c0d --- /dev/null +++ b/putty/MACOSX/INFO.PLI @@ -0,0 +1,8 @@ + + + + + CFBundleIconFile + PuTTY.icns + + diff --git a/putty/MACOSX/MAKEFILE b/putty/MACOSX/MAKEFILE new file mode 100644 index 0000000..8620f12 --- /dev/null +++ b/putty/MACOSX/MAKEFILE @@ -0,0 +1,805 @@ +# Makefile for putty under Mac OS X. +# +# This file was created by `mkfiles.pl' from the `Recipe' file. +# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead. +# +# Extra options you can set: +# +# - VER="-DSNAPSHOT=1999-01-25 -DSVN_REV=1234" +# Generates executables whose About box report them as being a +# development snapshot. SVN_REV is a Subversion revision number. +# +# - VER=-DRELEASE=0.43 +# Generates executables whose About box report them as being a +# release version. +# +# - COMPAT=-DAUTO_WINSOCK (Windows only) +# Causes PuTTY to assume that includes its own WinSock +# header file, so that it won't try to include . +# +# - COMPAT=-DWINSOCK_TWO (Windows only) +# Causes the PuTTY utilities to include instead of +# , except Plink which _needs_ WinSock 2 so it already +# does this. +# +# - COMPAT=-DNO_SECURITY (Windows only) +# Disables Pageant's use of , which is not available +# with some development environments (such as older versions of +# the Cygwin/mingw GNU toolchain). This means that 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). +# +# - COMPAT=-DNO_MULTIMON (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. This means that PuTTY's +# full-screen mode (configurable to work on Alt-Enter) will +# not behave usefully in a multi-monitor environment. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin. +# +# - COMPAT=-DNO_HTMLHELP (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. The resulting binary +# will only look for an old-style WinHelp file (.HLP/.CNT), and +# will ignore any .CHM file. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin (although you can use +# the htmlhelp.h 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. +# +# - RCFL=-DASCIICTLS (Windows only) +# Uses ASCII rather than Unicode to specify the tab control in +# the resource file. Probably most useful when compiling with +# Cygnus/mingw32, whose resource compiler may have less of a +# problem with it. +# +# - XFLAGS=-DTELNET_DEFAULT +# Causes PuTTY to default to the Telnet protocol (in the absence +# of Default Settings and so on to the contrary). Normally PuTTY +# will default to SSH. +# +# - 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. +# +CC = $(TOOLPATH)gcc + +CFLAGS = -O2 -Wall -Werror -g -I.././ -I../charset/ -I../windows/ -I../unix/ \ + -I../macosx/ +MLDFLAGS = -framework Cocoa +ULDFLAGS = + +CFLAGS += -DMACOSX + +all: PuTTY plink pscp psftp puttygen +PuTTY.app: + mkdir -p $@ +PuTTY.app/Contents: PuTTY.app + mkdir -p $@ +PuTTY.app/Contents/MacOS: PuTTY.app/Contents + mkdir -p $@ +PuTTY.app/Contents/Resources: PuTTY.app/Contents + mkdir -p $@ +PuTTY.app/Contents/Resources/PuTTY.icns: PuTTY.app/Contents/Resources putty.icns + cp putty.icns $@ +PuTTY.app/Contents/Info.plist: PuTTY.app/Contents/Resources info.plist + cp info.plist $@ +PuTTY: PuTTY.app/Contents/MacOS/PuTTY \ + PuTTY.app/Contents/Resources/PuTTY.icns \ + PuTTY.app/Contents/Info.plist $(PuTTY_extra) + +PuTTY.app/Contents/MacOS/PuTTY: PuTTY.app/Contents/MacOS be_all_s.o config.o \ + cproxy.o dialog.o fromucs.o ldisc.o ldiscucs.o localenc.o \ + logging.o macenc.o mimeenc.o minibidi.o misc.o osxctrls.o \ + osxdlg.o osxmain.o osxsel.o osxwin.o pgssapi.o pinger.o \ + portfwd.o proxy.o raw.o rlogin.o sbcs.o sbcsdat.o sercfg.o \ + settings.o slookup.o ssh.o sshaes.o ssharcf.o sshblowf.o \ + sshbn.o sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o \ + sshgssc.o sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh256.o \ + sshsh512.o sshsha.o sshzlib.o telnet.o terminal.o testback.o \ + time.o timing.o toucs.o tree234.o utf8.o ux_x11.o uxagentc.o \ + uxcfg.o uxgss.o uxmisc.o uxnet.o uxnoise.o uxprint.o \ + uxproxy.o uxpty.o uxsel.o uxser.o uxsignal.o uxstore.o \ + uxucs.o version.o wcwidth.o wildcard.o x11fwd.o xenc.o + $(CC) $(MLDFLAGS) -o $@ be_all_s.o config.o cproxy.o dialog.o \ + fromucs.o ldisc.o ldiscucs.o localenc.o logging.o macenc.o \ + mimeenc.o minibidi.o misc.o osxctrls.o osxdlg.o osxmain.o \ + osxsel.o osxwin.o pgssapi.o pinger.o portfwd.o proxy.o raw.o \ + rlogin.o sbcs.o sbcsdat.o sercfg.o settings.o slookup.o \ + ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o telnet.o terminal.o testback.o time.o timing.o \ + toucs.o tree234.o utf8.o ux_x11.o uxagentc.o uxcfg.o uxgss.o \ + uxmisc.o uxnet.o uxnoise.o uxprint.o uxproxy.o uxpty.o \ + uxsel.o uxser.o uxsignal.o uxstore.o uxucs.o version.o \ + wcwidth.o wildcard.o x11fwd.o xenc.o + +plink: be_all_s.o cmdline.o cproxy.o ldisc.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o raw.o rlogin.o settings.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + telnet.o time.o timing.o tree234.o ux_x11.o uxagentc.o \ + uxcons.o uxgss.o uxmisc.o uxnet.o uxnoise.o uxplink.o \ + uxproxy.o uxsel.o uxser.o uxsignal.o uxstore.o version.o \ + wildcard.o x11fwd.o + $(CC) $(ULDFLAGS) -o $@ be_all_s.o cmdline.o cproxy.o ldisc.o \ + logging.o misc.o pgssapi.o pinger.o portfwd.o proxy.o raw.o \ + rlogin.o settings.o ssh.o sshaes.o ssharcf.o sshblowf.o \ + sshbn.o sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o \ + sshgssc.o sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh256.o \ + sshsh512.o sshsha.o sshzlib.o telnet.o time.o timing.o \ + tree234.o ux_x11.o uxagentc.o uxcons.o uxgss.o uxmisc.o \ + uxnet.o uxnoise.o uxplink.o uxproxy.o uxsel.o uxser.o \ + uxsignal.o uxstore.o version.o wildcard.o x11fwd.o + +pscp: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o pscp.o settings.o sftp.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + time.o timing.o tree234.o uxagentc.o uxcons.o uxgss.o \ + uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o uxsftp.o \ + uxstore.o version.o wildcard.o x11fwd.o + $(CC) $(ULDFLAGS) -o $@ be_none.o cmdline.o cproxy.o int64.o \ + logging.o misc.o pgssapi.o pinger.o portfwd.o proxy.o pscp.o \ + settings.o sftp.o ssh.o sshaes.o ssharcf.o sshblowf.o \ + sshbn.o sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o \ + sshgssc.o sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh256.o \ + sshsh512.o sshsha.o sshzlib.o time.o timing.o tree234.o \ + uxagentc.o uxcons.o uxgss.o uxmisc.o uxnet.o uxnoise.o \ + uxproxy.o uxsel.o uxsftp.o uxstore.o version.o wildcard.o \ + x11fwd.o + +psftp: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o psftp.o settings.o sftp.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + time.o timing.o tree234.o uxagentc.o uxcons.o uxgss.o \ + uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o uxsftp.o \ + uxstore.o version.o wildcard.o x11fwd.o + $(CC) $(ULDFLAGS) -o $@ be_none.o cmdline.o cproxy.o int64.o \ + logging.o misc.o pgssapi.o pinger.o portfwd.o proxy.o \ + psftp.o settings.o sftp.o ssh.o sshaes.o ssharcf.o \ + sshblowf.o sshbn.o sshcrc.o sshcrcda.o sshdes.o sshdh.o \ + sshdss.o sshgssc.o sshmd5.o sshpubk.o sshrand.o sshrsa.o \ + sshsh256.o sshsh512.o sshsha.o sshzlib.o time.o timing.o \ + tree234.o uxagentc.o uxcons.o uxgss.o uxmisc.o uxnet.o \ + uxnoise.o uxproxy.o uxsel.o uxsftp.o uxstore.o version.o \ + wildcard.o x11fwd.o + +puttygen: cmdgen.o import.o misc.o notiming.o sshaes.o sshbn.o sshdes.o \ + sshdss.o sshdssg.o sshmd5.o sshprime.o sshpubk.o sshrand.o \ + sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o time.o \ + tree234.o uxcons.o uxgen.o uxmisc.o uxnoise.o uxstore.o \ + version.o + $(CC) $(ULDFLAGS) -o $@ cmdgen.o import.o misc.o notiming.o sshaes.o \ + sshbn.o sshdes.o sshdss.o sshdssg.o sshmd5.o sshprime.o \ + sshpubk.o sshrand.o sshrsa.o sshrsag.o sshsh256.o sshsh512.o \ + sshsha.o time.o tree234.o uxcons.o uxgen.o uxmisc.o \ + uxnoise.o uxstore.o version.o + +be_all_s.o: ../be_all_s.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +be_none.o: ../be_none.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +be_nos_s.o: ../be_nos_s.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +cmdgen.o: ../cmdgen.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +cmdline.o: ../cmdline.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +config.o: ../config.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +cproxy.o: ../cproxy.c ../putty.h ../ssh.h ../network.h ../proxy.h \ + ../puttyps.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +dialog.o: ../dialog.c ../putty.h ../dialog.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +fromucs.o: ../charset/fromucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +gtkcfg.o: ../unix/gtkcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +gtkcols.o: ../unix/gtkcols.c ../unix/gtkcols.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +gtkdlg.o: ../unix/gtkdlg.c ../unix/gtkcols.h ../unix/gtkfont.h ../putty.h \ + ../storage.h ../dialog.h ../tree234.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +gtkfont.o: ../unix/gtkfont.c ../putty.h ../unix/gtkfont.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +gtkwin.o: ../unix/gtkwin.c ../putty.h ../terminal.h ../unix/gtkfont.h \ + ../puttyps.h ../network.h ../misc.h ../tree234.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +import.o: ../import.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +int64.o: ../int64.c ../int64.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +ldisc.o: ../ldisc.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \ + ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +ldiscucs.o: ../ldiscucs.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \ + ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +localenc.o: ../charset/localenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +logging.o: ../logging.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +macenc.o: ../charset/macenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +mimeenc.o: ../charset/mimeenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +minibidi.o: ../minibidi.c ../misc.h ../puttymem.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +misc.o: ../misc.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +nocproxy.o: ../nocproxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +nogss.o: ../nogss.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +notiming.o: ../notiming.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +osxctrls.o: ../macosx/osxctrls.m ../putty.h ../dialog.h ../macosx/osxclass.h \ + ../tree234.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) -x objective-c $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +osxdlg.o: ../macosx/osxdlg.m ../putty.h ../storage.h ../dialog.h \ + ../macosx/osxclass.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) -x objective-c $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +osxmain.o: ../macosx/osxmain.m ../putty.h ../macosx/osxclass.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) -x objective-c $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +osxsel.o: ../macosx/osxsel.m ../putty.h ../macosx/osxclass.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) -x objective-c $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +osxwin.o: ../macosx/osxwin.m ../putty.h ../terminal.h ../macosx/osxclass.h \ + ../puttyps.h ../network.h ../misc.h ../tree234.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) -x objective-c $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +pgssapi.o: ../pgssapi.c ../putty.h ../pgssapi.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +pinger.o: ../pinger.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +portfwd.o: ../portfwd.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +proxy.o: ../proxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +pscp.o: ../pscp.c ../putty.h ../psftp.h ../ssh.h ../sftp.h ../storage.h \ + ../int64.h ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +psftp.o: ../psftp.c ../putty.h ../psftp.h ../storage.h ../ssh.h ../sftp.h \ + ../int64.h ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +raw.o: ../raw.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +rlogin.o: ../rlogin.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sbcs.o: ../charset/sbcs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sbcsdat.o: ../charset/sbcsdat.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sercfg.o: ../sercfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +settings.o: ../settings.c ../putty.h ../storage.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sftp.o: ../sftp.c ../misc.h ../int64.h ../tree234.h ../sftp.h ../puttymem.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sizetip.o: ../windows/sizetip.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +slookup.o: ../charset/slookup.c ../charset/charset.h ../charset/internal.h \ + ../charset/enum.c ../charset/sbcsdat.c ../charset/utf8.c + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +ssh.o: ../ssh.c ../putty.h ../tree234.h ../ssh.h ../sshgssc.h ../sshgss.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h ../int64.h \ + ../pgssapi.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshaes.o: ../sshaes.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +ssharcf.o: ../ssharcf.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshblowf.o: ../sshblowf.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshbn.o: ../sshbn.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshcrc.o: ../sshcrc.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshcrcda.o: ../sshcrcda.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshdes.o: ../sshdes.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshdh.o: ../sshdh.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshdss.o: ../sshdss.c ../ssh.h ../misc.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshdssg.o: ../sshdssg.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshgssc.o: ../sshgssc.c ../putty.h ../sshgssc.h ../misc.h ../puttyps.h \ + ../network.h ../pgssapi.h ../sshgss.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../tree234.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshmd5.o: ../sshmd5.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshprime.o: ../sshprime.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshpubk.o: ../sshpubk.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshrand.o: ../sshrand.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshrsa.o: ../sshrsa.c ../ssh.h ../misc.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshrsag.o: ../sshrsag.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshsh256.o: ../sshsh256.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshsh512.o: ../sshsh512.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshsha.o: ../sshsha.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +sshzlib.o: ../sshzlib.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +telnet.o: ../telnet.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +terminal.o: ../terminal.c ../putty.h ../terminal.h ../puttyps.h ../network.h \ + ../misc.h ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +testback.o: ../testback.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +time.o: ../time.c + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +timing.o: ../timing.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +toucs.o: ../charset/toucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +tree234.o: ../tree234.c ../puttymem.h ../tree234.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +utf8.o: ../charset/utf8.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +ux_x11.o: ../unix/ux_x11.c ../putty.h ../ssh.h ../network.h ../puttyps.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxagentc.o: ../unix/uxagentc.c ../putty.h ../misc.h ../tree234.h \ + ../puttymem.h ../puttyps.h ../network.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxcfg.o: ../unix/uxcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxcons.o: ../unix/uxcons.c ../putty.h ../storage.h ../ssh.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxgen.o: ../unix/uxgen.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxgss.o: ../unix/uxgss.c ../putty.h ../pgssapi.h ../sshgss.h ../sshgssc.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxmisc.o: ../unix/uxmisc.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxnet.o: ../unix/uxnet.c ../putty.h ../network.h ../tree234.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxnoise.o: ../unix/uxnoise.c ../putty.h ../ssh.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxplink.o: ../unix/uxplink.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxprint.o: ../unix/uxprint.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxproxy.o: ../unix/uxproxy.c ../tree234.h ../putty.h ../network.h ../proxy.h \ + ../puttyps.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxpterm.o: ../unix/uxpterm.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxpty.o: ../unix/uxpty.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxputty.o: ../unix/uxputty.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxsel.o: ../unix/uxsel.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxser.o: ../unix/uxser.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxsftp.o: ../unix/uxsftp.c ../putty.h ../ssh.h ../psftp.h ../int64.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxsignal.o: ../unix/uxsignal.c + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxstore.o: ../unix/uxstore.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +uxucs.o: ../unix/uxucs.c ../putty.h ../charset/charset.h ../terminal.h \ + ../misc.h ../puttyps.h ../network.h ../tree234.h \ + ../puttymem.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +version.o: ../version.c + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +wcwidth.o: ../wcwidth.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +wildcard.o: ../wildcard.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +wincfg.o: ../windows/wincfg.c ../putty.h ../dialog.h ../storage.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +wincons.o: ../windows/wincons.c ../putty.h ../storage.h ../ssh.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winctrls.o: ../windows/winctrls.c ../putty.h ../misc.h ../dialog.h \ + ../puttyps.h ../network.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../tree234.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +windefs.o: ../windows/windefs.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +windlg.o: ../windows/windlg.c ../putty.h ../ssh.h ../windows/win_res.h \ + ../storage.h ../dialog.h ../puttyps.h ../network.h ../misc.h \ + ../puttymem.h ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +window.o: ../windows/window.c ../putty.h ../terminal.h ../storage.h \ + ../windows/win_res.h ../puttyps.h ../network.h ../misc.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +wingss.o: ../windows/wingss.c ../putty.h ../pgssapi.h ../sshgss.h \ + ../sshgssc.h ../misc.h ../puttyps.h ../network.h \ + ../puttymem.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winhandl.o: ../windows/winhandl.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winhelp.o: ../windows/winhelp.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winjump.o: ../windows/winjump.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winmisc.o: ../windows/winmisc.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winnet.o: ../windows/winnet.c ../putty.h ../network.h ../tree234.h \ + ../puttyps.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winnoise.o: ../windows/winnoise.c ../putty.h ../ssh.h ../storage.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winnojmp.o: ../windows/winnojmp.c + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winpgen.o: ../windows/winpgen.c ../putty.h ../ssh.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winpgnt.o: ../windows/winpgnt.c ../putty.h ../ssh.h ../misc.h ../tree234.h \ + ../puttyps.h ../network.h ../puttymem.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winpgntc.o: ../windows/winpgntc.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winplink.o: ../windows/winplink.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winprint.o: ../windows/winprint.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winproxy.o: ../windows/winproxy.c ../tree234.h ../putty.h ../network.h \ + ../proxy.h ../puttyps.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winser.o: ../windows/winser.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winsftp.o: ../windows/winsftp.c ../putty.h ../psftp.h ../ssh.h ../int64.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winstore.o: ../windows/winstore.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +wintime.o: ../windows/wintime.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winucs.o: ../windows/winucs.c ../putty.h ../terminal.h ../misc.h \ + ../puttyps.h ../network.h ../tree234.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winutils.o: ../windows/winutils.c ../putty.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +winx11.o: ../windows/winx11.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +x11fwd.o: ../x11fwd.c ../putty.h ../ssh.h ../tree234.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +xenc.o: ../charset/xenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +xkeysym.o: ../unix/xkeysym.c ../misc.h ../puttymem.h + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +xpmptcfg.o: ../unix/xpmptcfg.c + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +xpmpterm.o: ../unix/xpmpterm.c + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +xpmpucfg.o: ../unix/xpmpucfg.c + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< +xpmputty.o: ../unix/xpmputty.c + $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< + + +clean: + rm -f *.o *.dmg plink pscp psftp puttygen + rm -rf *.app + +FORCE: diff --git a/putty/MACOSX/OSX.H b/putty/MACOSX/OSX.H new file mode 100644 index 0000000..fcc152f --- /dev/null +++ b/putty/MACOSX/OSX.H @@ -0,0 +1,34 @@ +#ifndef PUTTY_OSX_H +#define PUTTY_OSX_H + +/* + * Cocoa defines `FontSpec' itself, so we must change its name. + * (Arrgh.) + */ +#define FontSpec FontSpec_OSX_Proof + +/* + * Define the various compatibility symbols to make uxpty.c compile + * correctly on OS X. + */ +#define BSD_PTYS +#define OMIT_UTMP +#define HAVE_NO_SETRESUID +#define NOT_X_WINDOWS + +/* + * OS X is largely just Unix, so we can include most of this + * unchanged. + */ +#include "unix.h" + +/* + * Functions exported by osxsel.m. (Both of these functions are + * expected to be called in the _main_ thread: the select subthread + * is an implementation detail of osxsel.m and ideally should not + * be visible at all outside it.) + */ +void osxsel_init(void); /* call this to kick things off */ +void osxsel_process_results(void); /* call this on receipt of a netevent */ + +#endif diff --git a/putty/MACOSX/OSXCLASS.H b/putty/MACOSX/OSXCLASS.H new file mode 100644 index 0000000..e005586 --- /dev/null +++ b/putty/MACOSX/OSXCLASS.H @@ -0,0 +1,107 @@ +/* + * Header file for the Objective-C parts of Mac OS X PuTTY. This + * file contains the class definitions, which would cause compile + * failures in the pure C modules if they appeared in osx.h. + */ + +#ifndef PUTTY_OSXCLASS_H +#define PUTTY_OSXCLASS_H + +#include "putty.h" + +/* + * The application controller class, defined in osxmain.m. + */ +@interface AppController : NSObject +{ + NSTimer *timer; +} +- (void)newSessionConfig:(id)sender; +- (void)newTerminal:(id)sender; +- (void)newSessionWithConfig:(id)cfg; +- (void)setTimer:(long)next; +@end +extern AppController *controller; + +/* + * The SessionWindow class, defined in osxwin.m. + */ + +struct alert_queue { + struct alert_queue *next; + NSAlert *alert; + void (*callback)(void *, int); + void *ctx; +}; + +@class SessionWindow; +@class TerminalView; + +@interface SessionWindow : NSWindow +{ + Terminal *term; + TerminalView *termview; + struct unicode_data ucsdata; + void *logctx; + Config cfg; + void *ldisc; + Backend *back; + void *backhandle; + int exited; + /* + * The following two members relate to the currently active + * alert sheet, if any. They are NULL if there isn't one. + */ + void (*alert_callback)(void *, int); + void *alert_ctx; + /* This queues future alerts that need to be shown. */ + struct alert_queue *alert_qhead, *alert_qtail; +} +- (id)initWithConfig:(Config)cfg; +- (void)drawStartFinish:(BOOL)start; +- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b; +- (Config *)cfg; +- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y + attr:(unsigned long)attr lattr:(int)lattr; +- (int)fromBackend:(const char *)data len:(int)len isStderr:(int)is_stderr; +- (int)fromBackendUntrusted:(const char *)data len:(int)len; +- (void)startAlert:(NSAlert *)alert + withCallback:(void (*)(void *, int))callback andCtx:(void *)ctx; +- (void)endSession:(int)clean; +- (void)notifyRemoteExit; +- (Terminal *)term; +@end + +/* + * The ConfigWindow class, defined in osxdlg.m. + */ + +@class ConfigWindow; + +@interface ConfigWindow : NSWindow +{ + NSOutlineView *treeview; + struct controlbox *ctrlbox; + void *dv; + Config cfg; +} +- (id)initWithConfig:(Config)cfg; +@end + +/* + * Functions exported by osxctrls.m. (They have to go in this + * header file and not osx.h, because some of them have Cocoa class + * types in their prototypes.) + */ +#define HSPACING 12 /* needed in osxdlg.m and osxctrls.m */ +#define VSPACING 8 + +void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action); +void fe_dlg_free(void *dv); +void create_ctrls(void *dv, NSView *parent, struct controlset *s, + int *minw, int *minh); +int place_ctrls(void *dv, struct controlset *s, int leftx, int topy, + int width); /* returns height used */ +void select_panel(void *dv, struct controlbox *b, const char *name); + +#endif /* PUTTY_OSXCLASS_H */ diff --git a/putty/MACOSX/OSXCTRLS.M b/putty/MACOSX/OSXCTRLS.M new file mode 100644 index 0000000..5e0699e --- /dev/null +++ b/putty/MACOSX/OSXCTRLS.M @@ -0,0 +1,1815 @@ +/* + * osxctrls.m: OS X implementation of the dialog.h interface. + */ + +#import +#include "putty.h" +#include "dialog.h" +#include "osxclass.h" +#include "tree234.h" + +/* + * Still to be implemented: + * + * - file selectors (NSOpenPanel / NSSavePanel) + * + * - font selectors + * - colour selectors + * * both of these have a conceptual oddity in Cocoa that + * you're only supposed to have one per application. But I + * currently expect to be able to have multiple PuTTY config + * boxes on screen at once; what happens if you trigger the + * font selector in each one at the same time? + * * if it comes to that, the _font_ selector can probably be + * managed by other means: nobody is forcing me to implement + * a font selector using a `Change...' button. The portable + * dialog interface gives me the flexibility to do this how I + * want. + * * The colour selector interface, in its present form, is + * more interesting and _if_ a radical change of plan is + * required then it may stretch across the interface into the + * portable side. + * * Before I do anything rash I should start by looking at the + * Mac Classic port and see how it's done there, on the basis + * that Apple seem reasonably unlikely to have invented this + * crazy restriction specifically for OS X. + * + * - focus management + * * I tried using makeFirstResponder to give keyboard focus, + * but it appeared not to work. Try again, and work out how + * it should be done. + * * also look into tab order. Currently pressing Tab suggests + * that only edit boxes and list boxes can get the keyboard + * focus, and that buttons (in all their forms) are unable to + * be driven by the keyboard. Find out for sure. + * + * - dlg_error_msg + * * this may run into the usual aggro with modal dialog boxes. + */ + +/* + * For Cocoa control layout, I need a two-stage process. In stage + * one, I allocate all the controls and measure their natural + * sizes, which allows me to compute the _minimum_ width and height + * of a given section of dialog. Then, in stage two, I lay out the + * dialog box as a whole, decide how much each section of the box + * needs to receive, and assign it its final size. + */ + +/* + * As yet unsolved issues [FIXME]: + * + * - Sometimes the height returned from create_ctrls and the + * height returned from place_ctrls differ. Find out why. It may + * be harmless (e.g. results of NSTextView being odd), but I + * want to know. + * + * - NSTextViews are indented a bit. It'd be nice to put their + * left margin at the same place as everything else's. + * + * - I don't yet know whether we even _can_ support tab order or + * keyboard shortcuts. If we can't, then fair enough, we can't. + * But if we can, we should. + * + * - I would _really_ like to know of a better way to correct + * NSButton's stupid size estimates than by subclassing it and + * overriding sizeToFit with hard-wired sensible values! + * + * - Speaking of stupid size estimates, the amount by which I'm + * adjusting a titled NSBox (currently equal to the point size + * of its title font) looks as if it isn't _quite_ enough. + * Figure out what the real amount should be and use it. + * + * - I don't understand why there's always a scrollbar displayed + * in each list box. I thought I told it to autohide scrollers? + * + * - Why do I have to fudge list box heights by adding one? (Might + * it be to do with the missing header view?) + */ + +/* + * Subclass of NSButton which corrects the fact that the normal + * one's sizeToFit method persistently returns 32 as its height, + * which is simply a lie. I have yet to work out a better + * alternative than hard-coding the real heights. + */ +@interface MyButton : NSButton +{ + int minht; +} +@end +@implementation MyButton +- (id)initWithFrame:(NSRect)r +{ + self = [super initWithFrame:r]; + minht = 25; + return self; +} +- (void)setButtonType:(NSButtonType)t +{ + if (t == NSRadioButton || t == NSSwitchButton) + minht = 18; + else + minht = 25; + [super setButtonType:t]; +} +- (void)sizeToFit +{ + NSRect r; + [super sizeToFit]; + r = [self frame]; + r.size.height = minht; + [self setFrame:r]; +} +@end + +/* + * Class used as the data source for NSTableViews. + */ +@interface MyTableSource : NSObject +{ + tree234 *tree; +} +- (id)init; +- (void)add:(const char *)str withId:(int)id; +- (int)getid:(int)index; +- (void)swap:(int)index1 with:(int)index2; +- (void)removestr:(int)index; +- (void)clear; +@end +@implementation MyTableSource +- (id)init +{ + self = [super init]; + tree = newtree234(NULL); + return self; +} +- (void)dealloc +{ + char *p; + while ((p = delpos234(tree, 0)) != NULL) + sfree(p); + freetree234(tree); + [super dealloc]; +} +- (void)add:(const char *)str withId:(int)id +{ + addpos234(tree, dupprintf("%d\t%s", id, str), count234(tree)); +} +- (int)getid:(int)index +{ + char *p = index234(tree, index); + return atoi(p); +} +- (void)removestr:(int)index +{ + char *p = delpos234(tree, index); + sfree(p); +} +- (void)swap:(int)index1 with:(int)index2 +{ + char *p1, *p2; + + if (index1 > index2) { + int t = index1; index1 = index2; index2 = t; + } + + /* delete later one first so it doesn't affect index of earlier one */ + p2 = delpos234(tree, index2); + p1 = delpos234(tree, index1); + + /* now insert earlier one before later one for the inverse reason */ + addpos234(tree, p2, index1); + addpos234(tree, p1, index2); +} +- (void)clear +{ + char *p; + while ((p = delpos234(tree, 0)) != NULL) + sfree(p); +} +- (int)numberOfRowsInTableView:(NSTableView *)aTableView +{ + return count234(tree); +} +- (id)tableView:(NSTableView *)aTableView + objectValueForTableColumn:(NSTableColumn *)aTableColumn + row:(int)rowIndex +{ + int j = [[aTableColumn identifier] intValue]; + char *p = index234(tree, rowIndex); + + while (j >= 0) { + p += strcspn(p, "\t"); + if (*p) p++; + j--; + } + + return [NSString stringWithCString:p length:strcspn(p, "\t")]; +} +@end + +/* + * Object to receive messages from various control classes. + */ +@class Receiver; + +struct fe_dlg { + NSWindow *window; + NSObject *target; + SEL action; + tree234 *byctrl; + tree234 *bywidget; + tree234 *boxes; + void *data; /* passed to portable side */ + Receiver *rec; +}; + +@interface Receiver : NSObject +{ + struct fe_dlg *d; +} +- (id)initWithStruct:(struct fe_dlg *)aStruct; +@end + +struct fe_ctrl { + union control *ctrl; + NSButton *button, *button2; + NSTextField *label, *editbox; + NSComboBox *combobox; + NSButton **radiobuttons; + NSTextView *textview; + NSPopUpButton *popupbutton; + NSTableView *tableview; + NSScrollView *scrollview; + int nradiobuttons; + void *privdata; + int privdata_needs_free; +}; + +static int fe_ctrl_cmp_by_ctrl(void *av, void *bv) +{ + struct fe_ctrl *a = (struct fe_ctrl *)av; + struct fe_ctrl *b = (struct fe_ctrl *)bv; + + if (a->ctrl < b->ctrl) + return -1; + if (a->ctrl > b->ctrl) + return +1; + return 0; +} + +static int fe_ctrl_find_by_ctrl(void *av, void *bv) +{ + union control *a = (union control *)av; + struct fe_ctrl *b = (struct fe_ctrl *)bv; + + if (a < b->ctrl) + return -1; + if (a > b->ctrl) + return +1; + return 0; +} + +struct fe_box { + struct controlset *s; + id box; +}; + +static int fe_boxcmp(void *av, void *bv) +{ + struct fe_box *a = (struct fe_box *)av; + struct fe_box *b = (struct fe_box *)bv; + + if (a->s < b->s) + return -1; + if (a->s > b->s) + return +1; + return 0; +} + +static int fe_boxfind(void *av, void *bv) +{ + struct controlset *a = (struct controlset *)av; + struct fe_box *b = (struct fe_box *)bv; + + if (a < b->s) + return -1; + if (a > b->s) + return +1; + return 0; +} + +struct fe_backwards { /* map Cocoa widgets back to fe_ctrls */ + id widget; + struct fe_ctrl *c; +}; + +static int fe_backwards_cmp_by_widget(void *av, void *bv) +{ + struct fe_backwards *a = (struct fe_backwards *)av; + struct fe_backwards *b = (struct fe_backwards *)bv; + + if (a->widget < b->widget) + return -1; + if (a->widget > b->widget) + return +1; + return 0; +} + +static int fe_backwards_find_by_widget(void *av, void *bv) +{ + id a = (id)av; + struct fe_backwards *b = (struct fe_backwards *)bv; + + if (a < b->widget) + return -1; + if (a > b->widget) + return +1; + return 0; +} + +static struct fe_ctrl *fe_ctrl_new(union control *ctrl) +{ + struct fe_ctrl *c; + + c = snew(struct fe_ctrl); + c->ctrl = ctrl; + + c->button = c->button2 = nil; + c->label = nil; + c->editbox = nil; + c->combobox = nil; + c->textview = nil; + c->popupbutton = nil; + c->tableview = nil; + c->scrollview = nil; + c->radiobuttons = NULL; + c->nradiobuttons = 0; + c->privdata = NULL; + c->privdata_needs_free = FALSE; + + return c; +} + +static void fe_ctrl_free(struct fe_ctrl *c) +{ + if (c->privdata_needs_free) + sfree(c->privdata); + sfree(c->radiobuttons); + sfree(c); +} + +static struct fe_ctrl *fe_ctrl_byctrl(struct fe_dlg *d, union control *ctrl) +{ + return find234(d->byctrl, ctrl, fe_ctrl_find_by_ctrl); +} + +static void add_box(struct fe_dlg *d, struct controlset *s, id box) +{ + struct fe_box *b = snew(struct fe_box); + b->box = box; + b->s = s; + add234(d->boxes, b); +} + +static id find_box(struct fe_dlg *d, struct controlset *s) +{ + struct fe_box *b = find234(d->boxes, s, fe_boxfind); + return b ? b->box : NULL; +} + +static void add_widget(struct fe_dlg *d, struct fe_ctrl *c, id widget) +{ + struct fe_backwards *b = snew(struct fe_backwards); + b->widget = widget; + b->c = c; + add234(d->bywidget, b); +} + +static struct fe_ctrl *find_widget(struct fe_dlg *d, id widget) +{ + struct fe_backwards *b = find234(d->bywidget, widget, + fe_backwards_find_by_widget); + return b ? b->c : NULL; +} + +void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action) +{ + struct fe_dlg *d; + + d = snew(struct fe_dlg); + d->window = window; + d->target = target; + d->action = action; + d->byctrl = newtree234(fe_ctrl_cmp_by_ctrl); + d->bywidget = newtree234(fe_backwards_cmp_by_widget); + d->boxes = newtree234(fe_boxcmp); + d->data = data; + d->rec = [[Receiver alloc] initWithStruct:d]; + + return d; +} + +void fe_dlg_free(void *dv) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c; + struct fe_box *b; + + while ( (c = delpos234(d->byctrl, 0)) != NULL ) + fe_ctrl_free(c); + freetree234(d->byctrl); + + while ( (c = delpos234(d->bywidget, 0)) != NULL ) + sfree(c); + freetree234(d->bywidget); + + while ( (b = delpos234(d->boxes, 0)) != NULL ) + sfree(b); + freetree234(d->boxes); + + [d->rec release]; + + sfree(d); +} + +@implementation Receiver +- (id)initWithStruct:(struct fe_dlg *)aStruct +{ + self = [super init]; + d = aStruct; + return self; +} +- (void)buttonPushed:(id)sender +{ + struct fe_ctrl *c = find_widget(d, sender); + + assert(c && c->ctrl->generic.type == CTRL_BUTTON); + c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION); +} +- (void)checkboxChanged:(id)sender +{ + struct fe_ctrl *c = find_widget(d, sender); + + assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); + c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE); +} +- (void)radioChanged:(id)sender +{ + struct fe_ctrl *c = find_widget(d, sender); + int j; + + assert(c && c->radiobuttons); + for (j = 0; j < c->nradiobuttons; j++) + if (sender != c->radiobuttons[j]) + [c->radiobuttons[j] setState:NSOffState]; + c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE); +} +- (void)popupMenuSelected:(id)sender +{ + struct fe_ctrl *c = find_widget(d, sender); + c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE); +} +- (void)controlTextDidChange:(NSNotification *)notification +{ + id widget = [notification object]; + struct fe_ctrl *c = find_widget(d, widget); + assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE); +} +- (void)controlTextDidEndEditing:(NSNotification *)notification +{ + id widget = [notification object]; + struct fe_ctrl *c = find_widget(d, widget); + assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_REFRESH); +} +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + id widget = [notification object]; + struct fe_ctrl *c = find_widget(d, widget); + assert(c && c->ctrl->generic.type == CTRL_LISTBOX); + c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_SELCHANGE); +} +- (BOOL)tableView:(NSTableView *)aTableView + shouldEditTableColumn:(NSTableColumn *)aTableColumn + row:(int)rowIndex +{ + return NO; /* no editing permitted */ +} +- (void)listDoubleClicked:(id)sender +{ + struct fe_ctrl *c = find_widget(d, sender); + assert(c && c->ctrl->generic.type == CTRL_LISTBOX); + c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION); +} +- (void)dragListButton:(id)sender +{ + struct fe_ctrl *c = find_widget(d, sender); + int direction, row, nrows; + assert(c && c->ctrl->generic.type == CTRL_LISTBOX && + c->ctrl->listbox.draglist); + + if (sender == c->button) + direction = -1; /* up */ + else + direction = +1; /* down */ + + row = [c->tableview selectedRow]; + nrows = [c->tableview numberOfRows]; + + if (row + direction < 0 || row + direction >= nrows) { + NSBeep(); + return; + } + + [[c->tableview dataSource] swap:row with:row+direction]; + [c->tableview reloadData]; + [c->tableview selectRow:row+direction byExtendingSelection:NO]; + + c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE); +} +@end + +void create_ctrls(void *dv, NSView *parent, struct controlset *s, + int *minw, int *minh) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + int ccw[100]; /* cumulative column widths */ + int cypos[100]; + int ncols; + int wmin = 0, hmin = 0; + int i, j, cw, ch; + NSRect rect; + NSFont *textviewfont = nil; + int boxh = 0, boxw = 0; + + if (!s->boxname && s->boxtitle) { + /* This controlset is a panel title. */ + + NSTextField *tf; + + tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)]; + [tf setEditable:NO]; + [tf setSelectable:NO]; + [tf setBordered:NO]; + [tf setDrawsBackground:NO]; + [tf setStringValue:[NSString stringWithCString:s->boxtitle]]; + [tf sizeToFit]; + rect = [tf frame]; + [parent addSubview:tf]; + + /* + * I'm going to store this NSTextField in the boxes tree, + * because I really can't face having a special tree234 + * mapping controlsets to panel titles. + */ + add_box(d, s, tf); + + *minw = rect.size.width; + *minh = rect.size.height; + + return; + } + + if (*s->boxname) { + /* + * Create an NSBox to contain this subset of controls. + */ + NSBox *box; + NSRect tmprect; + + box = [[NSBox alloc] initWithFrame:NSMakeRect(0,0,1,1)]; + if (s->boxtitle) + [box setTitle:[NSString stringWithCString:s->boxtitle]]; + else + [box setTitlePosition:NSNoTitle]; + add_box(d, s, box); + tmprect = [box frame]; + [box setContentViewMargins:NSMakeSize(20,20)]; + [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)]; + rect = [box frame]; + [box setFrame:tmprect]; + boxh = (int)(rect.size.height - 100); + boxw = (int)(rect.size.width - 100); + [parent addSubview:box]; + + if (s->boxtitle) + boxh += [[box titleFont] pointSize]; + + /* + * All subsequent controls will be placed within this box. + */ + parent = box; + } + + ncols = 1; + ccw[0] = 0; + ccw[1] = 100; + cypos[0] = 0; + + /* + * Now iterate through the controls themselves, create them, + * and add their width and height to the overall width/height + * calculation. + */ + for (i = 0; i < s->ncontrols; i++) { + union control *ctrl = s->ctrls[i]; + struct fe_ctrl *c; + int colstart = COLUMN_START(ctrl->generic.column); + int colspan = COLUMN_SPAN(ctrl->generic.column); + int colend = colstart + colspan; + int ytop, wthis; + + switch (ctrl->generic.type) { + case CTRL_COLUMNS: + for (j = 1; j < ncols; j++) + if (cypos[0] < cypos[j]) + cypos[0] = cypos[j]; + + assert(ctrl->columns.ncols < lenof(ccw)); + + ccw[0] = 0; + for (j = 0; j < ctrl->columns.ncols; j++) { + ccw[j+1] = ccw[j] + (ctrl->columns.percentages ? + ctrl->columns.percentages[j] : 100); + cypos[j] = cypos[0]; + } + + ncols = ctrl->columns.ncols; + + continue; /* no actual control created */ + case CTRL_TABDELAY: + /* + * I'm currently uncertain that we can implement tab + * order in OS X. + */ + continue; /* no actual control created */ + } + + c = fe_ctrl_new(ctrl); + add234(d->byctrl, c); + + cw = ch = 0; + + switch (ctrl->generic.type) { + case CTRL_BUTTON: + case CTRL_CHECKBOX: + { + NSButton *b; + + b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)]; + [b setBezelStyle:NSRoundedBezelStyle]; + if (ctrl->generic.type == CTRL_CHECKBOX) + [b setButtonType:NSSwitchButton]; + [b setTitle:[NSString stringWithCString:ctrl->generic.label]]; + if (ctrl->button.isdefault) + [b setKeyEquivalent:@"\r"]; + else if (ctrl->button.iscancel) + [b setKeyEquivalent:@"\033"]; + [b sizeToFit]; + rect = [b frame]; + + [parent addSubview:b]; + + [b setTarget:d->rec]; + if (ctrl->generic.type == CTRL_CHECKBOX) + [b setAction:@selector(checkboxChanged:)]; + else + [b setAction:@selector(buttonPushed:)]; + add_widget(d, c, b); + + c->button = b; + + cw = rect.size.width; + ch = rect.size.height; + } + break; + case CTRL_EDITBOX: + { + int editp = ctrl->editbox.percentwidth; + int labelp = editp == 100 ? 100 : 100 - editp; + NSTextField *tf; + NSComboBox *cb; + + tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)]; + [tf setEditable:NO]; + [tf setSelectable:NO]; + [tf setBordered:NO]; + [tf setDrawsBackground:NO]; + [tf setStringValue:[NSString + stringWithCString:ctrl->generic.label]]; + [tf sizeToFit]; + rect = [tf frame]; + [parent addSubview:tf]; + c->label = tf; + + cw = rect.size.width * 100 / labelp; + ch = rect.size.height; + + if (ctrl->editbox.has_list) { + cb = [[NSComboBox alloc] + initWithFrame:NSMakeRect(0,0,1,1)]; + [cb setStringValue:@"x"]; + [cb sizeToFit]; + rect = [cb frame]; + [parent addSubview:cb]; + c->combobox = cb; + } else { + if (ctrl->editbox.password) + tf = [NSSecureTextField alloc]; + else + tf = [NSTextField alloc]; + + tf = [tf initWithFrame:NSMakeRect(0,0,1,1)]; + [tf setEditable:YES]; + [tf setSelectable:YES]; + [tf setBordered:YES]; + [tf setStringValue:@"x"]; + [tf sizeToFit]; + rect = [tf frame]; + [parent addSubview:tf]; + c->editbox = tf; + + [tf setDelegate:d->rec]; + add_widget(d, c, tf); + } + + if (editp == 100) { + /* the edit box and its label are vertically separated */ + ch += VSPACING + rect.size.height; + } else { + /* the edit box and its label are horizontally separated */ + if (ch < rect.size.height) + ch = rect.size.height; + } + + if (cw < rect.size.width * 100 / editp) + cw = rect.size.width * 100 / editp; + } + break; + case CTRL_TEXT: + { + NSTextView *tv; + int testwid; + + if (!textviewfont) { + NSTextField *tf; + tf = [[NSTextField alloc] init]; + textviewfont = [tf font]; + [tf release]; + } + + testwid = (ccw[colend] - ccw[colstart]) * 3; + + tv = [[NSTextView alloc] + initWithFrame:NSMakeRect(0,0,testwid,1)]; + [tv setEditable:NO]; + [tv setSelectable:NO]; + //[tv setBordered:NO]; + [tv setDrawsBackground:NO]; + [tv setFont:textviewfont]; + [tv setString: + [NSString stringWithCString:ctrl->generic.label]]; + rect = [tv frame]; + [tv sizeToFit]; + [parent addSubview:tv]; + c->textview = tv; + + cw = rect.size.width; + ch = rect.size.height; + } + break; + case CTRL_RADIO: + { + NSTextField *tf; + int j; + + if (ctrl->generic.label) { + tf = [[NSTextField alloc] + initWithFrame:NSMakeRect(0,0,1,1)]; + [tf setEditable:NO]; + [tf setSelectable:NO]; + [tf setBordered:NO]; + [tf setDrawsBackground:NO]; + [tf setStringValue: + [NSString stringWithCString:ctrl->generic.label]]; + [tf sizeToFit]; + rect = [tf frame]; + [parent addSubview:tf]; + c->label = tf; + + cw = rect.size.width; + ch = rect.size.height; + } else { + cw = 0; + ch = -VSPACING; /* compensate for next advance */ + } + + c->nradiobuttons = ctrl->radio.nbuttons; + c->radiobuttons = snewn(ctrl->radio.nbuttons, NSButton *); + + for (j = 0; j < ctrl->radio.nbuttons; j++) { + NSButton *b; + int ncols; + + b = [[MyButton alloc] initWithFrame:NSMakeRect(0,0,1,1)]; + [b setBezelStyle:NSRoundedBezelStyle]; + [b setButtonType:NSRadioButton]; + [b setTitle:[NSString + stringWithCString:ctrl->radio.buttons[j]]]; + [b sizeToFit]; + rect = [b frame]; + [parent addSubview:b]; + + c->radiobuttons[j] = b; + + [b setTarget:d->rec]; + [b setAction:@selector(radioChanged:)]; + add_widget(d, c, b); + + /* + * Add to the height every time we place a + * button in column 0. + */ + if (j % ctrl->radio.ncolumns == 0) { + ch += rect.size.height + VSPACING; + } + + /* + * Add to the width by working out how many + * columns this button spans. + */ + if (j == ctrl->radio.nbuttons - 1) + ncols = (ctrl->radio.ncolumns - + (j % ctrl->radio.ncolumns)); + else + ncols = 1; + + if (cw < rect.size.width * ctrl->radio.ncolumns / ncols) + cw = rect.size.width * ctrl->radio.ncolumns / ncols; + } + } + break; + case CTRL_FILESELECT: + case CTRL_FONTSELECT: + { + NSTextField *tf; + NSButton *b; + int kh; + + tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)]; + [tf setEditable:NO]; + [tf setSelectable:NO]; + [tf setBordered:NO]; + [tf setDrawsBackground:NO]; + [tf setStringValue:[NSString + stringWithCString:ctrl->generic.label]]; + [tf sizeToFit]; + rect = [tf frame]; + [parent addSubview:tf]; + c->label = tf; + + cw = rect.size.width; + ch = rect.size.height; + + tf = [NSTextField alloc]; + tf = [tf initWithFrame:NSMakeRect(0,0,1,1)]; + if (ctrl->generic.type == CTRL_FILESELECT) { + [tf setEditable:YES]; + [tf setSelectable:YES]; + [tf setBordered:YES]; + } else { + [tf setEditable:NO]; + [tf setSelectable:NO]; + [tf setBordered:NO]; + [tf setDrawsBackground:NO]; + } + [tf setStringValue:@"x"]; + [tf sizeToFit]; + rect = [tf frame]; + [parent addSubview:tf]; + c->editbox = tf; + + kh = rect.size.height; + if (cw < rect.size.width * 4 / 3) + cw = rect.size.width * 4 / 3; + + b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)]; + [b setBezelStyle:NSRoundedBezelStyle]; + if (ctrl->generic.type == CTRL_FILESELECT) + [b setTitle:@"Browse..."]; + else + [b setTitle:@"Change..."]; + // [b setKeyEquivalent:somethingorother]; + // [b setTarget:somethingorother]; + // [b setAction:somethingorother]; + [b sizeToFit]; + rect = [b frame]; + [parent addSubview:b]; + + c->button = b; + + if (kh < rect.size.height) + kh = rect.size.height; + ch += VSPACING + kh; + if (cw < rect.size.width * 4) + cw = rect.size.width * 4; + } + break; + case CTRL_LISTBOX: + { + int listp = ctrl->listbox.percentwidth; + int labelp = listp == 100 ? 100 : 100 - listp; + NSTextField *tf; + NSPopUpButton *pb; + NSTableView *tv; + NSScrollView *sv; + + if (ctrl->generic.label) { + tf = [[NSTextField alloc] + initWithFrame:NSMakeRect(0,0,1,1)]; + [tf setEditable:NO]; + [tf setSelectable:NO]; + [tf setBordered:NO]; + [tf setDrawsBackground:NO]; + [tf setStringValue: + [NSString stringWithCString:ctrl->generic.label]]; + [tf sizeToFit]; + rect = [tf frame]; + [parent addSubview:tf]; + c->label = tf; + + cw = rect.size.width; + ch = rect.size.height; + } else { + cw = 0; + ch = -VSPACING; /* compensate for next advance */ + } + + if (ctrl->listbox.height == 0) { + pb = [[NSPopUpButton alloc] + initWithFrame:NSMakeRect(0,0,1,1)]; + [pb sizeToFit]; + rect = [pb frame]; + [parent addSubview:pb]; + c->popupbutton = pb; + + [pb setTarget:d->rec]; + [pb setAction:@selector(popupMenuSelected:)]; + add_widget(d, c, pb); + } else { + assert(listp == 100); + if (ctrl->listbox.draglist) { + int bi; + + listp = 75; + + for (bi = 0; bi < 2; bi++) { + NSButton *b; + b = [[MyButton alloc] + initWithFrame:NSMakeRect(0, 0, 1, 1)]; + [b setBezelStyle:NSRoundedBezelStyle]; + if (bi == 0) + [b setTitle:@"Up"]; + else + [b setTitle:@"Down"]; + [b sizeToFit]; + rect = [b frame]; + [parent addSubview:b]; + + if (bi == 0) + c->button = b; + else + c->button2 = b; + + [b setTarget:d->rec]; + [b setAction:@selector(dragListButton:)]; + add_widget(d, c, b); + + if (cw < rect.size.width * 4) + cw = rect.size.width * 4; + } + } + + sv = [[NSScrollView alloc] initWithFrame: + NSMakeRect(20,20,10,10)]; + [sv setBorderType:NSLineBorder]; + tv = [[NSTableView alloc] initWithFrame:[sv frame]]; + [[tv headerView] setFrame:NSMakeRect(0,0,0,0)]; + [sv setDocumentView:tv]; + [parent addSubview:sv]; + [sv setHasVerticalScroller:YES]; + [sv setAutohidesScrollers:YES]; + [tv setAllowsColumnReordering:NO]; + [tv setAllowsColumnResizing:NO]; + [tv setAllowsMultipleSelection:ctrl->listbox.multisel]; + [tv setAllowsEmptySelection:YES]; + [tv setAllowsColumnSelection:YES]; + [tv setDataSource:[[MyTableSource alloc] init]]; + rect = [tv frame]; + /* + * For some reason this consistently comes out + * one short. Add one. + */ + rect.size.height = (ctrl->listbox.height+1)*[tv rowHeight]; + [sv setFrame:rect]; + c->tableview = tv; + c->scrollview = sv; + + [tv setDelegate:d->rec]; + [tv setTarget:d->rec]; + [tv setDoubleAction:@selector(listDoubleClicked:)]; + add_widget(d, c, tv); + } + + if (c->tableview) { + int ncols, *percentages; + int hundred = 100; + + if (ctrl->listbox.ncols) { + ncols = ctrl->listbox.ncols; + percentages = ctrl->listbox.percentages; + } else { + ncols = 1; + percentages = &hundred; + } + + for (j = 0; j < ncols; j++) { + NSTableColumn *col; + + col = [[NSTableColumn alloc] initWithIdentifier: + [NSNumber numberWithInt:j]]; + [c->tableview addTableColumn:col]; + } + } + + if (labelp == 100) { + /* the list and its label are vertically separated */ + ch += VSPACING + rect.size.height; + } else { + /* the list and its label are horizontally separated */ + if (ch < rect.size.height) + ch = rect.size.height; + } + + if (cw < rect.size.width * 100 / listp) + cw = rect.size.width * 100 / listp; + } + break; + } + + /* + * Update the width and height data for the control we've + * just created. + */ + ytop = 0; + + for (j = colstart; j < colend; j++) { + if (ytop < cypos[j]) + ytop = cypos[j]; + } + + for (j = colstart; j < colend; j++) + cypos[j] = ytop + ch + VSPACING; + + if (hmin < ytop + ch) + hmin = ytop + ch; + + wthis = (cw + HSPACING) * 100 / (ccw[colend] - ccw[colstart]); + wthis -= HSPACING; + + if (wmin < wthis) + wmin = wthis; + } + + if (*s->boxname) { + /* + * Add a bit to the width and height for the box. + */ + wmin += boxw; + hmin += boxh; + } + + //printf("For controlset %s/%s, returning w=%d h=%d\n", + // s->pathname, s->boxname, wmin, hmin); + *minw = wmin; + *minh = hmin; +} + +int place_ctrls(void *dv, struct controlset *s, int leftx, int topy, + int width) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + int ccw[100]; /* cumulative column widths */ + int cypos[100]; + int ncols; + int i, j, ret; + int boxh = 0, boxw = 0; + + if (!s->boxname && s->boxtitle) { + /* Size and place the panel title. */ + + NSTextField *tf = find_box(d, s); + NSRect rect; + + rect = [tf frame]; + [tf setFrame:NSMakeRect(leftx, topy-rect.size.height, + width, rect.size.height)]; + return rect.size.height; + } + + if (*s->boxname) { + NSRect rect, tmprect; + NSBox *box = find_box(d, s); + + assert(box != NULL); + tmprect = [box frame]; + [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)]; + rect = [box frame]; + [box setFrame:tmprect]; + boxw = rect.size.width - 100; + boxh = rect.size.height - 100; + if (s->boxtitle) + boxh += [[box titleFont] pointSize]; + topy -= boxh; + width -= boxw; + } + + ncols = 1; + ccw[0] = 0; + ccw[1] = 100; + cypos[0] = topy; + ret = 0; + + /* + * Now iterate through the controls themselves, placing them + * appropriately. + */ + for (i = 0; i < s->ncontrols; i++) { + union control *ctrl = s->ctrls[i]; + struct fe_ctrl *c; + int colstart = COLUMN_START(ctrl->generic.column); + int colspan = COLUMN_SPAN(ctrl->generic.column); + int colend = colstart + colspan; + int xthis, ythis, wthis, ch; + NSRect rect; + + switch (ctrl->generic.type) { + case CTRL_COLUMNS: + for (j = 1; j < ncols; j++) + if (cypos[0] > cypos[j]) + cypos[0] = cypos[j]; + + assert(ctrl->columns.ncols < lenof(ccw)); + + ccw[0] = 0; + for (j = 0; j < ctrl->columns.ncols; j++) { + ccw[j+1] = ccw[j] + (ctrl->columns.percentages ? + ctrl->columns.percentages[j] : 100); + cypos[j] = cypos[0]; + } + + ncols = ctrl->columns.ncols; + + continue; /* no actual control created */ + case CTRL_TABDELAY: + continue; /* nothing to do here, move along */ + } + + c = fe_ctrl_byctrl(d, ctrl); + + ch = 0; + ythis = topy; + + for (j = colstart; j < colend; j++) { + if (ythis > cypos[j]) + ythis = cypos[j]; + } + + xthis = (width + HSPACING) * ccw[colstart] / 100; + wthis = (width + HSPACING) * ccw[colend] / 100 - HSPACING - xthis; + xthis += leftx; + + switch (ctrl->generic.type) { + case CTRL_BUTTON: + case CTRL_CHECKBOX: + rect = [c->button frame]; + [c->button setFrame:NSMakeRect(xthis,ythis-rect.size.height,wthis, + rect.size.height)]; + ch = rect.size.height; + break; + case CTRL_EDITBOX: + { + int editp = ctrl->editbox.percentwidth; + int labelp = editp == 100 ? 100 : 100 - editp; + int lheight, theight, rheight, ynext, editw; + NSControl *edit = (c->editbox ? c->editbox : c->combobox); + + rect = [c->label frame]; + lheight = rect.size.height; + rect = [edit frame]; + theight = rect.size.height; + + if (editp == 100) + rheight = lheight; + else + rheight = (lheight < theight ? theight : lheight); + + [c->label setFrame: + NSMakeRect(xthis, ythis-(rheight+lheight)/2, + (wthis + HSPACING) * labelp / 100 - HSPACING, + lheight)]; + if (editp == 100) { + ynext = ythis - rheight - VSPACING; + rheight = theight; + } else { + ynext = ythis; + } + + editw = (wthis + HSPACING) * editp / 100 - HSPACING; + + [edit setFrame: + NSMakeRect(xthis+wthis-editw, ynext-(rheight+theight)/2, + editw, theight)]; + + ch = (ythis - ynext) + theight; + } + break; + case CTRL_TEXT: + [c->textview setFrame:NSMakeRect(xthis, 0, wthis, 1)]; + [c->textview sizeToFit]; + rect = [c->textview frame]; + [c->textview setFrame:NSMakeRect(xthis, ythis-rect.size.height, + wthis, rect.size.height)]; + ch = rect.size.height; + break; + case CTRL_RADIO: + { + int j, ynext; + + if (c->label) { + rect = [c->label frame]; + [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height, + wthis,rect.size.height)]; + ynext = ythis - rect.size.height - VSPACING; + } else + ynext = ythis; + + for (j = 0; j < ctrl->radio.nbuttons; j++) { + int col = j % ctrl->radio.ncolumns; + int ncols; + int lx,rx; + + if (j == ctrl->radio.nbuttons - 1) + ncols = ctrl->radio.ncolumns - col; + else + ncols = 1; + + lx = (wthis + HSPACING) * col / ctrl->radio.ncolumns; + rx = ((wthis + HSPACING) * + (col+ncols) / ctrl->radio.ncolumns) - HSPACING; + + /* + * Set the frame size. + */ + rect = [c->radiobuttons[j] frame]; + [c->radiobuttons[j] setFrame: + NSMakeRect(lx+xthis, ynext-rect.size.height, + rx-lx, rect.size.height)]; + + /* + * Advance to next line if we're in the last + * column. + */ + if (col + ncols == ctrl->radio.ncolumns) + ynext -= rect.size.height + VSPACING; + } + ch = (ythis - ynext) - VSPACING; + } + break; + case CTRL_FILESELECT: + case CTRL_FONTSELECT: + { + int ynext, eh, bh, th, mx; + + rect = [c->label frame]; + [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height, + wthis,rect.size.height)]; + ynext = ythis - rect.size.height - VSPACING; + + rect = [c->editbox frame]; + eh = rect.size.height; + rect = [c->button frame]; + bh = rect.size.height; + th = (eh > bh ? eh : bh); + + mx = (wthis + HSPACING) * 3 / 4 - HSPACING; + + [c->editbox setFrame: + NSMakeRect(xthis, ynext-(th+eh)/2, mx, eh)]; + [c->button setFrame: + NSMakeRect(xthis+mx+HSPACING, ynext-(th+bh)/2, + wthis-mx-HSPACING, bh)]; + + ch = (ythis - ynext) + th + VSPACING; + } + break; + case CTRL_LISTBOX: + { + int listp = ctrl->listbox.percentwidth; + int labelp = listp == 100 ? 100 : 100 - listp; + int lheight, theight, rheight, ynext, listw, xlist; + NSControl *list = (c->scrollview ? (id)c->scrollview : + (id)c->popupbutton); + + if (ctrl->listbox.draglist) { + assert(listp == 100); + listp = 75; + } + + rect = [list frame]; + theight = rect.size.height; + + if (c->label) { + rect = [c->label frame]; + lheight = rect.size.height; + + if (labelp == 100) + rheight = lheight; + else + rheight = (lheight < theight ? theight : lheight); + + [c->label setFrame: + NSMakeRect(xthis, ythis-(rheight+lheight)/2, + (wthis + HSPACING) * labelp / 100 - HSPACING, + lheight)]; + if (labelp == 100) { + ynext = ythis - rheight - VSPACING; + rheight = theight; + } else { + ynext = ythis; + } + } else { + ynext = ythis; + rheight = theight; + } + + listw = (wthis + HSPACING) * listp / 100 - HSPACING; + + if (labelp == 100) + xlist = xthis; + else + xlist = xthis+wthis-listw; + + [list setFrame: NSMakeRect(xlist, ynext-(rheight+theight)/2, + listw, theight)]; + + /* + * Size the columns for the table view. + */ + if (c->tableview) { + int ncols, *percentages; + int hundred = 100; + int cpercent = 0, cpixels = 0; + NSArray *cols; + + if (ctrl->listbox.ncols) { + ncols = ctrl->listbox.ncols; + percentages = ctrl->listbox.percentages; + } else { + ncols = 1; + percentages = &hundred; + } + + cols = [c->tableview tableColumns]; + + for (j = 0; j < ncols; j++) { + NSTableColumn *col = [cols objectAtIndex:j]; + int newcpixels; + + cpercent += percentages[j]; + newcpixels = listw * cpercent / 100; + [col setWidth:newcpixels-cpixels]; + cpixels = newcpixels; + } + } + + ch = (ythis - ynext) + theight; + + if (c->button) { + int b2height, centre; + int bx, bw; + + /* + * Place the Up and Down buttons for a drag list. + */ + assert(c->button2); + + rect = [c->button frame]; + b2height = VSPACING + 2 * rect.size.height; + + centre = ynext - rheight/2; + + bx = (wthis + HSPACING) * 3 / 4; + bw = wthis - bx; + bx += leftx; + + [c->button setFrame: + NSMakeRect(bx, centre+b2height/2-rect.size.height, + bw, rect.size.height)]; + [c->button2 setFrame: + NSMakeRect(bx, centre-b2height/2, + bw, rect.size.height)]; + } + } + break; + } + + for (j = colstart; j < colend; j++) + cypos[j] = ythis - ch - VSPACING; + if (ret < topy - (ythis - ch)) + ret = topy - (ythis - ch); + } + + if (*s->boxname) { + NSBox *box = find_box(d, s); + assert(box != NULL); + [box sizeToFit]; + + if (s->boxtitle) { + NSRect rect = [box frame]; + rect.size.height += [[box titleFont] pointSize]; + [box setFrame:rect]; + } + + ret += boxh; + } + + //printf("For controlset %s/%s, returning ret=%d\n", + // s->pathname, s->boxname, ret); + return ret; +} + +void select_panel(void *dv, struct controlbox *b, const char *name) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + int i, j, hidden; + struct controlset *s; + union control *ctrl; + struct fe_ctrl *c; + NSBox *box; + + for (i = 0; i < b->nctrlsets; i++) { + s = b->ctrlsets[i]; + + if (*s->pathname) { + hidden = !strcmp(s->pathname, name) ? NO : YES; + + if ((box = find_box(d, s)) != NULL) { + [box setHidden:hidden]; + } else { + for (j = 0; j < s->ncontrols; j++) { + ctrl = s->ctrls[j]; + c = fe_ctrl_byctrl(d, ctrl); + + if (!c) + continue; + + if (c->label) + [c->label setHidden:hidden]; + if (c->button) + [c->button setHidden:hidden]; + if (c->button2) + [c->button2 setHidden:hidden]; + if (c->editbox) + [c->editbox setHidden:hidden]; + if (c->combobox) + [c->combobox setHidden:hidden]; + if (c->textview) + [c->textview setHidden:hidden]; + if (c->tableview) + [c->tableview setHidden:hidden]; + if (c->scrollview) + [c->scrollview setHidden:hidden]; + if (c->popupbutton) + [c->popupbutton setHidden:hidden]; + if (c->radiobuttons) { + int j; + for (j = 0; j < c->nradiobuttons; j++) + [c->radiobuttons[j] setHidden:hidden]; + } + break; + } + } + } + } +} + +void dlg_radiobutton_set(union control *ctrl, void *dv, int whichbutton) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + int j; + + assert(c->radiobuttons); + for (j = 0; j < c->nradiobuttons; j++) + [c->radiobuttons[j] setState: + (j == whichbutton ? NSOnState : NSOffState)]; +} + +int dlg_radiobutton_get(union control *ctrl, void *dv) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + int j; + + assert(c->radiobuttons); + for (j = 0; j < c->nradiobuttons; j++) + if ([c->radiobuttons[j] state] == NSOnState) + return j; + + return 0; /* should never reach here */ +} + +void dlg_checkbox_set(union control *ctrl, void *dv, int checked) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + + assert(c->button); + [c->button setState:(checked ? NSOnState : NSOffState)]; +} + +int dlg_checkbox_get(union control *ctrl, void *dv) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + + assert(c->button); + return ([c->button state] == NSOnState); +} + +void dlg_editbox_set(union control *ctrl, void *dv, char const *text) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + + if (c->editbox) { + [c->editbox setStringValue:[NSString stringWithCString:text]]; + } else { + assert(c->combobox); + [c->combobox setStringValue:[NSString stringWithCString:text]]; + } +} + +void dlg_editbox_get(union control *ctrl, void *dv, char *buffer, int length) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + NSString *str; + + if (c->editbox) { + str = [c->editbox stringValue]; + } else { + assert(c->combobox); + str = [c->combobox stringValue]; + } + if (!str) + str = @""; + + /* The length parameter to this method doesn't include a trailing NUL */ + [str getCString:buffer maxLength:length-1]; +} + +void dlg_listbox_clear(union control *ctrl, void *dv) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + + if (c->tableview) { + [[c->tableview dataSource] clear]; + [c->tableview reloadData]; + } else { + [c->popupbutton removeAllItems]; + } +} + +void dlg_listbox_del(union control *ctrl, void *dv, int index) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + + if (c->tableview) { + [[c->tableview dataSource] removestr:index]; + [c->tableview reloadData]; + } else { + [c->popupbutton removeItemAtIndex:index]; + } +} + +void dlg_listbox_addwithid(union control *ctrl, void *dv, + char const *text, int id) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + + if (c->tableview) { + [[c->tableview dataSource] add:text withId:id]; + [c->tableview reloadData]; + } else { + [c->popupbutton addItemWithTitle:[NSString stringWithCString:text]]; + [[c->popupbutton lastItem] setTag:id]; + } +} + +void dlg_listbox_add(union control *ctrl, void *dv, char const *text) +{ + dlg_listbox_addwithid(ctrl, dv, text, -1); +} + +int dlg_listbox_getid(union control *ctrl, void *dv, int index) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + + if (c->tableview) { + return [[c->tableview dataSource] getid:index]; + } else { + return [[c->popupbutton itemAtIndex:index] tag]; + } +} + +int dlg_listbox_index(union control *ctrl, void *dv) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + + if (c->tableview) { + return [c->tableview selectedRow]; + } else { + return [c->popupbutton indexOfSelectedItem]; + } +} + +int dlg_listbox_issel(union control *ctrl, void *dv, int index) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + + if (c->tableview) { + return [c->tableview isRowSelected:index]; + } else { + return [c->popupbutton indexOfSelectedItem] == index; + } +} + +void dlg_listbox_select(union control *ctrl, void *dv, int index) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + + if (c->tableview) { + [c->tableview selectRow:index byExtendingSelection:NO]; + } else { + [c->popupbutton selectItemAtIndex:index]; + } +} + +void dlg_text_set(union control *ctrl, void *dv, char const *text) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + + assert(c->textview); + [c->textview setString:[NSString stringWithCString:text]]; +} + +void dlg_label_change(union control *ctrl, void *dlg, char const *text) +{ + /* + * This function is currently only used by the config box to + * switch the labels on the host and port boxes between serial + * and network modes. Since OS X does not (yet?) have a serial + * back end, this function can safely do nothing for the + * moment. + */ +} + +void dlg_filesel_set(union control *ctrl, void *dv, Filename fn) +{ + /* FIXME */ +} + +void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn) +{ + /* FIXME */ +} + +void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn) +{ + /* FIXME */ +} + +void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn) +{ + /* FIXME */ +} + +void dlg_update_start(union control *ctrl, void *dv) +{ + /* FIXME */ +} + +void dlg_update_done(union control *ctrl, void *dv) +{ + /* FIXME */ +} + +void dlg_set_focus(union control *ctrl, void *dv) +{ + /* FIXME */ +} + +union control *dlg_last_focused(union control *ctrl, void *dv) +{ + return NULL; /* FIXME */ +} + +void dlg_beep(void *dv) +{ + NSBeep(); +} + +void dlg_error_msg(void *dv, char *msg) +{ + /* FIXME */ +} + +void dlg_end(void *dv, int value) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + [d->target performSelector:d->action + withObject:[NSNumber numberWithInt:value]]; +} + +void dlg_coloursel_start(union control *ctrl, void *dv, + int r, int g, int b) +{ + /* FIXME */ +} + +int dlg_coloursel_results(union control *ctrl, void *dv, + int *r, int *g, int *b) +{ + return 0; /* FIXME */ +} + +void dlg_refresh(union control *ctrl, void *dv) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c; + + if (ctrl) { + if (ctrl->generic.handler != NULL) + ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH); + } else { + int i; + + for (i = 0; (c = index234(d->byctrl, i)) != NULL; i++) { + assert(c->ctrl != NULL); + if (c->ctrl->generic.handler != NULL) + c->ctrl->generic.handler(c->ctrl, d, + d->data, EVENT_REFRESH); + } + } +} + +void *dlg_get_privdata(union control *ctrl, void *dv) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + return c->privdata; +} + +void dlg_set_privdata(union control *ctrl, void *dv, void *ptr) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + c->privdata = ptr; + c->privdata_needs_free = FALSE; +} + +void *dlg_alloc_privdata(union control *ctrl, void *dv, size_t size) +{ + struct fe_dlg *d = (struct fe_dlg *)dv; + struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); + /* + * This is an internal allocation routine, so it's allowed to + * use smalloc directly. + */ + c->privdata = smalloc(size); + c->privdata_needs_free = TRUE; + return c->privdata; +} diff --git a/putty/MACOSX/OSXDLG.M b/putty/MACOSX/OSXDLG.M new file mode 100644 index 0000000..9502914 --- /dev/null +++ b/putty/MACOSX/OSXDLG.M @@ -0,0 +1,509 @@ +/* + * osxdlg.m: various PuTTY dialog boxes for OS X. + */ + +#import +#include "putty.h" +#include "storage.h" +#include "dialog.h" +#include "osxclass.h" + +/* + * The `ConfigWindow' class is used to start up a new PuTTY + * session. + */ + +@class ConfigTree; +@interface ConfigTree : NSObject +{ + NSString **paths; + int *levels; + int nitems, itemsize; +} +- (void)addPath:(char *)path; +@end + +@implementation ConfigTree +- (id)init +{ + self = [super init]; + paths = NULL; + levels = NULL; + nitems = itemsize = 0; + return self; +} +- (void)addPath:(char *)path +{ + if (nitems >= itemsize) { + itemsize += 32; + paths = sresize(paths, itemsize, NSString *); + levels = sresize(levels, itemsize, int); + } + paths[nitems] = [[NSString stringWithCString:path] retain]; + levels[nitems] = ctrl_path_elements(path) - 1; + nitems++; +} +- (void)dealloc +{ + int i; + + for (i = 0; i < nitems; i++) + [paths[i] release]; + + sfree(paths); + sfree(levels); + + [super dealloc]; +} +- (id)iterateChildren:(int)index ofItem:(id)item count:(int *)count +{ + int i, plevel; + + if (item) { + for (i = 0; i < nitems; i++) + if (paths[i] == item) + break; + assert(i < nitems); + plevel = levels[i]; + i++; + } else { + i = 0; + plevel = -1; + } + + if (count) + *count = 0; + + while (index > 0) { + if (i >= nitems || levels[i] != plevel+1) + return nil; + if (count) + (*count)++; + do { + i++; + } while (i < nitems && levels[i] > plevel+1); + index--; + } + + return paths[i]; +} +- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item +{ + return [self iterateChildren:index ofItem:item count:NULL]; +} +- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item +{ + int count = 0; + /* pass nitems+1 to ensure we run off the end */ + [self iterateChildren:nitems+1 ofItem:item count:&count]; + return count; +} +- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item +{ + return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0; +} +- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item +{ + /* + * Trim off all path elements except the last one. + */ + NSArray *components = [item componentsSeparatedByString:@"/"]; + return [components objectAtIndex:[components count]-1]; +} +@end + +@implementation ConfigWindow +- (id)initWithConfig:(Config)aCfg +{ + NSScrollView *scrollview; + NSTableColumn *col; + ConfigTree *treedata; + int by = 0, mby = 0; + int wmin = 0; + int hmin = 0; + int panelht = 0; + + ctrlbox = ctrl_new_box(); + setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol, + 0 /* protcfginfo */); + unix_setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol); + + cfg = aCfg; /* structure copy */ + + self = [super initWithContentRect:NSMakeRect(0,0,300,300) + styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask | + NSClosableWindowMask) + backing:NSBackingStoreBuffered + defer:YES]; + [self setTitle:@"PuTTY Configuration"]; + + [self setIgnoresMouseEvents:NO]; + + dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:)); + + scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)]; + treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]]; + [scrollview setBorderType:NSLineBorder]; + [scrollview setDocumentView:treeview]; + [[self contentView] addSubview:scrollview]; + [scrollview setHasVerticalScroller:YES]; + [scrollview setAutohidesScrollers:YES]; + /* FIXME: the below is untested. Test it then remove this notice. */ + [treeview setAllowsColumnReordering:NO]; + [treeview setAllowsColumnResizing:NO]; + [treeview setAllowsMultipleSelection:NO]; + [treeview setAllowsEmptySelection:NO]; + [treeview setAllowsColumnSelection:YES]; + + treedata = [[[ConfigTree alloc] init] retain]; + + col = [[NSTableColumn alloc] initWithIdentifier:nil]; + [treeview addTableColumn:col]; + [treeview setOutlineTableColumn:col]; + + [[treeview headerView] setFrame:NSMakeRect(0,0,0,0)]; + + /* + * Create the controls. + */ + { + int i; + char *path = NULL; + + for (i = 0; i < ctrlbox->nctrlsets; i++) { + struct controlset *s = ctrlbox->ctrlsets[i]; + int mw, mh; + + if (!*s->pathname) { + + create_ctrls(dv, [self contentView], s, &mw, &mh); + + by += 20 + mh; + + if (wmin < mw + 40) + wmin = mw + 40; + } else { + int j = path ? ctrl_path_compare(s->pathname, path) : 0; + + if (j != INT_MAX) { /* add to treeview, start new panel */ + char *c; + + /* + * 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++; + + [treedata addPath:s->pathname]; + path = s->pathname; + + panelht = 0; + } + + create_ctrls(dv, [self contentView], s, &mw, &mh); + if (wmin < mw + 3*20+150) + wmin = mw + 3*20+150; + panelht += mh + 20; + if (hmin < panelht - 20) + hmin = panelht - 20; + } + } + } + + { + int i; + NSRect r; + + [treeview setDataSource:treedata]; + for (i = [treeview numberOfRows]; i-- ;) + [treeview expandItem:[treeview itemAtRow:i] expandChildren:YES]; + + [treeview sizeToFit]; + r = [treeview frame]; + if (hmin < r.size.height) + hmin = r.size.height; + } + + [self setContentSize:NSMakeSize(wmin, hmin+60+by)]; + [scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)]; + [treeview setDelegate:self]; + mby = by; + + /* + * Now place the controls. + */ + { + int i; + char *path = NULL; + panelht = 0; + + for (i = 0; i < ctrlbox->nctrlsets; i++) { + struct controlset *s = ctrlbox->ctrlsets[i]; + + if (!*s->pathname) { + by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40); + } else { + if (!path || strcmp(s->pathname, path)) + panelht = 0; + + panelht += VSPACING + place_ctrls(dv, s, 2*20+150, + 40+mby+hmin-panelht, + wmin - (3*20+150)); + + path = s->pathname; + } + } + } + + select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]); + + [treeview reloadData]; + + dlg_refresh(NULL, dv); + + [self center]; /* :-) */ + + return self; +} +- (void)configBoxFinished:(id)object +{ + int ret = [object intValue]; /* it'll be an NSNumber */ + if (ret) { + [controller performSelectorOnMainThread: + @selector(newSessionWithConfig:) + withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)] + waitUntilDone:NO]; + } + [self close]; +} +- (void)outlineViewSelectionDidChange:(NSNotification *)notification +{ + const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString]; + select_panel(dv, ctrlbox, path); +} +- (BOOL)outlineView:(NSOutlineView *)outlineView + shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + return NO; /* no editing! */ +} +@end + +/* ---------------------------------------------------------------------- + * Various special-purpose dialog boxes. + */ + +struct appendstate { + void (*callback)(void *ctx, int result); + void *ctx; +}; + +static void askappend_callback(void *ctx, int result) +{ + struct appendstate *state = (struct appendstate *)ctx; + + state->callback(state->ctx, (result == NSAlertFirstButtonReturn ? 2 : + result == NSAlertSecondButtonReturn ? 1 : 0)); + sfree(state); +} + +int askappend(void *frontend, Filename filename, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msgtemplate[] = + "The session log file \"%s\" already exists. " + "You can overwrite it with a new session log, " + "append your session log to the end of it, " + "or disable session logging for this session."; + + char *text; + SessionWindow *win = (SessionWindow *)frontend; + struct appendstate *state; + NSAlert *alert; + + text = dupprintf(msgtemplate, filename.path); + + state = snew(struct appendstate); + state->callback = callback; + state->ctx = ctx; + + alert = [[NSAlert alloc] init]; + [alert setInformativeText:[NSString stringWithCString:text]]; + [alert addButtonWithTitle:@"Overwrite"]; + [alert addButtonWithTitle:@"Append"]; + [alert addButtonWithTitle:@"Disable"]; + [win startAlert:alert withCallback:askappend_callback andCtx:state]; + + return -1; +} + +struct algstate { + void (*callback)(void *ctx, int result); + void *ctx; +}; + +static void askalg_callback(void *ctx, int result) +{ + struct algstate *state = (struct algstate *)ctx; + + state->callback(state->ctx, result == NSAlertFirstButtonReturn); + sfree(state); +} + +int askalg(void *frontend, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msg[] = + "The first %s supported by the server is " + "%s, which is below the configured warning threshold.\n" + "Continue with connection?"; + + char *text; + SessionWindow *win = (SessionWindow *)frontend; + struct algstate *state; + NSAlert *alert; + + text = dupprintf(msg, algtype, algname); + + state = snew(struct algstate); + state->callback = callback; + state->ctx = ctx; + + alert = [[NSAlert alloc] init]; + [alert setInformativeText:[NSString stringWithCString:text]]; + [alert addButtonWithTitle:@"Yes"]; + [alert addButtonWithTitle:@"No"]; + [win startAlert:alert withCallback:askalg_callback andCtx:state]; + + return -1; +} + +struct hostkeystate { + char *host, *keytype, *keystr; + int port; + void (*callback)(void *ctx, int result); + void *ctx; +}; + +static void verify_ssh_host_key_callback(void *ctx, int result) +{ + struct hostkeystate *state = (struct hostkeystate *)ctx; + + if (result == NSAlertThirdButtonReturn) /* `Accept' */ + store_host_key(state->host, state->port, + state->keytype, state->keystr); + state->callback(state->ctx, result != NSAlertFirstButtonReturn); + sfree(state->host); + sfree(state->keytype); + sfree(state->keystr); + sfree(state); +} + +int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, + char *keystr, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char absenttxt[] = + "The server's host key is not cached. You have no guarantee " + "that the server is the computer you think it is.\n" + "The server's %s key fingerprint is:\n" + "%s\n" + "If you trust this host, press \"Accept\" to add the key to " + "PuTTY's cache and carry on connecting.\n" + "If you want to carry on connecting just once, without " + "adding the key to the cache, press \"Connect Once\".\n" + "If you do not trust this host, press \"Cancel\" to abandon the " + "connection."; + static const char wrongtxt[] = + "WARNING - POTENTIAL SECURITY BREACH!\n" + "The server's host key does not match the one PuTTY has " + "cached. This means that either the server administrator " + "has changed the host key, or you have actually connected " + "to another computer pretending to be the server.\n" + "The new %s key fingerprint is:\n" + "%s\n" + "If you were expecting this change and trust the new key, " + "press \"Accept\" to update PuTTY's cache and continue connecting.\n" + "If you want to carry on connecting but without updating " + "the cache, press \"Connect Once\".\n" + "If you want to abandon the connection completely, press " + "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed " + "safe choice."; + + int ret; + char *text; + SessionWindow *win = (SessionWindow *)frontend; + struct hostkeystate *state; + NSAlert *alert; + + /* + * Verify the key. + */ + ret = verify_host_key(host, port, keytype, keystr); + + if (ret == 0) + return 1; + + text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint); + + state = snew(struct hostkeystate); + state->callback = callback; + state->ctx = ctx; + state->host = dupstr(host); + state->port = port; + state->keytype = dupstr(keytype); + state->keystr = dupstr(keystr); + + alert = [[NSAlert alloc] init]; + [alert setInformativeText:[NSString stringWithCString:text]]; + [alert addButtonWithTitle:@"Cancel"]; + [alert addButtonWithTitle:@"Connect Once"]; + [alert addButtonWithTitle:@"Accept"]; + [win startAlert:alert withCallback:verify_ssh_host_key_callback + andCtx:state]; + + return -1; +} + +void old_keyfile_warning(void) +{ + /* + * This should never happen on OS X. We hope. + */ +} + +static void connection_fatal_callback(void *ctx, int result) +{ + SessionWindow *win = (SessionWindow *)ctx; + + [win endSession:FALSE]; +} + +void connection_fatal(void *frontend, char *p, ...) +{ + SessionWindow *win = (SessionWindow *)frontend; + va_list ap; + char *msg; + NSAlert *alert; + + va_start(ap, p); + msg = dupvprintf(p, ap); + va_end(ap); + + alert = [[NSAlert alloc] init]; + [alert setInformativeText:[NSString stringWithCString:msg]]; + [alert addButtonWithTitle:@"Proceed"]; + [win startAlert:alert withCallback:connection_fatal_callback + andCtx:win]; +} diff --git a/putty/MACOSX/OSXMAIN.M b/putty/MACOSX/OSXMAIN.M new file mode 100644 index 0000000..1c7599c --- /dev/null +++ b/putty/MACOSX/OSXMAIN.M @@ -0,0 +1,408 @@ +/* + * osxmain.m: main-program file of Mac OS X PuTTY. + */ + +#import + +#define PUTTY_DO_GLOBALS /* actually _define_ globals */ + +#include "putty.h" +#include "osxclass.h" + +/* ---------------------------------------------------------------------- + * Global variables. + */ + +AppController *controller; + +/* ---------------------------------------------------------------------- + * Miscellaneous elements of the interface to the cross-platform + * and Unix PuTTY code. + */ + +char *platform_get_x_display(void) { + return NULL; +} + +FontSpec platform_default_fontspec(const char *name) +{ + FontSpec ret; + /* FIXME */ + return ret; +} + +Filename platform_default_filename(const char *name) +{ + Filename ret; + if (!strcmp(name, "LogFileName")) + strcpy(ret.path, "putty.log"); + else + *ret.path = '\0'; + return ret; +} + +char *platform_default_s(const char *name) +{ + return NULL; +} + +int platform_default_i(const char *name, int def) +{ + if (!strcmp(name, "CloseOnExit")) + return 2; /* maps to FORCE_ON after painful rearrangement :-( */ + return def; +} + +char *x_get_default(const char *key) +{ + return NULL; /* this is a stub */ +} + +static void commonfatalbox(char *p, va_list ap) +{ + char errorbuf[2048]; + NSAlert *alert; + + /* + * We may have come here because we ran out of memory, in which + * case it's entirely likely that that further memory + * allocations will fail. So (a) we use vsnprintf to format the + * error message rather than the usual dupvprintf; and (b) we + * have a fallback way to get the message out via stderr if + * even creating an NSAlert fails. + */ + vsnprintf(errorbuf, lenof(errorbuf), p, ap); + + alert = [NSAlert alloc]; + if (!alert) { + fprintf(stderr, "fatal error (and NSAlert failed): %s\n", errorbuf); + } else { + alert = [[alert init] autorelease]; + [alert addButtonWithTitle:@"Terminate"]; + [alert setInformativeText:[NSString stringWithCString:errorbuf]]; + [alert runModal]; + } + exit(1); +} + +void fatalbox(char *p, ...) +{ + va_list ap; + va_start(ap, p); + commonfatalbox(p, ap); + va_end(ap); +} + +void modalfatalbox(char *p, ...) +{ + va_list ap; + va_start(ap, p); + commonfatalbox(p, ap); + va_end(ap); +} + +void cmdline_error(char *p, ...) +{ + va_list ap; + fprintf(stderr, "%s: ", appname); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +/* + * Clean up and exit. + */ +void cleanup_exit(int code) +{ + /* + * Clean up. + */ + sk_cleanup(); + random_save_seed(); + exit(code); +} + +/* ---------------------------------------------------------------------- + * Tiny extension to NSMenuItem which carries a payload of a `void + * *', allowing several menu items to invoke the same message but + * pass different data through it. + */ +@interface DataMenuItem : NSMenuItem +{ + void *payload; +} +- (void)setPayload:(void *)d; +- (void *)getPayload; +@end +@implementation DataMenuItem +- (void)setPayload:(void *)d +{ + payload = d; +} +- (void *)getPayload +{ + return payload; +} +@end + +/* ---------------------------------------------------------------------- + * Utility routines for constructing OS X menus. + */ + +NSMenu *newmenu(const char *title) +{ + return [[[NSMenu allocWithZone:[NSMenu menuZone]] + initWithTitle:[NSString stringWithCString:title]] + autorelease]; +} + +NSMenu *newsubmenu(NSMenu *parent, const char *title) +{ + NSMenuItem *item; + NSMenu *child; + + item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] + initWithTitle:[NSString stringWithCString:title] + action:NULL + keyEquivalent:@""] + autorelease]; + child = newmenu(title); + [item setEnabled:YES]; + [item setSubmenu:child]; + [parent addItem:item]; + return child; +} + +id initnewitem(NSMenuItem *item, NSMenu *parent, const char *title, + const char *key, id target, SEL action) +{ + unsigned mask = NSCommandKeyMask; + + if (key[strcspn(key, "-")]) { + while (*key && *key != '-') { + int c = tolower((unsigned char)*key); + if (c == 's') { + mask |= NSShiftKeyMask; + } else if (c == 'o' || c == 'a') { + mask |= NSAlternateKeyMask; + } + key++; + } + if (*key) + key++; + } + + item = [[item initWithTitle:[NSString stringWithCString:title] + action:NULL + keyEquivalent:[NSString stringWithCString:key]] + autorelease]; + + if (*key) + [item setKeyEquivalentModifierMask: mask]; + + [item setEnabled:YES]; + [item setTarget:target]; + [item setAction:action]; + + [parent addItem:item]; + + return item; +} + +NSMenuItem *newitem(NSMenu *parent, char *title, char *key, + id target, SEL action) +{ + return initnewitem([NSMenuItem allocWithZone:[NSMenu menuZone]], + parent, title, key, target, action); +} + +/* ---------------------------------------------------------------------- + * AppController: the object which receives the messages from all + * menu selections that aren't standard OS X functions. + */ +@implementation AppController + +- (id)init +{ + self = [super init]; + timer = NULL; + return self; +} + +- (void)newTerminal:(id)sender +{ + id win; + Config cfg; + + do_defaults(NULL, &cfg); + + cfg.protocol = -1; /* PROT_TERMINAL */ + + win = [[SessionWindow alloc] initWithConfig:cfg]; + [win makeKeyAndOrderFront:self]; +} + +- (void)newSessionConfig:(id)sender +{ + id win; + Config cfg; + + do_defaults(NULL, &cfg); + + win = [[ConfigWindow alloc] initWithConfig:cfg]; + [win makeKeyAndOrderFront:self]; +} + +- (void)newSessionWithConfig:(id)vdata +{ + id win; + Config cfg; + NSData *data = (NSData *)vdata; + + assert([data length] == sizeof(cfg)); + [data getBytes:&cfg]; + + win = [[SessionWindow alloc] initWithConfig:cfg]; + [win makeKeyAndOrderFront:self]; +} + +- (NSMenu *)applicationDockMenu:(NSApplication *)sender +{ + NSMenu *menu = newmenu("Dock Menu"); + /* + * FIXME: Add some useful things to this, probably including + * the saved session list. + */ + return menu; +} + +- (void)timerFired:(id)sender +{ + long now, next; + + assert(sender == timer); + + /* `sender' is the timer itself, so its userInfo is an NSNumber. */ + now = [(NSNumber *)[sender userInfo] longValue]; + + [sender invalidate]; + + timer = NULL; + + if (run_timers(now, &next)) + [self setTimer:next]; +} + +- (void)setTimer:(long)next +{ + long interval = next - GETTICKCOUNT(); + float finterval; + + if (interval <= 0) + interval = 1; /* just in case */ + + finterval = interval / (float)TICKSPERSEC; + + if (timer) { + [timer invalidate]; + } + + timer = [NSTimer scheduledTimerWithTimeInterval:finterval + target:self selector:@selector(timerFired:) + userInfo:[NSNumber numberWithLong:next] repeats:NO]; +} + +@end + +void timer_change_notify(long next) +{ + [controller setTimer:next]; +} + +/* ---------------------------------------------------------------------- + * Annoyingly, it looks as if I have to actually subclass + * NSApplication if I want to catch NSApplicationDefined events. So + * here goes. + */ +@interface MyApplication : NSApplication +{ +} +@end +@implementation MyApplication +- (void)sendEvent:(NSEvent *)ev +{ + if ([ev type] == NSApplicationDefined) + osxsel_process_results(); + + [super sendEvent:ev]; +} +@end + +/* ---------------------------------------------------------------------- + * Main program. Constructs the menus and runs the application. + */ +int main(int argc, char **argv) +{ + NSAutoreleasePool *pool; + NSMenu *menu; + NSMenuItem *item; + NSImage *icon; + + pool = [[NSAutoreleasePool alloc] init]; + + icon = [NSImage imageNamed:@"NSApplicationIcon"]; + [MyApplication sharedApplication]; + [NSApp setApplicationIconImage:icon]; + + controller = [[[AppController alloc] init] autorelease]; + [NSApp setDelegate:controller]; + + [NSApp setMainMenu: newmenu("Main Menu")]; + + menu = newsubmenu([NSApp mainMenu], "Apple Menu"); + [NSApp setServicesMenu:newsubmenu(menu, "Services")]; + [menu addItem:[NSMenuItem separatorItem]]; + item = newitem(menu, "Hide PuTTY", "h", NSApp, @selector(hide:)); + item = newitem(menu, "Hide Others", "o-h", NSApp, @selector(hideOtherApplications:)); + item = newitem(menu, "Show All", "", NSApp, @selector(unhideAllApplications:)); + [menu addItem:[NSMenuItem separatorItem]]; + item = newitem(menu, "Quit", "q", NSApp, @selector(terminate:)); + [NSApp setAppleMenu: menu]; + + menu = newsubmenu([NSApp mainMenu], "File"); + item = newitem(menu, "New", "n", NULL, @selector(newSessionConfig:)); + item = newitem(menu, "New Terminal", "t", NULL, @selector(newTerminal:)); + item = newitem(menu, "Close", "w", NULL, @selector(performClose:)); + + menu = newsubmenu([NSApp mainMenu], "Window"); + [NSApp setWindowsMenu: menu]; + item = newitem(menu, "Minimise Window", "m", NULL, @selector(performMiniaturize:)); + +// menu = newsubmenu([NSApp mainMenu], "Help"); +// item = newitem(menu, "PuTTY Help", "?", NSApp, @selector(showHelp:)); + + /* + * Start up the sub-thread doing select(). + */ + osxsel_init(); + + /* + * Start up networking. + */ + sk_init(); + + /* + * FIXME: To make initial debugging more convenient I'm going + * to start by opening a session window unconditionally. This + * will probably change later on. + */ + [controller newSessionConfig:nil]; + + [NSApp run]; + [pool release]; + + return 0; +} diff --git a/putty/MACOSX/OSXSEL.M b/putty/MACOSX/OSXSEL.M new file mode 100644 index 0000000..8a0d3f7 --- /dev/null +++ b/putty/MACOSX/OSXSEL.M @@ -0,0 +1,308 @@ +/* + * osxsel.m: OS X implementation of the front end interface to uxsel. + */ + +#import +#include +#include "putty.h" +#include "osxclass.h" + +/* + * The unofficial Cocoa FAQ at + * + * http://www.alastairs-place.net/cocoa/faq.txt + * + * says that Cocoa has the native ability to be given an fd and + * tell you when it becomes readable, but cannot tell you when it + * becomes _writable_. This is unacceptable to PuTTY, which depends + * for correct functioning on being told both. Therefore, I can't + * use the Cocoa native mechanism. + * + * Instead, I'm going to resort to threads. I start a second thread + * whose job is to do selects. At the termination of every select, + * it posts a Cocoa event into the main thread's event queue, so + * that the main thread gets select results interleaved with other + * GUI operations. Communication from the main thread _to_ the + * select thread is performed by writing to a pipe whose other end + * is one of the file descriptors being selected on. (This is the + * only sensible way, because we have to be able to interrupt a + * select in order to provide a new fd list.) + */ + +/* + * In more detail, the select thread must: + * + * - start off by listening to _just_ the pipe, waiting to be told + * to begin a select. + * + * - when it receives the `start' command, it should read the + * shared uxsel data (which is protected by a mutex), set up its + * select, and begin it. + * + * - when the select terminates, it should write the results + * (perhaps minus the inter-thread pipe if it's there) into + * shared memory and dispatch a GUI event to let the main thread + * know. + * + * - the main thread will then think about it, do some processing, + * and _then_ send a command saying `now restart select'. Before + * sending that command it might easily have tinkered with the + * uxsel structures, which is why it waited before sending it. + * + * - EOF on the inter-thread pipe, of course, means the process + * has finished completely, so the select thread terminates. + * + * - The main thread may wish to adjust the uxsel settings in the + * middle of a select. In this situation it first writes the new + * data to the shared memory area, then notifies the select + * thread by writing to the inter-thread pipe. + * + * So the upshot is that the sequence of operations performed in + * the select thread must be: + * + * - read a byte from the pipe (which may block) + * + * - read the shared uxsel data and perform a select + * + * - notify the main thread of interesting select results (if any) + * + * - loop round again from the top. + * + * This is sufficient. Notifying the select thread asynchronously + * by writing to the pipe will cause its select to terminate and + * another to begin immediately without blocking. If the select + * thread's select terminates due to network data, its subsequent + * pipe read will block until the main thread is ready to let it + * loose again. + */ + +static int osxsel_pipe[2]; + +static NSLock *osxsel_inlock; +static fd_set osxsel_rfds_in; +static fd_set osxsel_wfds_in; +static fd_set osxsel_xfds_in; +static int osxsel_inmax; + +static NSLock *osxsel_outlock; +static fd_set osxsel_rfds_out; +static fd_set osxsel_wfds_out; +static fd_set osxsel_xfds_out; +static int osxsel_outmax; + +static int inhibit_start_select; + +/* + * NSThread requires an object method as its thread procedure, so + * here I define a trivial holding class. + */ +@class OSXSel; +@interface OSXSel : NSObject +{ +} +- (void)runThread:(id)arg; +@end +@implementation OSXSel +- (void)runThread:(id)arg +{ + char c; + fd_set r, w, x; + int n, ret; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + while (1) { + /* + * Read one byte from the pipe. + */ + ret = read(osxsel_pipe[0], &c, 1); + + if (ret <= 0) + return; /* terminate the thread */ + + /* + * Now set up the select data. + */ + [osxsel_inlock lock]; + memcpy(&r, &osxsel_rfds_in, sizeof(fd_set)); + memcpy(&w, &osxsel_wfds_in, sizeof(fd_set)); + memcpy(&x, &osxsel_xfds_in, sizeof(fd_set)); + n = osxsel_inmax; + [osxsel_inlock unlock]; + FD_SET(osxsel_pipe[0], &r); + if (n < osxsel_pipe[0]+1) + n = osxsel_pipe[0]+1; + + /* + * Perform the select. + */ + ret = select(n, &r, &w, &x, NULL); + + /* + * Detect the one special case in which the only + * interesting fd was the inter-thread pipe. In that + * situation only we are interested - the main thread will + * not be! + */ + if (ret == 1 && FD_ISSET(osxsel_pipe[0], &r)) + continue; /* just loop round again */ + + /* + * Write the select results to shared data. + * + * I _think_ we don't need this data to be lock-protected: + * it won't be read by the main thread until after we send + * a message indicating that we've finished writing it, and + * we won't start another select (hence potentially writing + * it again) until the main thread notifies us in return. + * + * However, I'm scared of multithreading and not totally + * convinced of my reasoning, so I'm going to lock it + * anyway. + */ + [osxsel_outlock lock]; + memcpy(&osxsel_rfds_out, &r, sizeof(fd_set)); + memcpy(&osxsel_wfds_out, &w, sizeof(fd_set)); + memcpy(&osxsel_xfds_out, &x, sizeof(fd_set)); + osxsel_outmax = n; + [osxsel_outlock unlock]; + + /* + * Post a message to the main thread's message queue + * telling it that select data is available. + */ + [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined + location:NSMakePoint(0,0) + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + subtype:0 + data1:0 + data2:0] + atStart:NO]; + } + + [pool release]; +} +@end + +void osxsel_init(void) +{ + uxsel_init(); + + if (pipe(osxsel_pipe) < 0) { + fatalbox("Unable to set up inter-thread pipe for select"); + } + [NSThread detachNewThreadSelector:@selector(runThread:) + toTarget:[[[OSXSel alloc] init] retain] withObject:nil]; + /* + * Also initialise (i.e. clear) the input fd_sets. Need not + * start a select just yet - the select thread will block until + * we have at least one fd for it! + */ + FD_ZERO(&osxsel_rfds_in); + FD_ZERO(&osxsel_wfds_in); + FD_ZERO(&osxsel_xfds_in); + osxsel_inmax = 0; + /* + * Initialise the mutex locks used to protect the data passed + * between threads. + */ + osxsel_inlock = [[[NSLock alloc] init] retain]; + osxsel_outlock = [[[NSLock alloc] init] retain]; +} + +static void osxsel_start_select(void) +{ + char c = 'g'; /* for `Go!' :-) but it's never used */ + + if (!inhibit_start_select) + write(osxsel_pipe[1], &c, 1); +} + +int uxsel_input_add(int fd, int rwx) +{ + /* + * Add the new fd to the appropriate input fd_sets, then write + * to the inter-thread pipe. + */ + [osxsel_inlock lock]; + if (rwx & 1) + FD_SET(fd, &osxsel_rfds_in); + else + FD_CLR(fd, &osxsel_rfds_in); + if (rwx & 2) + FD_SET(fd, &osxsel_wfds_in); + else + FD_CLR(fd, &osxsel_wfds_in); + if (rwx & 4) + FD_SET(fd, &osxsel_xfds_in); + else + FD_CLR(fd, &osxsel_xfds_in); + if (osxsel_inmax < fd+1) + osxsel_inmax = fd+1; + [osxsel_inlock unlock]; + osxsel_start_select(); + + /* + * We must return an `id' which will be passed back to us at + * the time of uxsel_input_remove. Since we have no need to + * store ids in that sense, we might as well go with the fd + * itself. + */ + return fd; +} + +void uxsel_input_remove(int id) +{ + /* + * Remove the fd from all the input fd_sets. In this + * implementation, the simplest way to do that is to call + * uxsel_input_add with rwx==0! + */ + uxsel_input_add(id, 0); +} + +/* + * Function called in the main thread to process results. It will + * have to read the output fd_sets, go through them, call back to + * uxsel with the results, and then write to the inter-thread pipe. + * + * This function will have to be called from an event handler in + * osxmain.m, which will therefore necessarily contain a small part + * of this mechanism (along with calling osxsel_init). + */ +void osxsel_process_results(void) +{ + int i; + + /* + * We must write to the pipe to start a fresh select _even if_ + * there were no changes. So for efficiency, we set a flag here + * which inhibits uxsel_input_{add,remove} from writing to the + * pipe; then once we finish processing, we clear the flag + * again and write a single byte ourselves. It's cleaner, + * because it wakes up the select thread fewer times. + */ + inhibit_start_select = TRUE; + + [osxsel_outlock lock]; + + for (i = 0; i < osxsel_outmax; i++) { + if (FD_ISSET(i, &osxsel_xfds_out)) + select_result(i, 4); + } + for (i = 0; i < osxsel_outmax; i++) { + if (FD_ISSET(i, &osxsel_rfds_out)) + select_result(i, 1); + } + for (i = 0; i < osxsel_outmax; i++) { + if (FD_ISSET(i, &osxsel_wfds_out)) + select_result(i, 2); + } + + [osxsel_outlock unlock]; + + inhibit_start_select = FALSE; + osxsel_start_select(); +} diff --git a/putty/MACOSX/OSXWIN.M b/putty/MACOSX/OSXWIN.M new file mode 100644 index 0000000..9de1e08 --- /dev/null +++ b/putty/MACOSX/OSXWIN.M @@ -0,0 +1,1227 @@ +/* + * osxwin.m: code to manage a session window in Mac OS X PuTTY. + */ + +#import +#include "putty.h" +#include "terminal.h" +#include "osxclass.h" + +/* Colours come in two flavours: configurable, and xterm-extended. */ +#define NCFGCOLOURS (lenof(((Config *)0)->colours)) +#define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */ +#define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS) + +/* + * The key component of the per-session data is the SessionWindow + * class. A pointer to this is used as the frontend handle, to be + * passed to all the platform-independent subsystems that require + * one. + */ + +@interface TerminalView : NSImageView +{ + NSFont *font; + NSImage *image; + Terminal *term; + Config cfg; + NSColor *colours[NALLCOLOURS]; + float fw, fasc, fdesc, fh; +} +- (void)drawStartFinish:(BOOL)start; +- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b; +- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y + attr:(unsigned long)attr lattr:(int)lattr; +@end + +@implementation TerminalView +- (BOOL)isFlipped +{ + return YES; +} +- (id)initWithTerminal:(Terminal *)aTerm config:(Config)aCfg +{ + float w, h; + + self = [self initWithFrame:NSMakeRect(0,0,100,100)]; + + term = aTerm; + cfg = aCfg; + + /* + * Initialise the fonts we're going to use. + * + * FIXME: for the moment I'm sticking with exactly one default font. + */ + font = [NSFont userFixedPitchFontOfSize:0]; + + /* + * Now determine the size of the primary font. + * + * FIXME: If we have multiple fonts, we may need to set fasc + * and fdesc to the _maximum_ asc and desc out of all the + * fonts, _before_ adding them together to get fh. + */ + fw = [font widthOfString:@"A"]; + fasc = [font ascender]; + fdesc = -[font descender]; + fh = fasc + fdesc; + fh = (int)fh + (fh > (int)fh); /* round up, ickily */ + + /* + * Use this to figure out the size of the terminal view. + */ + w = fw * term->cols; + h = fh * term->rows; + + /* + * And set our size and subimage. + */ + image = [[NSImage alloc] initWithSize:NSMakeSize(w,h)]; + [image setFlipped:YES]; + [self setImage:image]; + [self setFrame:NSMakeRect(0,0,w,h)]; + + term_invalidate(term); + + return self; +} +- (void)drawStartFinish:(BOOL)start +{ + if (start) + [image lockFocus]; + else + [image unlockFocus]; +} +- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y + attr:(unsigned long)attr lattr:(int)lattr +{ + int nfg, nbg, rlen, widefactor; + float ox, oy, tw, th; + NSDictionary *attrdict; + + /* FIXME: TATTR_COMBINING */ + + nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT); + nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT); + if (attr & ATTR_REVERSE) { + int t = nfg; + nfg = nbg; + nbg = t; + } + if (cfg.bold_colour && (attr & ATTR_BOLD)) { + if (nfg < 16) nfg |= 8; + else if (nfg >= 256) nfg |= 1; + } + if (cfg.bold_colour && (attr & ATTR_BLINK)) { + if (nbg < 16) nbg |= 8; + else if (nbg >= 256) nbg |= 1; + } + if (attr & TATTR_ACTCURS) { + nfg = 260; + nbg = 261; + } + + if (attr & ATTR_WIDE) { + widefactor = 2; + /* FIXME: what do we actually have to do about wide characters? */ + } else { + widefactor = 1; + } + + /* FIXME: ATTR_BOLD without cfg.bold_colour */ + + if ((lattr & LATTR_MODE) != LATTR_NORM) { + x *= 2; + if (x >= term->cols) + return; + if (x + len*2*widefactor > term->cols) + len = (term->cols-x)/2/widefactor;/* trim to LH half */ + rlen = len * 2; + } else + rlen = len; + + /* FIXME: how do we actually implement double-{width,height} lattrs? */ + + ox = x * fw; + oy = y * fh; + tw = rlen * widefactor * fw; + th = fh; + + /* + * Set the clipping rectangle. + */ + [[NSGraphicsContext currentContext] saveGraphicsState]; + [NSBezierPath clipRect:NSMakeRect(ox, oy, tw, th)]; + + attrdict = [NSDictionary dictionaryWithObjectsAndKeys: + colours[nfg], NSForegroundColorAttributeName, + colours[nbg], NSBackgroundColorAttributeName, + font, NSFontAttributeName, nil]; + + /* + * Create an NSString and draw it. + * + * Annoyingly, although our input is wchar_t which is four + * bytes wide on OS X and terminal.c supports 32-bit Unicode, + * we must convert into the two-byte type `unichar' to store in + * NSString, so we lose display capability for extra-BMP stuff + * at this point. + */ + { + NSString *string; + unichar *utext; + int i; + + utext = snewn(len, unichar); + for (i = 0; i < len; i++) + utext[i] = (text[i] >= 0x10000 ? 0xFFFD : text[i]); + + string = [NSString stringWithCharacters:utext length:len]; + [string drawAtPoint:NSMakePoint(ox, oy) withAttributes:attrdict]; + + sfree(utext); + } + + /* + * Restore the graphics state from before the clipRect: call. + */ + [[NSGraphicsContext currentContext] restoreGraphicsState]; + + /* + * And flag this area as needing display. + */ + [self setNeedsDisplayInRect:NSMakeRect(ox, oy, tw, th)]; +} + +- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b +{ + assert(n >= 0 && n < lenof(colours)); + colours[n] = [[NSColor colorWithDeviceRed:r green:g blue:b alpha:1.0] + retain]; +} +@end + +@implementation SessionWindow +- (id)initWithConfig:(Config)aCfg +{ + NSRect rect = { {0,0}, {0,0} }; + + alert_ctx = NULL; + + cfg = aCfg; /* structure copy */ + + init_ucs(&ucsdata, cfg.line_codepage, cfg.utf8_override, + CS_UTF8, cfg.vtmode); + term = term_init(&cfg, &ucsdata, self); + logctx = log_init(self, &cfg); + term_provide_logctx(term, logctx); + term_size(term, cfg.height, cfg.width, cfg.savelines); + + termview = [[[TerminalView alloc] initWithTerminal:term config:cfg] + autorelease]; + + /* + * Now work out the size of the window. + */ + rect = [termview frame]; + rect.origin = NSMakePoint(0,0); + rect.size.width += 2 * cfg.window_border; + rect.size.height += 2 * cfg.window_border; + + /* + * Set up a backend. + */ + back = backend_from_proto(cfg.protocol); + if (!back) + back = &pty_backend; + + { + const char *error; + char *realhost = NULL; + error = back->init(self, &backhandle, &cfg, cfg.host, cfg.port, + &realhost, cfg.tcp_nodelay, cfg.tcp_keepalives); + if (error) { + fatalbox("%s\n", error); /* FIXME: connection_fatal at worst */ + } + + if (realhost) + sfree(realhost); /* FIXME: do something with this */ + } + back->provide_logctx(backhandle, logctx); + + /* + * Create a line discipline. (This must be done after creating + * the terminal _and_ the backend, since it needs to be passed + * pointers to both.) + */ + ldisc = ldisc_create(&cfg, term, back, backhandle, self); + + /* + * FIXME: Set up a scrollbar. + */ + + self = [super initWithContentRect:rect + styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask | + NSClosableWindowMask) + backing:NSBackingStoreBuffered + defer:YES]; + [self setTitle:@"PuTTY"]; + + [self setIgnoresMouseEvents:NO]; + + /* + * Put the terminal view in the window. + */ + rect = [termview frame]; + rect.origin = NSMakePoint(cfg.window_border, cfg.window_border); + [termview setFrame:rect]; + [[self contentView] addSubview:termview]; + + /* + * Set up the colour palette. + */ + palette_reset(self); + + /* + * FIXME: Only the _first_ document window should be centred. + * The subsequent ones should appear down and to the right of + * it, probably using the cascade function provided by Cocoa. + * Also we're apparently required by the HIG to remember and + * reuse previous positions of windows, although I'm not sure + * how that works if the user opens more than one of the same + * session type. + */ + [self center]; /* :-) */ + + exited = FALSE; + + return self; +} + +- (void)dealloc +{ + /* + * FIXME: Here we must deallocate all sorts of stuff: the + * terminal, the backend, the ldisc, the logctx, you name it. + * Do so. + */ + sfree(alert_ctx); + if (back) + back->free(backhandle); + if (ldisc) + ldisc_free(ldisc); + /* ldisc must be freed before term, since ldisc_free expects term + * still to be around. */ + if (logctx) + log_free(logctx); + if (term) + term_free(term); + [super dealloc]; +} + +- (void)drawStartFinish:(BOOL)start +{ + [termview drawStartFinish:start]; +} + +- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b +{ + [termview setColour:n r:r g:g b:b]; +} + +- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y + attr:(unsigned long)attr lattr:(int)lattr +{ + /* Pass this straight on to the TerminalView. */ + [termview doText:text len:len x:x y:y attr:attr lattr:lattr]; +} + +- (Config *)cfg +{ + return &cfg; +} + +- (void)keyDown:(NSEvent *)ev +{ + NSString *s = [ev characters]; + int i; + int n = [s length], c = [s characterAtIndex:0], m = [ev modifierFlags]; + int cm = [[ev charactersIgnoringModifiers] characterAtIndex:0]; + wchar_t output[32]; + char coutput[32]; + int use_coutput = FALSE, special = FALSE, start, end; + +//printf("n=%d c=U+%04x cm=U+%04x m=%08x\n", n, c, cm, m); + + /* + * FIXME: Alt+numberpad codes. + */ + + /* + * Shift and Ctrl with PageUp/PageDown for scrollback. + */ + if (n == 1 && c == NSPageUpFunctionKey && (m & NSShiftKeyMask)) { + term_scroll(term, 0, -term->rows/2); + return; + } + if (n == 1 && c == NSPageUpFunctionKey && (m & NSControlKeyMask)) { + term_scroll(term, 0, -1); + return; + } + if (n == 1 && c == NSPageDownFunctionKey && (m & NSShiftKeyMask)) { + term_scroll(term, 0, +term->rows/2); + return; + } + if (n == 1 && c == NSPageDownFunctionKey && (m & NSControlKeyMask)) { + term_scroll(term, 0, +1); + return; + } + + /* + * FIXME: Shift-Ins for paste? Or is that not Maccy enough? + */ + + /* + * FIXME: Alt (Option? Command?) prefix in general. + * + * (Note that Alt-Shift-thing will work just by looking at + * charactersIgnoringModifiers; but Alt-Ctrl-thing will need + * processing properly, and Alt-as-in-Option won't happen at + * all. Hmmm.) + * + * (Note also that we need to be able to override menu key + * equivalents before this is particularly useful.) + */ + start = 1; + end = start; + + /* + * Ctrl-` is the same as Ctrl-\, unless we already have a + * better idea. + */ + if ((m & NSControlKeyMask) && n == 1 && cm == '`' && c == '`') { + output[1] = '\x1c'; + end = 2; + } + + /* We handle Return ourselves, because it needs to be flagged as + * special to ldisc. */ + if (n == 1 && c == '\015') { + coutput[1] = '\015'; + use_coutput = TRUE; + end = 2; + special = TRUE; + } + + /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */ + if (n == 1 && (m & NSControlKeyMask) && (m & NSShiftKeyMask) && + cm == ' ') { + output[1] = '\240'; + end = 2; + } + + /* Control-2, Control-Space and Control-@ are all NUL. */ + if ((m & NSControlKeyMask) && n == 1 && + (cm == '2' || cm == '@' || cm == ' ') && c == cm) { + output[1] = '\0'; + end = 2; + } + + /* We don't let MacOS tell us what Backspace is! We know better. */ + if (cm == 0x7F && !(m & NSShiftKeyMask)) { + coutput[1] = cfg.bksp_is_delete ? '\x7F' : '\x08'; + end = 2; + use_coutput = special = TRUE; + } + /* For Shift Backspace, do opposite of what is configured. */ + if (cm == 0x7F && (m & NSShiftKeyMask)) { + coutput[1] = cfg.bksp_is_delete ? '\x08' : '\x7F'; + end = 2; + use_coutput = special = TRUE; + } + + /* Shift-Tab is ESC [ Z. Oddly, this combination generates ^Y by + * default on MacOS! */ + if (cm == 0x19 && (m & NSShiftKeyMask) && !(m & NSControlKeyMask)) { + end = 1; + output[end++] = '\033'; + output[end++] = '['; + output[end++] = 'Z'; + } + + /* + * NetHack keypad mode. + */ + if (cfg.nethack_keypad && (m & NSNumericPadKeyMask)) { + wchar_t *keys = NULL; + switch (cm) { + case '1': keys = L"bB"; break; + case '2': keys = L"jJ"; break; + case '3': keys = L"nN"; break; + case '4': keys = L"hH"; break; + case '5': keys = L".."; break; + case '6': keys = L"lL"; break; + case '7': keys = L"yY"; break; + case '8': keys = L"kK"; break; + case '9': keys = L"uU"; break; + } + if (keys) { + end = 2; + if (m & NSShiftKeyMask) + output[1] = keys[1]; + else + output[1] = keys[0]; + goto done; + } + } + + /* + * Application keypad mode. + */ + if (term->app_keypad_keys && !cfg.no_applic_k && + (m & NSNumericPadKeyMask)) { + int xkey = 0; + switch (cm) { + case NSClearLineFunctionKey: xkey = 'P'; break; + case '=': xkey = 'Q'; break; + case '/': xkey = 'R'; break; + case '*': xkey = 'S'; break; + /* + * FIXME: keypad - and + need to be mapped to ESC O l + * and ESC O k, or ESC O l and ESC O m, depending on + * xterm function key mode, and I can't remember which + * goes where. + */ + case '\003': xkey = 'M'; break; + 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; + } + if (xkey) { + if (term->vt52_mode) { + if (xkey >= 'P' && xkey <= 'S') { + output[end++] = '\033'; + output[end++] = xkey; + } else { + output[end++] = '\033'; + output[end++] = '?'; + output[end++] = xkey; + } + } else { + output[end++] = '\033'; + output[end++] = 'O'; + output[end++] = xkey; + } + goto done; + } + } + + /* + * Next, all the keys that do tilde codes. (ESC '[' nn '~', + * for integer decimal nn.) + * + * We also deal with the weird ones here. Linux VCs replace F1 + * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but + * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w + * respectively. + */ + { + int code = 0; + switch (cm) { + case NSF1FunctionKey: + code = (m & NSShiftKeyMask ? 23 : 11); + break; + case NSF2FunctionKey: + code = (m & NSShiftKeyMask ? 24 : 12); + break; + case NSF3FunctionKey: + code = (m & NSShiftKeyMask ? 25 : 13); + break; + case NSF4FunctionKey: + code = (m & NSShiftKeyMask ? 26 : 14); + break; + case NSF5FunctionKey: + code = (m & NSShiftKeyMask ? 28 : 15); + break; + case NSF6FunctionKey: + code = (m & NSShiftKeyMask ? 29 : 17); + break; + case NSF7FunctionKey: + code = (m & NSShiftKeyMask ? 31 : 18); + break; + case NSF8FunctionKey: + code = (m & NSShiftKeyMask ? 32 : 19); + break; + case NSF9FunctionKey: + code = (m & NSShiftKeyMask ? 33 : 20); + break; + case NSF10FunctionKey: + code = (m & NSShiftKeyMask ? 34 : 21); + break; + case NSF11FunctionKey: + code = 23; + break; + case NSF12FunctionKey: + code = 24; + break; + case NSF13FunctionKey: + code = 25; + break; + case NSF14FunctionKey: + code = 26; + break; + case NSF15FunctionKey: + code = 28; + break; + case NSF16FunctionKey: + code = 29; + break; + case NSF17FunctionKey: + code = 31; + break; + case NSF18FunctionKey: + code = 32; + break; + case NSF19FunctionKey: + code = 33; + break; + case NSF20FunctionKey: + code = 34; + break; + } + if (!(m & NSControlKeyMask)) switch (cm) { + case NSHomeFunctionKey: + code = 1; + break; +#ifdef FIXME + case GDK_Insert: case GDK_KP_Insert: + code = 2; + break; +#endif + case NSDeleteFunctionKey: + code = 3; + break; + case NSEndFunctionKey: + code = 4; + break; + case NSPageUpFunctionKey: + code = 5; + break; + case NSPageDownFunctionKey: + code = 6; + break; + } + /* Reorder edit keys to physical order */ + if (cfg.funky_type == FUNKY_VT400 && code <= 6) + code = "\0\2\1\4\5\3\6"[code]; + + if (term->vt52_mode && code > 0 && code <= 6) { + output[end++] = '\033'; + output[end++] = " HLMEIG"[code]; + goto done; + } + + if (cfg.funky_type == FUNKY_SCO && /* SCO function keys */ + code >= 11 && code <= 34) { + char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{"; + int index = 0; + switch (cm) { + case NSF1FunctionKey: index = 0; break; + case NSF2FunctionKey: index = 1; break; + case NSF3FunctionKey: index = 2; break; + case NSF4FunctionKey: index = 3; break; + case NSF5FunctionKey: index = 4; break; + case NSF6FunctionKey: index = 5; break; + case NSF7FunctionKey: index = 6; break; + case NSF8FunctionKey: index = 7; break; + case NSF9FunctionKey: index = 8; break; + case NSF10FunctionKey: index = 9; break; + case NSF11FunctionKey: index = 10; break; + case NSF12FunctionKey: index = 11; break; + } + if (m & NSShiftKeyMask) index += 12; + if (m & NSControlKeyMask) index += 24; + output[end++] = '\033'; + output[end++] = '['; + output[end++] = codes[index]; + goto done; + } + if (cfg.funky_type == FUNKY_SCO && /* SCO small keypad */ + code >= 1 && code <= 6) { + char codes[] = "HL.FIG"; + if (code == 3) { + output[1] = '\x7F'; + end = 2; + } else { + output[end++] = '\033'; + output[end++] = '['; + output[end++] = codes[code-1]; + } + goto done; + } + if ((term->vt52_mode || cfg.funky_type == FUNKY_VT100P) && + code >= 11 && code <= 24) { + int offt = 0; + if (code > 15) + offt++; + if (code > 21) + offt++; + if (term->vt52_mode) { + output[end++] = '\033'; + output[end++] = code + 'P' - 11 - offt; + } else { + output[end++] = '\033'; + output[end++] = 'O'; + output[end++] = code + 'P' - 11 - offt; + } + goto done; + } + if (cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { + output[end++] = '\033'; + output[end++] = '['; + output[end++] = '['; + output[end++] = code + 'A' - 11; + goto done; + } + if (cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { + if (term->vt52_mode) { + output[end++] = '\033'; + output[end++] = code + 'P' - 11; + } else { + output[end++] = '\033'; + output[end++] = 'O'; + output[end++] = code + 'P' - 11; + } + goto done; + } + if (cfg.rxvt_homeend && (code == 1 || code == 4)) { + if (code == 1) { + output[end++] = '\033'; + output[end++] = '['; + output[end++] = 'H'; + } else { + output[end++] = '\033'; + output[end++] = 'O'; + output[end++] = 'w'; + } + goto done; + } + if (code) { + char buf[20]; + sprintf(buf, "\x1B[%d~", code); + for (i = 0; buf[i]; i++) + output[end++] = buf[i]; + goto done; + } + } + + /* + * Cursor keys. (This includes the numberpad cursor keys, + * if we haven't already done them due to app keypad mode.) + */ + { + int xkey = 0; + switch (cm) { + case NSUpArrowFunctionKey: xkey = 'A'; break; + case NSDownArrowFunctionKey: xkey = 'B'; break; + case NSRightArrowFunctionKey: xkey = 'C'; break; + case NSLeftArrowFunctionKey: xkey = 'D'; break; + } + if (xkey) { + end += format_arrow_key(output+end, term, xkey, + m & NSControlKeyMask); + goto done; + } + } + + done: + + /* + * Failing everything else, send the exact Unicode we got from + * OS X. + */ + if (end == start) { + if (n > lenof(output)-start) + n = lenof(output)-start; /* _shouldn't_ happen! */ + for (i = 0; i < n; i++) { + output[i+start] = [s characterAtIndex:i]; + } + end = n+start; + } + + if (use_coutput) { + assert(special); + assert(end < lenof(coutput)); + coutput[end] = '\0'; + ldisc_send(ldisc, coutput+start, -2, TRUE); + } else { + luni_send(ldisc, output+start, end-start, TRUE); + } +} + +- (int)fromBackend:(const char *)data len:(int)len isStderr:(int)is_stderr +{ + return term_data(term, is_stderr, data, len); +} + +- (int)fromBackendUntrusted:(const char *)data len:(int)len +{ + return term_data_untrusted(term, data, len); +} + +- (void)startAlert:(NSAlert *)alert + withCallback:(void (*)(void *, int))callback andCtx:(void *)ctx +{ + if (alert_ctx || alert_qhead) { + /* + * Queue this alert to be shown later. + */ + struct alert_queue *qitem = snew(struct alert_queue); + qitem->next = NULL; + qitem->alert = alert; + qitem->callback = callback; + qitem->ctx = ctx; + if (alert_qtail) + alert_qtail->next = qitem; + else + alert_qhead = qitem; + alert_qtail = qitem; + } else { + alert_callback = callback; + alert_ctx = ctx; /* NB this is assumed to need freeing! */ + [alert beginSheetModalForWindow:self modalDelegate:self + didEndSelector:@selector(alertSheetDidEnd:returnCode:contextInfo:) + contextInfo:NULL]; + } +} + +- (void)alertSheetDidEnd:(NSAlert *)alert returnCode:(int)returnCode + contextInfo:(void *)contextInfo +{ + [self performSelectorOnMainThread: + @selector(alertSheetDidFinishEnding:) + withObject:[NSNumber numberWithInt:returnCode] + waitUntilDone:NO]; +} + +- (void)alertSheetDidFinishEnding:(id)object +{ + int returnCode = [object intValue]; + + alert_callback(alert_ctx, returnCode); /* transfers ownership of ctx */ + + /* + * If there's an alert in our queue (either already or because + * the callback just queued it), start it. + */ + if (alert_qhead) { + struct alert_queue *qnext; + + alert_callback = alert_qhead->callback; + alert_ctx = alert_qhead->ctx; + [alert_qhead->alert beginSheetModalForWindow:self modalDelegate:self + didEndSelector:@selector(alertSheetDidEnd:returnCode:contextInfo:) + contextInfo:NULL]; + + qnext = alert_qhead->next; + sfree(alert_qhead); + alert_qhead = qnext; + if (!qnext) + alert_qtail = NULL; + } else { + alert_ctx = NULL; + } +} + +- (void)notifyRemoteExit +{ + int exitcode; + + if (!exited && (exitcode = back->exitcode(backhandle)) >= 0) + [self endSession:(exitcode == 0)]; +} + +- (void)endSession:(int)clean +{ + exited = TRUE; + if (ldisc) { + ldisc_free(ldisc); + ldisc = NULL; + } + if (back) { + back->free(backhandle); + backhandle = NULL; + back = NULL; + //FIXME: update specials menu; + } + if (cfg.close_on_exit == FORCE_ON || + (cfg.close_on_exit == AUTO && clean)) + [self close]; + // FIXME: else show restart menu item +} + +- (Terminal *)term +{ + return term; +} + +@end + +int from_backend(void *frontend, int is_stderr, const char *data, int len) +{ + SessionWindow *win = (SessionWindow *)frontend; + return [win fromBackend:data len:len isStderr:is_stderr]; +} + +int from_backend_untrusted(void *frontend, const char *data, int len) +{ + SessionWindow *win = (SessionWindow *)frontend; + return [win fromBackendUntrusted:data len:len]; +} + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + SessionWindow *win = (SessionWindow *)p->frontend; + Terminal *term = [win term]; + return term_get_userpass_input(term, p, in, inlen); +} + +void frontend_keypress(void *handle) +{ + /* FIXME */ +} + +void notify_remote_exit(void *frontend) +{ + SessionWindow *win = (SessionWindow *)frontend; + + [win notifyRemoteExit]; +} + +void ldisc_update(void *frontend, int echo, int edit) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* + * In a GUI front end, this need do nothing. + */ +} + +char *get_ttymode(void *frontend, const char *mode) +{ + SessionWindow *win = (SessionWindow *)frontend; + Terminal *term = [win term]; + return term_get_ttymode(term, mode); +} + +void update_specials_menu(void *frontend) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +/* + * This is still called when mode==BELL_VISUAL, even though the + * visual bell is handled entirely within terminal.c, because we + * may want to perform additional actions on any kind of bell (for + * example, taskbar flashing in Windows). + */ +void do_beep(void *frontend, int mode) +{ + //SessionWindow *win = (SessionWindow *)frontend; + if (mode != BELL_VISUAL) + NSBeep(); +} + +int char_width(Context ctx, int uc) +{ + /* + * Under X, any fixed-width font really _is_ fixed-width. + * Double-width characters will be dealt with using a separate + * font. For the moment we can simply return 1. + */ + return 1; +} + +void palette_set(void *frontend, int n, int r, int g, int b) +{ + SessionWindow *win = (SessionWindow *)frontend; + + if (n >= 16) + n += 256 - 16; + if (n > NALLCOLOURS) + return; + [win setColour:n r:r/255.0 g:g/255.0 b:b/255.0]; + + /* + * FIXME: do we need an OS X equivalent of set_window_background? + */ +} + +void palette_reset(void *frontend) +{ + SessionWindow *win = (SessionWindow *)frontend; + Config *cfg = [win cfg]; + + /* This maps colour indices in cfg to those used in colours[]. */ + static const int ww[] = { + 256, 257, 258, 259, 260, 261, + 0, 8, 1, 9, 2, 10, 3, 11, + 4, 12, 5, 13, 6, 14, 7, 15 + }; + + int i; + + for (i = 0; i < NCFGCOLOURS; i++) { + [win setColour:ww[i] r:cfg->colours[i][0]/255.0 + g:cfg->colours[i][1]/255.0 b:cfg->colours[i][2]/255.0]; + } + + for (i = 0; i < NEXTCOLOURS; i++) { + if (i < 216) { + int r = i / 36, g = (i / 6) % 6, b = i % 6; + r = r ? r*40+55 : 0; g = g ? b*40+55 : 0; b = b ? b*40+55 : 0; + [win setColour:i+16 r:r/255.0 g:g/255.0 b:b/255.0]; + } else { + int shade = i - 216; + float fshade = (shade * 10 + 8) / 255.0; + [win setColour:i+16 r:fshade g:fshade b:fshade]; + } + } + + /* + * FIXME: do we need an OS X equivalent of set_window_background? + */ +} + +Context get_ctx(void *frontend) +{ + SessionWindow *win = (SessionWindow *)frontend; + + /* + * Lock the drawing focus on the image inside the TerminalView. + */ + [win drawStartFinish:YES]; + + [[NSGraphicsContext currentContext] setShouldAntialias:YES]; + + /* + * Cocoa drawing functions don't take a graphics context: that + * parameter is implicit. Therefore, we'll use the frontend + * handle itself as the context, on the grounds that it's as + * good a thing to use as any. + */ + return frontend; +} + +void free_ctx(Context ctx) +{ + SessionWindow *win = (SessionWindow *)ctx; + + [win drawStartFinish:NO]; +} + +void do_text(Context ctx, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr) +{ + SessionWindow *win = (SessionWindow *)ctx; + + [win doText:text len:len x:x y:y attr:attr lattr:lattr]; +} + +void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr) +{ + SessionWindow *win = (SessionWindow *)ctx; + Config *cfg = [win cfg]; + int active, passive; + + if (attr & TATTR_PASCURS) { + attr &= ~TATTR_PASCURS; + passive = 1; + } else + passive = 0; + if ((attr & TATTR_ACTCURS) && cfg->cursor_type != 0) { + attr &= ~TATTR_ACTCURS; + active = 1; + } else + active = 0; + + [win doText:text len:len x:x y:y attr:attr lattr:lattr]; + + /* + * FIXME: now draw the various cursor types (both passive and + * active underlines and vertical lines, plus passive blocks). + */ +} + +/* + * Minimise or restore the window in response to a server-side + * request. + */ +void set_iconic(void *frontend, int iconic) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +/* + * Move the window in response to a server-side request. + */ +void move_window(void *frontend, int x, int y) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +/* + * Move the window to the top or bottom of the z-order in response + * to a server-side request. + */ +void set_zorder(void *frontend, int top) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +/* + * Refresh the window in response to a server-side request. + */ +void refresh_window(void *frontend) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +/* + * Maximise or restore the window in response to a server-side + * request. + */ +void set_zoomed(void *frontend, int zoomed) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +/* + * Report whether the window is iconic, for terminal reports. + */ +int is_iconic(void *frontend) +{ + //SessionWindow *win = (SessionWindow *)frontend; + return NO; /* FIXME */ +} + +/* + * Report the window's position, for terminal reports. + */ +void get_window_pos(void *frontend, int *x, int *y) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +/* + * Report the window's pixel size, for terminal reports. + */ +void get_window_pixels(void *frontend, int *x, int *y) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +/* + * Return the window or icon title. + */ +char *get_window_title(void *frontend, int icon) +{ + //SessionWindow *win = (SessionWindow *)frontend; + return NULL; /* FIXME */ +} + +void set_title(void *frontend, char *title) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +void set_icon(void *frontend, char *title) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +void set_sbar(void *frontend, int total, int start, int page) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +void get_clip(void *frontend, wchar_t ** p, int *len) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +void write_clip(void *frontend, wchar_t *data, int *attr, int len, int must_deselect) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +void request_paste(void *frontend) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +void set_raw_mouse_mode(void *frontend, int activate) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +void request_resize(void *frontend, int w, int h) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +} + +void sys_cursor(void *frontend, int x, int y) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* + * This is probably meaningless under OS X. FIXME: find out for + * sure. + */ +} + +void logevent(void *frontend, const char *string) +{ + //SessionWindow *win = (SessionWindow *)frontend; + /* FIXME */ +printf("logevent: %s\n", string); +} + +int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */ +{ + //SessionWindow *win = (SessionWindow *)frontend; + return 1; /* FIXME */ +} + +void set_busy_status(void *frontend, int status) +{ + /* + * We need do nothing here: the OS X `application is busy' + * beachball pointer appears _automatically_ when the + * application isn't responding to GUI messages. + */ +} diff --git a/putty/MACOSX/PUTTY.ICN b/putty/MACOSX/PUTTY.ICN new file mode 100644 index 0000000..72eab29 Binary files /dev/null and b/putty/MACOSX/PUTTY.ICN differ diff --git a/putty/MACOSX/README.OSX b/putty/MACOSX/README.OSX new file mode 100644 index 0000000..e9243a0 --- /dev/null +++ b/putty/MACOSX/README.OSX @@ -0,0 +1,83 @@ +This directory contains a Mac OS X port of PuTTY/pterm, running as a +native Aqua GUI application. + +THIS PORT IS CURRENTLY UNFINISHED AND EXPERIMENTAL. It is _not_ +considered to be of release quality, even if you've found it (and +are reading this) in a PuTTY release source archive. You are welcome +to try using it, but don't be surprised at unexpected behaviour. I'm +not kidding. + +In particular, I have not yet decided where OS X PuTTY should store +its configuration data. Options include storing it in ~/.putty to be +compatible with Unix PuTTY, storing it wherever is compatible with +Mac Classic PuTTY, storing it in a natively OS X location, or +sorting out the `config-locations' wishlist item and doing all +three. Therefore, if you start using this port and create a whole +load of saved sessions, you should not be surprised if a future +version of the port decides to look somewhere completely different +for the data and therefore loses them all. If that happens, don't +say you weren't warned! + +Other ways in which the port is currently unfinished include: + +Missing terminal window features +-------------------------------- + + - terminal display is horribly slow + + - fonts aren't configurable + + - several features are unimplemented in the terminal display: + underlining, non-solid-block cursors, double-width and + double-height line attributes, bold as font rather than as + colour, wide (CJK) characters, combining characters. + + - there's no scrollbar + + - terminal window resizing isn't implemented yet + + - proper window placement (cascading down and right from the + starting position, plus remembering previous window positions per + the Apple HIG) is not implemented + +Missing alert box features +-------------------------- + + - warn-on-close isn't implemented + +Missing input features +---------------------- + + - use of Alt+numberpad to enter arbitrary numeric character codes + is not yet supported + + - there's no Meta key yet. (I'd like to at least have the + possibility of using Command rather than Option as the Meta key, + since the latter is necessary to send some characters, including + the rather important # on Apple UK keyboards; but trapping + Command- and sending it to the window rather than the + application menu requires me to make a positive effort of some + sort and I haven't got round to it yet. For those Mac users who + consider their Command key sacrosanct, don't worry, this option + _will_ be configurable and _will_ be off by default.) + + - there's no specials menu + + - mouse activity isn't supported (neither cut-and-paste nor xterm + mouse tracking) + +Missing terminal emulation features +----------------------------------- + + - currently no support for server-side window management requests + (i.e. escape sequences to minimise or maximise the window, + request or change its position and size, change its title etc) + + - window title is currently fixed + +Other missing features +---------------------- + + - no Event Log + + - no mid-session Change Settings diff --git a/putty/MINIBIDI.C b/putty/MINIBIDI.C new file mode 100644 index 0000000..4c2eea5 --- /dev/null +++ b/putty/MINIBIDI.C @@ -0,0 +1,2031 @@ +/************************************************************************ + * $Id: minibidi.c 9169 2011-05-07 10:57:19Z simon $ + * + * ------------ + * Description: + * ------------ + * This is an implemention of Unicode's Bidirectional Algorithm + * (known as UAX #9). + * + * http://www.unicode.org/reports/tr9/ + * + * Author: Ahmad Khalifa + * + * ----------------- + * Revision Details: (Updated by Revision Control System) + * ----------------- + * $Date: 2011-05-07 11:57:19 +0100 (Sat, 07 May 2011) $ + * $Author: simon $ + * $Revision: 9169 $ + * + * (www.arabeyes.org - under MIT license) + * + ************************************************************************/ + +/* + * TODO: + * ===== + * - Explicit marks need to be handled (they are not 100% now) + * - Ligatures + */ + +#include /* definition of wchar_t*/ + +#include "misc.h" + +#define LMASK 0x3F /* Embedding Level mask */ +#define OMASK 0xC0 /* Override mask */ +#define OISL 0x80 /* Override is L */ +#define OISR 0x40 /* Override is R */ + +/* For standalone compilation in a testing mode. + * Still depends on the PuTTY headers for snewn and sfree, but can avoid + * _linking_ with any other PuTTY code. */ +#ifdef TEST_GETTYPE +#define safemalloc malloc +#define safefree free +#endif + +/* Shaping Helpers */ +#define STYPE(xh) ((((xh) >= SHAPE_FIRST) && ((xh) <= SHAPE_LAST)) ? \ +shapetypes[(xh)-SHAPE_FIRST].type : SU) /*))*/ +#define SISOLATED(xh) (shapetypes[(xh)-SHAPE_FIRST].form_b) +#define SFINAL(xh) ((xh)+1) +#define SINITIAL(xh) ((xh)+2) +#define SMEDIAL(ch) ((ch)+3) + +#define leastGreaterOdd(x) ( ((x)+1) | 1 ) +#define leastGreaterEven(x) ( ((x)+2) &~ 1 ) + +typedef struct bidi_char { + wchar_t origwc, wc; + unsigned short index; +} bidi_char; + +/* function declarations */ +void flipThisRun(bidi_char *from, unsigned char* level, int max, int count); +int findIndexOfRun(unsigned char* level , int start, int count, int tlevel); +unsigned char getType(int ch); +unsigned char setOverrideBits(unsigned char level, unsigned char override); +int getPreviousLevel(unsigned char* level, int from); +int do_shape(bidi_char *line, bidi_char *to, int count); +int do_bidi(bidi_char *line, int count); +void doMirror(wchar_t* ch); + +/* character types */ +enum { + L, + LRE, + LRO, + R, + AL, + RLE, + RLO, + PDF, + EN, + ES, + ET, + AN, + CS, + NSM, + BN, + B, + S, + WS, + ON +}; + +/* Shaping Types */ +enum { + SL, /* Left-Joining, doesnt exist in U+0600 - U+06FF */ + SR, /* Right-Joining, ie has Isolated, Final */ + SD, /* Dual-Joining, ie has Isolated, Final, Initial, Medial */ + SU, /* Non-Joining */ + SC /* Join-Causing, like U+0640 (TATWEEL) */ +}; + +typedef struct { + char type; + wchar_t form_b; +} shape_node; + +/* Kept near the actual table, for verification. */ +#define SHAPE_FIRST 0x621 +#define SHAPE_LAST (SHAPE_FIRST + lenof(shapetypes) - 1) + +const shape_node shapetypes[] = { + /* index, Typ, Iso, Ligature Index*/ + /* 621 */ {SU, 0xFE80}, + /* 622 */ {SR, 0xFE81}, + /* 623 */ {SR, 0xFE83}, + /* 624 */ {SR, 0xFE85}, + /* 625 */ {SR, 0xFE87}, + /* 626 */ {SD, 0xFE89}, + /* 627 */ {SR, 0xFE8D}, + /* 628 */ {SD, 0xFE8F}, + /* 629 */ {SR, 0xFE93}, + /* 62A */ {SD, 0xFE95}, + /* 62B */ {SD, 0xFE99}, + /* 62C */ {SD, 0xFE9D}, + /* 62D */ {SD, 0xFEA1}, + /* 62E */ {SD, 0xFEA5}, + /* 62F */ {SR, 0xFEA9}, + /* 630 */ {SR, 0xFEAB}, + /* 631 */ {SR, 0xFEAD}, + /* 632 */ {SR, 0xFEAF}, + /* 633 */ {SD, 0xFEB1}, + /* 634 */ {SD, 0xFEB5}, + /* 635 */ {SD, 0xFEB9}, + /* 636 */ {SD, 0xFEBD}, + /* 637 */ {SD, 0xFEC1}, + /* 638 */ {SD, 0xFEC5}, + /* 639 */ {SD, 0xFEC9}, + /* 63A */ {SD, 0xFECD}, + /* 63B */ {SU, 0x0}, + /* 63C */ {SU, 0x0}, + /* 63D */ {SU, 0x0}, + /* 63E */ {SU, 0x0}, + /* 63F */ {SU, 0x0}, + /* 640 */ {SC, 0x0}, + /* 641 */ {SD, 0xFED1}, + /* 642 */ {SD, 0xFED5}, + /* 643 */ {SD, 0xFED9}, + /* 644 */ {SD, 0xFEDD}, + /* 645 */ {SD, 0xFEE1}, + /* 646 */ {SD, 0xFEE5}, + /* 647 */ {SD, 0xFEE9}, + /* 648 */ {SR, 0xFEED}, + /* 649 */ {SR, 0xFEEF}, /* SD */ + /* 64A */ {SD, 0xFEF1}, + /* 64B */ {SU, 0x0}, + /* 64C */ {SU, 0x0}, + /* 64D */ {SU, 0x0}, + /* 64E */ {SU, 0x0}, + /* 64F */ {SU, 0x0}, + /* 650 */ {SU, 0x0}, + /* 651 */ {SU, 0x0}, + /* 652 */ {SU, 0x0}, + /* 653 */ {SU, 0x0}, + /* 654 */ {SU, 0x0}, + /* 655 */ {SU, 0x0}, + /* 656 */ {SU, 0x0}, + /* 657 */ {SU, 0x0}, + /* 658 */ {SU, 0x0}, + /* 659 */ {SU, 0x0}, + /* 65A */ {SU, 0x0}, + /* 65B */ {SU, 0x0}, + /* 65C */ {SU, 0x0}, + /* 65D */ {SU, 0x0}, + /* 65E */ {SU, 0x0}, + /* 65F */ {SU, 0x0}, + /* 660 */ {SU, 0x0}, + /* 661 */ {SU, 0x0}, + /* 662 */ {SU, 0x0}, + /* 663 */ {SU, 0x0}, + /* 664 */ {SU, 0x0}, + /* 665 */ {SU, 0x0}, + /* 666 */ {SU, 0x0}, + /* 667 */ {SU, 0x0}, + /* 668 */ {SU, 0x0}, + /* 669 */ {SU, 0x0}, + /* 66A */ {SU, 0x0}, + /* 66B */ {SU, 0x0}, + /* 66C */ {SU, 0x0}, + /* 66D */ {SU, 0x0}, + /* 66E */ {SU, 0x0}, + /* 66F */ {SU, 0x0}, + /* 670 */ {SU, 0x0}, + /* 671 */ {SR, 0xFB50}, + /* 672 */ {SU, 0x0}, + /* 673 */ {SU, 0x0}, + /* 674 */ {SU, 0x0}, + /* 675 */ {SU, 0x0}, + /* 676 */ {SU, 0x0}, + /* 677 */ {SU, 0x0}, + /* 678 */ {SU, 0x0}, + /* 679 */ {SD, 0xFB66}, + /* 67A */ {SD, 0xFB5E}, + /* 67B */ {SD, 0xFB52}, + /* 67C */ {SU, 0x0}, + /* 67D */ {SU, 0x0}, + /* 67E */ {SD, 0xFB56}, + /* 67F */ {SD, 0xFB62}, + /* 680 */ {SD, 0xFB5A}, + /* 681 */ {SU, 0x0}, + /* 682 */ {SU, 0x0}, + /* 683 */ {SD, 0xFB76}, + /* 684 */ {SD, 0xFB72}, + /* 685 */ {SU, 0x0}, + /* 686 */ {SD, 0xFB7A}, + /* 687 */ {SD, 0xFB7E}, + /* 688 */ {SR, 0xFB88}, + /* 689 */ {SU, 0x0}, + /* 68A */ {SU, 0x0}, + /* 68B */ {SU, 0x0}, + /* 68C */ {SR, 0xFB84}, + /* 68D */ {SR, 0xFB82}, + /* 68E */ {SR, 0xFB86}, + /* 68F */ {SU, 0x0}, + /* 690 */ {SU, 0x0}, + /* 691 */ {SR, 0xFB8C}, + /* 692 */ {SU, 0x0}, + /* 693 */ {SU, 0x0}, + /* 694 */ {SU, 0x0}, + /* 695 */ {SU, 0x0}, + /* 696 */ {SU, 0x0}, + /* 697 */ {SU, 0x0}, + /* 698 */ {SR, 0xFB8A}, + /* 699 */ {SU, 0x0}, + /* 69A */ {SU, 0x0}, + /* 69B */ {SU, 0x0}, + /* 69C */ {SU, 0x0}, + /* 69D */ {SU, 0x0}, + /* 69E */ {SU, 0x0}, + /* 69F */ {SU, 0x0}, + /* 6A0 */ {SU, 0x0}, + /* 6A1 */ {SU, 0x0}, + /* 6A2 */ {SU, 0x0}, + /* 6A3 */ {SU, 0x0}, + /* 6A4 */ {SD, 0xFB6A}, + /* 6A5 */ {SU, 0x0}, + /* 6A6 */ {SD, 0xFB6E}, + /* 6A7 */ {SU, 0x0}, + /* 6A8 */ {SU, 0x0}, + /* 6A9 */ {SD, 0xFB8E}, + /* 6AA */ {SU, 0x0}, + /* 6AB */ {SU, 0x0}, + /* 6AC */ {SU, 0x0}, + /* 6AD */ {SD, 0xFBD3}, + /* 6AE */ {SU, 0x0}, + /* 6AF */ {SD, 0xFB92}, + /* 6B0 */ {SU, 0x0}, + /* 6B1 */ {SD, 0xFB9A}, + /* 6B2 */ {SU, 0x0}, + /* 6B3 */ {SD, 0xFB96}, + /* 6B4 */ {SU, 0x0}, + /* 6B5 */ {SU, 0x0}, + /* 6B6 */ {SU, 0x0}, + /* 6B7 */ {SU, 0x0}, + /* 6B8 */ {SU, 0x0}, + /* 6B9 */ {SU, 0x0}, + /* 6BA */ {SR, 0xFB9E}, + /* 6BB */ {SD, 0xFBA0}, + /* 6BC */ {SU, 0x0}, + /* 6BD */ {SU, 0x0}, + /* 6BE */ {SD, 0xFBAA}, + /* 6BF */ {SU, 0x0}, + /* 6C0 */ {SR, 0xFBA4}, + /* 6C1 */ {SD, 0xFBA6}, + /* 6C2 */ {SU, 0x0}, + /* 6C3 */ {SU, 0x0}, + /* 6C4 */ {SU, 0x0}, + /* 6C5 */ {SR, 0xFBE0}, + /* 6C6 */ {SR, 0xFBD9}, + /* 6C7 */ {SR, 0xFBD7}, + /* 6C8 */ {SR, 0xFBDB}, + /* 6C9 */ {SR, 0xFBE2}, + /* 6CA */ {SU, 0x0}, + /* 6CB */ {SR, 0xFBDE}, + /* 6CC */ {SD, 0xFBFC}, + /* 6CD */ {SU, 0x0}, + /* 6CE */ {SU, 0x0}, + /* 6CF */ {SU, 0x0}, + /* 6D0 */ {SU, 0x0}, + /* 6D1 */ {SU, 0x0}, + /* 6D2 */ {SR, 0xFBAE}, +}; + +/* + * Flips the text buffer, according to max level, and + * all higher levels + * + * Input: + * from: text buffer, on which to apply flipping + * level: resolved levels buffer + * max: the maximum level found in this line (should be unsigned char) + * count: line size in bidi_char + */ +void flipThisRun(bidi_char *from, unsigned char *level, int max, int count) +{ + int i, j, k, tlevel; + bidi_char temp; + + j = i = 0; + while (i j; k--, j++) { + temp = from[k]; + from[k] = from[j]; + from[j] = temp; + } + } +} + +/* + * Finds the index of a run with level equals tlevel + */ +int findIndexOfRun(unsigned char* level , int start, int count, int tlevel) +{ + int i; + for (i=start; i 1) { + k = (i + j) / 2; + if (ch < lookup[k].first) + j = k; + else if (ch > lookup[k].last) + i = k; + else + return lookup[k].type; + } + + /* + * If we reach here, the character was not in any of the + * intervals listed in the lookup table. This means we return + * ON (`Other Neutrals'). This is the appropriate code for any + * character genuinely not listed in the Unicode table, and + * also the table above has deliberately left out any + * characters _explicitly_ listed as ON (to save space!). + */ + return ON; +} + +/* + * Function exported to front ends to allow them to identify + * bidi-active characters (in case, for example, the platform's + * text display function can't conveniently be prevented from doing + * its own bidi and so special treatment is required for characters + * that would cause the bidi algorithm to activate). + * + * This function is passed a single Unicode code point, and returns + * nonzero if the presence of this code point can possibly cause + * the bidi algorithm to do any reordering. Thus, any string + * composed entirely of characters for which is_rtl() returns zero + * should be safe to pass to a bidi-active platform display + * function without fear. + * + * (is_rtl() must therefore also return true for any character + * which would be affected by Arabic shaping, but this isn't + * important because all such characters are right-to-left so it + * would have flagged them anyway.) + */ +int is_rtl(int c) +{ + /* + * After careful reading of the Unicode bidi algorithm (URL as + * given at the top of this file) I believe that the only + * character classes which can possibly cause trouble are R, + * AL, RLE and RLO. I think that any string containing no + * character in any of those classes will be displayed + * uniformly left-to-right by the Unicode bidi algorithm. + */ + const int mask = (1< 0) { + unsigned char current = level[--from]; + + while (from >= 0 && level[from] == current) + from--; + + if (from >= 0) + return level[from]; + + return -1; + } else + return -1; +} + +/* The Main shaping function, and the only one to be used + * by the outside world. + * + * line: buffer to apply shaping to. this must be passed by doBidi() first + * to: output buffer for the shaped data + * count: number of characters in line + */ +int do_shape(bidi_char *line, bidi_char *to, int count) +{ + int i, tempShape, ligFlag; + + for (ligFlag=i=0; i 0) switch (line[i-1].wc) { + case 0x622: + ligFlag = 1; + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = 0xFEF6; + else + to[i].wc = 0xFEF5; + break; + case 0x623: + ligFlag = 1; + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = 0xFEF8; + else + to[i].wc = 0xFEF7; + break; + case 0x625: + ligFlag = 1; + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = 0xFEFA; + else + to[i].wc = 0xFEF9; + break; + case 0x627: + ligFlag = 1; + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = 0xFEFC; + else + to[i].wc = 0xFEFB; + break; + } + if (ligFlag) { + to[i-1].wc = 0x20; + ligFlag = 0; + break; + } + } + + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) { + tempShape = (i > 0 ? STYPE(line[i-1].wc) : SU); + if ((tempShape == SR) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = SMEDIAL((SISOLATED(line[i].wc))); + else + to[i].wc = SFINAL((SISOLATED(line[i].wc))); + break; + } + + tempShape = (i > 0 ? STYPE(line[i-1].wc) : SU); + if ((tempShape == SR) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = SINITIAL((SISOLATED(line[i].wc))); + else + to[i].wc = SISOLATED(line[i].wc); + break; + + + } + } + return 1; +} + +/* + * The Main Bidi Function, and the only function that should + * be used by the outside world. + * + * line: a buffer of size count containing text to apply + * the Bidirectional algorithm to. + */ + +int do_bidi(bidi_char *line, int count) +{ + unsigned char* types; + unsigned char* levels; + unsigned char paragraphLevel; + unsigned char currentEmbedding; + unsigned char currentOverride; + unsigned char tempType; + int i, j, yes, bover; + + /* Check the presence of R or AL types as optimization */ + yes = 0; + for (i=0; i= 0) { + if (types[j] == AL) { + types[i] = AN; + break; + } else if (types[j] == R || types[j] == L) { + break; + } + j--; + } + } + } + + /* Rule (W3) + * W3. Change all ALs to R. + * + * Optimization: on Rule Xn, we might set a flag on AL type + * to prevent this loop in L R lines only... + */ + for (i=0; i 0 && types[i-1] == EN) { + types[i] = EN; + continue; + } else if (i < count-1 && types[i+1] == EN) { + types[i] = EN; + continue; + } else if (i < count-1 && types[i+1] == ET) { + j=i; + while (j = 0) { + if (types[j] == L) { + types[i] = L; + break; + } else if (types[j] == R || types[j] == AL) { + break; + } + j--; + } + } + } + + /* Rule (N1) + * N1. A sequence of neutrals takes the direction of the surrounding + * strong text if the text on both sides has the same direction. European + * and Arabic numbers are treated as though they were R. + */ + if (count >= 2 && types[0] == ON) { + if ((types[1] == R) || (types[1] == EN) || (types[1] == AN)) + types[0] = R; + else if (types[1] == L) + types[0] = L; + } + for (i=1; i<(count-1); i++) { + if (types[i] == ON) { + if (types[i-1] == L) { + j=i; + while (j<(count-1) && types[j] == ON) { + j++; + } + if (types[j] == L) { + while (i= 2 && types[count-1] == ON) { + if (types[count-2] == R || types[count-2] == EN || types[count-2] == AN) + types[count-1] = R; + else if (types[count-2] == L) + types[count-1] = L; + } + + /* Rule (N2) + * N2. Any remaining neutrals take the embedding direction. + */ + for (i=0; i0 && (getType(line[j].wc) == WS)) { + j--; + } + if (j < (count-1)) { + for (j++; j=i ; j--) { + levels[j] = paragraphLevel; + } + } + } else if (tempType == B || tempType == S) { + levels[i] = paragraphLevel; + } + } + + /* Rule (L4) NOT IMPLEMENTED + * L4. A character that possesses the mirrored property as specified by + * Section 4.7, Mirrored, must be depicted by a mirrored glyph if the + * resolved directionality of that character is R. + */ + /* Note: this is implemented before L2 for efficiency */ + for (i=0; i tempType) + tempType = levels[i]; + i++; + } + /* maximum level in tempType. */ + while (tempType > 0) { /* loop from highest level to the least odd, */ + /* which i assume is 1 */ + flipThisRun(line, levels, tempType, count); + tempType--; + } + + /* Rule (L3) NOT IMPLEMENTED + * L3. Combining marks applied to a right-to-left base character will at + * this point precede their base character. If the rendering engine + * expects them to follow the base characters in the final display + * process, then the ordering of the marks and the base character must + * be reversed. + */ + sfree(types); + sfree(levels); + return R; +} + + +/* + * Bad, Horrible function + * takes a pointer to a character that is checked for + * having a mirror glyph. + */ +void doMirror(wchar_t* ch) +{ + if ((*ch & 0xFF00) == 0) { + switch (*ch) { + case 0x0028: *ch = 0x0029; break; + case 0x0029: *ch = 0x0028; break; + case 0x003C: *ch = 0x003E; break; + case 0x003E: *ch = 0x003C; break; + case 0x005B: *ch = 0x005D; break; + case 0x005D: *ch = 0x005B; break; + case 0x007B: *ch = 0x007D; break; + case 0x007D: *ch = 0x007B; break; + case 0x00AB: *ch = 0x00BB; break; + case 0x00BB: *ch = 0x00AB; break; + } + } else if ((*ch & 0xFF00) == 0x2000) { + switch (*ch) { + case 0x2039: *ch = 0x203A; break; + case 0x203A: *ch = 0x2039; break; + case 0x2045: *ch = 0x2046; break; + case 0x2046: *ch = 0x2045; break; + case 0x207D: *ch = 0x207E; break; + case 0x207E: *ch = 0x207D; break; + case 0x208D: *ch = 0x208E; break; + case 0x208E: *ch = 0x208D; break; + } + } else if ((*ch & 0xFF00) == 0x2200) { + switch (*ch) { + case 0x2208: *ch = 0x220B; break; + case 0x2209: *ch = 0x220C; break; + case 0x220A: *ch = 0x220D; break; + case 0x220B: *ch = 0x2208; break; + case 0x220C: *ch = 0x2209; break; + case 0x220D: *ch = 0x220A; break; + case 0x2215: *ch = 0x29F5; break; + case 0x223C: *ch = 0x223D; break; + case 0x223D: *ch = 0x223C; break; + case 0x2243: *ch = 0x22CD; break; + case 0x2252: *ch = 0x2253; break; + case 0x2253: *ch = 0x2252; break; + case 0x2254: *ch = 0x2255; break; + case 0x2255: *ch = 0x2254; break; + case 0x2264: *ch = 0x2265; break; + case 0x2265: *ch = 0x2264; break; + case 0x2266: *ch = 0x2267; break; + case 0x2267: *ch = 0x2266; break; + case 0x2268: *ch = 0x2269; break; + case 0x2269: *ch = 0x2268; break; + case 0x226A: *ch = 0x226B; break; + case 0x226B: *ch = 0x226A; break; + case 0x226E: *ch = 0x226F; break; + case 0x226F: *ch = 0x226E; break; + case 0x2270: *ch = 0x2271; break; + case 0x2271: *ch = 0x2270; break; + case 0x2272: *ch = 0x2273; break; + case 0x2273: *ch = 0x2272; break; + case 0x2274: *ch = 0x2275; break; + case 0x2275: *ch = 0x2274; break; + case 0x2276: *ch = 0x2277; break; + case 0x2277: *ch = 0x2276; break; + case 0x2278: *ch = 0x2279; break; + case 0x2279: *ch = 0x2278; break; + case 0x227A: *ch = 0x227B; break; + case 0x227B: *ch = 0x227A; break; + case 0x227C: *ch = 0x227D; break; + case 0x227D: *ch = 0x227C; break; + case 0x227E: *ch = 0x227F; break; + case 0x227F: *ch = 0x227E; break; + case 0x2280: *ch = 0x2281; break; + case 0x2281: *ch = 0x2280; break; + case 0x2282: *ch = 0x2283; break; + case 0x2283: *ch = 0x2282; break; + case 0x2284: *ch = 0x2285; break; + case 0x2285: *ch = 0x2284; break; + case 0x2286: *ch = 0x2287; break; + case 0x2287: *ch = 0x2286; break; + case 0x2288: *ch = 0x2289; break; + case 0x2289: *ch = 0x2288; break; + case 0x228A: *ch = 0x228B; break; + case 0x228B: *ch = 0x228A; break; + case 0x228F: *ch = 0x2290; break; + case 0x2290: *ch = 0x228F; break; + case 0x2291: *ch = 0x2292; break; + case 0x2292: *ch = 0x2291; break; + case 0x2298: *ch = 0x29B8; break; + case 0x22A2: *ch = 0x22A3; break; + case 0x22A3: *ch = 0x22A2; break; + case 0x22A6: *ch = 0x2ADE; break; + case 0x22A8: *ch = 0x2AE4; break; + case 0x22A9: *ch = 0x2AE3; break; + case 0x22AB: *ch = 0x2AE5; break; + case 0x22B0: *ch = 0x22B1; break; + case 0x22B1: *ch = 0x22B0; break; + case 0x22B2: *ch = 0x22B3; break; + case 0x22B3: *ch = 0x22B2; break; + case 0x22B4: *ch = 0x22B5; break; + case 0x22B5: *ch = 0x22B4; break; + case 0x22B6: *ch = 0x22B7; break; + case 0x22B7: *ch = 0x22B6; break; + case 0x22C9: *ch = 0x22CA; break; + case 0x22CA: *ch = 0x22C9; break; + case 0x22CB: *ch = 0x22CC; break; + case 0x22CC: *ch = 0x22CB; break; + case 0x22CD: *ch = 0x2243; break; + case 0x22D0: *ch = 0x22D1; break; + case 0x22D1: *ch = 0x22D0; break; + case 0x22D6: *ch = 0x22D7; break; + case 0x22D7: *ch = 0x22D6; break; + case 0x22D8: *ch = 0x22D9; break; + case 0x22D9: *ch = 0x22D8; break; + case 0x22DA: *ch = 0x22DB; break; + case 0x22DB: *ch = 0x22DA; break; + case 0x22DC: *ch = 0x22DD; break; + case 0x22DD: *ch = 0x22DC; break; + case 0x22DE: *ch = 0x22DF; break; + case 0x22DF: *ch = 0x22DE; break; + case 0x22E0: *ch = 0x22E1; break; + case 0x22E1: *ch = 0x22E0; break; + case 0x22E2: *ch = 0x22E3; break; + case 0x22E3: *ch = 0x22E2; break; + case 0x22E4: *ch = 0x22E5; break; + case 0x22E5: *ch = 0x22E4; break; + case 0x22E6: *ch = 0x22E7; break; + case 0x22E7: *ch = 0x22E6; break; + case 0x22E8: *ch = 0x22E9; break; + case 0x22E9: *ch = 0x22E8; break; + case 0x22EA: *ch = 0x22EB; break; + case 0x22EB: *ch = 0x22EA; break; + case 0x22EC: *ch = 0x22ED; break; + case 0x22ED: *ch = 0x22EC; break; + case 0x22F0: *ch = 0x22F1; break; + case 0x22F1: *ch = 0x22F0; break; + case 0x22F2: *ch = 0x22FA; break; + case 0x22F3: *ch = 0x22FB; break; + case 0x22F4: *ch = 0x22FC; break; + case 0x22F6: *ch = 0x22FD; break; + case 0x22F7: *ch = 0x22FE; break; + case 0x22FA: *ch = 0x22F2; break; + case 0x22FB: *ch = 0x22F3; break; + case 0x22FC: *ch = 0x22F4; break; + case 0x22FD: *ch = 0x22F6; break; + case 0x22FE: *ch = 0x22F7; break; + } + } else if ((*ch & 0xFF00) == 0x2300) { + switch (*ch) { + case 0x2308: *ch = 0x2309; break; + case 0x2309: *ch = 0x2308; break; + case 0x230A: *ch = 0x230B; break; + case 0x230B: *ch = 0x230A; break; + case 0x2329: *ch = 0x232A; break; + case 0x232A: *ch = 0x2329; break; + } + } else if ((*ch & 0xFF00) == 0x2700) { + switch (*ch) { + case 0x2768: *ch = 0x2769; break; + case 0x2769: *ch = 0x2768; break; + case 0x276A: *ch = 0x276B; break; + case 0x276B: *ch = 0x276A; break; + case 0x276C: *ch = 0x276D; break; + case 0x276D: *ch = 0x276C; break; + case 0x276E: *ch = 0x276F; break; + case 0x276F: *ch = 0x276E; break; + case 0x2770: *ch = 0x2771; break; + case 0x2771: *ch = 0x2770; break; + case 0x2772: *ch = 0x2773; break; + case 0x2773: *ch = 0x2772; break; + case 0x2774: *ch = 0x2775; break; + case 0x2775: *ch = 0x2774; break; + case 0x27D5: *ch = 0x27D6; break; + case 0x27D6: *ch = 0x27D5; break; + case 0x27DD: *ch = 0x27DE; break; + case 0x27DE: *ch = 0x27DD; break; + case 0x27E2: *ch = 0x27E3; break; + case 0x27E3: *ch = 0x27E2; break; + case 0x27E4: *ch = 0x27E5; break; + case 0x27E5: *ch = 0x27E4; break; + case 0x27E6: *ch = 0x27E7; break; + case 0x27E7: *ch = 0x27E6; break; + case 0x27E8: *ch = 0x27E9; break; + case 0x27E9: *ch = 0x27E8; break; + case 0x27EA: *ch = 0x27EB; break; + case 0x27EB: *ch = 0x27EA; break; + } + } else if ((*ch & 0xFF00) == 0x2900) { + switch (*ch) { + case 0x2983: *ch = 0x2984; break; + case 0x2984: *ch = 0x2983; break; + case 0x2985: *ch = 0x2986; break; + case 0x2986: *ch = 0x2985; break; + case 0x2987: *ch = 0x2988; break; + case 0x2988: *ch = 0x2987; break; + case 0x2989: *ch = 0x298A; break; + case 0x298A: *ch = 0x2989; break; + case 0x298B: *ch = 0x298C; break; + case 0x298C: *ch = 0x298B; break; + case 0x298D: *ch = 0x2990; break; + case 0x298E: *ch = 0x298F; break; + case 0x298F: *ch = 0x298E; break; + case 0x2990: *ch = 0x298D; break; + case 0x2991: *ch = 0x2992; break; + case 0x2992: *ch = 0x2991; break; + case 0x2993: *ch = 0x2994; break; + case 0x2994: *ch = 0x2993; break; + case 0x2995: *ch = 0x2996; break; + case 0x2996: *ch = 0x2995; break; + case 0x2997: *ch = 0x2998; break; + case 0x2998: *ch = 0x2997; break; + case 0x29B8: *ch = 0x2298; break; + case 0x29C0: *ch = 0x29C1; break; + case 0x29C1: *ch = 0x29C0; break; + case 0x29C4: *ch = 0x29C5; break; + case 0x29C5: *ch = 0x29C4; break; + case 0x29CF: *ch = 0x29D0; break; + case 0x29D0: *ch = 0x29CF; break; + case 0x29D1: *ch = 0x29D2; break; + case 0x29D2: *ch = 0x29D1; break; + case 0x29D4: *ch = 0x29D5; break; + case 0x29D5: *ch = 0x29D4; break; + case 0x29D8: *ch = 0x29D9; break; + case 0x29D9: *ch = 0x29D8; break; + case 0x29DA: *ch = 0x29DB; break; + case 0x29DB: *ch = 0x29DA; break; + case 0x29F5: *ch = 0x2215; break; + case 0x29F8: *ch = 0x29F9; break; + case 0x29F9: *ch = 0x29F8; break; + case 0x29FC: *ch = 0x29FD; break; + case 0x29FD: *ch = 0x29FC; break; + } + } else if ((*ch & 0xFF00) == 0x2A00) { + switch (*ch) { + case 0x2A2B: *ch = 0x2A2C; break; + case 0x2A2C: *ch = 0x2A2B; break; + case 0x2A2D: *ch = 0x2A2C; break; + case 0x2A2E: *ch = 0x2A2D; break; + case 0x2A34: *ch = 0x2A35; break; + case 0x2A35: *ch = 0x2A34; break; + case 0x2A3C: *ch = 0x2A3D; break; + case 0x2A3D: *ch = 0x2A3C; break; + case 0x2A64: *ch = 0x2A65; break; + case 0x2A65: *ch = 0x2A64; break; + case 0x2A79: *ch = 0x2A7A; break; + case 0x2A7A: *ch = 0x2A79; break; + case 0x2A7D: *ch = 0x2A7E; break; + case 0x2A7E: *ch = 0x2A7D; break; + case 0x2A7F: *ch = 0x2A80; break; + case 0x2A80: *ch = 0x2A7F; break; + case 0x2A81: *ch = 0x2A82; break; + case 0x2A82: *ch = 0x2A81; break; + case 0x2A83: *ch = 0x2A84; break; + case 0x2A84: *ch = 0x2A83; break; + case 0x2A8B: *ch = 0x2A8C; break; + case 0x2A8C: *ch = 0x2A8B; break; + case 0x2A91: *ch = 0x2A92; break; + case 0x2A92: *ch = 0x2A91; break; + case 0x2A93: *ch = 0x2A94; break; + case 0x2A94: *ch = 0x2A93; break; + case 0x2A95: *ch = 0x2A96; break; + case 0x2A96: *ch = 0x2A95; break; + case 0x2A97: *ch = 0x2A98; break; + case 0x2A98: *ch = 0x2A97; break; + case 0x2A99: *ch = 0x2A9A; break; + case 0x2A9A: *ch = 0x2A99; break; + case 0x2A9B: *ch = 0x2A9C; break; + case 0x2A9C: *ch = 0x2A9B; break; + case 0x2AA1: *ch = 0x2AA2; break; + case 0x2AA2: *ch = 0x2AA1; break; + case 0x2AA6: *ch = 0x2AA7; break; + case 0x2AA7: *ch = 0x2AA6; break; + case 0x2AA8: *ch = 0x2AA9; break; + case 0x2AA9: *ch = 0x2AA8; break; + case 0x2AAA: *ch = 0x2AAB; break; + case 0x2AAB: *ch = 0x2AAA; break; + case 0x2AAC: *ch = 0x2AAD; break; + case 0x2AAD: *ch = 0x2AAC; break; + case 0x2AAF: *ch = 0x2AB0; break; + case 0x2AB0: *ch = 0x2AAF; break; + case 0x2AB3: *ch = 0x2AB4; break; + case 0x2AB4: *ch = 0x2AB3; break; + case 0x2ABB: *ch = 0x2ABC; break; + case 0x2ABC: *ch = 0x2ABB; break; + case 0x2ABD: *ch = 0x2ABE; break; + case 0x2ABE: *ch = 0x2ABD; break; + case 0x2ABF: *ch = 0x2AC0; break; + case 0x2AC0: *ch = 0x2ABF; break; + case 0x2AC1: *ch = 0x2AC2; break; + case 0x2AC2: *ch = 0x2AC1; break; + case 0x2AC3: *ch = 0x2AC4; break; + case 0x2AC4: *ch = 0x2AC3; break; + case 0x2AC5: *ch = 0x2AC6; break; + case 0x2AC6: *ch = 0x2AC5; break; + case 0x2ACD: *ch = 0x2ACE; break; + case 0x2ACE: *ch = 0x2ACD; break; + case 0x2ACF: *ch = 0x2AD0; break; + case 0x2AD0: *ch = 0x2ACF; break; + case 0x2AD1: *ch = 0x2AD2; break; + case 0x2AD2: *ch = 0x2AD1; break; + case 0x2AD3: *ch = 0x2AD4; break; + case 0x2AD4: *ch = 0x2AD3; break; + case 0x2AD5: *ch = 0x2AD6; break; + case 0x2AD6: *ch = 0x2AD5; break; + case 0x2ADE: *ch = 0x22A6; break; + case 0x2AE3: *ch = 0x22A9; break; + case 0x2AE4: *ch = 0x22A8; break; + case 0x2AE5: *ch = 0x22AB; break; + case 0x2AEC: *ch = 0x2AED; break; + case 0x2AED: *ch = 0x2AEC; break; + case 0x2AF7: *ch = 0x2AF8; break; + case 0x2AF8: *ch = 0x2AF7; break; + case 0x2AF9: *ch = 0x2AFA; break; + case 0x2AFA: *ch = 0x2AF9; break; + } + } else if ((*ch & 0xFF00) == 0x3000) { + switch (*ch) { + case 0x3008: *ch = 0x3009; break; + case 0x3009: *ch = 0x3008; break; + case 0x300A: *ch = 0x300B; break; + case 0x300B: *ch = 0x300A; break; + case 0x300C: *ch = 0x300D; break; + case 0x300D: *ch = 0x300C; break; + case 0x300E: *ch = 0x300F; break; + case 0x300F: *ch = 0x300E; break; + case 0x3010: *ch = 0x3011; break; + case 0x3011: *ch = 0x3010; break; + case 0x3014: *ch = 0x3015; break; + case 0x3015: *ch = 0x3014; break; + case 0x3016: *ch = 0x3017; break; + case 0x3017: *ch = 0x3016; break; + case 0x3018: *ch = 0x3019; break; + case 0x3019: *ch = 0x3018; break; + case 0x301A: *ch = 0x301B; break; + case 0x301B: *ch = 0x301A; break; + } + } else if ((*ch & 0xFF00) == 0xFF00) { + switch (*ch) { + case 0xFF08: *ch = 0xFF09; break; + case 0xFF09: *ch = 0xFF08; break; + case 0xFF1C: *ch = 0xFF1E; break; + case 0xFF1E: *ch = 0xFF1C; break; + case 0xFF3B: *ch = 0xFF3D; break; + case 0xFF3D: *ch = 0xFF3B; break; + case 0xFF5B: *ch = 0xFF5D; break; + case 0xFF5D: *ch = 0xFF5B; break; + case 0xFF5F: *ch = 0xFF60; break; + case 0xFF60: *ch = 0xFF5F; break; + case 0xFF62: *ch = 0xFF63; break; + case 0xFF63: *ch = 0xFF62; break; + } + } +} + +#ifdef TEST_GETTYPE + +#include +#include + +int main(int argc, char **argv) +{ + static const struct { int type; char *name; } typetoname[] = { +#define TYPETONAME(X) { X , #X } + TYPETONAME(L), + TYPETONAME(LRE), + TYPETONAME(LRO), + TYPETONAME(R), + TYPETONAME(AL), + TYPETONAME(RLE), + TYPETONAME(RLO), + TYPETONAME(PDF), + TYPETONAME(EN), + TYPETONAME(ES), + TYPETONAME(ET), + TYPETONAME(AN), + TYPETONAME(CS), + TYPETONAME(NSM), + TYPETONAME(BN), + TYPETONAME(B), + TYPETONAME(S), + TYPETONAME(WS), + TYPETONAME(ON), +#undef TYPETONAME + }; + int i; + + for (i = 1; i < argc; i++) { + unsigned long chr = strtoul(argv[i], NULL, 0); + int type = getType(chr); + assert(typetoname[type].type == type); + printf("U+%04x: %s\n", chr, typetoname[type].name); + } + + return 0; +} + +#endif diff --git a/putty/MISC.C b/putty/MISC.C new file mode 100644 index 0000000..4aeab50 --- /dev/null +++ b/putty/MISC.C @@ -0,0 +1,655 @@ +/* + * Platform-independent routines shared between all PuTTY programs. + */ + +#include +#include +#include +#include +#include +#include +#include "putty.h" + +/* + * Parse a string block size specification. This is approximately a + * subset of the block size specs supported by GNU fileutils: + * "nk" = n kilobytes + * "nM" = n megabytes + * "nG" = n gigabytes + * All numbers are decimal, and suffixes refer to powers of two. + * Case-insensitive. + */ +unsigned long parse_blocksize(const char *bs) +{ + char *suf; + unsigned long r = strtoul(bs, &suf, 10); + if (*suf != '\0') { + while (*suf && isspace((unsigned char)*suf)) suf++; + switch (*suf) { + case 'k': case 'K': + r *= 1024ul; + break; + case 'm': case 'M': + r *= 1024ul * 1024ul; + break; + case 'g': case 'G': + r *= 1024ul * 1024ul * 1024ul; + break; + case '\0': + default: + break; + } + } + return r; +} + +/* + * Parse a ^C style character specification. + * Returns NULL in `next' if we didn't recognise it as a control character, + * in which case `c' should be ignored. + * The precise current parsing is an oddity inherited from the terminal + * answerback-string parsing code. All sequences start with ^; all except + * ^<123> are two characters. The ones that are worth keeping are probably: + * ^? 127 + * ^@A-Z[\]^_ 0-31 + * a-z 1-26 + * specified by number (decimal, 0octal, 0xHEX) + * ~ ^ escape + */ +char ctrlparse(char *s, char **next) +{ + char c = 0; + if (*s != '^') { + *next = NULL; + } else { + s++; + if (*s == '\0') { + *next = NULL; + } else if (*s == '<') { + s++; + c = (char)strtol(s, next, 0); + if ((*next == s) || (**next != '>')) { + c = 0; + *next = NULL; + } else + (*next)++; + } else if (*s >= 'a' && *s <= 'z') { + c = (*s - ('a' - 1)); + *next = s+1; + } else if ((*s >= '@' && *s <= '_') || *s == '?' || (*s & 0x80)) { + c = ('@' ^ *s); + *next = s+1; + } else if (*s == '~') { + c = '^'; + *next = s+1; + } + } + return c; +} + +prompts_t *new_prompts(void *frontend) +{ + prompts_t *p = snew(prompts_t); + p->prompts = NULL; + p->n_prompts = 0; + p->frontend = frontend; + p->data = NULL; + p->to_server = TRUE; /* to be on the safe side */ + p->name = p->instruction = NULL; + p->name_reqd = p->instr_reqd = FALSE; + return p; +} +void add_prompt(prompts_t *p, char *promptstr, int echo, size_t len) +{ + prompt_t *pr = snew(prompt_t); + char *result = snewn(len, char); + pr->prompt = promptstr; + pr->echo = echo; + pr->result = result; + pr->result_len = len; + p->n_prompts++; + p->prompts = sresize(p->prompts, p->n_prompts, prompt_t *); + p->prompts[p->n_prompts-1] = pr; +} +void free_prompts(prompts_t *p) +{ + size_t i; + for (i=0; i < p->n_prompts; i++) { + prompt_t *pr = p->prompts[i]; + memset(pr->result, 0, pr->result_len); /* burn the evidence */ + sfree(pr->result); + sfree(pr->prompt); + sfree(pr); + } + sfree(p->prompts); + sfree(p->name); + sfree(p->instruction); + sfree(p); +} + +/* ---------------------------------------------------------------------- + * String handling routines. + */ + +char *dupstr(const char *s) +{ + char *p = NULL; + if (s) { + int len = strlen(s); + p = snewn(len + 1, char); + strcpy(p, s); + } + return p; +} + +/* Allocate the concatenation of N strings. Terminate arg list with NULL. */ +char *dupcat(const char *s1, ...) +{ + int len; + char *p, *q, *sn; + va_list ap; + + len = strlen(s1); + va_start(ap, s1); + while (1) { + sn = va_arg(ap, char *); + if (!sn) + break; + len += strlen(sn); + } + va_end(ap); + + p = snewn(len + 1, char); + strcpy(p, s1); + q = p + strlen(p); + + va_start(ap, s1); + while (1) { + sn = va_arg(ap, char *); + if (!sn) + break; + strcpy(q, sn); + q += strlen(q); + } + va_end(ap); + + return p; +} + +/* + * Do an sprintf(), but into a custom-allocated buffer. + * + * Currently I'm doing this via vsnprintf. This has worked so far, + * but it's not good, because vsnprintf is not available on all + * platforms. There's an ifdef to use `_vsnprintf', which seems + * to be the local name for it on Windows. Other platforms may + * lack it completely, in which case it'll be time to rewrite + * this function in a totally different way. + * + * The only `properly' portable solution I can think of is to + * implement my own format string scanner, which figures out an + * upper bound for the length of each formatting directive, + * allocates the buffer as it goes along, and calls sprintf() to + * actually process each directive. If I ever need to actually do + * this, some caveats: + * + * - It's very hard to find a reliable upper bound for + * floating-point values. %f, in particular, when supplied with + * a number near to the upper or lower limit of representable + * numbers, could easily take several hundred characters. It's + * probably feasible to predict this statically using the + * constants in , or even to predict it dynamically by + * looking at the exponent of the specific float provided, but + * it won't be fun. + * + * - Don't forget to _check_, after calling sprintf, that it's + * used at most the amount of space we had available. + * + * - Fault any formatting directive we don't fully understand. The + * aim here is to _guarantee_ that we never overflow the buffer, + * because this is a security-critical function. If we see a + * directive we don't know about, we should panic and die rather + * than run any risk. + */ +char *dupprintf(const char *fmt, ...) +{ + char *ret; + va_list ap; + va_start(ap, fmt); + ret = dupvprintf(fmt, ap); + va_end(ap); + return ret; +} +char *dupvprintf(const char *fmt, va_list ap) +{ + char *buf; + int len, size; + + buf = snewn(512, char); + size = 512; + + while (1) { +#ifdef _WINDOWS +#define vsnprintf _vsnprintf +#endif +#ifdef va_copy + /* Use the `va_copy' macro mandated by C99, if present. + * XXX some environments may have this as __va_copy() */ + va_list aq; + va_copy(aq, ap); + len = vsnprintf(buf, size, fmt, aq); + va_end(aq); +#else + /* Ugh. No va_copy macro, so do something nasty. + * Technically, you can't reuse a va_list like this: it is left + * unspecified whether advancing a va_list pointer modifies its + * value or something it points to, so on some platforms calling + * vsnprintf twice on the same va_list might fail hideously + * (indeed, it has been observed to). + * XXX the autoconf manual suggests that using memcpy() will give + * "maximum portability". */ + len = vsnprintf(buf, size, fmt, ap); +#endif + if (len >= 0 && len < size) { + /* This is the C99-specified criterion for snprintf to have + * been completely successful. */ + return buf; + } else if (len > 0) { + /* This is the C99 error condition: the returned length is + * the required buffer size not counting the NUL. */ + size = len + 1; + } else { + /* This is the pre-C99 glibc error condition: <0 means the + * buffer wasn't big enough, so we enlarge it a bit and hope. */ + size += 512; + } + buf = sresize(buf, size, char); + } +} + +/* + * Read an entire line of text from a file. Return a buffer + * malloced to be as big as necessary (caller must free). + */ +char *fgetline(FILE *fp) +{ + char *ret = snewn(512, char); + int size = 512, len = 0; + while (fgets(ret + len, size - len, fp)) { + len += strlen(ret + len); + if (ret[len-1] == '\n') + break; /* got a newline, we're done */ + size = len + 512; + ret = sresize(ret, size, char); + } + if (len == 0) { /* first fgets returned NULL */ + sfree(ret); + return NULL; + } + ret[len] = '\0'; + return ret; +} + +/* ---------------------------------------------------------------------- + * Base64 encoding routine. This is required in public-key writing + * but also in HTTP proxy handling, so it's centralised here. + */ + +void base64_encode_atom(unsigned char *data, int n, char *out) +{ + static const char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + unsigned word; + + word = data[0] << 16; + if (n > 1) + word |= data[1] << 8; + if (n > 2) + word |= data[2]; + out[0] = base64_chars[(word >> 18) & 0x3F]; + out[1] = base64_chars[(word >> 12) & 0x3F]; + if (n > 1) + out[2] = base64_chars[(word >> 6) & 0x3F]; + else + out[2] = '='; + if (n > 2) + out[3] = base64_chars[word & 0x3F]; + else + out[3] = '='; +} + +/* ---------------------------------------------------------------------- + * 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_GRANULE 512 + +struct bufchain_granule { + struct bufchain_granule *next; + int buflen, bufpos; + char buf[BUFFER_GRANULE]; +}; + +void bufchain_init(bufchain *ch) +{ + ch->head = ch->tail = NULL; + ch->buffersize = 0; +} + +void bufchain_clear(bufchain *ch) +{ + struct bufchain_granule *b; + while (ch->head) { + b = ch->head; + ch->head = ch->head->next; + sfree(b); + } + ch->tail = NULL; + ch->buffersize = 0; +} + +int bufchain_size(bufchain *ch) +{ + return ch->buffersize; +} + +void bufchain_add(bufchain *ch, const void *data, int len) +{ + const char *buf = (const char *)data; + + if (len == 0) return; + + ch->buffersize += len; + + if (ch->tail && ch->tail->buflen < BUFFER_GRANULE) { + int copylen = min(len, BUFFER_GRANULE - ch->tail->buflen); + memcpy(ch->tail->buf + ch->tail->buflen, buf, copylen); + buf += copylen; + len -= copylen; + ch->tail->buflen += copylen; + } + while (len > 0) { + int grainlen = min(len, BUFFER_GRANULE); + struct bufchain_granule *newbuf; + newbuf = snew(struct bufchain_granule); + newbuf->bufpos = 0; + newbuf->buflen = grainlen; + memcpy(newbuf->buf, buf, grainlen); + buf += grainlen; + len -= grainlen; + if (ch->tail) + ch->tail->next = newbuf; + else + ch->head = ch->tail = newbuf; + newbuf->next = NULL; + ch->tail = newbuf; + } +} + +void bufchain_consume(bufchain *ch, int len) +{ + struct bufchain_granule *tmp; + + assert(ch->buffersize >= len); + while (len > 0) { + int remlen = len; + assert(ch->head != NULL); + if (remlen >= ch->head->buflen - ch->head->bufpos) { + remlen = ch->head->buflen - ch->head->bufpos; + tmp = ch->head; + ch->head = tmp->next; + sfree(tmp); + if (!ch->head) + ch->tail = NULL; + } else + ch->head->bufpos += remlen; + ch->buffersize -= remlen; + len -= remlen; + } +} + +void bufchain_prefix(bufchain *ch, void **data, int *len) +{ + *len = ch->head->buflen - ch->head->bufpos; + *data = ch->head->buf + ch->head->bufpos; +} + +void bufchain_fetch(bufchain *ch, void *data, int len) +{ + struct bufchain_granule *tmp; + char *data_c = (char *)data; + + tmp = ch->head; + + assert(ch->buffersize >= len); + while (len > 0) { + int remlen = len; + + assert(tmp != NULL); + if (remlen >= tmp->buflen - tmp->bufpos) + remlen = tmp->buflen - tmp->bufpos; + memcpy(data_c, tmp->buf + tmp->bufpos, remlen); + + tmp = tmp->next; + len -= remlen; + data_c += remlen; + } +} + +/* ---------------------------------------------------------------------- + * My own versions of malloc, realloc and free. Because I want + * malloc and realloc to bomb out and exit the program if they run + * out of memory, realloc to reliably call malloc if passed a NULL + * pointer, and free to reliably do nothing if passed a NULL + * pointer. We can also put trace printouts in, if we need to; and + * we can also replace the allocator with an ElectricFence-like + * one. + */ + +#ifdef MINEFIELD +void *minefield_c_malloc(size_t size); +void minefield_c_free(void *p); +void *minefield_c_realloc(void *p, size_t size); +#endif + +#ifdef MALLOC_LOG +static FILE *fp = NULL; + +static char *mlog_file = NULL; +static int mlog_line = 0; + +void mlog(char *file, int line) +{ + mlog_file = file; + mlog_line = line; + if (!fp) { + fp = fopen("putty_mem.log", "w"); + setvbuf(fp, NULL, _IONBF, BUFSIZ); + } + if (fp) + fprintf(fp, "%s:%d: ", file, line); +} +#endif + +void *safemalloc(size_t n, size_t size) +{ + void *p; + + if (n > INT_MAX / size) { + p = NULL; + } else { + size *= n; + if (size == 0) size = 1; +#ifdef MINEFIELD + p = minefield_c_malloc(size); +#else + p = malloc(size); +#endif + } + + if (!p) { + char str[200]; +#ifdef MALLOC_LOG + sprintf(str, "Out of memory! (%s:%d, size=%d)", + mlog_file, mlog_line, size); + fprintf(fp, "*** %s\n", str); + fclose(fp); +#else + strcpy(str, "Out of memory!"); +#endif + modalfatalbox(str); + } +#ifdef MALLOC_LOG + if (fp) + fprintf(fp, "malloc(%d) returns %p\n", size, p); +#endif + return p; +} + +void *saferealloc(void *ptr, size_t n, size_t size) +{ + void *p; + + if (n > INT_MAX / size) { + p = NULL; + } else { + size *= n; + if (!ptr) { +#ifdef MINEFIELD + p = minefield_c_malloc(size); +#else + p = malloc(size); +#endif + } else { +#ifdef MINEFIELD + p = minefield_c_realloc(ptr, size); +#else + p = realloc(ptr, size); +#endif + } + } + + if (!p) { + char str[200]; +#ifdef MALLOC_LOG + sprintf(str, "Out of memory! (%s:%d, size=%d)", + mlog_file, mlog_line, size); + fprintf(fp, "*** %s\n", str); + fclose(fp); +#else + strcpy(str, "Out of memory!"); +#endif + modalfatalbox(str); + } +#ifdef MALLOC_LOG + if (fp) + fprintf(fp, "realloc(%p,%d) returns %p\n", ptr, size, p); +#endif + return p; +} + +void safefree(void *ptr) +{ + if (ptr) { +#ifdef MALLOC_LOG + if (fp) + fprintf(fp, "free(%p)\n", ptr); +#endif +#ifdef MINEFIELD + minefield_c_free(ptr); +#else + free(ptr); +#endif + } +#ifdef MALLOC_LOG + else if (fp) + fprintf(fp, "freeing null pointer - no action taken\n"); +#endif +} + +/* ---------------------------------------------------------------------- + * Debugging routines. + */ + +#ifdef DEBUG +extern void dputs(char *); /* defined in per-platform *misc.c */ + +void debug_printf(char *fmt, ...) +{ + char *buf; + va_list ap; + + va_start(ap, fmt); + buf = dupvprintf(fmt, ap); + dputs(buf); + sfree(buf); + va_end(ap); +} + + +void debug_memdump(void *buf, int len, int L) +{ + int i; + unsigned char *p = buf; + char foo[17]; + if (L) { + int delta; + debug_printf("\t%d (0x%x) bytes:\n", len, len); + delta = 15 & (unsigned long int) p; + p -= delta; + len += delta; + } + for (; 0 < len; p += 16, len -= 16) { + dputs(" "); + if (L) + debug_printf("%p: ", p); + strcpy(foo, "................"); /* sixteen dots */ + for (i = 0; i < 16 && i < len; ++i) { + if (&p[i] < (unsigned char *) buf) { + dputs(" "); /* 3 spaces */ + foo[i] = ' '; + } else { + debug_printf("%c%02.2x", + &p[i] != (unsigned char *) buf + && i % 4 ? '.' : ' ', p[i] + ); + if (p[i] >= ' ' && p[i] <= '~') + foo[i] = (char) p[i]; + } + } + foo[i] = '\0'; + debug_printf("%*s%s\n", (16 - i) * 3 + 2, "", foo); + } +} + +#endif /* def DEBUG */ + +/* + * Determine whether or not a Config structure represents a session + * which can sensibly be launched right now. + */ +int cfg_launchable(const Config *cfg) +{ + if (cfg->protocol == PROT_SERIAL) + return cfg->serline[0] != 0; + else + return cfg->host[0] != 0; +} + +char const *cfg_dest(const Config *cfg) +{ + if (cfg->protocol == PROT_SERIAL) + return cfg->serline; + else + return cfg->host; +} diff --git a/putty/MISC.H b/putty/MISC.H new file mode 100644 index 0000000..1123314 --- /dev/null +++ b/putty/MISC.H @@ -0,0 +1,132 @@ +/* + * Header for misc.c. + */ + +#ifndef PUTTY_MISC_H +#define PUTTY_MISC_H + +#include "puttymem.h" + +#include /* for FILE * */ +#include /* for va_list */ +#include /* for struct tm */ + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +typedef struct Filename Filename; +typedef struct FontSpec FontSpec; + +unsigned long parse_blocksize(const char *bs); +char ctrlparse(char *s, char **next); + +char *dupstr(const char *s); +char *dupcat(const char *s1, ...); +char *dupprintf(const char *fmt, ...); +char *dupvprintf(const char *fmt, va_list ap); + +char *fgetline(FILE *fp); + +void base64_encode_atom(unsigned char *data, int n, char *out); + +struct bufchain_granule; +typedef struct bufchain_tag { + struct bufchain_granule *head, *tail; + int buffersize; /* current amount of buffered data */ +} bufchain; + +void bufchain_init(bufchain *ch); +void bufchain_clear(bufchain *ch); +int bufchain_size(bufchain *ch); +void bufchain_add(bufchain *ch, const void *data, int len); +void bufchain_prefix(bufchain *ch, void **data, int *len); +void bufchain_consume(bufchain *ch, int len); +void bufchain_fetch(bufchain *ch, void *data, int len); + +struct tm ltime(void); + +/* + * Debugging functions. + * + * Output goes to debug.log + * + * debug(()) (note the double brackets) is like printf(). + * + * dmemdump() and dmemdumpl() both do memory dumps. The difference + * is that dmemdumpl() is more suited for when the memory address is + * important (say because you'll be recording pointer values later + * on). dmemdump() is more concise. + */ + +#ifdef DEBUG +void debug_printf(char *fmt, ...); +void debug_memdump(void *buf, int len, int L); +#define debug(x) (debug_printf x) +#define dmemdump(buf,len) debug_memdump (buf, len, 0); +#define dmemdumpl(buf,len) debug_memdump (buf, len, 1); +#else +#define debug(x) +#define dmemdump(buf,len) +#define dmemdumpl(buf,len) +#endif + +#ifndef lenof +#define lenof(x) ( (sizeof((x))) / (sizeof(*(x)))) +#endif + +#ifndef min +#define min(x,y) ( (x) < (y) ? (x) : (y) ) +#endif +#ifndef max +#define max(x,y) ( (x) > (y) ? (x) : (y) ) +#endif + +#define GET_32BIT_LSB_FIRST(cp) \ + (((unsigned long)(unsigned char)(cp)[0]) | \ + ((unsigned long)(unsigned char)(cp)[1] << 8) | \ + ((unsigned long)(unsigned char)(cp)[2] << 16) | \ + ((unsigned long)(unsigned char)(cp)[3] << 24)) + +#define PUT_32BIT_LSB_FIRST(cp, value) ( \ + (cp)[0] = (unsigned char)(value), \ + (cp)[1] = (unsigned char)((value) >> 8), \ + (cp)[2] = (unsigned char)((value) >> 16), \ + (cp)[3] = (unsigned char)((value) >> 24) ) + +#define GET_16BIT_LSB_FIRST(cp) \ + (((unsigned long)(unsigned char)(cp)[0]) | \ + ((unsigned long)(unsigned char)(cp)[1] << 8)) + +#define PUT_16BIT_LSB_FIRST(cp, value) ( \ + (cp)[0] = (unsigned char)(value), \ + (cp)[1] = (unsigned char)((value) >> 8) ) + +#define GET_32BIT_MSB_FIRST(cp) \ + (((unsigned long)(unsigned char)(cp)[0] << 24) | \ + ((unsigned long)(unsigned char)(cp)[1] << 16) | \ + ((unsigned long)(unsigned char)(cp)[2] << 8) | \ + ((unsigned long)(unsigned char)(cp)[3])) + +#define GET_32BIT(cp) GET_32BIT_MSB_FIRST(cp) + +#define PUT_32BIT_MSB_FIRST(cp, value) ( \ + (cp)[0] = (unsigned char)((value) >> 24), \ + (cp)[1] = (unsigned char)((value) >> 16), \ + (cp)[2] = (unsigned char)((value) >> 8), \ + (cp)[3] = (unsigned char)(value) ) + +#define PUT_32BIT(cp, value) PUT_32BIT_MSB_FIRST(cp, value) + +#define GET_16BIT_MSB_FIRST(cp) \ + (((unsigned long)(unsigned char)(cp)[0] << 8) | \ + ((unsigned long)(unsigned char)(cp)[1])) + +#define PUT_16BIT_MSB_FIRST(cp, value) ( \ + (cp)[0] = (unsigned char)((value) >> 8), \ + (cp)[1] = (unsigned char)(value) ) + +#endif diff --git a/putty/MKAUTO.SH b/putty/MKAUTO.SH new file mode 100644 index 0000000..6548951 --- /dev/null +++ b/putty/MKAUTO.SH @@ -0,0 +1,47 @@ +#! /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 .. + +# Persuade automake to give us a copy of its install-sh. This is a +# pain because I don't actually want to have to _use_ automake. +# Instead, I construct a trivial unrelated automake project in a +# temporary subdirectory, run automake so that it'll copy +# install-sh into that directory, then copy it back out again. +# Hideous, but it should work. + +mkdir automake-grievous-hack +cat > automake-grievous-hack/hello.c << EOF +#include +int main(int argc, char **argv) +{ + printf("hello, world\n"); + return 0; +} +EOF +cat > automake-grievous-hack/Makefile.am << EOF +bin_PROGRAMS = hello +hello_SOURCES = hello.c +EOF +cat > automake-grievous-hack/configure.ac << EOF +AC_INIT +AM_INIT_AUTOMAKE(hello, 1.0) +AC_CONFIG_FILES([Makefile]) +AC_PROG_CC +AC_OUTPUT +EOF +echo Some news > automake-grievous-hack/NEWS +echo Some text > automake-grievous-hack/README +echo Some people > automake-grievous-hack/AUTHORS +echo Some changes > automake-grievous-hack/ChangeLog +rm -f install-sh # this won't work if we accidentally have one _here_ +(cd automake-grievous-hack && autoreconf -i && \ + cp install-sh ../unix/install-sh) +rm -rf automake-grievous-hack + +# That was the hard bit. Now run autoconf on our real configure.in. +(cd unix && autoreconf && rm -rf aclocal.m4 autom4te.cache) diff --git a/putty/MKFILES.PL b/putty/MKFILES.PL new file mode 100644 index 0000000..8075ddb --- /dev/null +++ b/putty/MKFILES.PL @@ -0,0 +1,1493 @@ +#!/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 Cwd; + +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. We need to generate that _now_, before attempting +# dependency analysis. +eval 'chdir "charset"; require "sbcsgen.pl"; chdir ".."'; + +@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 () { + 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 "!forceobj") { $forceobj{$_[1]} = 1; next; } + if ($_[0] eq "!begin") { + if (&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] eq "=") { + $groups{$_[0]} = [] if !defined $groups{$_[0]}; + $listref = $groups{$_[0]}; + $prog = undef; + shift @objs; # eat the group name + } elsif ($_[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 eq "[G]" or $i eq "[C]" or $i eq "[M]" or + $i eq "[X]" or $i eq "[U]" or $i eq "[MX]") and defined $prog) { + $type = substr($i,1,(length $i)-2); + } 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; + +# 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 = (); +while (scalar @scanlist > 0) { + $file = shift @scanlist; + next if defined $further{$file}; # skip if we've already done it + $further{$file} = []; + $dirfile = &findfile($file); + open IN, "$dirfile" or die "unable to open source file $file\n"; + while () { + 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","borland","lcc","devcppproj","gtk","unix", + "ac","osx",)) { + 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 (); +} + +# Now we're ready to output the actual Makefiles. + +if (defined $makefiles{'cygwin'}) { + $dirpfx = &dirpfx($makefiles{'cygwin'}, "/"); + + ##-- CygWin makefile + open OUT, ">$makefiles{'cygwin'}"; select OUT; + print + "# Makefile for $project_name under cygwin.\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". + "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 = -mno-cygwin -Wall -O2 -D_WINDOWS -DDEBUG -DWIN32S_COMPAT". + " -D_NO_OLDNAMES -DNO_MULTIMON -DNO_HTMLHELP " . + (join " ", map {"-I$dirpfx$_"} @srcdirs)) . + "\n". + "LDFLAGS = -mno-cygwin -s\n". + &splitline("RCFLAGS = \$(RCINC) --define WIN32=1 --define _WIN32=1". + " --define WINVER=0x0400")."\n". + "\n". + $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]." ".$d->{obj}."\n\n"; + } else { + print "\t\$(CC) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) -c ".$d->{deps}->[0]."\n\n"; + } + } + print "\n"; + print $makefile_extra{'cygwin'}->{'end'}; + print "\nclean:\n". + "\trm -f *.o *.exe *.res.o *.map\n". + "\n". + "FORCE:\n"; + select STDOUT; close OUT; + +} + +##-- Borland makefile +if (defined $makefiles{'borland'}) { + $dirpfx = &dirpfx($makefiles{'borland'}, "\\"); + + %stdlibs = ( # Borland provides many Win32 API libraries intrinsically + "advapi32" => 1, + "comctl32" => 1, + "comdlg32" => 1, + "gdi32" => 1, + "imm32" => 1, + "shell32" => 1, + "user32" => 1, + "winmm" => 1, + "winspool" => 1, + "wsock32" => 1, + ); + open OUT, ">$makefiles{'borland'}"; select OUT; + print + "# Makefile for $project_name under Borland 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"; + # bcc32 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.bor\n". + "\n". + "# C compilation flags\n". + "CFLAGS = -D_WINDOWS -DWINVER=0x0500\n". + "# Resource compilation flags\n". + "RCFLAGS = -DNO_WINRESRC_H -DWIN32 -D_WIN32 -DWINVER=0x0401\n". + "\n". + "# Get include directory for resource compiler\n". + "!if !\$d(BCB)\n". + "BCB = \$(MAKEDIR)\\..\n". + "!endif\n". + "\n". + $makefile_extra{'borland'}->{'vars'} . + "\n". + ".c.obj:\n". + &splitline("\tbcc32 -w-aus -w-ccc -w-par -w-pia \$(COMPAT)". + " \$(CFLAGS) \$(XFLAGS) ". + (join " ", map {"-I$dirpfx$_"} @srcdirs) . + " /c \$*.c",69)."\n". + ".rc.res:\n". + &splitline("\tbrcc32 \$(RCFL) -i \$(BCB)\\include -r". + " \$(RCFLAGS) \$*.rc",69)."\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.obj", "X.res", undef); + print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n"; + my $ap = ($type eq "G") ? "-aa" : "-ap"; + print "\tilink32 $ap -Gn -L\$(BCB)\\lib \@$prog.rsp\n\n"; + } + foreach $p (&prognames("G:C")) { + ($prog, $type) = split ",", $p; + print $prog, ".rsp: \$(MAKEFILE)\n"; + $objstr = &objects($p, "X.obj", undef, undef); + @objlist = split " ", $objstr; + @objlines = (""); + foreach $i (@objlist) { + if (length($objlines[$#objlines] . " $i") > 50) { + push @objlines, ""; + } + $objlines[$#objlines] .= " $i"; + } + $c0w = ($type eq "G") ? "c0w32" : "c0x32"; + print "\techo $c0w + > $prog.rsp\n"; + for ($i=0; $i<=$#objlines; $i++) { + $plus = ($i < $#objlines ? " +" : ""); + print "\techo$objlines[$i]$plus >> $prog.rsp\n"; + } + print "\techo $prog.exe >> $prog.rsp\n"; + $objstr = &objects($p, "X.obj", "X.res", undef); + @libs = split " ", &objects($p, undef, undef, "X"); + @libs = grep { !$stdlibs{$_} } @libs; + unshift @libs, "cw32", "import32"; + $libstr = join ' ', @libs; + print "\techo nul,$libstr, >> $prog.rsp\n"; + print "\techo " . &objects($p, undef, "X.res", undef) . " >> $prog.rsp\n"; + print "\n"; + } + foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\", "borland")) { + if ($forceobj{$d->{obj_orig}}) { + printf("%s: FORCE\n", $d->{obj}); + } else { + print &splitline(sprintf("%s: %s", $d->{obj}, + join " ", @{$d->{deps}})), "\n"; + } + } + print "\n"; + print $makefile_extra{'borland'}->{'end'}; + print "\nclean:\n". + "\t-del *.obj\n". + "\t-del *.exe\n". + "\t-del *.res\n". + "\t-del *.pch\n". + "\t-del *.aps\n". + "\t-del *.il*\n". + "\t-del *.pdb\n". + "\t-del *.rsp\n". + "\t-del *.tds\n". + "\t-del *.\$\$\$\$\$\$\n". + "\n". + "FORCE:\n". + "\t-rem dummy command\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\n". + "LFLAGS = /incremental:no /fixed\n". + "RCFLAGS = -DWIN32 -D_WIN32 -DWINVER=0x0400\n". + "\n". + $makefile_extra{'vc'}->{'vars'} . + "\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.obj", "X.res", undef); + print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n"; + print "\tlink \$(LFLAGS) \$(XLFLAGS) -out:$prog.exe -map:$prog.map \@$prog.rsp\n\n"; + } + foreach $p (&prognames("G:C")) { + ($prog, $type) = split ",", $p; + print $prog, ".rsp: \$(MAKEFILE)\n"; + $objstr = &objects($p, "X.obj", "X.res", "X.lib"); + @objlist = split " ", $objstr; + @objlines = (""); + foreach $i (@objlist) { + if (length($objlines[$#objlines] . " $i") > 50) { + push @objlines, ""; + } + $objlines[$#objlines] .= " $i"; + } + $subsys = ($type eq "G") ? "windows" : "console"; + print "\techo /nologo /subsystem:$subsys > $prog.rsp\n"; + for ($i=0; $i<=$#objlines; $i++) { + print "\techo$objlines[$i] >> $prog.rsp\n"; + } + print "\n"; + } + foreach $d (&deps("X.obj", "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} =~ /.obj$/) { + print "\tcl \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c ".$d->{deps}->[0],"\n\n"; + } else { + print "\trc \$(RCFL) -r \$(RCFLAGS) ".$d->{deps}->[0],"\n\n"; + } + } + print "\n"; + print $makefile_extra{'vc'}->{'end'}; + print "\nclean: tidy\n". + "\t-del *.exe\n\n". + "tidy:\n". + "\t-del *.obj\n". + "\t-del *.res\n". + "\t-del *.pch\n". + "\t-del *.aps\n". + "\t-del *.ilk\n". + "\t-del *.pdb\n". + "\t-del *.rsp\n". + "\t-del *.dsp\n". + "\t-del *.dsw\n". + "\t-del *.ncb\n". + "\t-del *.opt\n". + "\t-del *.plg\n". + "\t-del *.map\n". + "\t-del *.idb\n". + "\t-del debug.log\n"; + select STDOUT; close OUT; +} + +if (defined $makefiles{'vcproj'}) { + $dirpfx = &dirpfx($makefiles{'vcproj'}, "\\"); + + $orig_dir = cwd; + + ##-- 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{'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+-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 -Werror -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:U")); + print "\n\n"; + foreach $p (&prognames("X:U")) { + ($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) -o \$@ " . + $objstr . " \$(${type}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 $makefile_extra{'gtk'}->{'end'}; + print "\nclean:\n". + "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:U")) . "\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 -Werror -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")); + print "\n\n"; + foreach $p (&prognames("U")) { + ($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) -o \$@ " . + $objstr . " \$(${type}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")) . "\n"; + print "\nFORCE:\n"; + select STDOUT; close OUT; +} + +if (defined $makefiles{'ac'}) { + $dirpfx = &dirpfx($makefiles{'ac'}, "/"); + + ##-- Unix/autoconf makefile + open OUT, ">$makefiles{'ac'}"; select OUT; + print + "# Makefile.in for $project_name under Unix with Autoconf.\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". + "CC = \@CC\@\n". + "\n". + &splitline("CFLAGS = \@CFLAGS\@ \@PUTTYCFLAGS\@ \@CPPFLAGS\@ " . + "\@DEFS\@ \@GTK_CFLAGS\@ " . + (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n". + "XLDFLAGS = \@LDFLAGS\@ \@LIBS\@ \@GTK_LIBS\@\n". + "ULDFLAGS = \@LDFLAGS\@ \@LIBS\@\n". + "INSTALL=\@INSTALL\@\n". + "INSTALL_PROGRAM=\$(INSTALL)\n". + "INSTALL_DATA=\$(INSTALL)\n". + "prefix=\@prefix\@\n". + "exec_prefix=\@exec_prefix\@\n". + "bindir=\@bindir\@\n". + "datarootdir=\@datarootdir\@\n". + "mandir=\@mandir\@\n". + "man1dir=\$(mandir)/man1\n". + "\n". + &def($makefile_extra{'gtk'}->{'vars'}) . + "\n". + ".SUFFIXES:\n". + "\n". + "\n". + "all: \@all_targets\@\n". + &splitline("all-cli:" . join "", map { " $_" } &progrealnames("U"))."\n". + &splitline("all-gtk:" . join "", map { " $_" } &progrealnames("X"))."\n"; + print "\n"; + foreach $p (&prognames("X:U")) { + ($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) -o \$@ " . + $objstr . " \$(${type}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 $makefile_extra{'gtk'}->{'end'}; + print "\nclean:\n". + "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:U")) . "\n"; + print "\ndistclean: clean\n". + "\t". &splitline("rm -f config.status config.cache config.log ". + "configure.lineno config.status.lineno Makefile") . "\n"; + print "\nFORCE:\n"; + 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 = \n". + "\n". + "# Get include directory for resource compiler\n". + "\n". + $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 $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 -Werror -g " . + (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n". + "MLDFLAGS = -framework Cocoa\n". + "ULDFLAGS =\n". + "\n" . + $makefile_extra{'osx'}->{'vars'} . + "\n" . + &splitline("all:" . join "", map { " $_" } &progrealnames("MX:U")) . + "\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")) { + ($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")) . "\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); + } + + 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 mutiple 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 ".."; + } +} diff --git a/putty/MKUNXARC.SH b/putty/MKUNXARC.SH new file mode 100644 index 0000000..8047850 --- /dev/null +++ b/putty/MKUNXARC.SH @@ -0,0 +1,61 @@ +#!/bin/sh + +# Build a Unix source distribution from the PuTTY CVS area. +# +# Pass an argument of the form `2004-02-08' to have the archive +# tagged as a development snapshot; of the form `0.54' to have it +# tagged as a release; of the form `r1234' to have it tagged as a +# custom build. Otherwise it'll be tagged as unidentified. + +case "$1" in + ????-??-??) + case "$1" in *[!-0-9]*) echo "Malformed snapshot ID '$1'" >&2;exit 1;;esac + arcsuffix="-`cat LATEST.VER`-$1" + ver="-DSNAPSHOT=$1" + docver= + ;; + r*) + arcsuffix="-$1" + ver="-DSVN_REV=$1" + docver= + ;; + '') + arcsuffix= + ver= + docver= + ;; + *) + case "$1" in *[!.0-9a-z]*) echo "Malformed release ID '$1'">&2;exit 1;;esac + arcsuffix="-$1" + ver="-DRELEASE=$1" + docver="VERSION=\"PuTTY release $1\"" + ;; +esac + +perl mkfiles.pl +(cd doc && make -s ${docver:+"$docver"}) +sh mkauto.sh 2>/dev/null + +relver=`cat LATEST.VER` +arcname="putty$arcsuffix" +mkdir uxarc +mkdir uxarc/$arcname +find . -name uxarc -prune -o \ + -name CVS -prune -o \ + -name .svn -prune -o \ + -name . -o \ + -type d -exec mkdir uxarc/$arcname/{} \; +find . -name uxarc -prune -o \ + -name CVS -prune -o \ + -name .cvsignore -prune -o \ + -name .svn -prune -o \ + -name '*.zip' -prune -o \ + -name '*.tar.gz' -prune -o \ + -type f -exec ln -s $PWD/{} uxarc/$arcname/{} \; +if test "x$ver" != "x"; then + (cd uxarc/$arcname; + md5sum `find . -name '*.[ch]' -print` > manifest; + echo "$ver" > version.def) +fi +tar -C uxarc -chzof $arcname.tar.gz $arcname +rm -rf uxarc diff --git a/putty/NETWORK.H b/putty/NETWORK.H new file mode 100644 index 0000000..b1b5590 --- /dev/null +++ b/putty/NETWORK.H @@ -0,0 +1,247 @@ +/* + * Networking abstraction in PuTTY. + * + * The way this works is: a back end can choose to open any number + * of sockets - including zero, which might be necessary in some. + * It can register a bunch of callbacks (most notably for when + * data is received) for each socket, and it can call the networking + * abstraction to send data without having to worry about blocking. + * The stuff behind the abstraction takes care of selects and + * nonblocking writes and all that sort of painful gubbins. + */ + +#ifndef PUTTY_NETWORK_H +#define PUTTY_NETWORK_H + +#ifndef DONE_TYPEDEFS +#define DONE_TYPEDEFS +typedef struct config_tag Config; +typedef struct backend_tag Backend; +typedef struct terminal_tag Terminal; +#endif + +typedef struct SockAddr_tag *SockAddr; +/* pay attention to levels of indirection */ +typedef struct socket_function_table **Socket; +typedef struct plug_function_table **Plug; + +#ifndef OSSOCKET_DEFINED +typedef void *OSSocket; +#endif + +struct socket_function_table { + Plug(*plug) (Socket s, Plug p); + /* use a different plug (return the old one) */ + /* if p is NULL, it doesn't change the plug */ + /* but it does return the one it's using */ + void (*close) (Socket s); + int (*write) (Socket s, const char *data, int len); + int (*write_oob) (Socket s, const char *data, int len); + void (*flush) (Socket s); + void (*set_private_ptr) (Socket s, void *ptr); + void *(*get_private_ptr) (Socket s); + void (*set_frozen) (Socket s, int is_frozen); + /* ignored by tcp, but vital for ssl */ + const char *(*socket_error) (Socket s); +}; + +struct plug_function_table { + void (*log)(Plug p, int type, SockAddr addr, int port, + const char *error_msg, int error_code); + /* + * Passes the client progress reports on the process of setting + * up the connection. + * + * - type==0 means we are about to try to connect to address + * `addr' (error_msg and error_code are ignored) + * - type==1 means we have failed to connect to address `addr' + * (error_msg and error_code are supplied). This is not a + * fatal error - we may well have other candidate addresses + * to fall back to. When it _is_ fatal, the closing() + * function will be called. + */ + int (*closing) + (Plug p, const char *error_msg, int error_code, int calling_back); + /* error_msg is NULL iff it is not an error (ie it closed normally) */ + /* calling_back != 0 iff there is a Plug function */ + /* currently running (would cure the fixme in try_send()) */ + int (*receive) (Plug p, int urgent, char *data, int len); + /* + * - urgent==0. `data' points to `len' bytes of perfectly + * ordinary data. + * + * - urgent==1. `data' points to `len' bytes of data, + * which were read from before an Urgent pointer. + * + * - urgent==2. `data' points to `len' bytes of data, + * the first of which was the one at the Urgent mark. + */ + void (*sent) (Plug p, int bufsize); + /* + * The `sent' function is called when the pending send backlog + * on a socket is cleared or partially cleared. The new backlog + * size is passed in the `bufsize' parameter. + */ + int (*accepting)(Plug p, OSSocket sock); + /* + * returns 0 if the host at address addr is a valid host for connecting or error + */ +}; + +/* proxy indirection layer */ +/* NB, control of 'addr' is passed via new_connection, which takes + * responsibility for freeing it */ +Socket new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, const Config *cfg); +Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only, + const Config *cfg, int addressfamily); +SockAddr name_lookup(char *host, int port, char **canonicalname, + const Config *cfg, int addressfamily); + +/* platform-dependent callback from new_connection() */ +/* (same caveat about addr as new_connection()) */ +Socket platform_new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, const Config *cfg); + +/* socket functions */ + +void sk_init(void); /* called once at program startup */ +void sk_cleanup(void); /* called just before program exit */ + +SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family); +SockAddr sk_nonamelookup(const char *host); +void sk_getaddr(SockAddr addr, char *buf, int buflen); +int sk_hostname_is_local(char *name); +int sk_address_is_local(SockAddr addr); +int sk_addrtype(SockAddr addr); +void sk_addrcopy(SockAddr addr, char *buf); +void sk_addr_free(SockAddr addr); +/* sk_addr_dup generates another SockAddr which contains the same data + * as the original one and can be freed independently. May not actually + * physically _duplicate_ it: incrementing a reference count so that + * one more free is required before it disappears is an acceptable + * implementation. */ +SockAddr sk_addr_dup(SockAddr addr); + +/* NB, control of 'addr' is passed via sk_new, which takes responsibility + * for freeing it, as for new_connection() */ +Socket sk_new(SockAddr addr, int port, int privport, int oobinline, + int nodelay, int keepalive, Plug p); + +Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int address_family); + +Socket sk_register(OSSocket sock, Plug plug); + +#define sk_plug(s,p) (((*s)->plug) (s, p)) +#define sk_close(s) (((*s)->close) (s)) +#define sk_write(s,buf,len) (((*s)->write) (s, buf, len)) +#define sk_write_oob(s,buf,len) (((*s)->write_oob) (s, buf, len)) +#define sk_flush(s) (((*s)->flush) (s)) + +#ifdef DEFINE_PLUG_METHOD_MACROS +#define plug_log(p,type,addr,port,msg,code) (((*p)->log) (p, type, addr, port, msg, code)) +#define plug_closing(p,msg,code,callback) (((*p)->closing) (p, msg, code, callback)) +#define plug_receive(p,urgent,buf,len) (((*p)->receive) (p, urgent, buf, len)) +#define plug_sent(p,bufsize) (((*p)->sent) (p, bufsize)) +#define plug_accepting(p, sock) (((*p)->accepting)(p, sock)) +#endif + +/* + * Each socket abstraction contains a `void *' private field in + * which the client can keep state. + * + * This is perhaps unnecessary now that we have the notion of a plug, + * but there is some existing code that uses it, so it stays. + */ +#define sk_set_private_ptr(s, ptr) (((*s)->set_private_ptr) (s, ptr)) +#define sk_get_private_ptr(s) (((*s)->get_private_ptr) (s)) + +/* + * Special error values are returned from sk_namelookup and sk_new + * if there's a problem. These functions extract an error message, + * or return NULL if there's no problem. + */ +const char *sk_addr_error(SockAddr addr); +#define sk_socket_error(s) (((*s)->socket_error) (s)) + +/* + * Set the `frozen' flag on a socket. A frozen socket is one in + * which all READABLE notifications are ignored, so that data is + * not accepted from the peer until the socket is unfrozen. This + * exists for two purposes: + * + * - Port forwarding: when a local listening port receives a + * connection, we do not want to receive data from the new + * socket until we have somewhere to send it. Hence, we freeze + * the socket until its associated SSH channel is ready; then we + * unfreeze it and pending data is delivered. + * + * - Socket buffering: if an SSH channel (or the whole connection) + * backs up or presents a zero window, we must freeze the + * associated local socket in order to avoid unbounded buffer + * growth. + */ +#define sk_set_frozen(s, is_frozen) (((*s)->set_frozen) (s, is_frozen)) + +/* + * Call this after an operation that might have tried to send on a + * socket, to clean up any pending network errors. + */ +void net_pending_errors(void); + +/* + * Simple wrapper on getservbyname(), needed by ssh.c. Returns the + * port number, in host byte order (suitable for printf and so on). + * Returns 0 on failure. Any platform not supporting getservbyname + * can just return 0 - this function is not required to handle + * numeric port specifications. + */ +int net_service_lookup(char *service); + +/* + * Look up the local hostname; return value needs freeing. + * May return NULL. + */ +char *get_hostname(void); + +/********** SSL stuff **********/ + +/* + * This section is subject to change, but you get the general idea + * of what it will eventually look like. + */ + +typedef struct certificate *Certificate; +typedef struct our_certificate *Our_Certificate; + /* to be defined somewhere else, somehow */ + +typedef struct ssl_client_socket_function_table **SSL_Client_Socket; +typedef struct ssl_client_plug_function_table **SSL_Client_Plug; + +struct ssl_client_socket_function_table { + struct socket_function_table base; + void (*renegotiate) (SSL_Client_Socket s); + /* renegotiate the cipher spec */ +}; + +struct ssl_client_plug_function_table { + struct plug_function_table base; + int (*refuse_cert) (SSL_Client_Plug p, Certificate cert[]); + /* do we accept this certificate chain? If not, why not? */ + /* cert[0] is the server's certificate, cert[] is NULL-terminated */ + /* the last certificate may or may not be the root certificate */ + Our_Certificate(*client_cert) (SSL_Client_Plug p); + /* the server wants us to identify ourselves */ + /* may return NULL if we want anonymity */ +}; + +SSL_Client_Socket sk_ssl_client_over(Socket s, /* pre-existing (tcp) connection */ + SSL_Client_Plug p); + +#define sk_renegotiate(s) (((*s)->renegotiate) (s)) + +#endif diff --git a/putty/NOCPROXY.C b/putty/NOCPROXY.C new file mode 100644 index 0000000..ed75def --- /dev/null +++ b/putty/NOCPROXY.C @@ -0,0 +1,36 @@ +/* + * Routines to refuse to do cryptographic interaction with proxies + * in PuTTY. This is a stub implementation of the same interfaces + * provided by cproxy.c, for use in PuTTYtel. + */ + +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "network.h" +#include "proxy.h" + +void proxy_socks5_offerencryptedauth(char * command, int * len) +{ + /* For telnet, don't add any new encrypted authentication routines */ +} + +int proxy_socks5_handlechap (Proxy_Socket p) +{ + + plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" + " in telnet-only build", + PROXY_ERROR_GENERAL, 0); + return 1; +} + +int proxy_socks5_selectchap(Proxy_Socket p) +{ + plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" + " in telnet-only build", + PROXY_ERROR_GENERAL, 0); + return 1; +} diff --git a/putty/NOGSS.C b/putty/NOGSS.C new file mode 100644 index 0000000..0f1e254 --- /dev/null +++ b/putty/NOGSS.C @@ -0,0 +1,11 @@ +/* + * Stub definitions of the GSSAPI library list, for Unix pterm and + * any other application that needs the symbols defined but has no + * use for them. + */ + +#include "putty.h" + +const int ngsslibs = 0; +const char *const gsslibnames[1] = { "dummy" }; +const struct keyvalwhere gsslibkeywords[1] = { { "dummy", 0, -1, -1 } }; diff --git a/putty/NOPRINT.C b/putty/NOPRINT.C new file mode 100644 index 0000000..627d798 --- /dev/null +++ b/putty/NOPRINT.C @@ -0,0 +1,38 @@ +/* + * Stub implementation of the printing interface for PuTTY, for the + * benefit of non-printing terminal applications. + */ + +#include +#include +#include "putty.h" + +struct printer_job_tag { + int dummy; +}; + +printer_job *printer_start_job(char *printer) +{ + return NULL; +} + +void printer_job_data(printer_job *pj, void *data, int len) +{ +} + +void printer_finish_job(printer_job *pj) +{ +} + +printer_enum *printer_start_enum(int *nprinters_ptr) +{ + *nprinters_ptr = 0; + return NULL; +} +char *printer_get_name(printer_enum *pe, int i) +{ + return NULL; +} +void printer_finish_enum(printer_enum *pe) +{ +} diff --git a/putty/NOTIMING.C b/putty/NOTIMING.C new file mode 100644 index 0000000..aed3e45 --- /dev/null +++ b/putty/NOTIMING.C @@ -0,0 +1,21 @@ +/* + * notiming.c: stub version of timing API. + * + * Used in any tool which needs a subsystem linked against the + * timing API but doesn't want to actually provide timing. For + * example, key generation tools need the random number generator, + * but they don't want the hassle of calling noise_regular() at + * regular intervals - and they don't _need_ it either, since they + * have their own rigorous and different means of noise collection. + */ + +#include "putty.h" + +long schedule_timer(int ticks, timer_fn_t fn, void *ctx) +{ + return 0; +} + +void expire_timer_context(void *ctx) +{ +} diff --git a/putty/PGSSAPI.C b/putty/PGSSAPI.C new file mode 100644 index 0000000..d3c768c --- /dev/null +++ b/putty/PGSSAPI.C @@ -0,0 +1,105 @@ +/* This file actually defines the GSSAPI function pointers for + * functions we plan to import from a GSSAPI library. + */ +#include "putty.h" + +#ifndef NO_GSSAPI + +#include "pgssapi.h" + +#ifndef NO_LIBDL + +/* Reserved static storage for GSS_oids. Comments are quotes from RFC 2744. */ +static const gss_OID_desc oids[] = { + /* The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01"}, + /* corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) user_name(1)}. The constant + * GSS_C_NT_USER_NAME should be initialized to point + * to that gss_OID_desc. + + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"}, + /* corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}. + * The constant GSS_C_NT_MACHINE_UID_NAME should be + * initialized to point to that gss_OID_desc. + + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03"}, + /* corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) string_uid_name(3)}. + * The constant GSS_C_NT_STRING_UID_NAME should be + * initialized to point to that gss_OID_desc. + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {6, (void *)"\x2b\x06\x01\x05\x06\x02"}, + /* corresponding to an object-identifier value of + * {iso(1) org(3) dod(6) internet(1) security(5) + * nametypes(6) gss-host-based-services(2)). The constant + * GSS_C_NT_HOSTBASED_SERVICE_X should be initialized to point + * to that gss_OID_desc. This is a deprecated OID value, and + * implementations wishing to support hostbased-service names + * should instead use the GSS_C_NT_HOSTBASED_SERVICE OID, + * defined below, to identify such names; + * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym + * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input + * parameter, but should not be emitted by GSS-API + * implementations + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"}, + /* corresponding to an object-identifier value of {iso(1) + * member-body(2) Unites States(840) mit(113554) infosys(1) + * gssapi(2) generic(1) service_name(4)}. The constant + * GSS_C_NT_HOSTBASED_SERVICE should be initialized + * to point to that gss_OID_desc. + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {6, (void *)"\x2b\x06\01\x05\x06\x03"}, + /* corresponding to an object identifier value of + * {1(iso), 3(org), 6(dod), 1(internet), 5(security), + * 6(nametypes), 3(gss-anonymous-name)}. The constant + * and GSS_C_NT_ANONYMOUS should be initialized to point + * to that gss_OID_desc. + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {6, (void *)"\x2b\x06\x01\x05\x06\x04"}, + /* corresponding to an object-identifier value of + * {1(iso), 3(org), 6(dod), 1(internet), 5(security), + * 6(nametypes), 4(gss-api-exported-name)}. The constant + * GSS_C_NT_EXPORT_NAME should be initialized to point + * to that gss_OID_desc. + */ +}; + +/* Here are the constants which point to the static structure above. + * + * Constants of the form GSS_C_NT_* are specified by rfc 2744. + */ +const_gss_OID GSS_C_NT_USER_NAME = oids+0; +const_gss_OID GSS_C_NT_MACHINE_UID_NAME = oids+1; +const_gss_OID GSS_C_NT_STRING_UID_NAME = oids+2; +const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = oids+3; +const_gss_OID GSS_C_NT_HOSTBASED_SERVICE = oids+4; +const_gss_OID GSS_C_NT_ANONYMOUS = oids+5; +const_gss_OID GSS_C_NT_EXPORT_NAME = oids+6; + +#endif /* NO_LIBDL */ + +static gss_OID_desc gss_mech_krb5_desc = +{ 9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; +/* iso(1) member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) krb5(2)*/ +const gss_OID GSS_MECH_KRB5 = &gss_mech_krb5_desc; + +#endif /* NO_GSSAPI */ diff --git a/putty/PGSSAPI.H b/putty/PGSSAPI.H new file mode 100644 index 0000000..4cf9fc4 --- /dev/null +++ b/putty/PGSSAPI.H @@ -0,0 +1,296 @@ +#ifndef PUTTY_PGSSAPI_H +#define PUTTY_PGSSAPI_H + +#include "putty.h" + +#ifndef NO_GSSAPI + +/* + * On Unix, if we're statically linking against GSSAPI, we leave the + * declaration of all this lot to the official header. If we're + * dynamically linking, we declare it ourselves, because that avoids + * us needing the official header at compile time. + * + * However, we still need the function pointer types, because even + * with statically linked GSSAPI we use the ssh_gss_library wrapper. + */ +#ifdef STATIC_GSSAPI +#include +typedef gss_OID const_gss_OID; /* for our prototypes below */ +#else /* STATIC_GSSAPI */ + +/******************************************************************************* + * GSSAPI Definitions, taken from RFC 2744 + ******************************************************************************/ + +/* GSSAPI Type Definitions */ +typedef uint32 OM_uint32; + +typedef struct gss_OID_desc_struct { + OM_uint32 length; + void *elements; +} gss_OID_desc; +typedef const gss_OID_desc *const_gss_OID; +typedef gss_OID_desc *gss_OID; + +typedef struct gss_OID_set_desc_struct { + size_t count; + gss_OID elements; +} gss_OID_set_desc; +typedef const gss_OID_set_desc *const_gss_OID_set; +typedef gss_OID_set_desc *gss_OID_set; + +typedef struct gss_buffer_desc_struct { + size_t length; + void *value; +} gss_buffer_desc, *gss_buffer_t; + +typedef struct gss_channel_bindings_struct { + OM_uint32 initiator_addrtype; + gss_buffer_desc initiator_address; + OM_uint32 acceptor_addrtype; + gss_buffer_desc acceptor_address; + gss_buffer_desc application_data; +} *gss_channel_bindings_t; + +typedef void * gss_ctx_id_t; +typedef void * gss_name_t; +typedef void * gss_cred_id_t; + +typedef OM_uint32 gss_qop_t; + +/* Flag bits for context-level services. */ + +#define GSS_C_DELEG_FLAG 1 +#define GSS_C_MUTUAL_FLAG 2 +#define GSS_C_REPLAY_FLAG 4 +#define GSS_C_SEQUENCE_FLAG 8 +#define GSS_C_CONF_FLAG 16 +#define GSS_C_INTEG_FLAG 32 +#define GSS_C_ANON_FLAG 64 +#define GSS_C_PROT_READY_FLAG 128 +#define GSS_C_TRANS_FLAG 256 + +/* Credential usage options */ +#define GSS_C_BOTH 0 +#define GSS_C_INITIATE 1 +#define GSS_C_ACCEPT 2 + +/* Status code types for gss_display_status */ +#define GSS_C_GSS_CODE 1 +#define GSS_C_MECH_CODE 2 + +/* The constant definitions for channel-bindings address families */ +#define GSS_C_AF_UNSPEC 0 +#define GSS_C_AF_LOCAL 1 +#define GSS_C_AF_INET 2 +#define GSS_C_AF_IMPLINK 3 +#define GSS_C_AF_PUP 4 +#define GSS_C_AF_CHAOS 5 +#define GSS_C_AF_NS 6 +#define GSS_C_AF_NBS 7 +#define GSS_C_AF_ECMA 8 +#define GSS_C_AF_DATAKIT 9 +#define GSS_C_AF_CCITT 10 +#define GSS_C_AF_SNA 11 +#define GSS_C_AF_DECnet 12 +#define GSS_C_AF_DLI 13 +#define GSS_C_AF_LAT 14 +#define GSS_C_AF_HYLINK 15 +#define GSS_C_AF_APPLETALK 16 +#define GSS_C_AF_BSC 17 +#define GSS_C_AF_DSS 18 +#define GSS_C_AF_OSI 19 +#define GSS_C_AF_X25 21 + +#define GSS_C_AF_NULLADDR 255 + +/* Various Null values */ +#define GSS_C_NO_NAME ((gss_name_t) 0) +#define GSS_C_NO_BUFFER ((gss_buffer_t) 0) +#define GSS_C_NO_OID ((gss_OID) 0) +#define GSS_C_NO_OID_SET ((gss_OID_set) 0) +#define GSS_C_NO_CONTEXT ((gss_ctx_id_t) 0) +#define GSS_C_NO_CREDENTIAL ((gss_cred_id_t) 0) +#define GSS_C_NO_CHANNEL_BINDINGS ((gss_channel_bindings_t) 0) +#define GSS_C_EMPTY_BUFFER {0, NULL} + +/* Major status codes */ +#define GSS_S_COMPLETE 0 + +/* Some "helper" definitions to make the status code macros obvious. */ +#define GSS_C_CALLING_ERROR_OFFSET 24 +#define GSS_C_ROUTINE_ERROR_OFFSET 16 + +#define GSS_C_SUPPLEMENTARY_OFFSET 0 +#define GSS_C_CALLING_ERROR_MASK 0377ul +#define GSS_C_ROUTINE_ERROR_MASK 0377ul +#define GSS_C_SUPPLEMENTARY_MASK 0177777ul + +/* + * The macros that test status codes for error conditions. + * Note that the GSS_ERROR() macro has changed slightly from + * the V1 GSS-API so that it now evaluates its argument + * only once. + */ +#define GSS_CALLING_ERROR(x) \ + (x & (GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET)) +#define GSS_ROUTINE_ERROR(x) \ + (x & (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET)) +#define GSS_SUPPLEMENTARY_INFO(x) \ + (x & (GSS_C_SUPPLEMENTARY_MASK << GSS_C_SUPPLEMENTARY_OFFSET)) +#define GSS_ERROR(x) \ + (x & ((GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET) | \ + (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET))) + +/* Now the actual status code definitions */ + +/* Calling errors: */ +#define GSS_S_CALL_INACCESSIBLE_READ \ + (1ul << GSS_C_CALLING_ERROR_OFFSET) +#define GSS_S_CALL_INACCESSIBLE_WRITE \ + (2ul << GSS_C_CALLING_ERROR_OFFSET) +#define GSS_S_CALL_BAD_STRUCTURE \ + (3ul << GSS_C_CALLING_ERROR_OFFSET) + +/* Routine errors: */ +#define GSS_S_BAD_MECH (1ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_NAME (2ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_NAMETYPE (3ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_BINDINGS (4ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_STATUS (5ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_SIG (6ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_MIC GSS_S_BAD_SIG +#define GSS_S_NO_CRED (7ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_NO_CONTEXT (8ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DEFECTIVE_TOKEN (9ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DEFECTIVE_CREDENTIAL (10ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_CREDENTIALS_EXPIRED (11ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_CONTEXT_EXPIRED (12ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_FAILURE (13ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_QOP (14ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_UNAUTHORIZED (15ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_UNAVAILABLE (16ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DUPLICATE_ELEMENT (17ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_NAME_NOT_MN (18ul << \ + GSS_C_ROUTINE_ERROR_OFFSET) + +/* Supplementary info bits: */ +#define GSS_S_CONTINUE_NEEDED \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 0)) +#define GSS_S_DUPLICATE_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 1)) +#define GSS_S_OLD_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 2)) +#define GSS_S_UNSEQ_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 3)) +#define GSS_S_GAP_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 4)) + +extern const_gss_OID GSS_C_NT_USER_NAME; +extern const_gss_OID GSS_C_NT_MACHINE_UID_NAME; +extern const_gss_OID GSS_C_NT_STRING_UID_NAME; +extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X; +extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE; +extern const_gss_OID GSS_C_NT_ANONYMOUS; +extern const_gss_OID GSS_C_NT_EXPORT_NAME; + +#endif /* STATIC_GSSAPI */ + +extern const gss_OID GSS_MECH_KRB5; + +/* GSSAPI functions we use. + * TODO: Replace with all GSSAPI functions from RFC? + */ + +/* Calling convention, just in case we need one. */ +#ifndef GSS_CC +#define GSS_CC +#endif /*GSS_CC*/ + +typedef OM_uint32 (GSS_CC *t_gss_release_cred) + (OM_uint32 * /*minor_status*/, + gss_cred_id_t * /*cred_handle*/); + +typedef OM_uint32 (GSS_CC *t_gss_init_sec_context) + (OM_uint32 * /*minor_status*/, + const gss_cred_id_t /*initiator_cred_handle*/, + gss_ctx_id_t * /*context_handle*/, + const gss_name_t /*target_name*/, + const gss_OID /*mech_type*/, + OM_uint32 /*req_flags*/, + OM_uint32 /*time_req*/, + const gss_channel_bindings_t /*input_chan_bindings*/, + const gss_buffer_t /*input_token*/, + gss_OID * /*actual_mech_type*/, + gss_buffer_t /*output_token*/, + OM_uint32 * /*ret_flags*/, + OM_uint32 * /*time_rec*/); + +typedef OM_uint32 (GSS_CC *t_gss_delete_sec_context) + (OM_uint32 * /*minor_status*/, + gss_ctx_id_t * /*context_handle*/, + gss_buffer_t /*output_token*/); + +typedef OM_uint32 (GSS_CC *t_gss_get_mic) + (OM_uint32 * /*minor_status*/, + const gss_ctx_id_t /*context_handle*/, + gss_qop_t /*qop_req*/, + const gss_buffer_t /*message_buffer*/, + gss_buffer_t /*msg_token*/); + +typedef OM_uint32 (GSS_CC *t_gss_display_status) + (OM_uint32 * /*minor_status*/, + OM_uint32 /*status_value*/, + int /*status_type*/, + const gss_OID /*mech_type*/, + OM_uint32 * /*message_context*/, + gss_buffer_t /*status_string*/); + + +typedef OM_uint32 (GSS_CC *t_gss_import_name) + (OM_uint32 * /*minor_status*/, + const gss_buffer_t /*input_name_buffer*/, + const_gss_OID /*input_name_type*/, + gss_name_t * /*output_name*/); + + +typedef OM_uint32 (GSS_CC *t_gss_release_name) + (OM_uint32 * /*minor_status*/, + gss_name_t * /*name*/); + +typedef OM_uint32 (GSS_CC *t_gss_release_buffer) + (OM_uint32 * /*minor_status*/, + gss_buffer_t /*buffer*/); + +struct gssapi_functions { + t_gss_delete_sec_context delete_sec_context; + t_gss_display_status display_status; + t_gss_get_mic get_mic; + t_gss_import_name import_name; + t_gss_init_sec_context init_sec_context; + t_gss_release_buffer release_buffer; + t_gss_release_cred release_cred; + t_gss_release_name release_name; +}; + +#endif /* NO_GSSAPI */ + +#endif /* PUTTY_PGSSAPI_H */ diff --git a/putty/PINGER.C b/putty/PINGER.C new file mode 100644 index 0000000..b6fde24 --- /dev/null +++ b/putty/PINGER.C @@ -0,0 +1,71 @@ +/* + * pinger.c: centralised module that deals with sending TS_PING + * keepalives, to avoid replicating this code in multiple backends. + */ + +#include "putty.h" + +struct pinger_tag { + int interval; + int pending; + long next; + Backend *back; + void *backhandle; +}; + +static void pinger_schedule(Pinger pinger); + +static void pinger_timer(void *ctx, long now) +{ + Pinger pinger = (Pinger)ctx; + + if (pinger->pending && now - pinger->next >= 0) { + pinger->back->special(pinger->backhandle, TS_PING); + pinger->pending = FALSE; + pinger_schedule(pinger); + } +} + +static void pinger_schedule(Pinger pinger) +{ + int next; + + if (!pinger->interval) { + pinger->pending = FALSE; /* cancel any pending ping */ + return; + } + + next = schedule_timer(pinger->interval * TICKSPERSEC, + pinger_timer, pinger); + if (!pinger->pending || next < pinger->next) { + pinger->next = next; + pinger->pending = TRUE; + } +} + +Pinger pinger_new(Config *cfg, Backend *back, void *backhandle) +{ + Pinger pinger = snew(struct pinger_tag); + + pinger->interval = cfg->ping_interval; + pinger->pending = FALSE; + pinger->back = back; + pinger->backhandle = backhandle; + pinger_schedule(pinger); + + return pinger; +} + +void pinger_reconfig(Pinger pinger, Config *oldcfg, Config *newcfg) +{ + if (oldcfg->ping_interval != newcfg->ping_interval) { + pinger->interval = newcfg->ping_interval; + pinger_schedule(pinger); + } +} + +void pinger_free(Pinger pinger) +{ + expire_timer_context(pinger); + sfree(pinger); +} diff --git a/putty/PORTFWD.C b/putty/PORTFWD.C new file mode 100644 index 0000000..e5874a6 --- /dev/null +++ b/putty/PORTFWD.C @@ -0,0 +1,556 @@ +/* + * SSH port forwarding. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +struct PFwdPrivate { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + void *c; /* (channel) data used by ssh.c */ + void *backhandle; /* instance of SSH backend itself */ + /* Note that backhandle need not be filled in if c is non-NULL */ + Socket s; + int throttled, throttle_override; + int ready; + /* + * `dynamic' does double duty. It's set to 0 for an ordinary + * forwarded port, and nonzero for SOCKS-style dynamic port + * forwarding; but it also represents the state of the SOCKS + * exchange. + */ + int dynamic; + /* + * `hostname' and `port' are the real hostname and port, once + * we know what we're connecting to; they're unused for this + * purpose while conducting a local SOCKS exchange, which means + * we can also use them as a buffer and pointer for reading + * data from the SOCKS client. + */ + char hostname[256+8]; + int port; + /* + * When doing dynamic port forwarding, we can receive + * connection data before we are actually able to send it; so + * we may have to temporarily hold some in a dynamically + * allocated buffer here. + */ + void *buffer; + int buflen; +}; + +static void pfd_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + /* we have to dump these since we have no interface to logging.c */ +} + +static int pfd_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + struct PFwdPrivate *pr = (struct PFwdPrivate *) plug; + + /* + * We have no way to communicate down the forwarded connection, + * so if an error occurred on the socket, we just ignore it + * and treat it like a proper close. + */ + if (pr->c) + sshfwd_close(pr->c); + pfd_close(pr->s); + return 1; +} + +static int pfd_receive(Plug plug, int urgent, char *data, int len) +{ + struct PFwdPrivate *pr = (struct PFwdPrivate *) plug; + if (pr->dynamic) { + while (len--) { + /* + * Throughout SOCKS negotiation, "hostname" is re-used as a + * random protocol buffer with "port" storing the length. + */ + if (pr->port >= lenof(pr->hostname)) { + /* Request too long. */ + if ((pr->dynamic >> 12) == 4) { + /* Send back a SOCKS 4 error before closing. */ + char data[8]; + memset(data, 0, sizeof(data)); + data[1] = 91; /* generic `request rejected' */ + sk_write(pr->s, data, 8); + } + pfd_close(pr->s); + return 1; + } + pr->hostname[pr->port++] = *data++; + + /* + * Now check what's in the buffer to see if it's a + * valid and complete message in the SOCKS exchange. + */ + if ((pr->dynamic == 1 || (pr->dynamic >> 12) == 4) && + pr->hostname[0] == 4) { + /* + * SOCKS 4. + */ + if (pr->dynamic == 1) + pr->dynamic = 0x4000; + if (pr->port < 2) continue;/* don't have command code yet */ + if (pr->hostname[1] != 1) { + /* Not CONNECT. */ + /* Send back a SOCKS 4 error before closing. */ + char data[8]; + memset(data, 0, sizeof(data)); + data[1] = 91; /* generic `request rejected' */ + sk_write(pr->s, data, 8); + pfd_close(pr->s); + return 1; + } + if (pr->port <= 8) continue; /* haven't started user/hostname */ + if (pr->hostname[pr->port-1] != 0) + continue; /* haven't _finished_ user/hostname */ + /* + * Now we have a full SOCKS 4 request. Check it to + * see if it's a SOCKS 4A request. + */ + if (pr->hostname[4] == 0 && pr->hostname[5] == 0 && + pr->hostname[6] == 0 && pr->hostname[7] != 0) { + /* + * It's SOCKS 4A. So if we haven't yet + * collected the host name, we should continue + * waiting for data in order to do so; if we + * have, we can go ahead. + */ + int len; + if (pr->dynamic == 0x4000) { + pr->dynamic = 0x4001; + pr->port = 8; /* reset buffer to overwrite name */ + continue; + } + pr->hostname[0] = 0; /* reply version code */ + pr->hostname[1] = 90; /* request granted */ + sk_write(pr->s, pr->hostname, 8); + len= pr->port - 8; + pr->port = GET_16BIT_MSB_FIRST(pr->hostname+2); + memmove(pr->hostname, pr->hostname + 8, len); + goto connect; + } else { + /* + * It's SOCKS 4, which means we should format + * the IP address into the hostname string and + * then just go. + */ + pr->hostname[0] = 0; /* reply version code */ + pr->hostname[1] = 90; /* request granted */ + sk_write(pr->s, pr->hostname, 8); + pr->port = GET_16BIT_MSB_FIRST(pr->hostname+2); + sprintf(pr->hostname, "%d.%d.%d.%d", + (unsigned char)pr->hostname[4], + (unsigned char)pr->hostname[5], + (unsigned char)pr->hostname[6], + (unsigned char)pr->hostname[7]); + goto connect; + } + } + + if ((pr->dynamic == 1 || (pr->dynamic >> 12) == 5) && + pr->hostname[0] == 5) { + /* + * SOCKS 5. + */ + if (pr->dynamic == 1) + pr->dynamic = 0x5000; + + if (pr->dynamic == 0x5000) { + int i, method; + char data[2]; + /* + * We're receiving a set of method identifiers. + */ + if (pr->port < 2) continue;/* no method count yet */ + if (pr->port < 2 + (unsigned char)pr->hostname[1]) + continue; /* no methods yet */ + method = 0xFF; /* invalid */ + for (i = 0; i < (unsigned char)pr->hostname[1]; i++) + if (pr->hostname[2+i] == 0) { + method = 0;/* no auth */ + break; + } + data[0] = 5; + data[1] = method; + sk_write(pr->s, data, 2); + pr->dynamic = 0x5001; + pr->port = 0; /* re-empty the buffer */ + continue; + } + + if (pr->dynamic == 0x5001) { + /* + * We're receiving a SOCKS request. + */ + unsigned char reply[10]; /* SOCKS5 atyp=1 reply */ + int atype, alen = 0; + + /* + * Pre-fill reply packet. + * In all cases, we set BND.{HOST,ADDR} to 0.0.0.0:0 + * (atyp=1) in the reply; if we succeed, we don't know + * the right answers, and if we fail, they should be + * ignored. + */ + memset(reply, 0, lenof(reply)); + reply[0] = 5; /* VER */ + reply[3] = 1; /* ATYP = 1 (IPv4, 0.0.0.0:0) */ + + if (pr->port < 6) continue; + atype = (unsigned char)pr->hostname[3]; + if (atype == 1) /* IPv4 address */ + alen = 4; + if (atype == 4) /* IPv6 address */ + alen = 16; + if (atype == 3) /* domain name has leading length */ + alen = 1 + (unsigned char)pr->hostname[4]; + if (pr->port < 6 + alen) continue; + if (pr->hostname[1] != 1 || pr->hostname[2] != 0) { + /* Not CONNECT or reserved field nonzero - error */ + reply[1] = 1; /* generic failure */ + sk_write(pr->s, (char *) reply, lenof(reply)); + pfd_close(pr->s); + return 1; + } + /* + * Now we have a viable connect request. Switch + * on atype. + */ + pr->port = GET_16BIT_MSB_FIRST(pr->hostname+4+alen); + if (atype == 1) { + /* REP=0 (success) already */ + sk_write(pr->s, (char *) reply, lenof(reply)); + sprintf(pr->hostname, "%d.%d.%d.%d", + (unsigned char)pr->hostname[4], + (unsigned char)pr->hostname[5], + (unsigned char)pr->hostname[6], + (unsigned char)pr->hostname[7]); + goto connect; + } else if (atype == 3) { + /* REP=0 (success) already */ + sk_write(pr->s, (char *) reply, lenof(reply)); + memmove(pr->hostname, pr->hostname + 5, alen-1); + pr->hostname[alen-1] = '\0'; + goto connect; + } else { + /* + * Unknown address type. (FIXME: support IPv6!) + */ + reply[1] = 8; /* atype not supported */ + sk_write(pr->s, (char *) reply, lenof(reply)); + pfd_close(pr->s); + return 1; + } + } + } + + /* + * If we get here without either having done `continue' + * or `goto connect', it must be because there is no + * sensible interpretation of what's in our buffer. So + * close the connection rudely. + */ + pfd_close(pr->s); + return 1; + } + return 1; + + /* + * We come here when we're ready to make an actual + * connection. + */ + connect: + + /* + * Freeze the socket until the SSH server confirms the + * connection. + */ + sk_set_frozen(pr->s, 1); + + pr->c = new_sock_channel(pr->backhandle, pr->s); + if (pr->c == NULL) { + pfd_close(pr->s); + return 1; + } else { + /* asks to forward to the specified host/port for this */ + ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding"); + } + pr->dynamic = 0; + + /* + * If there's any data remaining in our current buffer, + * save it to be sent on pfd_confirm(). + */ + if (len > 0) { + pr->buffer = snewn(len, char); + memcpy(pr->buffer, data, len); + pr->buflen = len; + } + } + if (pr->ready) { + if (sshfwd_write(pr->c, data, len) > 0) { + pr->throttled = 1; + sk_set_frozen(pr->s, 1); + } + } + return 1; +} + +static void pfd_sent(Plug plug, int bufsize) +{ + struct PFwdPrivate *pr = (struct PFwdPrivate *) plug; + + if (pr->c) + sshfwd_unthrottle(pr->c, bufsize); +} + +/* + * Called when receiving a PORT OPEN from the server + */ +const char *pfd_newconnect(Socket *s, char *hostname, int port, + void *c, const Config *cfg, int addressfamily) +{ + static const struct plug_function_table fn_table = { + pfd_log, + pfd_closing, + pfd_receive, + pfd_sent, + NULL + }; + + SockAddr addr; + const char *err; + char *dummy_realhost; + struct PFwdPrivate *pr; + + /* + * Try to find host. + */ + addr = name_lookup(hostname, port, &dummy_realhost, cfg, addressfamily); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + + /* + * Open socket. + */ + pr = snew(struct PFwdPrivate); + pr->buffer = NULL; + pr->fn = &fn_table; + pr->throttled = pr->throttle_override = 0; + pr->ready = 1; + pr->c = c; + pr->backhandle = NULL; /* we shouldn't need this */ + pr->dynamic = 0; + + pr->s = *s = new_connection(addr, dummy_realhost, port, + 0, 1, 0, 0, (Plug) pr, cfg); + if ((err = sk_socket_error(*s)) != NULL) { + sfree(pr); + return err; + } + + sk_set_private_ptr(*s, pr); + return NULL; +} + +/* + called when someone connects to the local port + */ + +static int pfd_accepting(Plug p, OSSocket sock) +{ + static const struct plug_function_table fn_table = { + pfd_log, + pfd_closing, + pfd_receive, + pfd_sent, + NULL + }; + struct PFwdPrivate *pr, *org; + Socket s; + const char *err; + + org = (struct PFwdPrivate *)p; + pr = snew(struct PFwdPrivate); + pr->buffer = NULL; + pr->fn = &fn_table; + + pr->c = NULL; + pr->backhandle = org->backhandle; + + pr->s = s = sk_register(sock, (Plug) pr); + if ((err = sk_socket_error(s)) != NULL) { + sfree(pr); + return err != NULL; + } + + sk_set_private_ptr(s, pr); + + pr->throttled = pr->throttle_override = 0; + pr->ready = 0; + + if (org->dynamic) { + pr->dynamic = 1; + pr->port = 0; /* "hostname" buffer is so far empty */ + sk_set_frozen(s, 0); /* we want to receive SOCKS _now_! */ + } else { + pr->dynamic = 0; + strcpy(pr->hostname, org->hostname); + pr->port = org->port; + pr->c = new_sock_channel(org->backhandle, s); + + if (pr->c == NULL) { + sfree(pr); + return 1; + } else { + /* asks to forward to the specified host/port for this */ + ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding"); + } + } + + return 0; +} + + +/* Add a new forwarding from port -> desthost:destport + sets up a listener on the local machine on (srcaddr:)port + */ +const char *pfd_addforward(char *desthost, int destport, char *srcaddr, + int port, void *backhandle, const Config *cfg, + void **sockdata, int address_family) +{ + static const struct plug_function_table fn_table = { + pfd_log, + pfd_closing, + pfd_receive, /* should not happen... */ + pfd_sent, /* also should not happen */ + pfd_accepting + }; + + const char *err; + struct PFwdPrivate *pr; + Socket s; + + /* + * Open socket. + */ + pr = snew(struct PFwdPrivate); + pr->buffer = NULL; + pr->fn = &fn_table; + pr->c = NULL; + if (desthost) { + strcpy(pr->hostname, desthost); + pr->port = destport; + pr->dynamic = 0; + } else + pr->dynamic = 1; + pr->throttled = pr->throttle_override = 0; + pr->ready = 0; + pr->backhandle = backhandle; + + pr->s = s = new_listener(srcaddr, port, (Plug) pr, + !cfg->lport_acceptall, cfg, address_family); + if ((err = sk_socket_error(s)) != NULL) { + sfree(pr); + return err; + } + + sk_set_private_ptr(s, pr); + + *sockdata = (void *)s; + + return NULL; +} + +void pfd_close(Socket s) +{ + struct PFwdPrivate *pr; + + if (!s) + return; + + pr = (struct PFwdPrivate *) sk_get_private_ptr(s); + + sfree(pr->buffer); + sfree(pr); + + sk_close(s); +} + +/* + * Terminate a listener. + */ +void pfd_terminate(void *sv) +{ + pfd_close((Socket)sv); +} + +void pfd_unthrottle(Socket s) +{ + struct PFwdPrivate *pr; + if (!s) + return; + pr = (struct PFwdPrivate *) sk_get_private_ptr(s); + + pr->throttled = 0; + sk_set_frozen(s, pr->throttled || pr->throttle_override); +} + +void pfd_override_throttle(Socket s, int enable) +{ + struct PFwdPrivate *pr; + if (!s) + return; + pr = (struct PFwdPrivate *) sk_get_private_ptr(s); + + pr->throttle_override = enable; + sk_set_frozen(s, pr->throttled || pr->throttle_override); +} + +/* + * Called to send data down the raw connection. + */ +int pfd_send(Socket s, char *data, int len) +{ + if (s == NULL) + return 0; + return sk_write(s, data, len); +} + + +void pfd_confirm(Socket s) +{ + struct PFwdPrivate *pr; + + if (s == NULL) + return; + + pr = (struct PFwdPrivate *) sk_get_private_ptr(s); + pr->ready = 1; + sk_set_frozen(s, 0); + sk_write(s, NULL, 0); + if (pr->buffer) { + sshfwd_write(pr->c, pr->buffer, pr->buflen); + sfree(pr->buffer); + pr->buffer = NULL; + } +} diff --git a/putty/PPROXY.C b/putty/PPROXY.C new file mode 100644 index 0000000..5ab31b2 --- /dev/null +++ b/putty/PPROXY.C @@ -0,0 +1,17 @@ +/* + * pproxy.c: dummy implementation of platform_new_connection(), to + * be supplanted on any platform which has its own local proxy + * method. + */ + +#include "putty.h" +#include "network.h" +#include "proxy.h" + +Socket platform_new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, const Config *cfg) +{ + return NULL; +} diff --git a/putty/PROXY.C b/putty/PROXY.C new file mode 100644 index 0000000..1f42999 --- /dev/null +++ b/putty/PROXY.C @@ -0,0 +1,1478 @@ +/* + * Network proxy abstraction in PuTTY + * + * A proxy layer, if necessary, wedges itself between the network + * code and the higher level backend. + */ + +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "network.h" +#include "proxy.h" + +#define do_proxy_dns(cfg) \ + (cfg->proxy_dns == FORCE_ON || \ + (cfg->proxy_dns == AUTO && cfg->proxy_type != PROXY_SOCKS4)) + +/* + * Call this when proxy negotiation is complete, so that this + * socket can begin working normally. + */ +void proxy_activate (Proxy_Socket p) +{ + void *data; + int len; + long output_before, output_after; + + p->state = PROXY_STATE_ACTIVE; + + /* we want to ignore new receive events until we have sent + * all of our buffered receive data. + */ + sk_set_frozen(p->sub_socket, 1); + + /* how many bytes of output have we buffered? */ + output_before = bufchain_size(&p->pending_oob_output_data) + + bufchain_size(&p->pending_output_data); + /* and keep track of how many bytes do not get sent. */ + output_after = 0; + + /* send buffered OOB writes */ + while (bufchain_size(&p->pending_oob_output_data) > 0) { + bufchain_prefix(&p->pending_oob_output_data, &data, &len); + output_after += sk_write_oob(p->sub_socket, data, len); + bufchain_consume(&p->pending_oob_output_data, len); + } + + /* send buffered normal writes */ + while (bufchain_size(&p->pending_output_data) > 0) { + bufchain_prefix(&p->pending_output_data, &data, &len); + output_after += sk_write(p->sub_socket, data, len); + bufchain_consume(&p->pending_output_data, len); + } + + /* if we managed to send any data, let the higher levels know. */ + if (output_after < output_before) + plug_sent(p->plug, output_after); + + /* if we were asked to flush the output during + * the proxy negotiation process, do so now. + */ + if (p->pending_flush) sk_flush(p->sub_socket); + + /* if the backend wanted the socket unfrozen, try to unfreeze. + * our set_frozen handler will flush buffered receive data before + * unfreezing the actual underlying socket. + */ + if (!p->freeze) + sk_set_frozen((Socket)p, 0); +} + +/* basic proxy socket functions */ + +static Plug sk_proxy_plug (Socket s, Plug p) +{ + Proxy_Socket ps = (Proxy_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_proxy_close (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + sk_close(ps->sub_socket); + sk_addr_free(ps->remote_addr); + sfree(ps); +} + +static int sk_proxy_write (Socket s, const char *data, int len) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + if (ps->state != PROXY_STATE_ACTIVE) { + bufchain_add(&ps->pending_output_data, data, len); + return bufchain_size(&ps->pending_output_data); + } + return sk_write(ps->sub_socket, data, len); +} + +static int sk_proxy_write_oob (Socket s, const char *data, int len) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + if (ps->state != PROXY_STATE_ACTIVE) { + bufchain_clear(&ps->pending_output_data); + bufchain_clear(&ps->pending_oob_output_data); + bufchain_add(&ps->pending_oob_output_data, data, len); + return len; + } + return sk_write_oob(ps->sub_socket, data, len); +} + +static void sk_proxy_flush (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->pending_flush = 1; + return; + } + sk_flush(ps->sub_socket); +} + +static void sk_proxy_set_private_ptr (Socket s, void *ptr) +{ + Proxy_Socket ps = (Proxy_Socket) s; + sk_set_private_ptr(ps->sub_socket, ptr); +} + +static void * sk_proxy_get_private_ptr (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + return sk_get_private_ptr(ps->sub_socket); +} + +static void sk_proxy_set_frozen (Socket s, int is_frozen) +{ + Proxy_Socket ps = (Proxy_Socket) s; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->freeze = is_frozen; + return; + } + + /* handle any remaining buffered recv data first */ + if (bufchain_size(&ps->pending_input_data) > 0) { + ps->freeze = is_frozen; + + /* loop while we still have buffered data, and while we are + * unfrozen. the plug_receive call in the loop could result + * in a call back into this function refreezing the socket, + * so we have to check each time. + */ + while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) { + void *data; + char databuf[512]; + int len; + bufchain_prefix(&ps->pending_input_data, &data, &len); + if (len > lenof(databuf)) + len = lenof(databuf); + memcpy(databuf, data, len); + bufchain_consume(&ps->pending_input_data, len); + plug_receive(ps->plug, 0, databuf, len); + } + + /* if we're still frozen, we'll have to wait for another + * call from the backend to finish unbuffering the data. + */ + if (ps->freeze) return; + } + + sk_set_frozen(ps->sub_socket, is_frozen); +} + +static const char * sk_proxy_socket_error (Socket s) +{ + Proxy_Socket ps = (Proxy_Socket) s; + if (ps->error != NULL || ps->sub_socket == NULL) { + return ps->error; + } + return sk_socket_error(ps->sub_socket); +} + +/* basic proxy plug functions */ + +static void plug_proxy_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + Proxy_Plug pp = (Proxy_Plug) plug; + Proxy_Socket ps = pp->proxy_socket; + + plug_log(ps->plug, type, addr, port, error_msg, error_code); +} + +static int plug_proxy_closing (Plug p, const char *error_msg, + int error_code, int calling_back) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->closing_error_msg = error_msg; + ps->closing_error_code = error_code; + ps->closing_calling_back = calling_back; + return ps->negotiate(ps, PROXY_CHANGE_CLOSING); + } + return plug_closing(ps->plug, error_msg, + error_code, calling_back); +} + +static int plug_proxy_receive (Plug p, int urgent, char *data, int len) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + if (ps->state != PROXY_STATE_ACTIVE) { + /* we will lose the urgentness of this data, but since most, + * if not all, of this data will be consumed by the negotiation + * process, hopefully it won't affect the protocol above us + */ + bufchain_add(&ps->pending_input_data, data, len); + ps->receive_urgent = urgent; + ps->receive_data = data; + ps->receive_len = len; + return ps->negotiate(ps, PROXY_CHANGE_RECEIVE); + } + return plug_receive(ps->plug, urgent, data, len); +} + +static void plug_proxy_sent (Plug p, int bufsize) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->sent_bufsize = bufsize; + ps->negotiate(ps, PROXY_CHANGE_SENT); + return; + } + plug_sent(ps->plug, bufsize); +} + +static int plug_proxy_accepting (Plug p, OSSocket sock) +{ + Proxy_Plug pp = (Proxy_Plug) p; + Proxy_Socket ps = pp->proxy_socket; + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->accepting_sock = sock; + return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING); + } + return plug_accepting(ps->plug, sock); +} + +/* + * This function can accept a NULL pointer as `addr', in which case + * it will only check the host name. + */ +static int proxy_for_destination (SockAddr addr, char *hostname, int port, + const Config *cfg) +{ + int s = 0, e = 0; + char hostip[64]; + int hostip_len, hostname_len; + const char *exclude_list; + + /* + * Check the host name and IP against the hard-coded + * representations of `localhost'. + */ + if (!cfg->even_proxy_localhost && + (sk_hostname_is_local(hostname) || + (addr && sk_address_is_local(addr)))) + return 0; /* do not proxy */ + + /* we want a string representation of the IP address for comparisons */ + if (addr) { + sk_getaddr(addr, hostip, 64); + hostip_len = strlen(hostip); + } else + hostip_len = 0; /* placate gcc; shouldn't be required */ + + hostname_len = strlen(hostname); + + exclude_list = cfg->proxy_exclude_list; + + /* now parse the exclude list, and see if either our IP + * or hostname matches anything in it. + */ + + while (exclude_list[s]) { + while (exclude_list[s] && + (isspace((unsigned char)exclude_list[s]) || + exclude_list[s] == ',')) s++; + + if (!exclude_list[s]) break; + + e = s; + + while (exclude_list[e] && + (isalnum((unsigned char)exclude_list[e]) || + exclude_list[e] == '-' || + exclude_list[e] == '.' || + exclude_list[e] == '*')) e++; + + if (exclude_list[s] == '*') { + /* wildcard at beginning of entry */ + + if ((addr && strnicmp(hostip + hostip_len - (e - s - 1), + exclude_list + s + 1, e - s - 1) == 0) || + strnicmp(hostname + hostname_len - (e - s - 1), + exclude_list + s + 1, e - s - 1) == 0) + return 0; /* IP/hostname range excluded. do not use proxy. */ + + } else if (exclude_list[e-1] == '*') { + /* wildcard at end of entry */ + + if ((addr && strnicmp(hostip, exclude_list + s, e - s - 1) == 0) || + strnicmp(hostname, exclude_list + s, e - s - 1) == 0) + return 0; /* IP/hostname range excluded. do not use proxy. */ + + } else { + /* no wildcard at either end, so let's try an absolute + * match (ie. a specific IP) + */ + + if (addr && strnicmp(hostip, exclude_list + s, e - s) == 0) + return 0; /* IP/hostname excluded. do not use proxy. */ + if (strnicmp(hostname, exclude_list + s, e - s) == 0) + return 0; /* IP/hostname excluded. do not use proxy. */ + } + + s = e; + + /* Make sure we really have reached the next comma or end-of-string */ + while (exclude_list[s] && + !isspace((unsigned char)exclude_list[s]) && + exclude_list[s] != ',') s++; + } + + /* no matches in the exclude list, so use the proxy */ + return 1; +} + +SockAddr name_lookup(char *host, int port, char **canonicalname, + const Config *cfg, int addressfamily) +{ + if (cfg->proxy_type != PROXY_NONE && + do_proxy_dns(cfg) && + proxy_for_destination(NULL, host, port, cfg)) { + *canonicalname = dupstr(host); + return sk_nonamelookup(host); + } + + return sk_namelookup(host, canonicalname, addressfamily); +} + +Socket new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, const Config *cfg) +{ + static const struct socket_function_table socket_fn_table = { + sk_proxy_plug, + sk_proxy_close, + sk_proxy_write, + sk_proxy_write_oob, + sk_proxy_flush, + sk_proxy_set_private_ptr, + sk_proxy_get_private_ptr, + sk_proxy_set_frozen, + sk_proxy_socket_error + }; + + static const struct plug_function_table plug_fn_table = { + plug_proxy_log, + plug_proxy_closing, + plug_proxy_receive, + plug_proxy_sent, + plug_proxy_accepting + }; + + if (cfg->proxy_type != PROXY_NONE && + proxy_for_destination(addr, hostname, port, cfg)) + { + Proxy_Socket ret; + Proxy_Plug pplug; + SockAddr proxy_addr; + char *proxy_canonical_name; + Socket sret; + + if ((sret = platform_new_connection(addr, hostname, port, privport, + oobinline, nodelay, keepalive, + plug, cfg)) != + NULL) + return sret; + + ret = snew(struct Socket_proxy_tag); + ret->fn = &socket_fn_table; + ret->cfg = *cfg; /* STRUCTURE COPY */ + ret->plug = plug; + ret->remote_addr = addr; /* will need to be freed on close */ + ret->remote_port = port; + + ret->error = NULL; + ret->pending_flush = 0; + ret->freeze = 0; + + bufchain_init(&ret->pending_input_data); + bufchain_init(&ret->pending_output_data); + bufchain_init(&ret->pending_oob_output_data); + + ret->sub_socket = NULL; + ret->state = PROXY_STATE_NEW; + ret->negotiate = NULL; + + if (cfg->proxy_type == PROXY_HTTP) { + ret->negotiate = proxy_http_negotiate; + } else if (cfg->proxy_type == PROXY_SOCKS4) { + ret->negotiate = proxy_socks4_negotiate; + } else if (cfg->proxy_type == PROXY_SOCKS5) { + ret->negotiate = proxy_socks5_negotiate; + } else if (cfg->proxy_type == PROXY_TELNET) { + ret->negotiate = proxy_telnet_negotiate; + } else { + ret->error = "Proxy error: Unknown proxy method"; + return (Socket) ret; + } + + /* create the proxy plug to map calls from the actual + * socket into our proxy socket layer */ + pplug = snew(struct Plug_proxy_tag); + pplug->fn = &plug_fn_table; + pplug->proxy_socket = ret; + + /* look-up proxy */ + proxy_addr = sk_namelookup(cfg->proxy_host, + &proxy_canonical_name, cfg->addressfamily); + if (sk_addr_error(proxy_addr) != NULL) { + ret->error = "Proxy error: Unable to resolve proxy host name"; + return (Socket)ret; + } + sfree(proxy_canonical_name); + + /* create the actual socket we will be using, + * connected to our proxy server and port. + */ + ret->sub_socket = sk_new(proxy_addr, cfg->proxy_port, + privport, oobinline, + nodelay, keepalive, (Plug) pplug); + if (sk_socket_error(ret->sub_socket) != NULL) + return (Socket) ret; + + /* start the proxy negotiation process... */ + sk_set_frozen(ret->sub_socket, 0); + ret->negotiate(ret, PROXY_CHANGE_NEW); + + return (Socket) ret; + } + + /* no proxy, so just return the direct socket */ + return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); +} + +Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only, + const Config *cfg, int addressfamily) +{ + /* TODO: SOCKS (and potentially others) support inbound + * TODO: connections via the proxy. support them. + */ + + return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily); +} + +/* ---------------------------------------------------------------------- + * HTTP CONNECT proxy type. + */ + +static int get_line_end (char * data, int len) +{ + int off = 0; + + while (off < len) + { + if (data[off] == '\n') { + /* we have a newline */ + off++; + + /* is that the only thing on this line? */ + if (off <= 2) return off; + + /* if not, then there is the possibility that this header + * continues onto the next line, if it starts with a space + * or a tab. + */ + + if (off + 1 < len && + data[off+1] != ' ' && + data[off+1] != '\t') return off; + + /* the line does continue, so we have to keep going + * until we see an the header's "real" end of line. + */ + off++; + } + + off++; + } + + return -1; +} + +int proxy_http_negotiate (Proxy_Socket p, int change) +{ + if (p->state == PROXY_STATE_NEW) { + /* we are just beginning the proxy negotiate process, + * so we'll send off the initial bits of the request. + * for this proxy method, it's just a simple HTTP + * request + */ + char *buf, dest[512]; + + 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); + + if (p->cfg.proxy_username[0] || p->cfg.proxy_password[0]) { + char buf[sizeof(p->cfg.proxy_username)+sizeof(p->cfg.proxy_password)]; + char buf2[sizeof(buf)*4/3 + 100]; + int i, j, len; + sprintf(buf, "%s:%s", p->cfg.proxy_username, p->cfg.proxy_password); + len = strlen(buf); + 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)); + } + + sk_write(p->sub_socket, "\r\n", 2); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + return plug_closing(p->plug, p->closing_error_msg, + p->closing_error_code, + p->closing_calling_back); + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, p->accepting_sock); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + char *data, *datap; + int len; + int eol; + + if (p->state == 1) { + + int min_ver, maj_ver, status; + + /* get the status line */ + len = bufchain_size(&p->pending_input_data); + assert(len > 0); /* or we wouldn't be here */ + data = snewn(len+1, char); + bufchain_fetch(&p->pending_input_data, data, len); + /* + * We must NUL-terminate this data, because Windows + * sscanf appears to require a NUL at the end of the + * string because it strlens it _first_. Sigh. + */ + data[len] = '\0'; + + eol = get_line_end(data, len); + if (eol < 0) { + sfree(data); + return 1; + } + + status = -1; + /* We can't rely on whether the %n incremented the sscanf return */ + if (sscanf((char *)data, "HTTP/%i.%i %n", + &maj_ver, &min_ver, &status) < 2 || status == -1) { + plug_closing(p->plug, "Proxy error: HTTP response was absent", + PROXY_ERROR_GENERAL, 0); + sfree(data); + return 1; + } + + /* remove the status line from the input buffer. */ + bufchain_consume(&p->pending_input_data, eol); + if (data[status] != '2') { + /* error */ + char *buf; + data[eol] = '\0'; + while (eol > status && + (data[eol-1] == '\r' || data[eol-1] == '\n')) + data[--eol] = '\0'; + buf = dupprintf("Proxy error: %s", data+status); + plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0); + sfree(buf); + sfree(data); + return 1; + } + + sfree(data); + + p->state = 2; + } + + if (p->state == 2) { + + /* get headers. we're done when we get a + * header of length 2, (ie. just "\r\n") + */ + + len = bufchain_size(&p->pending_input_data); + assert(len > 0); /* or we wouldn't be here */ + data = snewn(len, char); + datap = data; + bufchain_fetch(&p->pending_input_data, data, len); + + eol = get_line_end(datap, len); + if (eol < 0) { + sfree(data); + return 1; + } + while (eol > 2) + { + bufchain_consume(&p->pending_input_data, eol); + datap += eol; + len -= eol; + eol = get_line_end(datap, len); + } + + if (eol == 2) { + /* we're done */ + bufchain_consume(&p->pending_input_data, 2); + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + sfree(data); + return 1; + } + + sfree(data); + return 1; + } + } + + plug_closing(p->plug, "Proxy error: unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* ---------------------------------------------------------------------- + * SOCKS proxy type. + */ + +/* SOCKS version 4 */ +int proxy_socks4_negotiate (Proxy_Socket p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + + /* request format: + * version number (1 byte) = 4 + * command code (1 byte) + * 1 = CONNECT + * 2 = BIND + * dest. port (2 bytes) [network order] + * dest. address (4 bytes) + * user ID (variable length, null terminated string) + */ + + int length, type, namelen; + char *command, addr[4], hostname[512]; + + type = sk_addrtype(p->remote_addr); + if (type == ADDRTYPE_IPV6) { + plug_closing(p->plug, "Proxy error: SOCKS version 4 does" + " not support IPv6", PROXY_ERROR_GENERAL, 0); + return 1; + } else if (type == ADDRTYPE_IPV4) { + namelen = 0; + sk_addrcopy(p->remote_addr, addr); + } else { /* type == ADDRTYPE_NAME */ + assert(type == ADDRTYPE_NAME); + sk_getaddr(p->remote_addr, hostname, lenof(hostname)); + namelen = strlen(hostname) + 1; /* include the NUL */ + addr[0] = addr[1] = addr[2] = 0; + addr[3] = 1; + } + + length = strlen(p->cfg.proxy_username) + namelen + 9; + command = snewn(length, char); + strcpy(command + 8, p->cfg.proxy_username); + + command[0] = 4; /* version 4 */ + command[1] = 1; /* CONNECT command */ + + /* port */ + command[2] = (char) (p->remote_port >> 8) & 0xff; + command[3] = (char) p->remote_port & 0xff; + + /* address */ + memcpy(command + 4, addr, 4); + + /* hostname */ + memcpy(command + 8 + strlen(p->cfg.proxy_username) + 1, + hostname, namelen); + + sk_write(p->sub_socket, command, length); + sfree(command); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + return plug_closing(p->plug, p->closing_error_msg, + p->closing_error_code, + p->closing_calling_back); + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, p->accepting_sock); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + if (p->state == 1) { + /* response format: + * version number (1 byte) = 4 + * reply code (1 byte) + * 90 = request granted + * 91 = request rejected or failed + * 92 = request rejected due to lack of IDENTD on client + * 93 = request rejected due to difference in user ID + * (what we sent vs. what IDENTD said) + * dest. port (2 bytes) + * dest. address (4 bytes) + */ + + char data[8]; + + if (bufchain_size(&p->pending_input_data) < 8) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 8); + + if (data[0] != 0) { + plug_closing(p->plug, "Proxy error: SOCKS proxy responded with " + "unexpected reply code version", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 90) { + + switch (data[1]) { + case 92: + plug_closing(p->plug, "Proxy error: SOCKS server wanted IDENTD on client", + PROXY_ERROR_GENERAL, 0); + break; + case 93: + plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree", + PROXY_ERROR_GENERAL, 0); + break; + case 91: + default: + plug_closing(p->plug, "Proxy error: Error while communicating with proxy", + PROXY_ERROR_GENERAL, 0); + break; + } + + return 1; + } + bufchain_consume(&p->pending_input_data, 8); + + /* we're done */ + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + return 1; + } + } + + plug_closing(p->plug, "Proxy error: unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* SOCKS version 5 */ +int proxy_socks5_negotiate (Proxy_Socket p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + + /* initial command: + * version number (1 byte) = 5 + * number of available authentication methods (1 byte) + * available authentication methods (1 byte * previous value) + * authentication methods: + * 0x00 = no authentication + * 0x01 = GSSAPI + * 0x02 = username/password + * 0x03 = CHAP + */ + + char command[5]; + int len; + + command[0] = 5; /* version 5 */ + if (p->cfg.proxy_username[0] || p->cfg.proxy_password[0]) { + command[2] = 0x00; /* no authentication */ + len = 3; + proxy_socks5_offerencryptedauth (command, &len); + command[len++] = 0x02; /* username/password */ + command[1] = len - 2; /* Number of methods supported */ + } else { + command[1] = 1; /* one methods supported: */ + command[2] = 0x00; /* no authentication */ + len = 3; + } + + sk_write(p->sub_socket, command, len); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + return plug_closing(p->plug, p->closing_error_msg, + p->closing_error_code, + p->closing_calling_back); + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, p->accepting_sock); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + if (p->state == 1) { + + /* initial response: + * version number (1 byte) = 5 + * authentication method (1 byte) + * authentication methods: + * 0x00 = no authentication + * 0x01 = GSSAPI + * 0x02 = username/password + * 0x03 = CHAP + * 0xff = no acceptable methods + */ + char data[2]; + + if (bufchain_size(&p->pending_input_data) < 2) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 2); + + if (data[0] != 5) { + plug_closing(p->plug, "Proxy error: SOCKS proxy returned unexpected version", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] == 0x00) p->state = 2; /* no authentication needed */ + else if (data[1] == 0x01) p->state = 4; /* GSSAPI authentication */ + else if (data[1] == 0x02) p->state = 5; /* username/password authentication */ + else if (data[1] == 0x03) p->state = 6; /* CHAP authentication */ + else { + plug_closing(p->plug, "Proxy error: SOCKS proxy did not accept our authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + bufchain_consume(&p->pending_input_data, 2); + } + + if (p->state == 7) { + + /* password authentication reply format: + * version number (1 bytes) = 1 + * reply code (1 byte) + * 0 = succeeded + * >0 = failed + */ + char data[2]; + + if (bufchain_size(&p->pending_input_data) < 2) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 2); + + if (data[0] != 1) { + plug_closing(p->plug, "Proxy error: SOCKS password " + "subnegotiation contained wrong version number", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 0) { + + plug_closing(p->plug, "Proxy error: SOCKS proxy refused" + " password authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + bufchain_consume(&p->pending_input_data, 2); + p->state = 2; /* now proceed as authenticated */ + } + + if (p->state == 8) { + int ret; + ret = proxy_socks5_handlechap(p); + if (ret) return ret; + } + + if (p->state == 2) { + + /* request format: + * version number (1 byte) = 5 + * command code (1 byte) + * 1 = CONNECT + * 2 = BIND + * 3 = UDP ASSOCIATE + * reserved (1 byte) = 0x00 + * address type (1 byte) + * 1 = IPv4 + * 3 = domainname (first byte has length, no terminating null) + * 4 = IPv6 + * dest. address (variable) + * dest. port (2 bytes) [network order] + */ + + char command[512]; + int len; + int type; + + type = sk_addrtype(p->remote_addr); + if (type == ADDRTYPE_IPV4) { + len = 10; /* 4 hdr + 4 addr + 2 trailer */ + command[3] = 1; /* IPv4 */ + sk_addrcopy(p->remote_addr, command+4); + } else if (type == ADDRTYPE_IPV6) { + len = 22; /* 4 hdr + 16 addr + 2 trailer */ + command[3] = 4; /* IPv6 */ + sk_addrcopy(p->remote_addr, command+4); + } else { + assert(type == ADDRTYPE_NAME); + command[3] = 3; + sk_getaddr(p->remote_addr, command+5, 256); + command[4] = strlen(command+5); + len = 7 + command[4]; /* 4 hdr, 1 len, N addr, 2 trailer */ + } + + command[0] = 5; /* version 5 */ + command[1] = 1; /* CONNECT command */ + command[2] = 0x00; + + /* port */ + command[len-2] = (char) (p->remote_port >> 8) & 0xff; + command[len-1] = (char) p->remote_port & 0xff; + + sk_write(p->sub_socket, command, len); + + p->state = 3; + return 1; + } + + if (p->state == 3) { + + /* reply format: + * version number (1 bytes) = 5 + * reply code (1 byte) + * 0 = succeeded + * 1 = general SOCKS server failure + * 2 = connection not allowed by ruleset + * 3 = network unreachable + * 4 = host unreachable + * 5 = connection refused + * 6 = TTL expired + * 7 = command not supported + * 8 = address type not supported + * reserved (1 byte) = x00 + * address type (1 byte) + * 1 = IPv4 + * 3 = domainname (first byte has length, no terminating null) + * 4 = IPv6 + * server bound address (variable) + * server bound port (2 bytes) [network order] + */ + char data[5]; + int len; + + /* First 5 bytes of packet are enough to tell its length. */ + if (bufchain_size(&p->pending_input_data) < 5) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 5); + + if (data[0] != 5) { + plug_closing(p->plug, "Proxy error: SOCKS proxy returned wrong version number", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 0) { + char buf[256]; + + strcpy(buf, "Proxy error: "); + + switch (data[1]) { + case 1: strcat(buf, "General SOCKS server failure"); break; + case 2: strcat(buf, "Connection not allowed by ruleset"); break; + case 3: strcat(buf, "Network unreachable"); break; + case 4: strcat(buf, "Host unreachable"); break; + case 5: strcat(buf, "Connection refused"); break; + case 6: strcat(buf, "TTL expired"); break; + case 7: strcat(buf, "Command not supported"); break; + case 8: strcat(buf, "Address type not supported"); break; + default: sprintf(buf+strlen(buf), + "Unrecognised SOCKS error code %d", + data[1]); + break; + } + plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0); + + return 1; + } + + /* + * Eat the rest of the reply packet. + */ + len = 6; /* first 4 bytes, last 2 */ + switch (data[3]) { + case 1: len += 4; break; /* IPv4 address */ + case 4: len += 16; break;/* IPv6 address */ + case 3: len += (unsigned char)data[4]; break; /* domain name */ + default: + plug_closing(p->plug, "Proxy error: SOCKS proxy returned " + "unrecognised address format", + PROXY_ERROR_GENERAL, 0); + return 1; + } + if (bufchain_size(&p->pending_input_data) < len) + return 1; /* not got whole reply yet */ + bufchain_consume(&p->pending_input_data, len); + + /* we're done */ + proxy_activate(p); + return 1; + } + + if (p->state == 4) { + /* TODO: Handle GSSAPI authentication */ + plug_closing(p->plug, "Proxy error: We don't support GSSAPI authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (p->state == 5) { + if (p->cfg.proxy_username[0] || p->cfg.proxy_password[0]) { + char userpwbuf[514]; + int ulen, plen; + ulen = strlen(p->cfg.proxy_username); + if (ulen > 255) ulen = 255; if (ulen < 1) ulen = 1; + plen = strlen(p->cfg.proxy_password); + if (plen > 255) plen = 255; if (plen < 1) plen = 1; + userpwbuf[0] = 1; /* version number of subnegotiation */ + userpwbuf[1] = ulen; + memcpy(userpwbuf+2, p->cfg.proxy_username, ulen); + userpwbuf[ulen+2] = plen; + memcpy(userpwbuf+ulen+3, p->cfg.proxy_password, plen); + sk_write(p->sub_socket, userpwbuf, ulen + plen + 3); + p->state = 7; + } else + plug_closing(p->plug, "Proxy error: Server chose " + "username/password authentication but we " + "didn't offer it!", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (p->state == 6) { + int ret; + ret = proxy_socks5_selectchap(p); + if (ret) return ret; + } + + } + + plug_closing(p->plug, "Proxy error: Unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* ---------------------------------------------------------------------- + * `Telnet' proxy type. + * + * (This is for ad-hoc proxies where you connect to the proxy's + * telnet port and send a command such as `connect host port'. The + * command is configurable, since this proxy type is typically not + * standardised or at all well-defined.) + */ + +char *format_telnet_command(SockAddr addr, int port, const Config *cfg) +{ + char *ret = NULL; + int retlen = 0, retsize = 0; + int so = 0, eo = 0; +#define ENSURE(n) do { \ + if (retsize < retlen + n) { \ + retsize = retlen + n + 512; \ + ret = sresize(ret, retsize, char); \ + } \ +} while (0) + + /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, + * %%, %host, %port, %user, and %pass + */ + + while (cfg->proxy_telnet_command[eo] != 0) { + + /* scan forward until we hit end-of-line, + * or an escape character (\ or %) */ + while (cfg->proxy_telnet_command[eo] != 0 && + cfg->proxy_telnet_command[eo] != '%' && + cfg->proxy_telnet_command[eo] != '\\') eo++; + + /* if we hit eol, break out of our escaping loop */ + if (cfg->proxy_telnet_command[eo] == 0) break; + + /* if there was any unescaped text before the escape + * character, send that now */ + if (eo != so) { + ENSURE(eo - so); + memcpy(ret + retlen, cfg->proxy_telnet_command + so, eo - so); + retlen += eo - so; + } + + so = eo++; + + /* if the escape character was the last character of + * the line, we'll just stop and send it. */ + if (cfg->proxy_telnet_command[eo] == 0) break; + + if (cfg->proxy_telnet_command[so] == '\\') { + + /* we recognize \\, \%, \r, \n, \t, \x??. + * anything else, we just send unescaped (including the \). + */ + + switch (cfg->proxy_telnet_command[eo]) { + + case '\\': + ENSURE(1); + ret[retlen++] = '\\'; + eo++; + break; + + case '%': + ENSURE(1); + ret[retlen++] = '%'; + eo++; + break; + + case 'r': + ENSURE(1); + ret[retlen++] = '\r'; + eo++; + break; + + case 'n': + ENSURE(1); + ret[retlen++] = '\n'; + eo++; + break; + + case 't': + ENSURE(1); + ret[retlen++] = '\t'; + eo++; + break; + + case 'x': + case 'X': + { + /* escaped hexadecimal value (ie. \xff) */ + unsigned char v = 0; + int i = 0; + + for (;;) { + eo++; + if (cfg->proxy_telnet_command[eo] >= '0' && + cfg->proxy_telnet_command[eo] <= '9') + v += cfg->proxy_telnet_command[eo] - '0'; + else if (cfg->proxy_telnet_command[eo] >= 'a' && + cfg->proxy_telnet_command[eo] <= 'f') + v += cfg->proxy_telnet_command[eo] - 'a' + 10; + else if (cfg->proxy_telnet_command[eo] >= 'A' && + cfg->proxy_telnet_command[eo] <= 'F') + v += cfg->proxy_telnet_command[eo] - 'A' + 10; + else { + /* non hex character, so we abort and just + * send the whole thing unescaped (including \x) + */ + ENSURE(1); + ret[retlen++] = '\\'; + eo = so + 1; + break; + } + + /* we only extract two hex characters */ + if (i == 1) { + ENSURE(1); + ret[retlen++] = v; + eo++; + break; + } + + i++; + v <<= 4; + } + } + break; + + default: + ENSURE(2); + memcpy(ret+retlen, cfg->proxy_telnet_command + so, 2); + retlen += 2; + eo++; + break; + } + } else { + + /* % escape. we recognize %%, %host, %port, %user, %pass. + * %proxyhost, %proxyport. Anything else we just send + * unescaped (including the %). + */ + + if (cfg->proxy_telnet_command[eo] == '%') { + ENSURE(1); + ret[retlen++] = '%'; + eo++; + } + else if (strnicmp(cfg->proxy_telnet_command + eo, + "host", 4) == 0) { + char dest[512]; + int destlen; + sk_getaddr(addr, dest, lenof(dest)); + destlen = strlen(dest); + ENSURE(destlen); + memcpy(ret+retlen, dest, destlen); + retlen += destlen; + eo += 4; + } + else if (strnicmp(cfg->proxy_telnet_command + eo, + "port", 4) == 0) { + char portstr[8], portlen; + portlen = sprintf(portstr, "%i", port); + ENSURE(portlen); + memcpy(ret + retlen, portstr, portlen); + retlen += portlen; + eo += 4; + } + else if (strnicmp(cfg->proxy_telnet_command + eo, + "user", 4) == 0) { + int userlen = strlen(cfg->proxy_username); + ENSURE(userlen); + memcpy(ret+retlen, cfg->proxy_username, userlen); + retlen += userlen; + eo += 4; + } + else if (strnicmp(cfg->proxy_telnet_command + eo, + "pass", 4) == 0) { + int passlen = strlen(cfg->proxy_password); + ENSURE(passlen); + memcpy(ret+retlen, cfg->proxy_password, passlen); + retlen += passlen; + eo += 4; + } + else if (strnicmp(cfg->proxy_telnet_command + eo, + "proxyhost", 9) == 0) { + int phlen = strlen(cfg->proxy_host); + ENSURE(phlen); + memcpy(ret+retlen, cfg->proxy_host, phlen); + retlen += phlen; + eo += 9; + } + else if (strnicmp(cfg->proxy_telnet_command + eo, + "proxyport", 9) == 0) { + char pport[50]; + int pplen; + sprintf(pport, "%d", cfg->proxy_port); + pplen = strlen(pport); + ENSURE(pplen); + memcpy(ret+retlen, pport, pplen); + retlen += pplen; + eo += 9; + } + else { + /* we don't escape this, so send the % now, and + * don't advance eo, so that we'll consider the + * text immediately following the % as unescaped. + */ + ENSURE(1); + ret[retlen++] = '%'; + } + } + + /* resume scanning for additional escapes after this one. */ + so = eo; + } + + /* if there is any unescaped text at the end of the line, send it */ + if (eo != so) { + ENSURE(eo - so); + memcpy(ret + retlen, cfg->proxy_telnet_command + so, eo - so); + retlen += eo - so; + } + + ENSURE(1); + ret[retlen] = '\0'; + return ret; + +#undef ENSURE +} + +int proxy_telnet_negotiate (Proxy_Socket p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + char *formatted_cmd; + + formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port, + &p->cfg); + + sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd)); + sfree(formatted_cmd); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + return plug_closing(p->plug, p->closing_error_msg, + p->closing_error_code, + p->closing_calling_back); + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, p->accepting_sock); + } + + 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/putty/PROXY.H b/putty/PROXY.H new file mode 100644 index 0000000..683b260 --- /dev/null +++ b/putty/PROXY.H @@ -0,0 +1,123 @@ +/* + * Network proxy abstraction in PuTTY + * + * A proxy layer, if necessary, wedges itself between the + * network code and the higher level backend. + * + * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5 + */ + +#ifndef PUTTY_PROXY_H +#define PUTTY_PROXY_H + +#define PROXY_ERROR_GENERAL 8000 +#define PROXY_ERROR_UNEXPECTED 8001 + +typedef struct Socket_proxy_tag * Proxy_Socket; + +struct Socket_proxy_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + char * error; + + Socket sub_socket; + Plug plug; + SockAddr remote_addr; + int remote_port; + + bufchain pending_output_data; + bufchain pending_oob_output_data; + int pending_flush; + bufchain pending_input_data; + +#define PROXY_STATE_NEW -1 +#define PROXY_STATE_ACTIVE 0 + + int state; /* proxy states greater than 0 are implementation + * dependent, but represent various stages/states + * of the initialization/setup/negotiation with the + * proxy server. + */ + int freeze; /* should we freeze the underlying socket when + * we are done with the proxy negotiation? this + * simply caches the value of sk_set_frozen calls. + */ + +#define PROXY_CHANGE_NEW -1 +#define PROXY_CHANGE_CLOSING 0 +#define PROXY_CHANGE_SENT 1 +#define PROXY_CHANGE_RECEIVE 2 +#define PROXY_CHANGE_ACCEPTING 3 + + /* something has changed (a call from the sub socket + * layer into our Proxy Plug layer, or we were just + * created, etc), so the proxy layer needs to handle + * this change (the type of which is the second argument) + * and further the proxy negotiation process. + */ + + int (*negotiate) (Proxy_Socket /* this */, int /* change type */); + + /* current arguments of plug handlers + * (for use by proxy's negotiate function) + */ + + /* closing */ + const char *closing_error_msg; + int closing_error_code; + int closing_calling_back; + + /* receive */ + int receive_urgent; + char *receive_data; + int receive_len; + + /* sent */ + int sent_bufsize; + + /* accepting */ + OSSocket accepting_sock; + + /* configuration, used to look up proxy settings */ + Config cfg; + + /* CHAP transient data */ + int chap_num_attributes; + int chap_num_attributes_processed; + int chap_current_attribute; + int chap_current_datalen; +}; + +typedef struct Plug_proxy_tag * Proxy_Plug; + +struct Plug_proxy_tag { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + Proxy_Socket proxy_socket; + +}; + +extern void proxy_activate (Proxy_Socket); + +extern int proxy_http_negotiate (Proxy_Socket, int); +extern int proxy_telnet_negotiate (Proxy_Socket, int); +extern int proxy_socks4_negotiate (Proxy_Socket, int); +extern int proxy_socks5_negotiate (Proxy_Socket, int); + +/* + * This may be reused by local-command proxies on individual + * platforms. + */ +char *format_telnet_command(SockAddr addr, int port, const Config *cfg); + +/* + * These are implemented in cproxy.c or nocproxy.c, depending on + * whether encrypted proxy authentication is available. + */ +extern void proxy_socks5_offerencryptedauth(char *command, int *len); +extern int proxy_socks5_handlechap (Proxy_Socket p); +extern int proxy_socks5_selectchap(Proxy_Socket p); + +#endif diff --git a/putty/PSCP.C b/putty/PSCP.C new file mode 100644 index 0000000..14fff5c --- /dev/null +++ b/putty/PSCP.C @@ -0,0 +1,2308 @@ +/* + * scp.c - Scp (Secure Copy) client for PuTTY. + * Joris van Rantwijk, Simon Tatham + * + * This is mainly based on ssh-1.2.26/scp.c by Timo Rinne & Tatu Ylonen. + * They, in turn, used stuff from BSD rcp. + * + * (SGT, 2001-09-10: Joris van Rantwijk assures me that although + * this file as originally submitted was inspired by, and + * _structurally_ based on, ssh-1.2.26's scp.c, there wasn't any + * actual code duplicated, so the above comment shouldn't give rise + * to licensing issues.) + */ + +#include +#include +#include +#include +#include +#include + +#define PUTTY_DO_GLOBALS +#include "putty.h" +#include "psftp.h" +#include "ssh.h" +#include "sftp.h" +#include "storage.h" +#include "int64.h" + +static int list = 0; +static int verbose = 0; +static int recursive = 0; +static int preserve = 0; +static int targetshouldbedirectory = 0; +static int statistics = 1; +static int prev_stats_len = 0; +static int scp_unsafe_mode = 0; +static int errs = 0; +static int try_scp = 1; +static int try_sftp = 1; +static int main_cmd_is_sftp = 0; +static int fallback_cmd_is_sftp = 0; +static int using_sftp = 0; + +static Backend *back; +static void *backhandle; +static Config cfg; + +static void source(char *src); +static void rsource(char *src); +static void sink(char *targ, char *src); + +const char *const appname = "PSCP"; + +/* + * The maximum amount of queued data we accept before we stop and + * wait for the server to process some. + */ +#define MAX_SCP_BUFSIZE 16384 + +void ldisc_send(void *handle, char *buf, int len, int interactive) +{ + /* + * This is only here because of the calls to ldisc_send(NULL, + * 0) in ssh.c. Nothing in PSCP actually needs to use the ldisc + * as an ldisc. So if we get called with any real data, I want + * to know about it. + */ + assert(len == 0); +} + +static void tell_char(FILE * stream, char c) +{ + fputc(c, stream); +} + +static void tell_str(FILE * stream, char *str) +{ + unsigned int i; + + for (i = 0; i < strlen(str); ++i) + tell_char(stream, str[i]); +} + +static void tell_user(FILE * stream, char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + va_end(ap); + str2 = dupcat(str, "\n", NULL); + sfree(str); + tell_str(stream, str2); + sfree(str2); +} + +/* + * Print an error message and perform a fatal exit. + */ +void fatalbox(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + tell_str(stderr, str2); + sfree(str2); + errs++; + + cleanup_exit(1); +} +void modalfatalbox(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + tell_str(stderr, str2); + sfree(str2); + errs++; + + cleanup_exit(1); +} +void connection_fatal(void *frontend, char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + tell_str(stderr, str2); + sfree(str2); + errs++; + + cleanup_exit(1); +} + +/* + * In pscp, all agent requests should be synchronous, so this is a + * never-called stub. + */ +void agent_schedule_callback(void (*callback)(void *, void *, int), + void *callback_ctx, void *data, int len) +{ + assert(!"We shouldn't be here"); +} + +/* + * Receive a block of data from the SSH link. Block until all data + * is available. + * + * To do this, we repeatedly call the SSH protocol module, with our + * own trap in from_backend() to catch the data that comes back. We + * do this until we have enough data. + */ + +static unsigned char *outptr; /* where to put the data */ +static unsigned outlen; /* how much data required */ +static unsigned char *pending = NULL; /* any spare data */ +static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */ +int from_backend(void *frontend, int is_stderr, const char *data, int datalen) +{ + unsigned char *p = (unsigned char *) data; + unsigned len = (unsigned) datalen; + + /* + * stderr data is just spouted to local stderr and otherwise + * ignored. + */ + if (is_stderr) { + if (len > 0) + if (fwrite(data, 1, len, stderr) < len) + /* oh well */; + return 0; + } + + if ((outlen > 0) && (len > 0)) { + unsigned used = outlen; + if (used > len) + used = len; + memcpy(outptr, p, used); + outptr += used; + outlen -= used; + p += used; + len -= used; + } + + if (len > 0) { + if (pendsize < pendlen + len) { + pendsize = pendlen + len + 4096; + pending = sresize(pending, pendsize, unsigned char); + } + memcpy(pending + pendlen, p, len); + pendlen += len; + } + + return 0; +} +int from_backend_untrusted(void *frontend_handle, const char *data, int len) +{ + /* + * No "untrusted" output should get here (the way the code is + * currently, it's all diverted by FLAG_STDERR). + */ + assert(!"Unexpected call to from_backend_untrusted()"); + return 0; /* not reached */ +} +static int ssh_scp_recv(unsigned char *buf, int len) +{ + outptr = buf; + outlen = len; + + /* + * See if the pending-input block contains some of what we + * need. + */ + if (pendlen > 0) { + unsigned pendused = pendlen; + if (pendused > outlen) + pendused = outlen; + memcpy(outptr, pending, pendused); + memmove(pending, pending + pendused, pendlen - pendused); + outptr += pendused; + outlen -= pendused; + pendlen -= pendused; + if (pendlen == 0) { + pendsize = 0; + sfree(pending); + pending = NULL; + } + if (outlen == 0) + return len; + } + + while (outlen > 0) { + if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0) + return 0; /* doom */ + } + + return len; +} + +/* + * Loop through the ssh connection and authentication process. + */ +static void ssh_scp_init(void) +{ + while (!back->sendok(backhandle)) { + if (back->exitcode(backhandle) >= 0) { + errs++; + return; + } + if (ssh_sftp_loop_iteration() < 0) { + errs++; + return; /* doom */ + } + } + + /* Work out which backend we ended up using. */ + if (!ssh_fallback_cmd(backhandle)) + using_sftp = main_cmd_is_sftp; + else + using_sftp = fallback_cmd_is_sftp; + + if (verbose) { + if (using_sftp) + tell_user(stderr, "Using SFTP"); + else + tell_user(stderr, "Using SCP1"); + } +} + +/* + * Print an error message and exit after closing the SSH link. + */ +static void bump(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + va_end(ap); + str2 = dupcat(str, "\n", NULL); + sfree(str); + tell_str(stderr, str2); + sfree(str2); + errs++; + + if (back != NULL && back->connected(backhandle)) { + char ch; + back->special(backhandle, TS_EOF); + ssh_scp_recv((unsigned char *) &ch, 1); + } + + cleanup_exit(1); +} + +/* + * Open an SSH connection to user@host and execute cmd. + */ +static void do_cmd(char *host, char *user, char *cmd) +{ + const char *err; + char *realhost; + void *logctx; + + if (host == NULL || host[0] == '\0') + bump("Empty host name"); + + /* + * Remove fiddly bits of address: remove a colon suffix, and + * the square brackets around an IPv6 literal address. + */ + if (host[0] == '[') { + host++; + host[strcspn(host, "]")] = '\0'; + } else { + host[strcspn(host, ":")] = '\0'; + } + + /* + * If we haven't loaded session details already (e.g., from -load), + * try looking for a session called "host". + */ + if (!loaded_session) { + /* Try to load settings for `host' into a temporary config */ + Config cfg2; + cfg2.host[0] = '\0'; + do_defaults(host, &cfg2); + if (cfg2.host[0] != '\0') { + /* Settings present and include hostname */ + /* Re-load data into the real config. */ + do_defaults(host, &cfg); + } else { + /* Session doesn't exist or mention a hostname. */ + /* Use `host' as a bare hostname. */ + strncpy(cfg.host, host, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + } + } else { + /* Patch in hostname `host' to session details. */ + strncpy(cfg.host, host, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + } + + /* + * Force use of SSH. (If they got the protocol wrong we assume the + * port is useless too.) + */ + if (cfg.protocol != PROT_SSH) { + cfg.protocol = PROT_SSH; + cfg.port = 22; + } + + /* + * Enact command-line overrides. + */ + cmdline_run_saved(&cfg); + + /* + * Trim leading whitespace off the hostname if it's there. + */ + { + int space = strspn(cfg.host, " \t"); + memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space); + } + + /* See if host is of the form user@host */ + if (cfg.host[0] != '\0') { + char *atsign = strrchr(cfg.host, '@'); + /* Make sure we're not overflowing the user field */ + if (atsign) { + if (atsign - cfg.host < sizeof cfg.username) { + strncpy(cfg.username, cfg.host, atsign - cfg.host); + cfg.username[atsign - cfg.host] = '\0'; + } + memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1)); + } + } + + /* + * Remove any remaining whitespace from the hostname. + */ + { + int p1 = 0, p2 = 0; + while (cfg.host[p2] != '\0') { + if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') { + cfg.host[p1] = cfg.host[p2]; + p1++; + } + p2++; + } + cfg.host[p1] = '\0'; + } + + /* Set username */ + if (user != NULL && user[0] != '\0') { + strncpy(cfg.username, user, sizeof(cfg.username) - 1); + cfg.username[sizeof(cfg.username) - 1] = '\0'; + } else if (cfg.username[0] == '\0') { + user = get_username(); + if (!user) + bump("Empty user name"); + else { + if (verbose) + tell_user(stderr, "Guessing user name: %s", user); + strncpy(cfg.username, user, sizeof(cfg.username) - 1); + cfg.username[sizeof(cfg.username) - 1] = '\0'; + sfree(user); + } + } + + /* + * Disable scary things which shouldn't be enabled for simple + * things like SCP and SFTP: agent forwarding, port forwarding, + * X forwarding. + */ + cfg.x11_forward = 0; + cfg.agentfwd = 0; + cfg.portfwd[0] = cfg.portfwd[1] = '\0'; + cfg.ssh_simple = TRUE; + + /* + * Set up main and possibly fallback command depending on + * options specified by user. + * Attempt to start the SFTP subsystem as a first choice, + * falling back to the provided scp command if that fails. + */ + cfg.remote_cmd_ptr2 = NULL; + if (try_sftp) { + /* First choice is SFTP subsystem. */ + main_cmd_is_sftp = 1; + strcpy(cfg.remote_cmd, "sftp"); + cfg.ssh_subsys = TRUE; + if (try_scp) { + /* Fallback is to use the provided scp command. */ + fallback_cmd_is_sftp = 0; + cfg.remote_cmd_ptr2 = cmd; + cfg.ssh_subsys2 = FALSE; + } else { + /* Since we're not going to try SCP, we may as well try + * harder to find an SFTP server, since in the current + * implementation we have a spare slot. */ + fallback_cmd_is_sftp = 1; + /* see psftp.c for full explanation of this kludge */ + cfg.remote_cmd_ptr2 = + "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" + "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" + "exec sftp-server"; + cfg.ssh_subsys2 = FALSE; + } + } else { + /* Don't try SFTP at all; just try the scp command. */ + main_cmd_is_sftp = 0; + cfg.remote_cmd_ptr = cmd; + cfg.ssh_subsys = FALSE; + } + cfg.nopty = TRUE; + + back = &ssh_backend; + + err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost, + 0, cfg.tcp_keepalives); + if (err != NULL) + bump("ssh_init: %s", err); + logctx = log_init(NULL, &cfg); + back->provide_logctx(backhandle, logctx); + console_provide_logctx(logctx); + ssh_scp_init(); + if (verbose && realhost != NULL && errs == 0) + tell_user(stderr, "Connected to %s\n", realhost); + sfree(realhost); +} + +/* + * Update statistic information about current file. + */ +static void print_stats(char *name, uint64 size, uint64 done, + time_t start, time_t now) +{ + float ratebs; + unsigned long eta; + char *etastr; + int pct; + int len; + int elap; + double donedbl; + double sizedbl; + + elap = (unsigned long) difftime(now, start); + + if (now > start) + ratebs = (float) (uint64_to_double(done) / elap); + else + ratebs = (float) uint64_to_double(done); + + if (ratebs < 1.0) + eta = (unsigned long) (uint64_to_double(uint64_subtract(size, done))); + else { + eta = (unsigned long) + ((uint64_to_double(uint64_subtract(size, done)) / ratebs)); + } + + etastr = dupprintf("%02ld:%02ld:%02ld", + eta / 3600, (eta % 3600) / 60, eta % 60); + + donedbl = uint64_to_double(done); + sizedbl = uint64_to_double(size); + pct = (int) (100 * (donedbl * 1.0 / sizedbl)); + + { + char donekb[40]; + /* divide by 1024 to provide kB */ + uint64_decimal(uint64_shift_right(done, 10), donekb); + len = printf("\r%-25.25s | %s kB | %5.1f kB/s | ETA: %8s | %3d%%", + name, + donekb, ratebs / 1024.0, etastr, pct); + if (len < prev_stats_len) + printf("%*s", prev_stats_len - len, ""); + prev_stats_len = len; + + if (uint64_compare(done, size) == 0) + printf("\n"); + + fflush(stdout); + } + + free(etastr); +} + +/* + * Find a colon in str and return a pointer to the colon. + * This is used to separate hostname from filename. + */ +static char *colon(char *str) +{ + /* We ignore a leading colon, since the hostname cannot be + empty. We also ignore a colon as second character because + of filenames like f:myfile.txt. */ + if (str[0] == '\0' || str[0] == ':' || + (str[0] != '[' && str[1] == ':')) + return (NULL); + while (*str != '\0' && *str != ':' && *str != '/' && *str != '\\') { + if (*str == '[') { + /* Skip over IPv6 literal addresses + * (eg: 'jeroen@[2001:db8::1]:myfile.txt') */ + char *ipv6_end = strchr(str, ']'); + if (ipv6_end) { + str = ipv6_end; + } + } + str++; + } + if (*str == ':') + return (str); + else + return (NULL); +} + +/* + * Return a pointer to the portion of str that comes after the last + * slash (or backslash or colon, if `local' is TRUE). + */ +static char *stripslashes(char *str, int local) +{ + char *p; + + if (local) { + p = strchr(str, ':'); + if (p) str = p+1; + } + + p = strrchr(str, '/'); + if (p) str = p+1; + + if (local) { + p = strrchr(str, '\\'); + if (p) str = p+1; + } + + return str; +} + +/* + * Determine whether a string is entirely composed of dots. + */ +static int is_dots(char *str) +{ + return str[strspn(str, ".")] == '\0'; +} + +/* + * Wait for a response from the other side. + * Return 0 if ok, -1 if error. + */ +static int response(void) +{ + char ch, resp, rbuf[2048]; + int p; + + if (ssh_scp_recv((unsigned char *) &resp, 1) <= 0) + bump("Lost connection"); + + p = 0; + switch (resp) { + case 0: /* ok */ + return (0); + default: + rbuf[p++] = resp; + /* fallthrough */ + case 1: /* error */ + case 2: /* fatal error */ + do { + if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0) + bump("Protocol error: Lost connection"); + rbuf[p++] = ch; + } while (p < sizeof(rbuf) && ch != '\n'); + rbuf[p - 1] = '\0'; + if (resp == 1) + tell_user(stderr, "%s\n", rbuf); + else + bump("%s", rbuf); + errs++; + return (-1); + } +} + +int sftp_recvdata(char *buf, int len) +{ + return ssh_scp_recv((unsigned char *) buf, len); +} +int sftp_senddata(char *buf, int len) +{ + back->send(backhandle, buf, len); + return 1; +} + +/* ---------------------------------------------------------------------- + * sftp-based replacement for the hacky `pscp -ls'. + */ +static int sftp_ls_compare(const void *av, const void *bv) +{ + const struct fxp_name *a = (const struct fxp_name *) av; + const struct fxp_name *b = (const struct fxp_name *) bv; + return strcmp(a->filename, b->filename); +} +void scp_sftp_listdir(char *dirname) +{ + struct fxp_handle *dirh; + struct fxp_names *names; + struct fxp_name *ournames; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + int nnames, namesize; + int i; + + if (!fxp_init()) { + tell_user(stderr, "unable to initialise SFTP: %s", fxp_error()); + errs++; + return; + } + + printf("Listing directory %s\n", dirname); + + sftp_register(req = fxp_opendir_send(dirname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + dirh = fxp_opendir_recv(pktin, rreq); + + if (dirh == NULL) { + printf("Unable to open %s: %s\n", dirname, fxp_error()); + } else { + nnames = namesize = 0; + ournames = NULL; + + while (1) { + + sftp_register(req = fxp_readdir_send(dirh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + names = fxp_readdir_recv(pktin, rreq); + + if (names == NULL) { + if (fxp_error_type() == SSH_FX_EOF) + break; + printf("Reading directory %s: %s\n", dirname, fxp_error()); + break; + } + if (names->nnames == 0) { + fxp_free_names(names); + break; + } + + if (nnames + names->nnames >= namesize) { + namesize += names->nnames + 128; + ournames = sresize(ournames, namesize, struct fxp_name); + } + + for (i = 0; i < names->nnames; i++) + ournames[nnames++] = names->names[i]; + names->nnames = 0; /* prevent free_names */ + fxp_free_names(names); + } + sftp_register(req = fxp_close_send(dirh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + /* + * Now we have our filenames. Sort them by actual file + * name, and then output the longname parts. + */ + qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare); + + /* + * And print them. + */ + for (i = 0; i < nnames; i++) + printf("%s\n", ournames[i].longname); + } +} + +/* ---------------------------------------------------------------------- + * Helper routines that contain the actual SCP protocol elements, + * implemented both as SCP1 and SFTP. + */ + +static struct scp_sftp_dirstack { + struct scp_sftp_dirstack *next; + struct fxp_name *names; + int namepos, namelen; + char *dirpath; + char *wildcard; + int matched_something; /* wildcard match set was non-empty */ +} *scp_sftp_dirstack_head; +static char *scp_sftp_remotepath, *scp_sftp_currentname; +static char *scp_sftp_wildcard; +static int scp_sftp_targetisdir, scp_sftp_donethistarget; +static int scp_sftp_preserve, scp_sftp_recursive; +static unsigned long scp_sftp_mtime, scp_sftp_atime; +static int scp_has_times; +static struct fxp_handle *scp_sftp_filehandle; +static struct fxp_xfer *scp_sftp_xfer; +static uint64 scp_sftp_fileoffset; + +int scp_source_setup(char *target, int shouldbedir) +{ + if (using_sftp) { + /* + * Find out whether the target filespec is in fact a + * directory. + */ + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + struct fxp_attrs attrs; + int ret; + + if (!fxp_init()) { + tell_user(stderr, "unable to initialise SFTP: %s", fxp_error()); + errs++; + return 1; + } + + sftp_register(req = fxp_stat_send(target)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + ret = fxp_stat_recv(pktin, rreq, &attrs); + + if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) + scp_sftp_targetisdir = 0; + else + scp_sftp_targetisdir = (attrs.permissions & 0040000) != 0; + + if (shouldbedir && !scp_sftp_targetisdir) { + bump("pscp: remote filespec %s: not a directory\n", target); + } + + scp_sftp_remotepath = dupstr(target); + + scp_has_times = 0; + } else { + (void) response(); + } + return 0; +} + +int scp_send_errmsg(char *str) +{ + if (using_sftp) { + /* do nothing; we never need to send our errors to the server */ + } else { + back->send(backhandle, "\001", 1);/* scp protocol error prefix */ + back->send(backhandle, str, strlen(str)); + } + return 0; /* can't fail */ +} + +int scp_send_filetimes(unsigned long mtime, unsigned long atime) +{ + if (using_sftp) { + scp_sftp_mtime = mtime; + scp_sftp_atime = atime; + scp_has_times = 1; + return 0; + } else { + char buf[80]; + sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime); + back->send(backhandle, buf, strlen(buf)); + return response(); + } +} + +int scp_send_filename(char *name, uint64 size, int modes) +{ + if (using_sftp) { + char *fullname; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + + if (scp_sftp_targetisdir) { + fullname = dupcat(scp_sftp_remotepath, "/", name, NULL); + } else { + fullname = dupstr(scp_sftp_remotepath); + } + + sftp_register(req = fxp_open_send(fullname, SSH_FXF_WRITE | + SSH_FXF_CREAT | SSH_FXF_TRUNC)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + scp_sftp_filehandle = fxp_open_recv(pktin, rreq); + + if (!scp_sftp_filehandle) { + tell_user(stderr, "pscp: unable to open %s: %s", + fullname, fxp_error()); + errs++; + return 1; + } + scp_sftp_fileoffset = uint64_make(0, 0); + scp_sftp_xfer = xfer_upload_init(scp_sftp_filehandle, + scp_sftp_fileoffset); + sfree(fullname); + return 0; + } else { + char buf[40]; + char sizestr[40]; + uint64_decimal(size, sizestr); + sprintf(buf, "C%04o %s ", modes, sizestr); + back->send(backhandle, buf, strlen(buf)); + back->send(backhandle, name, strlen(name)); + back->send(backhandle, "\n", 1); + return response(); + } +} + +int scp_send_filedata(char *data, int len) +{ + if (using_sftp) { + int ret; + struct sftp_packet *pktin; + + if (!scp_sftp_filehandle) { + return 1; + } + + while (!xfer_upload_ready(scp_sftp_xfer)) { + pktin = sftp_recv(); + ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin); + if (!ret) { + tell_user(stderr, "error while writing: %s\n", fxp_error()); + errs++; + return 1; + } + } + + xfer_upload_data(scp_sftp_xfer, data, len); + + scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, len); + return 0; + } else { + int bufsize = back->send(backhandle, data, len); + + /* + * If the network transfer is backing up - that is, the + * remote site is not accepting data as fast as we can + * produce it - then we must loop on network events until + * we have space in the buffer again. + */ + while (bufsize > MAX_SCP_BUFSIZE) { + if (ssh_sftp_loop_iteration() < 0) + return 1; + bufsize = back->sendbuffer(backhandle); + } + + return 0; + } +} + +int scp_send_finish(void) +{ + if (using_sftp) { + struct fxp_attrs attrs; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + int ret; + + while (!xfer_done(scp_sftp_xfer)) { + pktin = sftp_recv(); + xfer_upload_gotpkt(scp_sftp_xfer, pktin); + } + xfer_cleanup(scp_sftp_xfer); + + if (!scp_sftp_filehandle) { + return 1; + } + if (scp_has_times) { + attrs.flags = SSH_FILEXFER_ATTR_ACMODTIME; + attrs.atime = scp_sftp_atime; + attrs.mtime = scp_sftp_mtime; + sftp_register(req = fxp_fsetstat_send(scp_sftp_filehandle, attrs)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + ret = fxp_fsetstat_recv(pktin, rreq); + if (!ret) { + tell_user(stderr, "unable to set file times: %s\n", fxp_error()); + errs++; + } + } + sftp_register(req = fxp_close_send(scp_sftp_filehandle)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + scp_has_times = 0; + return 0; + } else { + back->send(backhandle, "", 1); + return response(); + } +} + +char *scp_save_remotepath(void) +{ + if (using_sftp) + return scp_sftp_remotepath; + else + return NULL; +} + +void scp_restore_remotepath(char *data) +{ + if (using_sftp) + scp_sftp_remotepath = data; +} + +int scp_send_dirname(char *name, int modes) +{ + if (using_sftp) { + char *fullname; + char const *err; + struct fxp_attrs attrs; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + int ret; + + if (scp_sftp_targetisdir) { + fullname = dupcat(scp_sftp_remotepath, "/", name, NULL); + } else { + fullname = dupstr(scp_sftp_remotepath); + } + + /* + * We don't worry about whether we managed to create the + * directory, because if it exists already it's OK just to + * use it. Instead, we will stat it afterwards, and if it + * exists and is a directory we will assume we were either + * successful or it didn't matter. + */ + sftp_register(req = fxp_mkdir_send(fullname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + ret = fxp_mkdir_recv(pktin, rreq); + + if (!ret) + err = fxp_error(); + else + err = "server reported no error"; + + sftp_register(req = fxp_stat_send(fullname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + ret = fxp_stat_recv(pktin, rreq, &attrs); + + if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) || + !(attrs.permissions & 0040000)) { + tell_user(stderr, "unable to create directory %s: %s", + fullname, err); + errs++; + return 1; + } + + scp_sftp_remotepath = fullname; + + return 0; + } else { + char buf[40]; + sprintf(buf, "D%04o 0 ", modes); + back->send(backhandle, buf, strlen(buf)); + back->send(backhandle, name, strlen(name)); + back->send(backhandle, "\n", 1); + return response(); + } +} + +int scp_send_enddir(void) +{ + if (using_sftp) { + sfree(scp_sftp_remotepath); + return 0; + } else { + back->send(backhandle, "E\n", 2); + return response(); + } +} + +/* + * Yes, I know; I have an scp_sink_setup _and_ an scp_sink_init. + * That's bad. The difference is that scp_sink_setup is called once + * right at the start, whereas scp_sink_init is called to + * initialise every level of recursion in the protocol. + */ +int scp_sink_setup(char *source, int preserve, int recursive) +{ + if (using_sftp) { + char *newsource; + + if (!fxp_init()) { + tell_user(stderr, "unable to initialise SFTP: %s", fxp_error()); + errs++; + return 1; + } + /* + * It's possible that the source string we've been given + * contains a wildcard. If so, we must split the directory + * away from the wildcard itself (throwing an error if any + * wildcardness comes before the final slash) and arrange + * things so that a dirstack entry will be set up. + */ + newsource = snewn(1+strlen(source), char); + if (!wc_unescape(newsource, source)) { + /* Yes, here we go; it's a wildcard. Bah. */ + char *dupsource, *lastpart, *dirpart, *wildcard; + dupsource = dupstr(source); + lastpart = stripslashes(dupsource, 0); + wildcard = dupstr(lastpart); + *lastpart = '\0'; + if (*dupsource && dupsource[1]) { + /* + * The remains of dupsource are at least two + * characters long, meaning the pathname wasn't + * empty or just `/'. Hence, we remove the trailing + * slash. + */ + lastpart[-1] = '\0'; + } else if (!*dupsource) { + /* + * The remains of dupsource are _empty_ - the whole + * pathname was a wildcard. Hence we need to + * replace it with ".". + */ + sfree(dupsource); + dupsource = dupstr("."); + } + + /* + * Now we have separated our string into dupsource (the + * directory part) and wildcard. Both of these will + * need freeing at some point. Next step is to remove + * wildcard escapes from the directory part, throwing + * an error if it contains a real wildcard. + */ + dirpart = snewn(1+strlen(dupsource), char); + if (!wc_unescape(dirpart, dupsource)) { + tell_user(stderr, "%s: multiple-level wildcards unsupported", + source); + errs++; + sfree(dirpart); + sfree(wildcard); + sfree(dupsource); + return 1; + } + + /* + * Now we have dirpart (unescaped, ie a valid remote + * path), and wildcard (a wildcard). This will be + * sufficient to arrange a dirstack entry. + */ + scp_sftp_remotepath = dirpart; + scp_sftp_wildcard = wildcard; + sfree(dupsource); + } else { + scp_sftp_remotepath = newsource; + scp_sftp_wildcard = NULL; + } + scp_sftp_preserve = preserve; + scp_sftp_recursive = recursive; + scp_sftp_donethistarget = 0; + scp_sftp_dirstack_head = NULL; + } + return 0; +} + +int scp_sink_init(void) +{ + if (!using_sftp) { + back->send(backhandle, "", 1); + } + return 0; +} + +#define SCP_SINK_FILE 1 +#define SCP_SINK_DIR 2 +#define SCP_SINK_ENDDIR 3 +#define SCP_SINK_RETRY 4 /* not an action; just try again */ +struct scp_sink_action { + int action; /* FILE, DIR, ENDDIR */ + char *buf; /* will need freeing after use */ + char *name; /* filename or dirname (not ENDDIR) */ + int mode; /* access mode (not ENDDIR) */ + uint64 size; /* file size (not ENDDIR) */ + int settime; /* 1 if atime and mtime are filled */ + unsigned long atime, mtime; /* access times for the file */ +}; + +int scp_get_sink_action(struct scp_sink_action *act) +{ + if (using_sftp) { + char *fname; + int must_free_fname; + struct fxp_attrs attrs; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + int ret; + + if (!scp_sftp_dirstack_head) { + if (!scp_sftp_donethistarget) { + /* + * Simple case: we are only dealing with one file. + */ + fname = scp_sftp_remotepath; + must_free_fname = 0; + scp_sftp_donethistarget = 1; + } else { + /* + * Even simpler case: one file _which we've done_. + * Return 1 (finished). + */ + return 1; + } + } else { + /* + * We're now in the middle of stepping through a list + * of names returned from fxp_readdir(); so let's carry + * on. + */ + struct scp_sftp_dirstack *head = scp_sftp_dirstack_head; + while (head->namepos < head->namelen && + (is_dots(head->names[head->namepos].filename) || + (head->wildcard && + !wc_match(head->wildcard, + head->names[head->namepos].filename)))) + head->namepos++; /* skip . and .. */ + if (head->namepos < head->namelen) { + head->matched_something = 1; + fname = dupcat(head->dirpath, "/", + head->names[head->namepos++].filename, + NULL); + must_free_fname = 1; + } else { + /* + * We've come to the end of the list; pop it off + * the stack and return an ENDDIR action (or RETRY + * if this was a wildcard match). + */ + if (head->wildcard) { + act->action = SCP_SINK_RETRY; + if (!head->matched_something) { + tell_user(stderr, "pscp: wildcard '%s' matched " + "no files", head->wildcard); + errs++; + } + sfree(head->wildcard); + + } else { + act->action = SCP_SINK_ENDDIR; + } + + sfree(head->dirpath); + sfree(head->names); + scp_sftp_dirstack_head = head->next; + sfree(head); + + return 0; + } + } + + /* + * Now we have a filename. Stat it, and see if it's a file + * or a directory. + */ + sftp_register(req = fxp_stat_send(fname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + ret = fxp_stat_recv(pktin, rreq, &attrs); + + if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { + tell_user(stderr, "unable to identify %s: %s", fname, + ret ? "file type not supplied" : fxp_error()); + errs++; + return 1; + } + + if (attrs.permissions & 0040000) { + struct scp_sftp_dirstack *newitem; + struct fxp_handle *dirhandle; + int nnames, namesize; + struct fxp_name *ournames; + struct fxp_names *names; + + /* + * It's a directory. If we're not in recursive mode, + * this merits a complaint (which is fatal if the name + * was specified directly, but not if it was matched by + * a wildcard). + * + * We skip this complaint completely if + * scp_sftp_wildcard is set, because that's an + * indication that we're not actually supposed to + * _recursively_ transfer the dir, just scan it for + * things matching the wildcard. + */ + if (!scp_sftp_recursive && !scp_sftp_wildcard) { + tell_user(stderr, "pscp: %s: is a directory", fname); + errs++; + if (must_free_fname) sfree(fname); + if (scp_sftp_dirstack_head) { + act->action = SCP_SINK_RETRY; + return 0; + } else { + return 1; + } + } + + /* + * Otherwise, the fun begins. We must fxp_opendir() the + * directory, slurp the filenames into memory, return + * SCP_SINK_DIR (unless this is a wildcard match), and + * set targetisdir. The next time we're called, we will + * run through the list of filenames one by one, + * matching them against a wildcard if present. + * + * If targetisdir is _already_ set (meaning we're + * already in the middle of going through another such + * list), we must push the other (target,namelist) pair + * on a stack. + */ + sftp_register(req = fxp_opendir_send(fname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + dirhandle = fxp_opendir_recv(pktin, rreq); + + if (!dirhandle) { + tell_user(stderr, "scp: unable to open directory %s: %s", + fname, fxp_error()); + if (must_free_fname) sfree(fname); + errs++; + return 1; + } + nnames = namesize = 0; + ournames = NULL; + while (1) { + int i; + + sftp_register(req = fxp_readdir_send(dirhandle)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + names = fxp_readdir_recv(pktin, rreq); + + if (names == NULL) { + if (fxp_error_type() == SSH_FX_EOF) + break; + tell_user(stderr, "scp: reading directory %s: %s\n", + fname, fxp_error()); + if (must_free_fname) sfree(fname); + sfree(ournames); + errs++; + return 1; + } + if (names->nnames == 0) { + fxp_free_names(names); + break; + } + if (nnames + names->nnames >= namesize) { + namesize += names->nnames + 128; + ournames = sresize(ournames, namesize, struct fxp_name); + } + for (i = 0; i < names->nnames; i++) { + if (!strcmp(names->names[i].filename, ".") || + !strcmp(names->names[i].filename, "..")) { + /* + * . and .. are normal consequences of + * reading a directory, and aren't worth + * complaining about. + */ + } else if (!vet_filename(names->names[i].filename)) { + tell_user(stderr, "ignoring potentially dangerous server-" + "supplied filename '%s'\n", + names->names[i].filename); + } else + ournames[nnames++] = names->names[i]; + } + names->nnames = 0; /* prevent free_names */ + fxp_free_names(names); + } + sftp_register(req = fxp_close_send(dirhandle)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + newitem = snew(struct scp_sftp_dirstack); + newitem->next = scp_sftp_dirstack_head; + newitem->names = ournames; + newitem->namepos = 0; + newitem->namelen = nnames; + if (must_free_fname) + newitem->dirpath = fname; + else + newitem->dirpath = dupstr(fname); + if (scp_sftp_wildcard) { + newitem->wildcard = scp_sftp_wildcard; + newitem->matched_something = 0; + scp_sftp_wildcard = NULL; + } else { + newitem->wildcard = NULL; + } + scp_sftp_dirstack_head = newitem; + + if (newitem->wildcard) { + act->action = SCP_SINK_RETRY; + } else { + act->action = SCP_SINK_DIR; + act->buf = dupstr(stripslashes(fname, 0)); + act->name = act->buf; + act->size = uint64_make(0,0); /* duhh, it's a directory */ + act->mode = 07777 & attrs.permissions; + if (scp_sftp_preserve && + (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { + act->atime = attrs.atime; + act->mtime = attrs.mtime; + act->settime = 1; + } else + act->settime = 0; + } + return 0; + + } else { + /* + * It's a file. Return SCP_SINK_FILE. + */ + act->action = SCP_SINK_FILE; + act->buf = dupstr(stripslashes(fname, 0)); + act->name = act->buf; + if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) { + act->size = attrs.size; + } else + act->size = uint64_make(ULONG_MAX,ULONG_MAX); /* no idea */ + act->mode = 07777 & attrs.permissions; + if (scp_sftp_preserve && + (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { + act->atime = attrs.atime; + act->mtime = attrs.mtime; + act->settime = 1; + } else + act->settime = 0; + if (must_free_fname) + scp_sftp_currentname = fname; + else + scp_sftp_currentname = dupstr(fname); + return 0; + } + + } else { + int done = 0; + int i, bufsize; + int action; + char ch; + + act->settime = 0; + act->buf = NULL; + bufsize = 0; + + while (!done) { + if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0) + return 1; + if (ch == '\n') + bump("Protocol error: Unexpected newline"); + i = 0; + action = ch; + do { + if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0) + bump("Lost connection"); + if (i >= bufsize) { + bufsize = i + 128; + act->buf = sresize(act->buf, bufsize, char); + } + act->buf[i++] = ch; + } while (ch != '\n'); + act->buf[i - 1] = '\0'; + switch (action) { + case '\01': /* error */ + tell_user(stderr, "%s\n", act->buf); + errs++; + continue; /* go round again */ + case '\02': /* fatal error */ + bump("%s", act->buf); + case 'E': + back->send(backhandle, "", 1); + act->action = SCP_SINK_ENDDIR; + return 0; + case 'T': + if (sscanf(act->buf, "%ld %*d %ld %*d", + &act->mtime, &act->atime) == 2) { + act->settime = 1; + back->send(backhandle, "", 1); + continue; /* go round again */ + } + bump("Protocol error: Illegal time format"); + case 'C': + case 'D': + act->action = (action == 'C' ? SCP_SINK_FILE : SCP_SINK_DIR); + break; + default: + bump("Protocol error: Expected control record"); + } + /* + * We will go round this loop only once, unless we hit + * `continue' above. + */ + done = 1; + } + + /* + * If we get here, we must have seen SCP_SINK_FILE or + * SCP_SINK_DIR. + */ + { + char sizestr[40]; + + if (sscanf(act->buf, "%o %s %n", &act->mode, sizestr, &i) != 2) + bump("Protocol error: Illegal file descriptor format"); + act->size = uint64_from_decimal(sizestr); + act->name = act->buf + i; + return 0; + } + } +} + +int scp_accept_filexfer(void) +{ + if (using_sftp) { + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + + sftp_register(req = fxp_open_send(scp_sftp_currentname, SSH_FXF_READ)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + scp_sftp_filehandle = fxp_open_recv(pktin, rreq); + + if (!scp_sftp_filehandle) { + tell_user(stderr, "pscp: unable to open %s: %s", + scp_sftp_currentname, fxp_error()); + errs++; + return 1; + } + scp_sftp_fileoffset = uint64_make(0, 0); + scp_sftp_xfer = xfer_download_init(scp_sftp_filehandle, + scp_sftp_fileoffset); + sfree(scp_sftp_currentname); + return 0; + } else { + back->send(backhandle, "", 1); + return 0; /* can't fail */ + } +} + +int scp_recv_filedata(char *data, int len) +{ + if (using_sftp) { + struct sftp_packet *pktin; + int ret, actuallen; + void *vbuf; + + xfer_download_queue(scp_sftp_xfer); + pktin = sftp_recv(); + ret = xfer_download_gotpkt(scp_sftp_xfer, pktin); + + if (ret < 0) { + tell_user(stderr, "pscp: error while reading: %s", fxp_error()); + errs++; + return -1; + } + + if (xfer_download_data(scp_sftp_xfer, &vbuf, &actuallen)) { + /* + * This assertion relies on the fact that the natural + * block size used in the xfer manager is at most that + * used in this module. I don't like crossing layers in + * this way, but it'll do for now. + */ + assert(actuallen <= len); + memcpy(data, vbuf, actuallen); + sfree(vbuf); + } else + actuallen = 0; + + scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, actuallen); + + return actuallen; + } else { + return ssh_scp_recv((unsigned char *) data, len); + } +} + +int scp_finish_filerecv(void) +{ + if (using_sftp) { + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + + /* + * Ensure that xfer_done() will work correctly, so we can + * clean up any outstanding requests from the file + * transfer. + */ + xfer_set_error(scp_sftp_xfer); + while (!xfer_done(scp_sftp_xfer)) { + void *vbuf; + int len; + + pktin = sftp_recv(); + xfer_download_gotpkt(scp_sftp_xfer, pktin); + if (xfer_download_data(scp_sftp_xfer, &vbuf, &len)) + sfree(vbuf); + } + xfer_cleanup(scp_sftp_xfer); + + sftp_register(req = fxp_close_send(scp_sftp_filehandle)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + return 0; + } else { + back->send(backhandle, "", 1); + return response(); + } +} + +/* ---------------------------------------------------------------------- + * Send an error message to the other side and to the screen. + * Increment error counter. + */ +static void run_err(const char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + errs++; + str = dupvprintf(fmt, ap); + str2 = dupcat("scp: ", str, "\n", NULL); + sfree(str); + scp_send_errmsg(str2); + tell_user(stderr, "%s", str2); + va_end(ap); + sfree(str2); +} + +/* + * Execute the source part of the SCP protocol. + */ +static void source(char *src) +{ + uint64 size; + unsigned long mtime, atime; + char *last; + RFile *f; + int attr; + uint64 i; + uint64 stat_bytes; + time_t stat_starttime, stat_lasttime; + + attr = file_type(src); + if (attr == FILE_TYPE_NONEXISTENT || + attr == FILE_TYPE_WEIRD) { + run_err("%s: %s file or directory", src, + (attr == FILE_TYPE_WEIRD ? "Not a" : "No such")); + return; + } + + if (attr == FILE_TYPE_DIRECTORY) { + if (recursive) { + /* + * Avoid . and .. directories. + */ + char *p; + p = strrchr(src, '/'); + if (!p) + p = strrchr(src, '\\'); + if (!p) + p = src; + else + p++; + if (!strcmp(p, ".") || !strcmp(p, "..")) + /* skip . and .. */ ; + else + rsource(src); + } else { + run_err("%s: not a regular file", src); + } + return; + } + + if ((last = strrchr(src, '/')) == NULL) + last = src; + else + last++; + if (strrchr(last, '\\') != NULL) + last = strrchr(last, '\\') + 1; + if (last == src && strchr(src, ':') != NULL) + last = strchr(src, ':') + 1; + + f = open_existing_file(src, &size, &mtime, &atime); + if (f == NULL) { + run_err("%s: Cannot open file", src); + return; + } + if (preserve) { + if (scp_send_filetimes(mtime, atime)) + return; + } + + if (verbose) { + char sizestr[40]; + uint64_decimal(size, sizestr); + tell_user(stderr, "Sending file %s, size=%s", last, sizestr); + } + if (scp_send_filename(last, size, 0644)) + return; + + stat_bytes = uint64_make(0,0); + stat_starttime = time(NULL); + stat_lasttime = 0; + + for (i = uint64_make(0,0); + uint64_compare(i,size) < 0; + i = uint64_add32(i,4096)) { + char transbuf[4096]; + int j, k = 4096; + + if (uint64_compare(uint64_add32(i, k),size) > 0) /* i + k > size */ + k = (uint64_subtract(size, i)).lo; /* k = size - i; */ + if ((j = read_from_file(f, transbuf, k)) != k) { + if (statistics) + printf("\n"); + bump("%s: Read error", src); + } + if (scp_send_filedata(transbuf, k)) + bump("%s: Network error occurred", src); + + if (statistics) { + stat_bytes = uint64_add32(stat_bytes, k); + if (time(NULL) != stat_lasttime || + (uint64_compare(uint64_add32(i, k), size) == 0)) { + stat_lasttime = time(NULL); + print_stats(last, size, stat_bytes, + stat_starttime, stat_lasttime); + } + } + + } + close_rfile(f); + + (void) scp_send_finish(); +} + +/* + * Recursively send the contents of a directory. + */ +static void rsource(char *src) +{ + char *last; + char *save_target; + DirHandle *dir; + + if ((last = strrchr(src, '/')) == NULL) + last = src; + else + last++; + if (strrchr(last, '\\') != NULL) + last = strrchr(last, '\\') + 1; + if (last == src && strchr(src, ':') != NULL) + last = strchr(src, ':') + 1; + + /* maybe send filetime */ + + save_target = scp_save_remotepath(); + + if (verbose) + tell_user(stderr, "Entering directory: %s", last); + if (scp_send_dirname(last, 0755)) + return; + + dir = open_directory(src); + if (dir != NULL) { + char *filename; + while ((filename = read_filename(dir)) != NULL) { + char *foundfile = dupcat(src, "/", filename, NULL); + source(foundfile); + sfree(foundfile); + sfree(filename); + } + } + close_directory(dir); + + (void) scp_send_enddir(); + + scp_restore_remotepath(save_target); +} + +/* + * Execute the sink part of the SCP protocol. + */ +static void sink(char *targ, char *src) +{ + char *destfname; + int targisdir = 0; + int exists; + int attr; + WFile *f; + uint64 received; + int wrerror = 0; + uint64 stat_bytes; + time_t stat_starttime, stat_lasttime; + char *stat_name; + + attr = file_type(targ); + if (attr == FILE_TYPE_DIRECTORY) + targisdir = 1; + + if (targetshouldbedirectory && !targisdir) + bump("%s: Not a directory", targ); + + scp_sink_init(); + while (1) { + struct scp_sink_action act; + if (scp_get_sink_action(&act)) + return; + + if (act.action == SCP_SINK_ENDDIR) + return; + + if (act.action == SCP_SINK_RETRY) + continue; + + if (targisdir) { + /* + * Prevent the remote side from maliciously writing to + * files outside the target area by sending a filename + * containing `../'. In fact, it shouldn't be sending + * filenames with any slashes or colons in at all; so + * we'll find the last slash, backslash or colon in the + * filename and use only the part after that. (And + * warn!) + * + * In addition, we also ensure here that if we're + * copying a single file and the target is a directory + * (common usage: `pscp host:filename .') the remote + * can't send us a _different_ file name. We can + * distinguish this case because `src' will be non-NULL + * and the last component of that will fail to match + * (the last component of) the name sent. + * + * Well, not always; if `src' is a wildcard, we do + * expect to get back filenames that don't correspond + * exactly to it. Ideally in this case, we would like + * to ensure that the returned filename actually + * matches the wildcard pattern - but one of SCP's + * protocol infelicities is that wildcard matching is + * done at the server end _by the server's rules_ and + * so in general this is infeasible. Hence, we only + * accept filenames that don't correspond to `src' if + * unsafe mode is enabled or we are using SFTP (which + * resolves remote wildcards on the client side and can + * be trusted). + */ + char *striptarget, *stripsrc; + + striptarget = stripslashes(act.name, 1); + if (striptarget != act.name) { + tell_user(stderr, "warning: remote host sent a compound" + " pathname '%s'", act.name); + tell_user(stderr, " renaming local file to '%s'", + striptarget); + } + + /* + * Also check to see if the target filename is '.' or + * '..', or indeed '...' and so on because Windows + * appears to interpret those like '..'. + */ + if (is_dots(striptarget)) { + bump("security violation: remote host attempted to write to" + " a '.' or '..' path!"); + } + + if (src) { + stripsrc = stripslashes(src, 1); + if (strcmp(striptarget, stripsrc) && + !using_sftp && !scp_unsafe_mode) { + tell_user(stderr, "warning: remote host tried to write " + "to a file called '%s'", striptarget); + tell_user(stderr, " when we requested a file " + "called '%s'.", stripsrc); + tell_user(stderr, " If this is a wildcard, " + "consider upgrading to SSH-2 or using"); + tell_user(stderr, " the '-unsafe' option. Renaming" + " of this file has been disallowed."); + /* Override the name the server provided with our own. */ + striptarget = stripsrc; + } + } + + if (targ[0] != '\0') + destfname = dir_file_cat(targ, striptarget); + else + destfname = dupstr(striptarget); + } else { + /* + * In this branch of the if, the target area is a + * single file with an explicitly specified name in any + * case, so there's no danger. + */ + destfname = dupstr(targ); + } + attr = file_type(destfname); + exists = (attr != FILE_TYPE_NONEXISTENT); + + if (act.action == SCP_SINK_DIR) { + if (exists && attr != FILE_TYPE_DIRECTORY) { + run_err("%s: Not a directory", destfname); + continue; + } + if (!exists) { + if (!create_directory(destfname)) { + run_err("%s: Cannot create directory", destfname); + continue; + } + } + sink(destfname, NULL); + /* can we set the timestamp for directories ? */ + continue; + } + + f = open_new_file(destfname); + if (f == NULL) { + run_err("%s: Cannot create file", destfname); + continue; + } + + if (scp_accept_filexfer()) + return; + + stat_bytes = uint64_make(0, 0); + stat_starttime = time(NULL); + stat_lasttime = 0; + stat_name = stripslashes(destfname, 1); + + received = uint64_make(0, 0); + while (uint64_compare(received,act.size) < 0) { + char transbuf[32768]; + uint64 blksize; + int read; + blksize = uint64_make(0, 32768); + if (uint64_compare(blksize,uint64_subtract(act.size,received)) > 0) + blksize = uint64_subtract(act.size,received); + read = scp_recv_filedata(transbuf, (int)blksize.lo); + if (read <= 0) + bump("Lost connection"); + if (wrerror) + continue; + if (write_to_file(f, transbuf, read) != (int)read) { + wrerror = 1; + /* FIXME: in sftp we can actually abort the transfer */ + if (statistics) + printf("\r%-25.25s | %50s\n", + stat_name, + "Write error.. waiting for end of file"); + continue; + } + if (statistics) { + stat_bytes = uint64_add32(stat_bytes,read); + if (time(NULL) > stat_lasttime || + uint64_compare(uint64_add32(received, read), act.size) == 0) { + stat_lasttime = time(NULL); + print_stats(stat_name, act.size, stat_bytes, + stat_starttime, stat_lasttime); + } + } + received = uint64_add32(received, read); + } + if (act.settime) { + set_file_times(f, act.mtime, act.atime); + } + + close_wfile(f); + if (wrerror) { + run_err("%s: Write error", destfname); + continue; + } + (void) scp_finish_filerecv(); + sfree(destfname); + sfree(act.buf); + } +} + +/* + * We will copy local files to a remote server. + */ +static void toremote(int argc, char *argv[]) +{ + char *src, *targ, *host, *user; + char *cmd; + int i, wc_type; + + targ = argv[argc - 1]; + + /* Separate host from filename */ + host = targ; + targ = colon(targ); + if (targ == NULL) + bump("targ == NULL in toremote()"); + *targ++ = '\0'; + if (*targ == '\0') + targ = "."; + /* Substitute "." for empty target */ + + /* Separate host and username */ + user = host; + host = strrchr(host, '@'); + if (host == NULL) { + host = user; + user = NULL; + } else { + *host++ = '\0'; + if (*user == '\0') + user = NULL; + } + + if (argc == 2) { + if (colon(argv[0]) != NULL) + bump("%s: Remote to remote not supported", argv[0]); + + wc_type = test_wildcard(argv[0], 1); + if (wc_type == WCTYPE_NONEXISTENT) + bump("%s: No such file or directory\n", argv[0]); + else if (wc_type == WCTYPE_WILDCARD) + targetshouldbedirectory = 1; + } + + cmd = dupprintf("scp%s%s%s%s -t %s", + verbose ? " -v" : "", + recursive ? " -r" : "", + preserve ? " -p" : "", + targetshouldbedirectory ? " -d" : "", targ); + do_cmd(host, user, cmd); + sfree(cmd); + + if (scp_source_setup(targ, targetshouldbedirectory)) + return; + + for (i = 0; i < argc - 1; i++) { + src = argv[i]; + if (colon(src) != NULL) { + tell_user(stderr, "%s: Remote to remote not supported\n", src); + errs++; + continue; + } + + wc_type = test_wildcard(src, 1); + if (wc_type == WCTYPE_NONEXISTENT) { + run_err("%s: No such file or directory", src); + continue; + } else if (wc_type == WCTYPE_FILENAME) { + source(src); + continue; + } else { + WildcardMatcher *wc; + char *filename; + + wc = begin_wildcard_matching(src); + if (wc == NULL) { + run_err("%s: No such file or directory", src); + continue; + } + + while ((filename = wildcard_get_filename(wc)) != NULL) { + source(filename); + sfree(filename); + } + + finish_wildcard_matching(wc); + } + } +} + +/* + * We will copy files from a remote server to the local machine. + */ +static void tolocal(int argc, char *argv[]) +{ + char *src, *targ, *host, *user; + char *cmd; + + if (argc != 2) + bump("More than one remote source not supported"); + + src = argv[0]; + targ = argv[1]; + + /* Separate host from filename */ + host = src; + src = colon(src); + if (src == NULL) + bump("Local to local copy not supported"); + *src++ = '\0'; + if (*src == '\0') + src = "."; + /* Substitute "." for empty filename */ + + /* Separate username and hostname */ + user = host; + host = strrchr(host, '@'); + if (host == NULL) { + host = user; + user = NULL; + } else { + *host++ = '\0'; + if (*user == '\0') + user = NULL; + } + + cmd = dupprintf("scp%s%s%s%s -f %s", + verbose ? " -v" : "", + recursive ? " -r" : "", + preserve ? " -p" : "", + targetshouldbedirectory ? " -d" : "", src); + do_cmd(host, user, cmd); + sfree(cmd); + + if (scp_sink_setup(src, preserve, recursive)) + return; + + sink(targ, src); +} + +/* + * We will issue a list command to get a remote directory. + */ +static void get_dir_list(int argc, char *argv[]) +{ + char *src, *host, *user; + char *cmd, *p, *q; + char c; + + src = argv[0]; + + /* Separate host from filename */ + host = src; + src = colon(src); + if (src == NULL) + bump("Local file listing not supported"); + *src++ = '\0'; + if (*src == '\0') + src = "."; + /* Substitute "." for empty filename */ + + /* Separate username and hostname */ + user = host; + host = strrchr(host, '@'); + if (host == NULL) { + host = user; + user = NULL; + } else { + *host++ = '\0'; + if (*user == '\0') + user = NULL; + } + + cmd = snewn(4 * strlen(src) + 100, char); + strcpy(cmd, "ls -la '"); + p = cmd + strlen(cmd); + for (q = src; *q; q++) { + if (*q == '\'') { + *p++ = '\''; + *p++ = '\\'; + *p++ = '\''; + *p++ = '\''; + } else { + *p++ = *q; + } + } + *p++ = '\''; + *p = '\0'; + + do_cmd(host, user, cmd); + sfree(cmd); + + if (using_sftp) { + scp_sftp_listdir(src); + } else { + while (ssh_scp_recv((unsigned char *) &c, 1) > 0) + tell_char(stdout, c); + } +} + +/* + * Short description of parameters. + */ +static void usage(void) +{ + printf("PuTTY Secure Copy client\n"); + printf("%s\n", ver); + printf("Usage: pscp [options] [user@]host:source target\n"); + printf + (" pscp [options] source [source...] [user@]host:target\n"); + printf(" pscp [options] -ls [user@]host:filespec\n"); + printf("Options:\n"); + printf(" -V print version information and exit\n"); + printf(" -pgpfp print PGP key fingerprints and exit\n"); + printf(" -p preserve file attributes\n"); + printf(" -q quiet, don't show statistics\n"); + printf(" -r copy directories recursively\n"); + printf(" -v show verbose messages\n"); + printf(" -load sessname Load settings from saved session\n"); + printf(" -P port connect to specified port\n"); + printf(" -l user connect with specified username\n"); + printf(" -pw passw login with specified password\n"); + printf(" -1 -2 force use of particular SSH protocol version\n"); + printf(" -4 -6 force use of IPv4 or IPv6\n"); + printf(" -C enable compression\n"); + printf(" -i key private key file for authentication\n"); + printf(" -noagent disable use of Pageant\n"); + printf(" -agent enable use of Pageant\n"); + printf(" -batch disable all interactive prompts\n"); + printf(" -unsafe allow server-side wildcards (DANGEROUS)\n"); + printf(" -sftp force use of SFTP protocol\n"); + printf(" -scp force use of SCP protocol\n"); +#if 0 + /* + * -gui is an internal option, used by GUI front ends to get + * pscp to pass progress reports back to them. It's not an + * ordinary user-accessible option, so it shouldn't be part of + * the command-line help. The only people who need to know + * about it are programmers, and they can read the source. + */ + printf + (" -gui hWnd GUI mode with the windows handle for receiving messages\n"); +#endif + cleanup_exit(1); +} + +void version(void) +{ + printf("pscp: %s\n", ver); + cleanup_exit(1); +} + +void cmdline_error(char *p, ...) +{ + va_list ap; + fprintf(stderr, "pscp: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fprintf(stderr, "\n try typing just \"pscp\" for help\n"); + exit(1); +} + +/* + * Main program. (Called `psftp_main' because it gets called from + * *sftp.c; bit silly, I know, but it had to be called _something_.) + */ +int psftp_main(int argc, char *argv[]) +{ + int i; + + default_protocol = PROT_TELNET; + + flags = FLAG_STDERR +#ifdef FLAG_SYNCAGENT + | FLAG_SYNCAGENT +#endif + ; + cmdline_tooltype = TOOLTYPE_FILETRANSFER; + sk_init(); + + /* Load Default Settings before doing anything else. */ + do_defaults(NULL, &cfg); + loaded_session = FALSE; + + for (i = 1; i < argc; i++) { + int ret; + if (argv[i][0] != '-') + break; + ret = cmdline_process_param(argv[i], i+1 2) + targetshouldbedirectory = 1; + + if (colon(argv[argc - 1]) != NULL) + toremote(argc, argv); + else + tolocal(argc, argv); + } + + if (back != NULL && back->connected(backhandle)) { + char ch; + back->special(backhandle, TS_EOF); + ssh_scp_recv((unsigned char *) &ch, 1); + } + random_save_seed(); + + cmdline_cleanup(); + console_provide_logctx(NULL); + back->free(backhandle); + backhandle = NULL; + back = NULL; + sk_cleanup(); + return (errs == 0 ? 0 : 1); +} + +/* end */ diff --git a/putty/PSFTP.C b/putty/PSFTP.C new file mode 100644 index 0000000..3583fd7 --- /dev/null +++ b/putty/PSFTP.C @@ -0,0 +1,2946 @@ +/* + * psftp.c: (platform-independent) front end for PSFTP. + */ + +#include +#include +#include +#include +#include + +#define PUTTY_DO_GLOBALS +#include "putty.h" +#include "psftp.h" +#include "storage.h" +#include "ssh.h" +#include "sftp.h" +#include "int64.h" + +const char *const appname = "PSFTP"; + +/* + * Since SFTP is a request-response oriented protocol, it requires + * no buffer management: when we send data, we stop and wait for an + * acknowledgement _anyway_, and so we can't possibly overfill our + * send buffer. + */ + +static int psftp_connect(char *userhost, char *user, int portnumber); +static int do_sftp_init(void); +void do_sftp_cleanup(); + +/* ---------------------------------------------------------------------- + * sftp client state. + */ + +char *pwd, *homedir; +static Backend *back; +static void *backhandle; +static Config cfg; + +/* ---------------------------------------------------------------------- + * Higher-level helper functions used in commands. + */ + +/* + * Attempt to canonify a pathname starting from the pwd. If + * canonification fails, at least fall back to returning a _valid_ + * pathname (though it may be ugly, eg /home/simon/../foobar). + */ +char *canonify(char *name) +{ + char *fullname, *canonname; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + + if (name[0] == '/') { + fullname = dupstr(name); + } else { + char *slash; + if (pwd[strlen(pwd) - 1] == '/') + slash = ""; + else + slash = "/"; + fullname = dupcat(pwd, slash, name, NULL); + } + + sftp_register(req = fxp_realpath_send(fullname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + canonname = fxp_realpath_recv(pktin, rreq); + + if (canonname) { + sfree(fullname); + return canonname; + } else { + /* + * Attempt number 2. Some FXP_REALPATH implementations + * (glibc-based ones, in particular) require the _whole_ + * path to point to something that exists, whereas others + * (BSD-based) only require all but the last component to + * exist. So if the first call failed, we should strip off + * everything from the last slash onwards and try again, + * then put the final component back on. + * + * Special cases: + * + * - if the last component is "/." or "/..", then we don't + * bother trying this because there's no way it can work. + * + * - if the thing actually ends with a "/", we remove it + * before we start. Except if the string is "/" itself + * (although I can't see why we'd have got here if so, + * because surely "/" would have worked the first + * time?), in which case we don't bother. + * + * - if there's no slash in the string at all, give up in + * confusion (we expect at least one because of the way + * we constructed the string). + */ + + int i; + char *returnname; + + i = strlen(fullname); + if (i > 2 && fullname[i - 1] == '/') + fullname[--i] = '\0'; /* strip trailing / unless at pos 0 */ + while (i > 0 && fullname[--i] != '/'); + + /* + * Give up on special cases. + */ + if (fullname[i] != '/' || /* no slash at all */ + !strcmp(fullname + i, "/.") || /* ends in /. */ + !strcmp(fullname + i, "/..") || /* ends in /.. */ + !strcmp(fullname, "/")) { + return fullname; + } + + /* + * Now i points at the slash. Deal with the final special + * case i==0 (ie the whole path was "/nonexistentfile"). + */ + fullname[i] = '\0'; /* separate the string */ + if (i == 0) { + sftp_register(req = fxp_realpath_send("/")); + } else { + sftp_register(req = fxp_realpath_send(fullname)); + } + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + canonname = fxp_realpath_recv(pktin, rreq); + + if (!canonname) { + /* Even that failed. Restore our best guess at the + * constructed filename and give up */ + fullname[i] = '/'; /* restore slash and last component */ + return fullname; + } + + /* + * We have a canonical name for all but the last path + * component. Concatenate the last component and return. + */ + returnname = dupcat(canonname, + canonname[strlen(canonname) - 1] == + '/' ? "" : "/", fullname + i + 1, NULL); + sfree(fullname); + sfree(canonname); + return returnname; + } +} + +/* + * Return a pointer to the portion of str that comes after the last + * slash (or backslash or colon, if `local' is TRUE). + */ +static char *stripslashes(char *str, int local) +{ + char *p; + + if (local) { + p = strchr(str, ':'); + if (p) str = p+1; + } + + p = strrchr(str, '/'); + if (p) str = p+1; + + if (local) { + p = strrchr(str, '\\'); + if (p) str = p+1; + } + + return str; +} + +/* + * qsort comparison routine for fxp_name structures. Sorts by real + * file name. + */ +static int sftp_name_compare(const void *av, const void *bv) +{ + const struct fxp_name *const *a = (const struct fxp_name *const *) av; + const struct fxp_name *const *b = (const struct fxp_name *const *) bv; + return strcmp((*a)->filename, (*b)->filename); +} + +/* + * Likewise, but for a bare char *. + */ +static int bare_name_compare(const void *av, const void *bv) +{ + const char **a = (const char **) av; + const char **b = (const char **) bv; + return strcmp(*a, *b); +} + +static void not_connected(void) +{ + printf("psftp: not connected to a host; use \"open host.name\"\n"); +} + +/* ---------------------------------------------------------------------- + * The meat of the `get' and `put' commands. + */ +int sftp_get_file(char *fname, char *outfname, int recurse, int restart) +{ + struct fxp_handle *fh; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + struct fxp_xfer *xfer; + uint64 offset; + WFile *file; + int ret, shown_err = FALSE; + + /* + * In recursive mode, see if we're dealing with a directory. + * (If we're not in recursive mode, we need not even check: the + * subsequent FXP_OPEN will return a usable error message.) + */ + if (recurse) { + struct fxp_attrs attrs; + int result; + + sftp_register(req = fxp_stat_send(fname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_stat_recv(pktin, rreq, &attrs); + + if (result && + (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && + (attrs.permissions & 0040000)) { + + struct fxp_handle *dirhandle; + int nnames, namesize; + struct fxp_name **ournames; + struct fxp_names *names; + int i; + + /* + * First, attempt to create the destination directory, + * unless it already exists. + */ + if (file_type(outfname) != FILE_TYPE_DIRECTORY && + !create_directory(outfname)) { + printf("%s: Cannot create directory\n", outfname); + return 0; + } + + /* + * Now get the list of filenames in the remote + * directory. + */ + sftp_register(req = fxp_opendir_send(fname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + dirhandle = fxp_opendir_recv(pktin, rreq); + + if (!dirhandle) { + printf("%s: unable to open directory: %s\n", + fname, fxp_error()); + return 0; + } + nnames = namesize = 0; + ournames = NULL; + while (1) { + int i; + + sftp_register(req = fxp_readdir_send(dirhandle)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + names = fxp_readdir_recv(pktin, rreq); + + if (names == NULL) { + if (fxp_error_type() == SSH_FX_EOF) + break; + printf("%s: reading directory: %s\n", fname, fxp_error()); + sfree(ournames); + return 0; + } + if (names->nnames == 0) { + fxp_free_names(names); + break; + } + if (nnames + names->nnames >= namesize) { + namesize += names->nnames + 128; + ournames = sresize(ournames, namesize, struct fxp_name *); + } + for (i = 0; i < names->nnames; i++) + if (strcmp(names->names[i].filename, ".") && + strcmp(names->names[i].filename, "..")) { + if (!vet_filename(names->names[i].filename)) { + printf("ignoring potentially dangerous server-" + "supplied filename '%s'\n", + names->names[i].filename); + } else { + ournames[nnames++] = + fxp_dup_name(&names->names[i]); + } + } + fxp_free_names(names); + } + sftp_register(req = fxp_close_send(dirhandle)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + /* + * Sort the names into a clear order. This ought to + * make things more predictable when we're doing a + * reget of the same directory, just in case two + * readdirs on the same remote directory return a + * different order. + */ + qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); + + /* + * If we're in restart mode, find the last filename on + * this list that already exists. We may have to do a + * reget on _that_ file, but shouldn't have to do + * anything on the previous files. + * + * If none of them exists, of course, we start at 0. + */ + i = 0; + if (restart) { + while (i < nnames) { + char *nextoutfname; + int ret; + if (outfname) + nextoutfname = dir_file_cat(outfname, + ournames[i]->filename); + else + nextoutfname = dupstr(ournames[i]->filename); + ret = (file_type(nextoutfname) == FILE_TYPE_NONEXISTENT); + sfree(nextoutfname); + if (ret) + break; + i++; + } + if (i > 0) + i--; + } + + /* + * Now we're ready to recurse. Starting at ournames[i] + * and continuing on to the end of the list, we + * construct a new source and target file name, and + * call sftp_get_file again. + */ + for (; i < nnames; i++) { + char *nextfname, *nextoutfname; + int ret; + + nextfname = dupcat(fname, "/", ournames[i]->filename, NULL); + if (outfname) + nextoutfname = dir_file_cat(outfname, + ournames[i]->filename); + else + nextoutfname = dupstr(ournames[i]->filename); + ret = sftp_get_file(nextfname, nextoutfname, recurse, restart); + restart = FALSE; /* after first partial file, do full */ + sfree(nextoutfname); + sfree(nextfname); + if (!ret) { + for (i = 0; i < nnames; i++) { + fxp_free_name(ournames[i]); + } + sfree(ournames); + return 0; + } + } + + /* + * Done this recursion level. Free everything. + */ + for (i = 0; i < nnames; i++) { + fxp_free_name(ournames[i]); + } + sfree(ournames); + + return 1; + } + } + + sftp_register(req = fxp_open_send(fname, SSH_FXF_READ)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fh = fxp_open_recv(pktin, rreq); + + if (!fh) { + printf("%s: open for read: %s\n", fname, fxp_error()); + return 0; + } + + if (restart) { + file = open_existing_wfile(outfname, NULL); + } else { + file = open_new_file(outfname); + } + + if (!file) { + printf("local: unable to open %s\n", outfname); + + sftp_register(req = fxp_close_send(fh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + return 0; + } + + if (restart) { + char decbuf[30]; + if (seek_file(file, uint64_make(0,0) , FROM_END) == -1) { + close_wfile(file); + printf("reget: cannot restart %s - file too large\n", + outfname); + sftp_register(req = fxp_close_send(fh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + return 0; + } + + offset = get_file_posn(file); + uint64_decimal(offset, decbuf); + printf("reget: restarting at file position %s\n", decbuf); + } else { + offset = uint64_make(0, 0); + } + + printf("remote:%s => local:%s\n", fname, outfname); + + /* + * FIXME: we can use FXP_FSTAT here to get the file size, and + * thus put up a progress bar. + */ + ret = 1; + xfer = xfer_download_init(fh, offset); + while (!xfer_done(xfer)) { + void *vbuf; + int ret, len; + int wpos, wlen; + + xfer_download_queue(xfer); + pktin = sftp_recv(); + ret = xfer_download_gotpkt(xfer, pktin); + + if (ret < 0) { + if (!shown_err) { + printf("error while reading: %s\n", fxp_error()); + shown_err = TRUE; + } + ret = 0; + } + + while (xfer_download_data(xfer, &vbuf, &len)) { + unsigned char *buf = (unsigned char *)vbuf; + + wpos = 0; + while (wpos < len) { + wlen = write_to_file(file, buf + wpos, len - wpos); + if (wlen <= 0) { + printf("error while writing local file\n"); + ret = 0; + xfer_set_error(xfer); + break; + } + wpos += wlen; + } + if (wpos < len) { /* we had an error */ + ret = 0; + xfer_set_error(xfer); + } + + sfree(vbuf); + } + } + + xfer_cleanup(xfer); + + close_wfile(file); + + sftp_register(req = fxp_close_send(fh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + return ret; +} + +int sftp_put_file(char *fname, char *outfname, int recurse, int restart) +{ + struct fxp_handle *fh; + struct fxp_xfer *xfer; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + uint64 offset; + RFile *file; + int ret, err, eof; + + /* + * In recursive mode, see if we're dealing with a directory. + * (If we're not in recursive mode, we need not even check: the + * subsequent fopen will return an error message.) + */ + if (recurse && file_type(fname) == FILE_TYPE_DIRECTORY) { + struct fxp_attrs attrs; + int result; + int nnames, namesize; + char *name, **ournames; + DirHandle *dh; + int i; + + /* + * First, attempt to create the destination directory, + * unless it already exists. + */ + sftp_register(req = fxp_stat_send(outfname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_stat_recv(pktin, rreq, &attrs); + if (!result || + !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) || + !(attrs.permissions & 0040000)) { + sftp_register(req = fxp_mkdir_send(outfname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_mkdir_recv(pktin, rreq); + + if (!result) { + printf("%s: create directory: %s\n", + outfname, fxp_error()); + return 0; + } + } + + /* + * Now get the list of filenames in the local directory. + */ + nnames = namesize = 0; + ournames = NULL; + + dh = open_directory(fname); + if (!dh) { + printf("%s: unable to open directory\n", fname); + return 0; + } + while ((name = read_filename(dh)) != NULL) { + if (nnames >= namesize) { + namesize += 128; + ournames = sresize(ournames, namesize, char *); + } + ournames[nnames++] = name; + } + close_directory(dh); + + /* + * Sort the names into a clear order. This ought to make + * things more predictable when we're doing a reput of the + * same directory, just in case two readdirs on the same + * local directory return a different order. + */ + qsort(ournames, nnames, sizeof(*ournames), bare_name_compare); + + /* + * If we're in restart mode, find the last filename on this + * list that already exists. We may have to do a reput on + * _that_ file, but shouldn't have to do anything on the + * previous files. + * + * If none of them exists, of course, we start at 0. + */ + i = 0; + if (restart) { + while (i < nnames) { + char *nextoutfname; + nextoutfname = dupcat(outfname, "/", ournames[i], NULL); + sftp_register(req = fxp_stat_send(nextoutfname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_stat_recv(pktin, rreq, &attrs); + sfree(nextoutfname); + if (!result) + break; + i++; + } + if (i > 0) + i--; + } + + /* + * Now we're ready to recurse. Starting at ournames[i] + * and continuing on to the end of the list, we + * construct a new source and target file name, and + * call sftp_put_file again. + */ + for (; i < nnames; i++) { + char *nextfname, *nextoutfname; + int ret; + + if (fname) + nextfname = dir_file_cat(fname, ournames[i]); + else + nextfname = dupstr(ournames[i]); + nextoutfname = dupcat(outfname, "/", ournames[i], NULL); + ret = sftp_put_file(nextfname, nextoutfname, recurse, restart); + restart = FALSE; /* after first partial file, do full */ + sfree(nextoutfname); + sfree(nextfname); + if (!ret) { + for (i = 0; i < nnames; i++) { + sfree(ournames[i]); + } + sfree(ournames); + return 0; + } + } + + /* + * Done this recursion level. Free everything. + */ + for (i = 0; i < nnames; i++) { + sfree(ournames[i]); + } + sfree(ournames); + + return 1; + } + + file = open_existing_file(fname, NULL, NULL, NULL); + if (!file) { + printf("local: unable to open %s\n", fname); + return 0; + } + if (restart) { + sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE)); + } else { + sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE | + SSH_FXF_CREAT | SSH_FXF_TRUNC)); + } + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fh = fxp_open_recv(pktin, rreq); + + if (!fh) { + close_rfile(file); + printf("%s: open for write: %s\n", outfname, fxp_error()); + return 0; + } + + if (restart) { + char decbuf[30]; + struct fxp_attrs attrs; + int ret; + + sftp_register(req = fxp_fstat_send(fh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + ret = fxp_fstat_recv(pktin, rreq, &attrs); + + if (!ret) { + close_rfile(file); + printf("read size of %s: %s\n", outfname, fxp_error()); + return 0; + } + if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { + close_rfile(file); + printf("read size of %s: size was not given\n", outfname); + return 0; + } + offset = attrs.size; + uint64_decimal(offset, decbuf); + printf("reput: restarting at file position %s\n", decbuf); + + if (seek_file((WFile *)file, offset, FROM_START) != 0) + seek_file((WFile *)file, uint64_make(0,0), FROM_END); /* *shrug* */ + } else { + offset = uint64_make(0, 0); + } + + printf("local:%s => remote:%s\n", fname, outfname); + + /* + * FIXME: we can use FXP_FSTAT here to get the file size, and + * thus put up a progress bar. + */ + ret = 1; + xfer = xfer_upload_init(fh, offset); + err = eof = 0; + while ((!err && !eof) || !xfer_done(xfer)) { + char buffer[4096]; + int len, ret; + + while (xfer_upload_ready(xfer) && !err && !eof) { + len = read_from_file(file, buffer, sizeof(buffer)); + if (len == -1) { + printf("error while reading local file\n"); + err = 1; + } else if (len == 0) { + eof = 1; + } else { + xfer_upload_data(xfer, buffer, len); + } + } + + if (!xfer_done(xfer)) { + pktin = sftp_recv(); + ret = xfer_upload_gotpkt(xfer, pktin); + if (ret <= 0 && !err) { + printf("error while writing: %s\n", fxp_error()); + err = 1; + } + } + } + + xfer_cleanup(xfer); + + sftp_register(req = fxp_close_send(fh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + close_rfile(file); + + return ret; +} + +/* ---------------------------------------------------------------------- + * A remote wildcard matcher, providing a similar interface to the + * local one in psftp.h. + */ + +typedef struct SftpWildcardMatcher { + struct fxp_handle *dirh; + struct fxp_names *names; + int namepos; + char *wildcard, *prefix; +} SftpWildcardMatcher; + +SftpWildcardMatcher *sftp_begin_wildcard_matching(char *name) +{ + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + char *wildcard; + char *unwcdir, *tmpdir, *cdir; + int len, check; + SftpWildcardMatcher *swcm; + struct fxp_handle *dirh; + + /* + * We don't handle multi-level wildcards; so we expect to find + * a fully specified directory part, followed by a wildcard + * after that. + */ + wildcard = stripslashes(name, 0); + + unwcdir = dupstr(name); + len = wildcard - name; + unwcdir[len] = '\0'; + if (len > 0 && unwcdir[len-1] == '/') + unwcdir[len-1] = '\0'; + tmpdir = snewn(1 + len, char); + check = wc_unescape(tmpdir, unwcdir); + sfree(tmpdir); + + if (!check) { + printf("Multiple-level wildcards are not supported\n"); + sfree(unwcdir); + return NULL; + } + + cdir = canonify(unwcdir); + + sftp_register(req = fxp_opendir_send(cdir)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + dirh = fxp_opendir_recv(pktin, rreq); + + if (dirh) { + swcm = snew(SftpWildcardMatcher); + swcm->dirh = dirh; + swcm->names = NULL; + swcm->wildcard = dupstr(wildcard); + swcm->prefix = unwcdir; + } else { + printf("Unable to open %s: %s\n", cdir, fxp_error()); + swcm = NULL; + sfree(unwcdir); + } + + sfree(cdir); + + return swcm; +} + +char *sftp_wildcard_get_filename(SftpWildcardMatcher *swcm) +{ + struct fxp_name *name; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + + while (1) { + if (swcm->names && swcm->namepos >= swcm->names->nnames) { + fxp_free_names(swcm->names); + swcm->names = NULL; + } + + if (!swcm->names) { + sftp_register(req = fxp_readdir_send(swcm->dirh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + swcm->names = fxp_readdir_recv(pktin, rreq); + + if (!swcm->names) { + if (fxp_error_type() != SSH_FX_EOF) + printf("%s: reading directory: %s\n", swcm->prefix, + fxp_error()); + return NULL; + } + + swcm->namepos = 0; + } + + assert(swcm->names && swcm->namepos < swcm->names->nnames); + + name = &swcm->names->names[swcm->namepos++]; + + if (!strcmp(name->filename, ".") || !strcmp(name->filename, "..")) + continue; /* expected bad filenames */ + + if (!vet_filename(name->filename)) { + printf("ignoring potentially dangerous server-" + "supplied filename '%s'\n", name->filename); + continue; /* unexpected bad filename */ + } + + if (!wc_match(swcm->wildcard, name->filename)) + continue; /* doesn't match the wildcard */ + + /* + * We have a working filename. Return it. + */ + return dupprintf("%s%s%s", swcm->prefix, + (!swcm->prefix[0] || + swcm->prefix[strlen(swcm->prefix)-1]=='/' ? + "" : "/"), + name->filename); + } +} + +void sftp_finish_wildcard_matching(SftpWildcardMatcher *swcm) +{ + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + + sftp_register(req = fxp_close_send(swcm->dirh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + if (swcm->names) + fxp_free_names(swcm->names); + + sfree(swcm->prefix); + sfree(swcm->wildcard); + + sfree(swcm); +} + +/* + * General function to match a potential wildcard in a filename + * argument and iterate over every matching file. Used in several + * PSFTP commands (rmdir, rm, chmod, mv). + */ +int wildcard_iterate(char *filename, int (*func)(void *, char *), void *ctx) +{ + char *unwcfname, *newname, *cname; + int is_wc, ret; + + unwcfname = snewn(strlen(filename)+1, char); + is_wc = !wc_unescape(unwcfname, filename); + + if (is_wc) { + SftpWildcardMatcher *swcm = sftp_begin_wildcard_matching(filename); + int matched = FALSE; + sfree(unwcfname); + + if (!swcm) + return 0; + + ret = 1; + + while ( (newname = sftp_wildcard_get_filename(swcm)) != NULL ) { + cname = canonify(newname); + if (!cname) { + printf("%s: canonify: %s\n", newname, fxp_error()); + ret = 0; + } + matched = TRUE; + ret &= func(ctx, cname); + sfree(cname); + } + + if (!matched) { + /* Politely warn the user that nothing matched. */ + printf("%s: nothing matched\n", filename); + } + + sftp_finish_wildcard_matching(swcm); + } else { + cname = canonify(unwcfname); + if (!cname) { + printf("%s: canonify: %s\n", filename, fxp_error()); + ret = 0; + } + ret = func(ctx, cname); + sfree(cname); + sfree(unwcfname); + } + + return ret; +} + +/* + * Handy helper function. + */ +int is_wildcard(char *name) +{ + char *unwcfname = snewn(strlen(name)+1, char); + int is_wc = !wc_unescape(unwcfname, name); + sfree(unwcfname); + return is_wc; +} + +/* ---------------------------------------------------------------------- + * Actual sftp commands. + */ +struct sftp_command { + char **words; + int nwords, wordssize; + int (*obey) (struct sftp_command *); /* returns <0 to quit */ +}; + +int sftp_cmd_null(struct sftp_command *cmd) +{ + return 1; /* success */ +} + +int sftp_cmd_unknown(struct sftp_command *cmd) +{ + printf("psftp: unknown command \"%s\"\n", cmd->words[0]); + return 0; /* failure */ +} + +int sftp_cmd_quit(struct sftp_command *cmd) +{ + return -1; +} + +int sftp_cmd_close(struct sftp_command *cmd) +{ + if (back == NULL) { + not_connected(); + return 0; + } + + if (back != NULL && back->connected(backhandle)) { + char ch; + back->special(backhandle, TS_EOF); + sftp_recvdata(&ch, 1); + } + do_sftp_cleanup(); + + return 0; +} + +/* + * List a directory. If no arguments are given, list pwd; otherwise + * list the directory given in words[1]. + */ +int sftp_cmd_ls(struct sftp_command *cmd) +{ + struct fxp_handle *dirh; + struct fxp_names *names; + struct fxp_name **ournames; + int nnames, namesize; + char *dir, *cdir, *unwcdir, *wildcard; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + int i; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 2) + dir = "."; + else + dir = cmd->words[1]; + + unwcdir = snewn(1 + strlen(dir), char); + if (wc_unescape(unwcdir, dir)) { + dir = unwcdir; + wildcard = NULL; + } else { + char *tmpdir; + int len, check; + + wildcard = stripslashes(dir, 0); + unwcdir = dupstr(dir); + len = wildcard - dir; + unwcdir[len] = '\0'; + if (len > 0 && unwcdir[len-1] == '/') + unwcdir[len-1] = '\0'; + tmpdir = snewn(1 + len, char); + check = wc_unescape(tmpdir, unwcdir); + sfree(tmpdir); + if (!check) { + printf("Multiple-level wildcards are not supported\n"); + sfree(unwcdir); + return 0; + } + dir = unwcdir; + } + + cdir = canonify(dir); + if (!cdir) { + printf("%s: canonify: %s\n", dir, fxp_error()); + sfree(unwcdir); + return 0; + } + + printf("Listing directory %s\n", cdir); + + sftp_register(req = fxp_opendir_send(cdir)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + dirh = fxp_opendir_recv(pktin, rreq); + + if (dirh == NULL) { + printf("Unable to open %s: %s\n", dir, fxp_error()); + } else { + nnames = namesize = 0; + ournames = NULL; + + while (1) { + + sftp_register(req = fxp_readdir_send(dirh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + names = fxp_readdir_recv(pktin, rreq); + + if (names == NULL) { + if (fxp_error_type() == SSH_FX_EOF) + break; + printf("Reading directory %s: %s\n", dir, fxp_error()); + break; + } + if (names->nnames == 0) { + fxp_free_names(names); + break; + } + + if (nnames + names->nnames >= namesize) { + namesize += names->nnames + 128; + ournames = sresize(ournames, namesize, struct fxp_name *); + } + + for (i = 0; i < names->nnames; i++) + if (!wildcard || wc_match(wildcard, names->names[i].filename)) + ournames[nnames++] = fxp_dup_name(&names->names[i]); + + fxp_free_names(names); + } + sftp_register(req = fxp_close_send(dirh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + /* + * Now we have our filenames. Sort them by actual file + * name, and then output the longname parts. + */ + qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); + + /* + * And print them. + */ + for (i = 0; i < nnames; i++) { + printf("%s\n", ournames[i]->longname); + fxp_free_name(ournames[i]); + } + sfree(ournames); + } + + sfree(cdir); + sfree(unwcdir); + + return 1; +} + +/* + * Change directories. We do this by canonifying the new name, then + * trying to OPENDIR it. Only if that succeeds do we set the new pwd. + */ +int sftp_cmd_cd(struct sftp_command *cmd) +{ + struct fxp_handle *dirh; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + char *dir; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 2) + dir = dupstr(homedir); + else + dir = canonify(cmd->words[1]); + + if (!dir) { + printf("%s: canonify: %s\n", dir, fxp_error()); + return 0; + } + + sftp_register(req = fxp_opendir_send(dir)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + dirh = fxp_opendir_recv(pktin, rreq); + + if (!dirh) { + printf("Directory %s: %s\n", dir, fxp_error()); + sfree(dir); + return 0; + } + + sftp_register(req = fxp_close_send(dirh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + sfree(pwd); + pwd = dir; + printf("Remote directory is now %s\n", pwd); + + return 1; +} + +/* + * Print current directory. Easy as pie. + */ +int sftp_cmd_pwd(struct sftp_command *cmd) +{ + if (back == NULL) { + not_connected(); + return 0; + } + + printf("Remote directory is %s\n", pwd); + return 1; +} + +/* + * Get a file and save it at the local end. We have three very + * similar commands here. The basic one is `get'; `reget' differs + * in that it checks for the existence of the destination file and + * starts from where a previous aborted transfer left off; `mget' + * differs in that it interprets all its arguments as files to + * transfer (never as a different local name for a remote file) and + * can handle wildcards. + */ +int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) +{ + char *fname, *unwcfname, *origfname, *origwfname, *outfname; + int i, ret; + int recurse = FALSE; + + if (back == NULL) { + not_connected(); + return 0; + } + + i = 1; + while (i < cmd->nwords && cmd->words[i][0] == '-') { + if (!strcmp(cmd->words[i], "--")) { + /* finish processing options */ + i++; + break; + } else if (!strcmp(cmd->words[i], "-r")) { + recurse = TRUE; + } else { + printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]); + return 0; + } + i++; + } + + if (i >= cmd->nwords) { + printf("%s: expects a filename\n", cmd->words[0]); + return 0; + } + + ret = 1; + do { + SftpWildcardMatcher *swcm; + + origfname = cmd->words[i++]; + unwcfname = snewn(strlen(origfname)+1, char); + + if (multiple && !wc_unescape(unwcfname, origfname)) { + swcm = sftp_begin_wildcard_matching(origfname); + if (!swcm) { + sfree(unwcfname); + continue; + } + origwfname = sftp_wildcard_get_filename(swcm); + if (!origwfname) { + /* Politely warn the user that nothing matched. */ + printf("%s: nothing matched\n", origfname); + sftp_finish_wildcard_matching(swcm); + sfree(unwcfname); + continue; + } + } else { + origwfname = origfname; + swcm = NULL; + } + + while (origwfname) { + fname = canonify(origwfname); + + if (!fname) { + printf("%s: canonify: %s\n", origwfname, fxp_error()); + sfree(unwcfname); + return 0; + } + + if (!multiple && i < cmd->nwords) + outfname = cmd->words[i++]; + else + outfname = stripslashes(origwfname, 0); + + ret = sftp_get_file(fname, outfname, recurse, restart); + + sfree(fname); + + if (swcm) { + sfree(origwfname); + origwfname = sftp_wildcard_get_filename(swcm); + } else { + origwfname = NULL; + } + } + sfree(unwcfname); + if (swcm) + sftp_finish_wildcard_matching(swcm); + if (!ret) + return ret; + + } while (multiple && i < cmd->nwords); + + return ret; +} +int sftp_cmd_get(struct sftp_command *cmd) +{ + return sftp_general_get(cmd, 0, 0); +} +int sftp_cmd_mget(struct sftp_command *cmd) +{ + return sftp_general_get(cmd, 0, 1); +} +int sftp_cmd_reget(struct sftp_command *cmd) +{ + return sftp_general_get(cmd, 1, 0); +} + +/* + * Send a file and store it at the remote end. We have three very + * similar commands here. The basic one is `put'; `reput' differs + * in that it checks for the existence of the destination file and + * starts from where a previous aborted transfer left off; `mput' + * differs in that it interprets all its arguments as files to + * transfer (never as a different remote name for a local file) and + * can handle wildcards. + */ +int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) +{ + char *fname, *wfname, *origoutfname, *outfname; + int i, ret; + int recurse = FALSE; + + if (back == NULL) { + not_connected(); + return 0; + } + + i = 1; + while (i < cmd->nwords && cmd->words[i][0] == '-') { + if (!strcmp(cmd->words[i], "--")) { + /* finish processing options */ + i++; + break; + } else if (!strcmp(cmd->words[i], "-r")) { + recurse = TRUE; + } else { + printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]); + return 0; + } + i++; + } + + if (i >= cmd->nwords) { + printf("%s: expects a filename\n", cmd->words[0]); + return 0; + } + + ret = 1; + do { + WildcardMatcher *wcm; + fname = cmd->words[i++]; + + if (multiple && test_wildcard(fname, FALSE) == WCTYPE_WILDCARD) { + wcm = begin_wildcard_matching(fname); + wfname = wildcard_get_filename(wcm); + if (!wfname) { + /* Politely warn the user that nothing matched. */ + printf("%s: nothing matched\n", fname); + finish_wildcard_matching(wcm); + continue; + } + } else { + wfname = fname; + wcm = NULL; + } + + while (wfname) { + if (!multiple && i < cmd->nwords) + origoutfname = cmd->words[i++]; + else + origoutfname = stripslashes(wfname, 1); + + outfname = canonify(origoutfname); + if (!outfname) { + printf("%s: canonify: %s\n", origoutfname, fxp_error()); + if (wcm) { + sfree(wfname); + finish_wildcard_matching(wcm); + } + return 0; + } + ret = sftp_put_file(wfname, outfname, recurse, restart); + sfree(outfname); + + if (wcm) { + sfree(wfname); + wfname = wildcard_get_filename(wcm); + } else { + wfname = NULL; + } + } + + if (wcm) + finish_wildcard_matching(wcm); + + if (!ret) + return ret; + + } while (multiple && i < cmd->nwords); + + return ret; +} +int sftp_cmd_put(struct sftp_command *cmd) +{ + return sftp_general_put(cmd, 0, 0); +} +int sftp_cmd_mput(struct sftp_command *cmd) +{ + return sftp_general_put(cmd, 0, 1); +} +int sftp_cmd_reput(struct sftp_command *cmd) +{ + return sftp_general_put(cmd, 1, 0); +} + +int sftp_cmd_mkdir(struct sftp_command *cmd) +{ + char *dir; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + int result; + int i, ret; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 2) { + printf("mkdir: expects a directory\n"); + return 0; + } + + ret = 1; + for (i = 1; i < cmd->nwords; i++) { + dir = canonify(cmd->words[i]); + if (!dir) { + printf("%s: canonify: %s\n", dir, fxp_error()); + return 0; + } + + sftp_register(req = fxp_mkdir_send(dir)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_mkdir_recv(pktin, rreq); + + if (!result) { + printf("mkdir %s: %s\n", dir, fxp_error()); + ret = 0; + } else + printf("mkdir %s: OK\n", dir); + + sfree(dir); + } + + return ret; +} + +static int sftp_action_rmdir(void *vctx, char *dir) +{ + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + int result; + + sftp_register(req = fxp_rmdir_send(dir)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_rmdir_recv(pktin, rreq); + + if (!result) { + printf("rmdir %s: %s\n", dir, fxp_error()); + return 0; + } + + printf("rmdir %s: OK\n", dir); + + return 1; +} + +int sftp_cmd_rmdir(struct sftp_command *cmd) +{ + int i, ret; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 2) { + printf("rmdir: expects a directory\n"); + return 0; + } + + ret = 1; + for (i = 1; i < cmd->nwords; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_rmdir, NULL); + + return ret; +} + +static int sftp_action_rm(void *vctx, char *fname) +{ + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + int result; + + sftp_register(req = fxp_remove_send(fname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_remove_recv(pktin, rreq); + + if (!result) { + printf("rm %s: %s\n", fname, fxp_error()); + return 0; + } + + printf("rm %s: OK\n", fname); + + return 1; +} + +int sftp_cmd_rm(struct sftp_command *cmd) +{ + int i, ret; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 2) { + printf("rm: expects a filename\n"); + return 0; + } + + ret = 1; + for (i = 1; i < cmd->nwords; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_rm, NULL); + + return ret; +} + +static int check_is_dir(char *dstfname) +{ + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + struct fxp_attrs attrs; + int result; + + sftp_register(req = fxp_stat_send(dstfname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_stat_recv(pktin, rreq, &attrs); + + if (result && + (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && + (attrs.permissions & 0040000)) + return TRUE; + else + return FALSE; +} + +struct sftp_context_mv { + char *dstfname; + int dest_is_dir; +}; + +static int sftp_action_mv(void *vctx, char *srcfname) +{ + struct sftp_context_mv *ctx = (struct sftp_context_mv *)vctx; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + const char *error; + char *finalfname, *newcanon = NULL; + int ret, result; + + if (ctx->dest_is_dir) { + char *p; + char *newname; + + p = srcfname + strlen(srcfname); + while (p > srcfname && p[-1] != '/') p--; + newname = dupcat(ctx->dstfname, "/", p, NULL); + newcanon = canonify(newname); + if (!newcanon) { + printf("%s: canonify: %s\n", newname, fxp_error()); + sfree(newname); + return 0; + } + sfree(newname); + + finalfname = newcanon; + } else { + finalfname = ctx->dstfname; + } + + sftp_register(req = fxp_rename_send(srcfname, finalfname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_rename_recv(pktin, rreq); + + error = result ? NULL : fxp_error(); + + if (error) { + printf("mv %s %s: %s\n", srcfname, finalfname, error); + ret = 0; + } else { + printf("%s -> %s\n", srcfname, finalfname); + ret = 1; + } + + sfree(newcanon); + return ret; +} + +int sftp_cmd_mv(struct sftp_command *cmd) +{ + struct sftp_context_mv actx, *ctx = &actx; + int i, ret; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 3) { + printf("mv: expects two filenames\n"); + return 0; + } + + ctx->dstfname = canonify(cmd->words[cmd->nwords-1]); + if (!ctx->dstfname) { + printf("%s: canonify: %s\n", ctx->dstfname, fxp_error()); + return 0; + } + + /* + * If there's more than one source argument, or one source + * argument which is a wildcard, we _require_ that the + * destination is a directory. + */ + ctx->dest_is_dir = check_is_dir(ctx->dstfname); + if ((cmd->nwords > 3 || is_wildcard(cmd->words[1])) && !ctx->dest_is_dir) { + printf("mv: multiple or wildcard arguments require the destination" + " to be a directory\n"); + sfree(ctx->dstfname); + return 0; + } + + /* + * Now iterate over the source arguments. + */ + ret = 1; + for (i = 1; i < cmd->nwords-1; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_mv, ctx); + + sfree(ctx->dstfname); + return ret; +} + +struct sftp_context_chmod { + unsigned attrs_clr, attrs_xor; +}; + +static int sftp_action_chmod(void *vctx, char *fname) +{ + struct fxp_attrs attrs; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + int result; + unsigned oldperms, newperms; + struct sftp_context_chmod *ctx = (struct sftp_context_chmod *)vctx; + + sftp_register(req = fxp_stat_send(fname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_stat_recv(pktin, rreq, &attrs); + + if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { + printf("get attrs for %s: %s\n", fname, + result ? "file permissions not provided" : fxp_error()); + return 0; + } + + attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */ + oldperms = attrs.permissions & 07777; + attrs.permissions &= ~ctx->attrs_clr; + attrs.permissions ^= ctx->attrs_xor; + newperms = attrs.permissions & 07777; + + if (oldperms == newperms) + return 1; /* no need to do anything! */ + + sftp_register(req = fxp_setstat_send(fname, attrs)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_setstat_recv(pktin, rreq); + + if (!result) { + printf("set attrs for %s: %s\n", fname, fxp_error()); + return 0; + } + + printf("%s: %04o -> %04o\n", fname, oldperms, newperms); + + return 1; +} + +int sftp_cmd_chmod(struct sftp_command *cmd) +{ + char *mode; + int i, ret; + struct sftp_context_chmod actx, *ctx = &actx; + + if (back == NULL) { + not_connected(); + return 0; + } + + if (cmd->nwords < 3) { + printf("chmod: expects a mode specifier and a filename\n"); + return 0; + } + + /* + * Attempt to parse the mode specifier in cmd->words[1]. We + * don't support the full horror of Unix chmod; instead we + * support a much simpler syntax in which the user can either + * specify an octal number, or a comma-separated sequence of + * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may + * _only_ be omitted if the only attribute mentioned is t, + * since all others require a user/group/other specification. + * Additionally, the s attribute may not be specified for any + * [ugoa] specifications other than exactly u or exactly g. + */ + ctx->attrs_clr = ctx->attrs_xor = 0; + mode = cmd->words[1]; + if (mode[0] >= '0' && mode[0] <= '9') { + if (mode[strspn(mode, "01234567")]) { + printf("chmod: numeric file modes should" + " contain digits 0-7 only\n"); + return 0; + } + ctx->attrs_clr = 07777; + sscanf(mode, "%o", &ctx->attrs_xor); + ctx->attrs_xor &= ctx->attrs_clr; + } else { + while (*mode) { + char *modebegin = mode; + unsigned subset, perms; + int action; + + subset = 0; + while (*mode && *mode != ',' && + *mode != '+' && *mode != '-' && *mode != '=') { + switch (*mode) { + case 'u': subset |= 04700; break; /* setuid, user perms */ + case 'g': subset |= 02070; break; /* setgid, group perms */ + case 'o': subset |= 00007; break; /* just other perms */ + case 'a': subset |= 06777; break; /* all of the above */ + default: + printf("chmod: file mode '%.*s' contains unrecognised" + " user/group/other specifier '%c'\n", + (int)strcspn(modebegin, ","), modebegin, *mode); + return 0; + } + mode++; + } + if (!*mode || *mode == ',') { + printf("chmod: file mode '%.*s' is incomplete\n", + (int)strcspn(modebegin, ","), modebegin); + return 0; + } + action = *mode++; + if (!*mode || *mode == ',') { + printf("chmod: file mode '%.*s' is incomplete\n", + (int)strcspn(modebegin, ","), modebegin); + return 0; + } + perms = 0; + while (*mode && *mode != ',') { + switch (*mode) { + case 'r': perms |= 00444; break; + case 'w': perms |= 00222; break; + case 'x': perms |= 00111; break; + case 't': perms |= 01000; subset |= 01000; break; + case 's': + if ((subset & 06777) != 04700 && + (subset & 06777) != 02070) { + printf("chmod: file mode '%.*s': set[ug]id bit should" + " be used with exactly one of u or g only\n", + (int)strcspn(modebegin, ","), modebegin); + return 0; + } + perms |= 06000; + break; + default: + printf("chmod: file mode '%.*s' contains unrecognised" + " permission specifier '%c'\n", + (int)strcspn(modebegin, ","), modebegin, *mode); + return 0; + } + mode++; + } + if (!(subset & 06777) && (perms &~ subset)) { + printf("chmod: file mode '%.*s' contains no user/group/other" + " specifier and permissions other than 't' \n", + (int)strcspn(modebegin, ","), modebegin); + return 0; + } + perms &= subset; + switch (action) { + case '+': + ctx->attrs_clr |= perms; + ctx->attrs_xor |= perms; + break; + case '-': + ctx->attrs_clr |= perms; + ctx->attrs_xor &= ~perms; + break; + case '=': + ctx->attrs_clr |= subset; + ctx->attrs_xor |= perms; + break; + } + if (*mode) mode++; /* eat comma */ + } + } + + ret = 1; + for (i = 2; i < cmd->nwords; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_chmod, ctx); + + return ret; +} + +static int sftp_cmd_open(struct sftp_command *cmd) +{ + int portnumber; + + if (back != NULL) { + printf("psftp: already connected\n"); + return 0; + } + + if (cmd->nwords < 2) { + printf("open: expects a host name\n"); + return 0; + } + + if (cmd->nwords > 2) { + portnumber = atoi(cmd->words[2]); + if (portnumber == 0) { + printf("open: invalid port number\n"); + return 0; + } + } else + portnumber = 0; + + if (psftp_connect(cmd->words[1], NULL, portnumber)) { + back = NULL; /* connection is already closed */ + return -1; /* this is fatal */ + } + do_sftp_init(); + return 1; +} + +static int sftp_cmd_lcd(struct sftp_command *cmd) +{ + char *currdir, *errmsg; + + if (cmd->nwords < 2) { + printf("lcd: expects a local directory name\n"); + return 0; + } + + errmsg = psftp_lcd(cmd->words[1]); + if (errmsg) { + printf("lcd: unable to change directory: %s\n", errmsg); + sfree(errmsg); + return 0; + } + + currdir = psftp_getcwd(); + printf("New local directory is %s\n", currdir); + sfree(currdir); + + return 1; +} + +static int sftp_cmd_lpwd(struct sftp_command *cmd) +{ + char *currdir; + + currdir = psftp_getcwd(); + printf("Current local directory is %s\n", currdir); + sfree(currdir); + + return 1; +} + +static int sftp_cmd_pling(struct sftp_command *cmd) +{ + int exitcode; + + exitcode = system(cmd->words[1]); + return (exitcode == 0); +} + +static int sftp_cmd_help(struct sftp_command *cmd); + +static struct sftp_cmd_lookup { + char *name; + /* + * For help purposes, there are two kinds of command: + * + * - primary commands, in which `longhelp' is non-NULL. In + * this case `shorthelp' is descriptive text, and `longhelp' + * is longer descriptive text intended to be printed after + * the command name. + * + * - alias commands, in which `longhelp' is NULL. In this case + * `shorthelp' is the name of a primary command, which + * contains the help that should double up for this command. + */ + int listed; /* do we list this in primary help? */ + char *shorthelp; + char *longhelp; + int (*obey) (struct sftp_command *); +} sftp_lookup[] = { + /* + * List of sftp commands. This is binary-searched so it MUST be + * in ASCII order. + */ + { + "!", TRUE, "run a local command", + "\n" + /* FIXME: this example is crap for non-Windows. */ + " Runs a local command. For example, \"!del myfile\".\n", + sftp_cmd_pling + }, + { + "bye", TRUE, "finish your SFTP session", + "\n" + " Terminates your SFTP session and quits the PSFTP program.\n", + sftp_cmd_quit + }, + { + "cd", TRUE, "change your remote working directory", + " [ ]\n" + " Change the remote working directory for your SFTP session.\n" + " If a new working directory is not supplied, you will be\n" + " returned to your home directory.\n", + sftp_cmd_cd + }, + { + "chmod", TRUE, "change file permissions and modes", + " [ ... ]\n" + " Change the file permissions on one or more remote files or\n" + " directories.\n" + " can be any octal Unix permission specifier.\n" + " Alternatively, can include the following modifiers:\n" + " u+r make file readable by owning user\n" + " u+w make file writable by owning user\n" + " u+x make file executable by owning user\n" + " u-r make file not readable by owning user\n" + " [also u-w, u-x]\n" + " g+r make file readable by members of owning group\n" + " [also g+w, g+x, g-r, g-w, g-x]\n" + " o+r make file readable by all other users\n" + " [also o+w, o+x, o-r, o-w, o-x]\n" + " a+r make file readable by absolutely everybody\n" + " [also a+w, a+x, a-r, a-w, a-x]\n" + " u+s enable the Unix set-user-ID bit\n" + " u-s disable the Unix set-user-ID bit\n" + " g+s enable the Unix set-group-ID bit\n" + " g-s disable the Unix set-group-ID bit\n" + " +t enable the Unix \"sticky bit\"\n" + " You can give more than one modifier for the same user (\"g-rwx\"), and\n" + " more than one user for the same modifier (\"ug+w\"). You can\n" + " use commas to separate different modifiers (\"u+rwx,g+s\").\n", + sftp_cmd_chmod + }, + { + "close", TRUE, "finish your SFTP session but do not quit PSFTP", + "\n" + " Terminates your SFTP session, but does not quit the PSFTP\n" + " program. You can then use \"open\" to start another SFTP\n" + " session, to the same server or to a different one.\n", + sftp_cmd_close + }, + { + "del", TRUE, "delete files on the remote server", + " [ ... ]\n" + " Delete a file or files from the server.\n", + sftp_cmd_rm + }, + { + "delete", FALSE, "del", NULL, sftp_cmd_rm + }, + { + "dir", TRUE, "list remote files", + " [ ]/[ ]\n" + " List the contents of a specified directory on the server.\n" + " If is not given, the current working directory\n" + " is assumed.\n" + " If is given, it is treated as a set of files to\n" + " list; otherwise, all files are listed.\n", + sftp_cmd_ls + }, + { + "exit", TRUE, "bye", NULL, sftp_cmd_quit + }, + { + "get", TRUE, "download a file from the server to your local machine", + " [ -r ] [ -- ] [ ]\n" + " Downloads a file on the server and stores it locally under\n" + " the same name, or under a different one if you supply the\n" + " argument .\n" + " If -r specified, recursively fetch a directory.\n", + sftp_cmd_get + }, + { + "help", TRUE, "give help", + " [ [ ... ] ]\n" + " Give general help if no commands are specified.\n" + " If one or more commands are specified, give specific help on\n" + " those particular commands.\n", + sftp_cmd_help + }, + { + "lcd", TRUE, "change local working directory", + " \n" + " Change the local working directory of the PSFTP program (the\n" + " default location where the \"get\" command will save files).\n", + sftp_cmd_lcd + }, + { + "lpwd", TRUE, "print local working directory", + "\n" + " Print the local working directory of the PSFTP program (the\n" + " default location where the \"get\" command will save files).\n", + sftp_cmd_lpwd + }, + { + "ls", TRUE, "dir", NULL, + sftp_cmd_ls + }, + { + "mget", TRUE, "download multiple files at once", + " [ -r ] [ -- ] [ ... ]\n" + " Downloads many files from the server, storing each one under\n" + " the same name it has on the server side. You can use wildcards\n" + " such as \"*.c\" to specify lots of files at once.\n" + " If -r specified, recursively fetch files and directories.\n", + sftp_cmd_mget + }, + { + "mkdir", TRUE, "create directories on the remote server", + " [ ... ]\n" + " Creates directories with the given names on the server.\n", + sftp_cmd_mkdir + }, + { + "mput", TRUE, "upload multiple files at once", + " [ -r ] [ -- ] [ ... ]\n" + " Uploads many files to the server, storing each one under the\n" + " same name it has on the client side. You can use wildcards\n" + " such as \"*.c\" to specify lots of files at once.\n" + " If -r specified, recursively store files and directories.\n", + sftp_cmd_mput + }, + { + "mv", TRUE, "move or rename file(s) on the remote server", + " [ ... ] \n" + " Moves or renames (s) on the server to ,\n" + " also on the server.\n" + " If specifies an existing directory, then \n" + " may be a wildcard, and multiple s may be given; all\n" + " source files are moved into .\n" + " Otherwise, must specify a single file, which is moved\n" + " or renamed so that it is accessible under the name .\n", + sftp_cmd_mv + }, + { + "open", TRUE, "connect to a host", + " [@] []\n" + " Establishes an SFTP connection to a given host. Only usable\n" + " when you are not already connected to a server.\n", + sftp_cmd_open + }, + { + "put", TRUE, "upload a file from your local machine to the server", + " [ -r ] [ -- ] [ ]\n" + " Uploads a file to the server and stores it there under\n" + " the same name, or under a different one if you supply the\n" + " argument .\n" + " If -r specified, recursively store a directory.\n", + sftp_cmd_put + }, + { + "pwd", TRUE, "print your remote working directory", + "\n" + " Print the current remote working directory for your SFTP session.\n", + sftp_cmd_pwd + }, + { + "quit", TRUE, "bye", NULL, + sftp_cmd_quit + }, + { + "reget", TRUE, "continue downloading files", + " [ -r ] [ -- ] [ ]\n" + " Works exactly like the \"get\" command, but the local file\n" + " must already exist. The download will begin at the end of the\n" + " file. This is for resuming a download that was interrupted.\n" + " If -r specified, resume interrupted \"get -r\".\n", + sftp_cmd_reget + }, + { + "ren", TRUE, "mv", NULL, + sftp_cmd_mv + }, + { + "rename", FALSE, "mv", NULL, + sftp_cmd_mv + }, + { + "reput", TRUE, "continue uploading files", + " [ -r ] [ -- ] [ ]\n" + " Works exactly like the \"put\" command, but the remote file\n" + " must already exist. The upload will begin at the end of the\n" + " file. This is for resuming an upload that was interrupted.\n" + " If -r specified, resume interrupted \"put -r\".\n", + sftp_cmd_reput + }, + { + "rm", TRUE, "del", NULL, + sftp_cmd_rm + }, + { + "rmdir", TRUE, "remove directories on the remote server", + " [ ... ]\n" + " Removes the directory with the given name on the server.\n" + " The directory will not be removed unless it is empty.\n" + " Wildcards may be used to specify multiple directories.\n", + sftp_cmd_rmdir + } +}; + +const struct sftp_cmd_lookup *lookup_command(char *name) +{ + int i, j, k, cmp; + + i = -1; + j = sizeof(sftp_lookup) / sizeof(*sftp_lookup); + while (j - i > 1) { + k = (j + i) / 2; + cmp = strcmp(name, sftp_lookup[k].name); + if (cmp < 0) + j = k; + else if (cmp > 0) + i = k; + else { + return &sftp_lookup[k]; + } + } + return NULL; +} + +static int sftp_cmd_help(struct sftp_command *cmd) +{ + int i; + if (cmd->nwords == 1) { + /* + * Give short help on each command. + */ + int maxlen; + maxlen = 0; + for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) { + int len; + if (!sftp_lookup[i].listed) + continue; + len = strlen(sftp_lookup[i].name); + if (maxlen < len) + maxlen = len; + } + for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) { + const struct sftp_cmd_lookup *lookup; + if (!sftp_lookup[i].listed) + continue; + lookup = &sftp_lookup[i]; + printf("%-*s", maxlen+2, lookup->name); + if (lookup->longhelp == NULL) + lookup = lookup_command(lookup->shorthelp); + printf("%s\n", lookup->shorthelp); + } + } else { + /* + * Give long help on specific commands. + */ + for (i = 1; i < cmd->nwords; i++) { + const struct sftp_cmd_lookup *lookup; + lookup = lookup_command(cmd->words[i]); + if (!lookup) { + printf("help: %s: command not found\n", cmd->words[i]); + } else { + printf("%s", lookup->name); + if (lookup->longhelp == NULL) + lookup = lookup_command(lookup->shorthelp); + printf("%s", lookup->longhelp); + } + } + } + return 1; +} + +/* ---------------------------------------------------------------------- + * Command line reading and parsing. + */ +struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) +{ + char *line; + struct sftp_command *cmd; + char *p, *q, *r; + int quoting; + + cmd = snew(struct sftp_command); + cmd->words = NULL; + cmd->nwords = 0; + cmd->wordssize = 0; + + line = NULL; + + if (fp) { + if (modeflags & 1) + printf("psftp> "); + line = fgetline(fp); + } else { + line = ssh_sftp_get_cmdline("psftp> ", back == NULL); + } + + if (!line || !*line) { + cmd->obey = sftp_cmd_quit; + if ((mode == 0) || (modeflags & 1)) + printf("quit\n"); + return cmd; /* eof */ + } + + line[strcspn(line, "\r\n")] = '\0'; + + if (modeflags & 1) { + printf("%s\n", line); + } + + p = line; + while (*p && (*p == ' ' || *p == '\t')) + p++; + + if (*p == '!') { + /* + * Special case: the ! command. This is always parsed as + * exactly two words: one containing the !, and the second + * containing everything else on the line. + */ + cmd->nwords = cmd->wordssize = 2; + cmd->words = sresize(cmd->words, cmd->wordssize, char *); + cmd->words[0] = dupstr("!"); + cmd->words[1] = dupstr(p+1); + } else if (*p == '#') { + /* + * Special case: comment. Entire line is ignored. + */ + cmd->nwords = cmd->wordssize = 0; + } else { + + /* + * Parse the command line into words. The syntax is: + * - double quotes are removed, but cause spaces within to be + * treated as non-separating. + * - a double-doublequote pair is a literal double quote, inside + * _or_ outside quotes. Like this: + * + * firstword "second word" "this has ""quotes"" in" and""this"" + * + * becomes + * + * >firstword< + * >second word< + * >this has "quotes" in< + * >and"this"< + */ + while (*p) { + /* skip whitespace */ + while (*p && (*p == ' ' || *p == '\t')) + p++; + /* mark start of word */ + q = r = p; /* q sits at start, r writes word */ + quoting = 0; + while (*p) { + if (!quoting && (*p == ' ' || *p == '\t')) + break; /* reached end of word */ + else if (*p == '"' && p[1] == '"') + p += 2, *r++ = '"'; /* a literal quote */ + else if (*p == '"') + p++, quoting = !quoting; + else + *r++ = *p++; + } + if (*p) + p++; /* skip over the whitespace */ + *r = '\0'; + if (cmd->nwords >= cmd->wordssize) { + cmd->wordssize = cmd->nwords + 16; + cmd->words = sresize(cmd->words, cmd->wordssize, char *); + } + cmd->words[cmd->nwords++] = dupstr(q); + } + } + + sfree(line); + + /* + * Now parse the first word and assign a function. + */ + + if (cmd->nwords == 0) + cmd->obey = sftp_cmd_null; + else { + const struct sftp_cmd_lookup *lookup; + lookup = lookup_command(cmd->words[0]); + if (!lookup) + cmd->obey = sftp_cmd_unknown; + else + cmd->obey = lookup->obey; + } + + return cmd; +} + +static int do_sftp_init(void) +{ + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + + /* + * Do protocol initialisation. + */ + if (!fxp_init()) { + fprintf(stderr, + "Fatal: unable to initialise SFTP: %s\n", fxp_error()); + return 1; /* failure */ + } + + /* + * Find out where our home directory is. + */ + sftp_register(req = fxp_realpath_send(".")); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + homedir = fxp_realpath_recv(pktin, rreq); + + if (!homedir) { + fprintf(stderr, + "Warning: failed to resolve home directory: %s\n", + fxp_error()); + homedir = dupstr("."); + } else { + printf("Remote working directory is %s\n", homedir); + } + pwd = dupstr(homedir); + return 0; +} + +void do_sftp_cleanup() +{ + char ch; + if (back) { + back->special(backhandle, TS_EOF); + sftp_recvdata(&ch, 1); + back->free(backhandle); + sftp_cleanup_request(); + back = NULL; + backhandle = NULL; + } + if (pwd) { + sfree(pwd); + pwd = NULL; + } + if (homedir) { + sfree(homedir); + homedir = NULL; + } +} + +void do_sftp(int mode, int modeflags, char *batchfile) +{ + FILE *fp; + int ret; + + /* + * Batch mode? + */ + if (mode == 0) { + + /* ------------------------------------------------------------------ + * Now we're ready to do Real Stuff. + */ + while (1) { + struct sftp_command *cmd; + cmd = sftp_getcmd(NULL, 0, 0); + if (!cmd) + break; + ret = cmd->obey(cmd); + if (cmd->words) { + int i; + for(i = 0; i < cmd->nwords; i++) + sfree(cmd->words[i]); + sfree(cmd->words); + } + sfree(cmd); + if (ret < 0) + break; + } + } else { + fp = fopen(batchfile, "r"); + if (!fp) { + printf("Fatal: unable to open %s\n", batchfile); + return; + } + while (1) { + struct sftp_command *cmd; + cmd = sftp_getcmd(fp, mode, modeflags); + if (!cmd) + break; + ret = cmd->obey(cmd); + if (ret < 0) + break; + if (ret == 0) { + if (!(modeflags & 2)) + break; + } + } + fclose(fp); + + } +} + +/* ---------------------------------------------------------------------- + * Dirty bits: integration with PuTTY. + */ + +static int verbose = 0; + +/* + * Print an error message and perform a fatal exit. + */ +void fatalbox(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + fputs(str2, stderr); + sfree(str2); + + cleanup_exit(1); +} +void modalfatalbox(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + fputs(str2, stderr); + sfree(str2); + + cleanup_exit(1); +} +void connection_fatal(void *frontend, char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Fatal: ", str, "\n", NULL); + sfree(str); + va_end(ap); + fputs(str2, stderr); + sfree(str2); + + cleanup_exit(1); +} + +void ldisc_send(void *handle, char *buf, int len, int interactive) +{ + /* + * This is only here because of the calls to ldisc_send(NULL, + * 0) in ssh.c. Nothing in PSFTP actually needs to use the + * ldisc as an ldisc. So if we get called with any real data, I + * want to know about it. + */ + assert(len == 0); +} + +/* + * In psftp, all agent requests should be synchronous, so this is a + * never-called stub. + */ +void agent_schedule_callback(void (*callback)(void *, void *, int), + void *callback_ctx, void *data, int len) +{ + assert(!"We shouldn't be here"); +} + +/* + * Receive a block of data from the SSH link. Block until all data + * is available. + * + * To do this, we repeatedly call the SSH protocol module, with our + * own trap in from_backend() to catch the data that comes back. We + * do this until we have enough data. + */ + +static unsigned char *outptr; /* where to put the data */ +static unsigned outlen; /* how much data required */ +static unsigned char *pending = NULL; /* any spare data */ +static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */ +int from_backend(void *frontend, int is_stderr, const char *data, int datalen) +{ + unsigned char *p = (unsigned char *) data; + unsigned len = (unsigned) datalen; + + /* + * stderr data is just spouted to local stderr and otherwise + * ignored. + */ + if (is_stderr) { + if (len > 0) + if (fwrite(data, 1, len, stderr) < len) + /* oh well */; + return 0; + } + + /* + * If this is before the real session begins, just return. + */ + if (!outptr) + return 0; + + if ((outlen > 0) && (len > 0)) { + unsigned used = outlen; + if (used > len) + used = len; + memcpy(outptr, p, used); + outptr += used; + outlen -= used; + p += used; + len -= used; + } + + if (len > 0) { + if (pendsize < pendlen + len) { + pendsize = pendlen + len + 4096; + pending = sresize(pending, pendsize, unsigned char); + } + memcpy(pending + pendlen, p, len); + pendlen += len; + } + + return 0; +} +int from_backend_untrusted(void *frontend_handle, const char *data, int len) +{ + /* + * No "untrusted" output should get here (the way the code is + * currently, it's all diverted by FLAG_STDERR). + */ + assert(!"Unexpected call to from_backend_untrusted()"); + return 0; /* not reached */ +} +int sftp_recvdata(char *buf, int len) +{ + outptr = (unsigned char *) buf; + outlen = len; + + /* + * See if the pending-input block contains some of what we + * need. + */ + if (pendlen > 0) { + unsigned pendused = pendlen; + if (pendused > outlen) + pendused = outlen; + memcpy(outptr, pending, pendused); + memmove(pending, pending + pendused, pendlen - pendused); + outptr += pendused; + outlen -= pendused; + pendlen -= pendused; + if (pendlen == 0) { + pendsize = 0; + sfree(pending); + pending = NULL; + } + if (outlen == 0) + return 1; + } + + while (outlen > 0) { + if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0) + return 0; /* doom */ + } + + return 1; +} +int sftp_senddata(char *buf, int len) +{ + back->send(backhandle, buf, len); + return 1; +} + +/* + * Short description of parameters. + */ +static void usage(void) +{ + printf("PuTTY Secure File Transfer (SFTP) client\n"); + printf("%s\n", ver); + printf("Usage: psftp [options] [user@]host\n"); + printf("Options:\n"); + printf(" -V print version information and exit\n"); + printf(" -pgpfp print PGP key fingerprints and exit\n"); + printf(" -b file use specified batchfile\n"); + printf(" -bc output batchfile commands\n"); + printf(" -be don't stop batchfile processing if errors\n"); + printf(" -v show verbose messages\n"); + printf(" -load sessname Load settings from saved session\n"); + printf(" -l user connect with specified username\n"); + printf(" -P port connect to specified port\n"); + printf(" -pw passw login with specified password\n"); + printf(" -1 -2 force use of particular SSH protocol version\n"); + printf(" -4 -6 force use of IPv4 or IPv6\n"); + printf(" -C enable compression\n"); + printf(" -i key private key file for authentication\n"); + printf(" -noagent disable use of Pageant\n"); + printf(" -agent enable use of Pageant\n"); + printf(" -batch disable all interactive prompts\n"); + cleanup_exit(1); +} + +static void version(void) +{ + printf("psftp: %s\n", ver); + cleanup_exit(1); +} + +/* + * Connect to a host. + */ +static int psftp_connect(char *userhost, char *user, int portnumber) +{ + char *host, *realhost; + const char *err; + void *logctx; + + /* Separate host and username */ + host = userhost; + host = strrchr(host, '@'); + if (host == NULL) { + host = userhost; + } else { + *host++ = '\0'; + if (user) { + printf("psftp: multiple usernames specified; using \"%s\"\n", + user); + } else + user = userhost; + } + + /* + * If we haven't loaded session details already (e.g., from -load), + * try looking for a session called "host". + */ + if (!loaded_session) { + /* Try to load settings for `host' into a temporary config */ + Config cfg2; + cfg2.host[0] = '\0'; + do_defaults(host, &cfg2); + if (cfg2.host[0] != '\0') { + /* Settings present and include hostname */ + /* Re-load data into the real config. */ + do_defaults(host, &cfg); + } else { + /* Session doesn't exist or mention a hostname. */ + /* Use `host' as a bare hostname. */ + strncpy(cfg.host, host, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + } + } else { + /* Patch in hostname `host' to session details. */ + strncpy(cfg.host, host, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + } + + /* + * Force use of SSH. (If they got the protocol wrong we assume the + * port is useless too.) + */ + if (cfg.protocol != PROT_SSH) { + cfg.protocol = PROT_SSH; + cfg.port = 22; + } + + /* + * If saved session / Default Settings says SSH-1 (`1 only' or `1'), + * then change it to SSH-2, on the grounds that that's more likely to + * work for SFTP. (Can be overridden with `-1' option.) + * But if it says `2 only' or `2', respect which. + */ + if (cfg.sshprot != 2 && cfg.sshprot != 3) + cfg.sshprot = 2; + + /* + * Enact command-line overrides. + */ + cmdline_run_saved(&cfg); + + /* + * Trim leading whitespace off the hostname if it's there. + */ + { + int space = strspn(cfg.host, " \t"); + memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space); + } + + /* See if host is of the form user@host */ + if (cfg.host[0] != '\0') { + char *atsign = strrchr(cfg.host, '@'); + /* Make sure we're not overflowing the user field */ + if (atsign) { + if (atsign - cfg.host < sizeof cfg.username) { + strncpy(cfg.username, cfg.host, atsign - cfg.host); + cfg.username[atsign - cfg.host] = '\0'; + } + memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1)); + } + } + + /* + * Trim a colon suffix off the hostname if it's there. + */ + cfg.host[strcspn(cfg.host, ":")] = '\0'; + + /* + * Remove any remaining whitespace from the hostname. + */ + { + int p1 = 0, p2 = 0; + while (cfg.host[p2] != '\0') { + if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') { + cfg.host[p1] = cfg.host[p2]; + p1++; + } + p2++; + } + cfg.host[p1] = '\0'; + } + + /* Set username */ + if (user != NULL && user[0] != '\0') { + strncpy(cfg.username, user, sizeof(cfg.username) - 1); + cfg.username[sizeof(cfg.username) - 1] = '\0'; + } + + if (portnumber) + cfg.port = portnumber; + + /* + * Disable scary things which shouldn't be enabled for simple + * things like SCP and SFTP: agent forwarding, port forwarding, + * X forwarding. + */ + cfg.x11_forward = 0; + cfg.agentfwd = 0; + cfg.portfwd[0] = cfg.portfwd[1] = '\0'; + cfg.ssh_simple = TRUE; + + /* Set up subsystem name. */ + strcpy(cfg.remote_cmd, "sftp"); + cfg.ssh_subsys = TRUE; + cfg.nopty = TRUE; + + /* + * Set up fallback option, for SSH-1 servers or servers with the + * sftp subsystem not enabled but the server binary installed + * in the usual place. We only support fallback on Unix + * systems, and we use a kludgy piece of shellery which should + * try to find sftp-server in various places (the obvious + * systemwide spots /usr/lib and /usr/local/lib, and then the + * user's PATH) and finally give up. + * + * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server + * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server + * exec sftp-server + * + * the idea being that this will attempt to use either of the + * obvious pathnames and then give up, and when it does give up + * it will print the preferred pathname in the error messages. + */ + cfg.remote_cmd_ptr2 = + "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" + "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" + "exec sftp-server"; + cfg.ssh_subsys2 = FALSE; + + back = &ssh_backend; + + err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost, + 0, cfg.tcp_keepalives); + if (err != NULL) { + fprintf(stderr, "ssh_init: %s\n", err); + return 1; + } + logctx = log_init(NULL, &cfg); + back->provide_logctx(backhandle, logctx); + console_provide_logctx(logctx); + while (!back->sendok(backhandle)) { + if (back->exitcode(backhandle) >= 0) + return 1; + if (ssh_sftp_loop_iteration() < 0) { + fprintf(stderr, "ssh_init: error during SSH connection setup\n"); + return 1; + } + } + if (verbose && realhost != NULL) + printf("Connected to %s\n", realhost); + if (realhost != NULL) + sfree(realhost); + return 0; +} + +void cmdline_error(char *p, ...) +{ + va_list ap; + fprintf(stderr, "psftp: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fprintf(stderr, "\n try typing \"psftp -h\" for help\n"); + exit(1); +} + +/* + * Main program. Parse arguments etc. + */ +int psftp_main(int argc, char *argv[]) +{ + int i; + int portnumber = 0; + char *userhost, *user; + int mode = 0; + int modeflags = 0; + char *batchfile = NULL; + + flags = FLAG_STDERR | FLAG_INTERACTIVE +#ifdef FLAG_SYNCAGENT + | FLAG_SYNCAGENT +#endif + ; + cmdline_tooltype = TOOLTYPE_FILETRANSFER; + sk_init(); + + userhost = user = NULL; + + /* Load Default Settings before doing anything else. */ + do_defaults(NULL, &cfg); + loaded_session = FALSE; + + for (i = 1; i < argc; i++) { + int ret; + if (argv[i][0] != '-') { + if (userhost) + usage(); + else + userhost = dupstr(argv[i]); + continue; + } + ret = cmdline_process_param(argv[i], i+1connected(backhandle)) { + char ch; + back->special(backhandle, TS_EOF); + sftp_recvdata(&ch, 1); + } + do_sftp_cleanup(); + random_save_seed(); + cmdline_cleanup(); + console_provide_logctx(NULL); + sk_cleanup(); + + return 0; +} diff --git a/putty/PSFTP.H b/putty/PSFTP.H new file mode 100644 index 0000000..3e13887 --- /dev/null +++ b/putty/PSFTP.H @@ -0,0 +1,178 @@ +/* + * psftp.h: interface between psftp.c / scp.c and each + * platform-specific SFTP module. + */ + +#include "int64.h" + +#ifndef PUTTY_PSFTP_H +#define PUTTY_PSFTP_H + +/* + * psftp_getcwd returns the local current directory. The returned + * string must be freed by the caller. + */ +char *psftp_getcwd(void); + +/* + * psftp_lcd changes the local current directory. The return value + * is NULL on success, or else an error message which must be freed + * by the caller. + */ +char *psftp_lcd(char *newdir); + +/* + * Retrieve file times on a local file. Must return two unsigned + * longs in POSIX time_t format. + */ +void get_file_times(char *filename, unsigned long *mtime, + unsigned long *atime); + +/* + * One iteration of the PSFTP event loop: wait for network data and + * process it, once. + */ +int ssh_sftp_loop_iteration(void); + +/* + * Read a command line for PSFTP from standard input. Caller must + * free. + * + * If `backend_required' is TRUE, should also listen for activity + * at the backend (rekeys, clientalives, unexpected closures etc) + * and respond as necessary, and if the backend closes it should + * treat this as a failure condition. If `backend_required' is + * FALSE, a back end is not (intentionally) active at all (e.g. + * psftp before an `open' command). + */ +char *ssh_sftp_get_cmdline(char *prompt, int backend_required); + +/* + * The main program in psftp.c. Called from main() in the platform- + * specific code, after doing any platform-specific initialisation. + */ +int psftp_main(int argc, char *argv[]); + +/* + * These functions are used by PSCP to transmit progress updates + * and error information to a GUI window managing it. This will + * probably only ever be supported on Windows, so these functions + * can safely be stubs on all other platforms. + */ +void gui_update_stats(char *name, unsigned long size, + int percentage, unsigned long elapsed, + unsigned long done, unsigned long eta, + unsigned long ratebs); +void gui_send_errcount(int list, int errs); +void gui_send_char(int is_stderr, int c); +void gui_enable(char *arg); + +/* + * It's likely that a given platform's implementation of file + * transfer utilities is going to want to do things with them that + * aren't present in stdio. Hence we supply an alternative + * abstraction for file access functions. + * + * This abstraction tells you the size and access times when you + * open an existing file (platforms may choose the meaning of the + * file times if it's not clear; whatever they choose will be what + * PSCP sends to the server as mtime and atime), and lets you set + * the times when saving a new file. + * + * On the other hand, the abstraction is pretty simple: it supports + * only opening a file and reading it, or creating a file and writing + * it. None of this read-and-write, seeking-back-and-forth stuff. + */ +typedef struct RFile RFile; +typedef struct WFile WFile; +/* Output params size, mtime and atime can all be NULL if desired */ +RFile *open_existing_file(char *name, uint64 *size, + unsigned long *mtime, unsigned long *atime); +WFile *open_existing_wfile(char *name, uint64 *size); +/* Returns <0 on error, 0 on eof, or number of bytes read, as usual */ +int read_from_file(RFile *f, void *buffer, int length); +/* Closes and frees the RFile */ +void close_rfile(RFile *f); +WFile *open_new_file(char *name); +/* Returns <0 on error, 0 on eof, or number of bytes written, as usual */ +int write_to_file(WFile *f, void *buffer, int length); +void set_file_times(WFile *f, unsigned long mtime, unsigned long atime); +/* Closes and frees the WFile */ +void close_wfile(WFile *f); +/* Seek offset bytes through file */ +enum { FROM_START, FROM_CURRENT, FROM_END }; +int seek_file(WFile *f, uint64 offset, int whence); +/* Get file position */ +uint64 get_file_posn(WFile *f); +/* + * Determine the type of a file: nonexistent, file, directory or + * weird. `weird' covers anything else - named pipes, Unix sockets, + * device files, fish, badgers, you name it. Things marked `weird' + * will be skipped over in recursive file transfers, so the only + * real reason for not lumping them in with `nonexistent' is that + * it allows a slightly more sane error message. + */ +enum { + FILE_TYPE_NONEXISTENT, FILE_TYPE_FILE, FILE_TYPE_DIRECTORY, FILE_TYPE_WEIRD +}; +int file_type(char *name); + +/* + * Read all the file names out of a directory. + */ +typedef struct DirHandle DirHandle; +DirHandle *open_directory(char *name); +/* The string returned from this will need freeing if not NULL */ +char *read_filename(DirHandle *dir); +void close_directory(DirHandle *dir); + +/* + * Test a filespec to see whether it's a local wildcard or not. + * Return values: + * + * - WCTYPE_WILDCARD (this is a wildcard). + * - WCTYPE_FILENAME (this is a single file name). + * - WCTYPE_NONEXISTENT (whichever it was, nothing of that name exists). + * + * Some platforms may choose not to support local wildcards when + * they come from the command line; in this case they simply never + * return WCTYPE_WILDCARD, but still test the file's existence. + * (However, all platforms will probably want to support wildcards + * inside the PSFTP CLI.) + */ +enum { + WCTYPE_NONEXISTENT, WCTYPE_FILENAME, WCTYPE_WILDCARD +}; +int test_wildcard(char *name, int cmdline); + +/* + * Actually return matching file names for a local wildcard. + */ +typedef struct WildcardMatcher WildcardMatcher; +WildcardMatcher *begin_wildcard_matching(char *name); +/* The string returned from this will need freeing if not NULL */ +char *wildcard_get_filename(WildcardMatcher *dir); +void finish_wildcard_matching(WildcardMatcher *dir); + +/* + * Vet a filename returned from the remote host, to ensure it isn't + * in some way malicious. The idea is that this function is applied + * to filenames returned from FXP_READDIR, which means we can panic + * if we see _anything_ resembling a directory separator. + * + * Returns TRUE if the filename is kosher, FALSE if dangerous. + */ +int vet_filename(char *name); + +/* + * Create a directory. Returns 0 on error, !=0 on success. + */ +int create_directory(char *name); + +/* + * Concatenate a directory name and a file name. The way this is + * done will depend on the OS. + */ +char *dir_file_cat(char *dir, char *file); + +#endif /* PUTTY_PSFTP_H */ diff --git a/putty/PUTTY.H b/putty/PUTTY.H new file mode 100644 index 0000000..e2baee8 --- /dev/null +++ b/putty/PUTTY.H @@ -0,0 +1,1306 @@ +#ifndef PUTTY_PUTTY_H +#define PUTTY_PUTTY_H + +#include /* for wchar_t */ + +/* + * Global variables. Most modules declare these `extern', but + * window.c will do `#define PUTTY_DO_GLOBALS' before including this + * module, and so will get them properly defined. + */ +#ifndef GLOBAL +#ifdef PUTTY_DO_GLOBALS +#define GLOBAL +#else +#define GLOBAL extern +#endif +#endif + +#ifndef DONE_TYPEDEFS +#define DONE_TYPEDEFS +typedef struct config_tag Config; +typedef struct backend_tag Backend; +typedef struct terminal_tag Terminal; +#endif + +#include "puttyps.h" +#include "network.h" +#include "misc.h" + +/* + * Fingerprints of the PGP master keys that can be used to establish a trust + * path between an executable and other files. + */ +#define PGP_RSA_MASTER_KEY_FP \ + "8F 15 97 DA 25 30 AB 0D 88 D1 92 54 11 CF 0C 4C" +#define PGP_DSA_MASTER_KEY_FP \ + "313C 3E76 4B74 C2C5 F2AE 83A8 4F5E 6DF5 6A93 B34E" + +/* Three attribute types: + * The ATTRs (normal attributes) are stored with the characters in + * the main display arrays + * + * The TATTRs (temporary attributes) are generated on the fly, they + * can overlap with characters but not with normal attributes. + * + * The LATTRs (line attributes) are an entirely disjoint space of + * flags. + * + * The DATTRs (display attributes) are internal to terminal.c (but + * defined here because their values have to match the others + * here); they reuse the TATTR_* space but are always masked off + * before sending to the front end. + * + * ATTR_INVALID is an illegal colour combination. + */ + +#define TATTR_ACTCURS 0x40000000UL /* active cursor (block) */ +#define TATTR_PASCURS 0x20000000UL /* passive cursor (box) */ +#define TATTR_RIGHTCURS 0x10000000UL /* cursor-on-RHS */ +#define TATTR_COMBINING 0x80000000UL /* combining characters */ + +#define DATTR_STARTRUN 0x80000000UL /* start of redraw run */ + +#define TDATTR_MASK 0xF0000000UL +#define TATTR_MASK (TDATTR_MASK) +#define DATTR_MASK (TDATTR_MASK) + +#define LATTR_NORM 0x00000000UL +#define LATTR_WIDE 0x00000001UL +#define LATTR_TOP 0x00000002UL +#define LATTR_BOT 0x00000003UL +#define LATTR_MODE 0x00000003UL +#define LATTR_WRAPPED 0x00000010UL /* this line wraps to next */ +#define LATTR_WRAPPED2 0x00000020UL /* with WRAPPED: CJK wide character + wrapped to next line, so last + single-width cell is empty */ + +#define ATTR_INVALID 0x03FFFFU + +/* Like Linux use the F000 page for direct to font. */ +#define CSET_OEMCP 0x0000F000UL /* OEM Codepage DTF */ +#define CSET_ACP 0x0000F100UL /* Ansi Codepage DTF */ + +/* These are internal use overlapping with the UTF-16 surrogates */ +#define CSET_ASCII 0x0000D800UL /* normal ASCII charset ESC ( B */ +#define CSET_LINEDRW 0x0000D900UL /* line drawing charset ESC ( 0 */ +#define CSET_SCOACS 0x0000DA00UL /* SCO Alternate charset */ +#define CSET_GBCHR 0x0000DB00UL /* UK variant charset ESC ( A */ +#define CSET_MASK 0xFFFFFF00UL /* Character set mask */ + +#define DIRECT_CHAR(c) ((c&0xFFFFFC00)==0xD800) +#define DIRECT_FONT(c) ((c&0xFFFFFE00)==0xF000) + +#define UCSERR (CSET_LINEDRW|'a') /* UCS Format error character. */ +/* + * UCSWIDE is a special value used in the terminal data to signify + * the character cell containing the right-hand half of a CJK wide + * character. We use 0xDFFF because it's part of the surrogate + * range and hence won't be used for anything else (it's impossible + * to input it via UTF-8 because our UTF-8 decoder correctly + * rejects surrogates). + */ +#define UCSWIDE 0xDFFF + +#define ATTR_NARROW 0x800000U +#define ATTR_WIDE 0x400000U +#define ATTR_BOLD 0x040000U +#define ATTR_UNDER 0x080000U +#define ATTR_REVERSE 0x100000U +#define ATTR_BLINK 0x200000U +#define ATTR_FGMASK 0x0001FFU +#define ATTR_BGMASK 0x03FE00U +#define ATTR_COLOURS 0x03FFFFU +#define ATTR_FGSHIFT 0 +#define ATTR_BGSHIFT 9 + +/* + * The definitive list of colour numbers stored in terminal + * attribute words is kept here. It is: + * + * - 0-7 are ANSI colours (KRGYBMCW). + * - 8-15 are the bold versions of those colours. + * - 16-255 are the remains of the xterm 256-colour mode (a + * 216-colour cube with R at most significant and B at least, + * followed by a uniform series of grey shades running between + * black and white but not including either on grounds of + * redundancy). + * - 256 is default foreground + * - 257 is default bold foreground + * - 258 is default background + * - 259 is default bold background + * - 260 is cursor foreground + * - 261 is cursor background + */ + +#define ATTR_DEFFG (256 << ATTR_FGSHIFT) +#define ATTR_DEFBG (258 << ATTR_BGSHIFT) +#define ATTR_DEFAULT (ATTR_DEFFG | ATTR_DEFBG) + +struct sesslist { + int nsessions; + char **sessions; + char *buffer; /* so memory can be freed later */ +}; + +struct unicode_data { + char **uni_tbl; + int dbcs_screenfont; + int font_codepage; + int line_codepage; + wchar_t unitab_scoacs[256]; + wchar_t unitab_line[256]; + wchar_t unitab_font[256]; + wchar_t unitab_xterm[256]; + wchar_t unitab_oemcp[256]; + unsigned char unitab_ctrl[256]; +}; + +#define LGXF_OVR 1 /* existing logfile overwrite */ +#define LGXF_APN 0 /* existing logfile append */ +#define LGXF_ASK -1 /* existing logfile ask */ +#define LGTYP_NONE 0 /* logmode: no logging */ +#define LGTYP_ASCII 1 /* logmode: pure ascii */ +#define LGTYP_DEBUG 2 /* logmode: all chars of traffic */ +#define LGTYP_PACKETS 3 /* logmode: SSH data packets */ +#define LGTYP_SSHRAW 4 /* logmode: SSH raw data */ + +typedef enum { + /* Actual special commands. Originally Telnet, but some codes have + * been re-used for similar specials in other protocols. */ + TS_AYT, TS_BRK, TS_SYNCH, TS_EC, TS_EL, TS_GA, TS_NOP, TS_ABORT, + TS_AO, TS_IP, TS_SUSP, TS_EOR, TS_EOF, TS_LECHO, TS_RECHO, TS_PING, + TS_EOL, + /* Special command for SSH. */ + TS_REKEY, + /* POSIX-style signals. (not Telnet) */ + TS_SIGABRT, TS_SIGALRM, TS_SIGFPE, TS_SIGHUP, TS_SIGILL, + TS_SIGINT, TS_SIGKILL, TS_SIGPIPE, TS_SIGQUIT, TS_SIGSEGV, + TS_SIGTERM, TS_SIGUSR1, TS_SIGUSR2, + /* Pseudo-specials used for constructing the specials menu. */ + TS_SEP, /* Separator */ + TS_SUBMENU, /* Start a new submenu with specified name */ + TS_EXITMENU /* Exit current submenu or end of specials */ +} Telnet_Special; + +struct telnet_special { + const char *name; + int code; +}; + +typedef enum { + MBT_NOTHING, + MBT_LEFT, MBT_MIDDLE, MBT_RIGHT, /* `raw' button designations */ + MBT_SELECT, MBT_EXTEND, MBT_PASTE, /* `cooked' button designations */ + MBT_WHEEL_UP, MBT_WHEEL_DOWN /* mouse wheel */ +} Mouse_Button; + +typedef enum { + MA_NOTHING, MA_CLICK, MA_2CLK, MA_3CLK, MA_DRAG, MA_RELEASE +} Mouse_Action; + +/* Keyboard modifiers -- keys the user is actually holding down */ + +#define PKM_SHIFT 0x01 +#define PKM_CONTROL 0x02 +#define PKM_META 0x04 +#define PKM_ALT 0x08 + +/* Keyboard flags that aren't really modifiers */ +#define PKF_CAPSLOCK 0x10 +#define PKF_NUMLOCK 0x20 +#define PKF_REPEAT 0x40 + +/* Stand-alone keysyms for function keys */ + +typedef enum { + PK_NULL, /* No symbol for this key */ + /* Main keypad keys */ + PK_ESCAPE, PK_TAB, PK_BACKSPACE, PK_RETURN, PK_COMPOSE, + /* Editing keys */ + PK_HOME, PK_INSERT, PK_DELETE, PK_END, PK_PAGEUP, PK_PAGEDOWN, + /* Cursor keys */ + PK_UP, PK_DOWN, PK_RIGHT, PK_LEFT, PK_REST, + /* Numeric keypad */ /* Real one looks like: */ + PK_PF1, PK_PF2, PK_PF3, PK_PF4, /* PF1 PF2 PF3 PF4 */ + PK_KPCOMMA, PK_KPMINUS, PK_KPDECIMAL, /* 7 8 9 - */ + PK_KP0, PK_KP1, PK_KP2, PK_KP3, PK_KP4, /* 4 5 6 , */ + PK_KP5, PK_KP6, PK_KP7, PK_KP8, PK_KP9, /* 1 2 3 en- */ + PK_KPBIGPLUS, PK_KPENTER, /* 0 . ter */ + /* Top row */ + PK_F1, PK_F2, PK_F3, PK_F4, PK_F5, + PK_F6, PK_F7, PK_F8, PK_F9, PK_F10, + PK_F11, PK_F12, PK_F13, PK_F14, PK_F15, + PK_F16, PK_F17, PK_F18, PK_F19, PK_F20, + PK_PAUSE +} Key_Sym; + +#define PK_ISEDITING(k) ((k) >= PK_HOME && (k) <= PK_PAGEDOWN) +#define PK_ISCURSOR(k) ((k) >= PK_UP && (k) <= PK_REST) +#define PK_ISKEYPAD(k) ((k) >= PK_PF1 && (k) <= PK_KPENTER) +#define PK_ISFKEY(k) ((k) >= PK_F1 && (k) <= PK_F20) + +enum { + VT_XWINDOWS, VT_OEMANSI, VT_OEMONLY, VT_POORMAN, VT_UNICODE +}; + +enum { + /* + * SSH-2 key exchange algorithms + */ + KEX_WARN, + KEX_DHGROUP1, + KEX_DHGROUP14, + KEX_DHGEX, + KEX_RSA, + KEX_MAX +}; + +enum { + /* + * SSH ciphers (both SSH-1 and SSH-2) + */ + CIPHER_WARN, /* pseudo 'cipher' */ + CIPHER_3DES, + CIPHER_BLOWFISH, + CIPHER_AES, /* (SSH-2 only) */ + CIPHER_DES, + CIPHER_ARCFOUR, + CIPHER_MAX /* no. ciphers (inc warn) */ +}; + +enum { + /* + * Several different bits of the PuTTY configuration seem to be + * three-way settings whose values are `always yes', `always + * no', and `decide by some more complex automated means'. This + * is true of line discipline options (local echo and line + * editing), proxy DNS, Close On Exit, and SSH server bug + * workarounds. Accordingly I supply a single enum here to deal + * with them all. + */ + FORCE_ON, FORCE_OFF, AUTO +}; + +enum { + /* + * Proxy types. + */ + PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5, + PROXY_HTTP, PROXY_TELNET, PROXY_CMD +}; + +enum { + /* + * Line discipline options which the backend might try to control. + */ + LD_EDIT, /* local line editing */ + LD_ECHO /* local echo */ +}; + +enum { + /* Actions on remote window title query */ + TITLE_NONE, TITLE_EMPTY, TITLE_REAL +}; + +enum { + /* Protocol back ends. (cfg.protocol) */ + PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH, + /* PROT_SERIAL is supported on a subset of platforms, but it doesn't + * hurt to define it globally. */ + PROT_SERIAL +}; + +enum { + /* Bell settings (cfg.beep) */ + BELL_DISABLED, BELL_DEFAULT, BELL_VISUAL, BELL_WAVEFILE, BELL_PCSPEAKER +}; + +enum { + /* Taskbar flashing indication on bell (cfg.beep_ind) */ + B_IND_DISABLED, B_IND_FLASH, B_IND_STEADY +}; + +enum { + /* Resize actions (cfg.resize_action) */ + RESIZE_TERM, RESIZE_DISABLED, RESIZE_FONT, RESIZE_EITHER +}; + +enum { + /* Function key types (cfg.funky_type) */ + FUNKY_TILDE, + FUNKY_LINUX, + FUNKY_XTERM, + FUNKY_VT400, + FUNKY_VT100P, + FUNKY_SCO +}; + +enum { + FQ_DEFAULT, FQ_ANTIALIASED, FQ_NONANTIALIASED, FQ_CLEARTYPE +}; + +enum { + SER_PAR_NONE, SER_PAR_ODD, SER_PAR_EVEN, SER_PAR_MARK, SER_PAR_SPACE +}; + +enum { + SER_FLOW_NONE, SER_FLOW_XONXOFF, SER_FLOW_RTSCTS, SER_FLOW_DSRDTR +}; + +/* + * Tables of string <-> enum value mappings used in settings.c. + * Defined here so that backends can export their GSS library tables + * to the cross-platform settings code. + */ +struct keyvalwhere { + /* + * Two fields which define a string and enum value to be + * equivalent to each other. + */ + char *s; + int v; + + /* + * The next pair of fields are used by gprefs() in settings.c to + * arrange that when it reads a list of strings representing a + * preference list and translates it into the corresponding list + * of integers, strings not appearing in the list are entered in a + * configurable position rather than uniformly at the end. + */ + + /* + * 'vrel' indicates which other value in the list to place this + * element relative to. It should be a value that has occurred in + * a 'v' field of some other element of the array, or -1 to + * indicate that we simply place relative to one or other end of + * the list. + * + * gprefs will try to process the elements in an order which makes + * this field work (i.e. so that the element referenced has been + * added before processing this one). + */ + int vrel; + + /* + * 'where' indicates whether to place the new value before or + * after the one referred to by vrel. -1 means before; +1 means + * after. + * + * When vrel is -1, this also implicitly indicates which end of + * the array to use. So vrel=-1, where=-1 means to place _before_ + * some end of the list (hence, at the last element); vrel=-1, + * where=+1 means to place _after_ an end (hence, at the first). + */ + int where; +}; + +#ifndef NO_GSSAPI +extern const int ngsslibs; +extern const char *const gsslibnames[]; /* for displaying in configuration */ +extern const struct keyvalwhere gsslibkeywords[]; /* for settings.c */ +#endif + +extern const char *const ttymodes[]; + +enum { + /* + * Network address types. Used for specifying choice of IPv4/v6 + * in config; also used in proxy.c to indicate whether a given + * host name has already been resolved or will be resolved at + * the proxy end. + */ + ADDRTYPE_UNSPEC, ADDRTYPE_IPV4, ADDRTYPE_IPV6, ADDRTYPE_NAME +}; + +struct backend_tag { + const char *(*init) (void *frontend_handle, void **backend_handle, + Config *cfg, + char *host, int port, char **realhost, int nodelay, + int keepalive); + void (*free) (void *handle); + /* back->reconfig() passes in a replacement configuration. */ + void (*reconfig) (void *handle, Config *cfg); + /* back->send() returns the current amount of buffered data. */ + int (*send) (void *handle, char *buf, int len); + /* back->sendbuffer() does the same thing but without attempting a send */ + int (*sendbuffer) (void *handle); + void (*size) (void *handle, int width, int height); + void (*special) (void *handle, Telnet_Special code); + const struct telnet_special *(*get_specials) (void *handle); + int (*connected) (void *handle); + int (*exitcode) (void *handle); + /* If back->sendok() returns FALSE, data sent to it from the frontend + * may be lost. */ + int (*sendok) (void *handle); + int (*ldisc) (void *handle, int); + void (*provide_ldisc) (void *handle, void *ldisc); + void (*provide_logctx) (void *handle, void *logctx); + /* + * back->unthrottle() tells the back end that the front end + * buffer is clearing. + */ + void (*unthrottle) (void *handle, int); + int (*cfg_info) (void *handle); + char *name; + int protocol; + int default_port; +}; + +extern Backend *backends[]; + +/* + * Suggested default protocol provided by the backend link module. + * The application is free to ignore this. + */ +extern const int be_default_protocol; + +/* + * Name of this particular application, for use in the config box + * and other pieces of text. + */ +extern const char *const appname; + +/* + * IMPORTANT POLICY POINT: everything in this structure which wants + * to be treated like an integer must be an actual, honest-to- + * goodness `int'. No enum-typed variables. This is because parts + * of the code will want to pass around `int *' pointers to them + * and we can't run the risk of porting to some system on which the + * enum comes out as a different size from int. + */ +struct config_tag { + /* Basic options */ + char host[512]; + int port; + int protocol; + int addressfamily; + int close_on_exit; + int warn_on_close; + int ping_interval; /* in seconds */ + int tcp_nodelay; + int tcp_keepalives; + char loghost[512]; /* logical host being contacted, for host key check */ + /* Proxy options */ + char proxy_exclude_list[512]; + int proxy_dns; + int even_proxy_localhost; + int proxy_type; + char proxy_host[512]; + int proxy_port; + char proxy_username[128]; + char proxy_password[128]; + char proxy_telnet_command[512]; + /* SSH options */ + char remote_cmd[512]; + char *remote_cmd_ptr; /* might point to a larger command + * but never for loading/saving */ + char *remote_cmd_ptr2; /* might point to a larger command + * but never for loading/saving */ + int nopty; + int compression; + int ssh_kexlist[KEX_MAX]; + int ssh_rekey_time; /* in minutes */ + char ssh_rekey_data[16]; + int tryagent; + int agentfwd; + int change_username; /* allow username switching in SSH-2 */ + int ssh_cipherlist[CIPHER_MAX]; + Filename keyfile; + int sshprot; /* use v1 or v2 when both available */ + int ssh2_des_cbc; /* "des-cbc" unrecommended SSH-2 cipher */ + int ssh_no_userauth; /* bypass "ssh-userauth" (SSH-2 only) */ + int ssh_show_banner; /* show USERAUTH_BANNERs (SSH-2 only) */ + int try_tis_auth; + int try_ki_auth; + int try_gssapi_auth; /* attempt gssapi auth */ + int gssapifwd; /* forward tgt via gss */ + int ssh_gsslist[4]; /* preference order for local GSS libs */ + Filename ssh_gss_custom; + int ssh_subsys; /* run a subsystem rather than a command */ + int ssh_subsys2; /* fallback to go with remote_cmd_ptr2 */ + int ssh_no_shell; /* avoid running a shell */ + char ssh_nc_host[512]; /* host to connect to in `nc' mode */ + int ssh_nc_port; /* port to connect to in `nc' mode */ + /* Telnet options */ + char termtype[32]; + char termspeed[32]; + char ttymodes[768]; /* MODE\tVvalue\0MODE\tA\0\0 */ + char environmt[1024]; /* VAR\tvalue\0VAR\tvalue\0\0 */ + char username[100]; + int username_from_env; + char localusername[100]; + int rfc_environ; + int passive_telnet; + /* Serial port options */ + char serline[256]; + int serspeed; + int serdatabits, serstopbits; + int serparity; + int serflow; + /* Keyboard options */ + int bksp_is_delete; + int rxvt_homeend; + int funky_type; + int no_applic_c; /* totally disable app cursor keys */ + int no_applic_k; /* totally disable app keypad */ + int no_mouse_rep; /* totally disable mouse reporting */ + int no_remote_resize; /* disable remote resizing */ + int no_alt_screen; /* disable alternate screen */ + int no_remote_wintitle; /* disable remote retitling */ + int no_dbackspace; /* disable destructive backspace */ + int no_remote_charset; /* disable remote charset config */ + int remote_qtitle_action; /* remote win title query action */ + int app_cursor; + int app_keypad; + int nethack_keypad; + int telnet_keyboard; + int telnet_newline; + int alt_f4; /* is it special? */ + int alt_space; /* is it special? */ + int alt_only; /* is it special? */ + int localecho; + int localedit; + int alwaysontop; + int fullscreenonaltenter; + int scroll_on_key; + int scroll_on_disp; + int erase_to_scrollback; + int compose_key; + int ctrlaltkeys; + char wintitle[256]; /* initial window title */ + /* Terminal options */ + int savelines; + int dec_om; + int wrap_mode; + int lfhascr; + int cursor_type; /* 0=block 1=underline 2=vertical */ + int blink_cur; + int beep; + int beep_ind; + int bellovl; /* bell overload protection active? */ + int bellovl_n; /* number of bells to cause overload */ + int bellovl_t; /* time interval for overload (seconds) */ + int bellovl_s; /* period of silence to re-enable bell (s) */ + Filename bell_wavefile; + int scrollbar; + int scrollbar_in_fullscreen; + int resize_action; + int bce; + int blinktext; + int win_name_always; + int width, height; + FontSpec font; + int font_quality; + Filename logfilename; + int logtype; + int logxfovr; + int logflush; + int logomitpass; + int logomitdata; + int hide_mouseptr; + int sunken_edge; + int window_border; + char answerback[256]; + char printer[128]; + int arabicshaping; + int bidi; + /* Colour options */ + int ansi_colour; + int xterm_256_colour; + int system_colour; + int try_palette; + int bold_colour; + unsigned char colours[22][3]; + /* Selection options */ + int mouse_is_xterm; + int rect_select; + int rawcnp; + int rtf_paste; + int mouse_override; + short wordness[256]; + /* translations */ + int vtmode; + char line_codepage[128]; + int cjk_ambig_wide; + int utf8_override; + int xlat_capslockcyr; + /* X11 forwarding */ + int x11_forward; + char x11_display[128]; + int x11_auth; + Filename xauthfile; + /* port forwarding */ + int lport_acceptall; /* accept conns from hosts other than localhost */ + int rport_acceptall; /* same for remote forwarded ports (SSH-2 only) */ + /* + * The port forwarding string contains a number of + * NUL-terminated substrings, terminated in turn by an empty + * string (i.e. a second NUL immediately after the previous + * one). Each string can be of one of the following forms: + * + * [LR]localport\thost:port + * [LR]localaddr:localport\thost:port + * Dlocalport + * Dlocaladdr:localport + */ + char portfwd[1024]; + /* SSH bug compatibility modes */ + int sshbug_ignore1, sshbug_plainpw1, sshbug_rsa1, + sshbug_hmac2, sshbug_derivekey2, sshbug_rsapad2, + sshbug_pksessid2, sshbug_rekey2, sshbug_maxpkt2, + sshbug_ignore2; + /* + * ssh_simple means that we promise never to open any channel other + * than the main one, which means it can safely use a very large + * window in SSH-2. + */ + int ssh_simple; + /* Options for pterm. Should split out into platform-dependent part. */ + int stamp_utmp; + int login_shell; + int scrollbar_on_left; + int shadowbold; + FontSpec boldfont; + FontSpec widefont; + FontSpec wideboldfont; + int shadowboldoffset; + int crhaslf; + char winclass[256]; +}; + +/* + * Some global flags denoting the type of application. + * + * FLAG_VERBOSE is set when the user requests verbose details. + * + * FLAG_STDERR is set in command-line applications (which have a + * functioning stderr that it makes sense to write to) and not in + * GUI applications (which don't). + * + * FLAG_INTERACTIVE is set when a full interactive shell session is + * being run, _either_ because no remote command has been provided + * _or_ because the application is GUI and can't run non- + * interactively. + * + * These flags describe the type of _application_ - they wouldn't + * vary between individual sessions - and so it's OK to have this + * variable be GLOBAL. + * + * Note that additional flags may be defined in platform-specific + * headers. It's probably best if those ones start from 0x1000, to + * avoid collision. + */ +#define FLAG_VERBOSE 0x0001 +#define FLAG_STDERR 0x0002 +#define FLAG_INTERACTIVE 0x0004 +GLOBAL int flags; + +/* + * Likewise, these two variables are set up when the application + * initialises, and inform all default-settings accesses after + * that. + */ +GLOBAL int default_protocol; +GLOBAL int default_port; + +/* + * This is set TRUE by cmdline.c iff a session is loaded with "-load". + */ +GLOBAL int loaded_session; +/* + * This is set to the name of the loaded session. + */ +GLOBAL char *cmdline_session_name; + +struct RSAKey; /* be a little careful of scope */ + +/* + * Mechanism for getting text strings such as usernames and passwords + * from the front-end. + * The fields are mostly modelled after SSH's keyboard-interactive auth. + * FIXME We should probably mandate a character set/encoding (probably UTF-8). + * + * Since many of the pieces of text involved may be chosen by the server, + * the caller must take care to ensure that the server can't spoof locally- + * generated prompts such as key passphrase prompts. Some ground rules: + * - If the front-end needs to truncate a string, it should lop off the + * end. + * - The front-end should filter out any dangerous characters and + * generally not trust the strings. (But \n is required to behave + * vaguely sensibly, at least in `instruction', and ideally in + * `prompt[]' too.) + */ +typedef struct { + char *prompt; + int echo; + char *result; /* allocated/freed by caller */ + size_t result_len; +} prompt_t; +typedef struct { + /* + * Indicates whether the information entered is to be used locally + * (for instance a key passphrase prompt), or is destined for the wire. + * This is a hint only; the front-end is at liberty not to use this + * information (so the caller should ensure that the supplied text is + * sufficient). + */ + int to_server; + char *name; /* Short description, perhaps for dialog box title */ + int name_reqd; /* Display of `name' required or optional? */ + char *instruction; /* Long description, maybe with embedded newlines */ + int instr_reqd; /* Display of `instruction' required or optional? */ + size_t n_prompts; /* May be zero (in which case display the foregoing, + * if any, and return success) */ + prompt_t **prompts; + void *frontend; + void *data; /* slot for housekeeping data, managed by + * get_userpass_input(); initially NULL */ +} prompts_t; +prompts_t *new_prompts(void *frontend); +void add_prompt(prompts_t *p, char *promptstr, int echo, size_t len); +/* Burn the evidence. (Assumes _all_ strings want free()ing.) */ +void free_prompts(prompts_t *p); + +/* + * Exports from the front end. + */ +void request_resize(void *frontend, int, int); +void do_text(Context, int, int, wchar_t *, int, unsigned long, int); +void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int); +int char_width(Context ctx, int uc); +#ifdef OPTIMISE_SCROLL +void do_scroll(Context, int, int, int); +#endif +void set_title(void *frontend, char *); +void set_icon(void *frontend, char *); +void set_sbar(void *frontend, int, int, int); +Context get_ctx(void *frontend); +void free_ctx(Context); +void palette_set(void *frontend, int, int, int, int); +void palette_reset(void *frontend); +void write_aclip(void *frontend, char *, int, int); +void write_clip(void *frontend, wchar_t *, int *, int, int); +void get_clip(void *frontend, wchar_t **, int *); +void optimised_move(void *frontend, int, int, int); +void set_raw_mouse_mode(void *frontend, int); +void connection_fatal(void *frontend, char *, ...); +void fatalbox(char *, ...); +void modalfatalbox(char *, ...); +#ifdef macintosh +#pragma noreturn(fatalbox) +#pragma noreturn(modalfatalbox) +#endif +void do_beep(void *frontend, int); +void begin_session(void *frontend); +void sys_cursor(void *frontend, int x, int y); +void request_paste(void *frontend); +void frontend_keypress(void *frontend); +void ldisc_update(void *frontend, int echo, int edit); +/* It's the backend's responsibility to invoke this at the start of a + * connection, if necessary; it can also invoke it later if the set of + * special commands changes. It does not need to invoke it at session + * shutdown. */ +void update_specials_menu(void *frontend); +int from_backend(void *frontend, int is_stderr, const char *data, int len); +int from_backend_untrusted(void *frontend, const char *data, int len); +void notify_remote_exit(void *frontend); +/* Get a sensible value for a tty mode. NULL return = don't set. + * Otherwise, returned value should be freed by caller. */ +char *get_ttymode(void *frontend, const char *mode); +/* + * >0 = `got all results, carry on' + * 0 = `user cancelled' (FIXME distinguish "give up entirely" and "next auth"?) + * <0 = `please call back later with more in/inlen' + */ +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen); +#define OPTIMISE_IS_SCROLL 1 + +void set_iconic(void *frontend, int iconic); +void move_window(void *frontend, int x, int y); +void set_zorder(void *frontend, int top); +void refresh_window(void *frontend); +void set_zoomed(void *frontend, int zoomed); +int is_iconic(void *frontend); +void get_window_pos(void *frontend, int *x, int *y); +void get_window_pixels(void *frontend, int *x, int *y); +char *get_window_title(void *frontend, int icon); +/* Hint from backend to frontend about time-consuming operations. + * Initial state is assumed to be BUSY_NOT. */ +enum { + BUSY_NOT, /* Not busy, all user interaction OK */ + BUSY_WAITING, /* Waiting for something; local event loops still running + so some local interaction (e.g. menus) OK, but network + stuff is suspended */ + BUSY_CPU /* Locally busy (e.g. crypto); user interaction suspended */ +}; +void set_busy_status(void *frontend, int status); + +void cleanup_exit(int); + +/* + * Exports from noise.c. + */ +void noise_get_heavy(void (*func) (void *, int)); +void noise_get_light(void (*func) (void *, int)); +void noise_regular(void); +void noise_ultralight(unsigned long data); +void random_save_seed(void); +void random_destroy_seed(void); + +/* + * Exports from settings.c. + */ +Backend *backend_from_name(const char *name); +Backend *backend_from_proto(int proto); +int get_remote_username(Config *cfg, char *user, size_t len); +char *save_settings(char *section, Config * cfg); +void save_open_settings(void *sesskey, Config *cfg); +void load_settings(char *section, Config * cfg); +void load_open_settings(void *sesskey, Config *cfg); +void get_sesslist(struct sesslist *, int allocate); +void do_defaults(char *, Config *); +void registry_cleanup(void); + +/* + * Functions used by settings.c to provide platform-specific + * default settings. + * + * (The integer one is expected to return `def' if it has no clear + * opinion of its own. This is because there's no integer value + * which I can reliably set aside to indicate `nil'. The string + * function is perfectly all right returning NULL, of course. The + * Filename and FontSpec functions are _not allowed_ to fail to + * return, since these defaults _must_ be per-platform.) + */ +char *platform_default_s(const char *name); +int platform_default_i(const char *name, int def); +Filename platform_default_filename(const char *name); +FontSpec platform_default_fontspec(const char *name); + +/* + * Exports from terminal.c. + */ + +Terminal *term_init(Config *, struct unicode_data *, void *); +void term_free(Terminal *); +void term_size(Terminal *, int, int, int); +void term_paint(Terminal *, Context, int, int, int, int, int); +void term_scroll(Terminal *, int, int); +void term_scroll_to_selection(Terminal *, int); +void term_pwron(Terminal *, int); +void term_clrsb(Terminal *); +void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action, + int,int,int,int,int); +void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int, + unsigned int); +void term_deselect(Terminal *); +void term_update(Terminal *); +void term_invalidate(Terminal *); +void term_blink(Terminal *, int set_cursor); +void term_do_paste(Terminal *); +int term_paste_pending(Terminal *); +void term_paste(Terminal *); +void term_nopaste(Terminal *); +int term_ldisc(Terminal *, int option); +void term_copyall(Terminal *); +void term_reconfig(Terminal *, Config *); +void term_seen_key_event(Terminal *); +int term_data(Terminal *, int is_stderr, const char *data, int len); +int term_data_untrusted(Terminal *, const char *data, int len); +void term_provide_resize_fn(Terminal *term, + void (*resize_fn)(void *, int, int), + void *resize_ctx); +void term_provide_logctx(Terminal *term, void *logctx); +void term_set_focus(Terminal *term, int has_focus); +char *term_get_ttymode(Terminal *term, const char *mode); +int term_get_userpass_input(Terminal *term, prompts_t *p, + unsigned char *in, int inlen); + +int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl); + +/* + * Exports from logging.c. + */ +void *log_init(void *frontend, Config *cfg); +void log_free(void *logctx); +void log_reconfig(void *logctx, Config *cfg); +void logfopen(void *logctx); +void logfclose(void *logctx); +void logtraffic(void *logctx, unsigned char c, int logmode); +void logflush(void *logctx); +void log_eventlog(void *logctx, const char *string); +enum { PKT_INCOMING, PKT_OUTGOING }; +enum { PKTLOG_EMIT, PKTLOG_BLANK, PKTLOG_OMIT }; +struct logblank_t { + int offset; + int len; + int type; +}; +void log_packet(void *logctx, int direction, int type, + char *texttype, const void *data, int len, + int n_blanks, const struct logblank_t *blanks, + const unsigned long *sequence); + +/* + * Exports from testback.c + */ + +extern Backend null_backend; +extern Backend loop_backend; + +/* + * Exports from raw.c. + */ + +extern Backend raw_backend; + +/* + * Exports from rlogin.c. + */ + +extern Backend rlogin_backend; + +/* + * Exports from telnet.c. + */ + +extern Backend telnet_backend; + +/* + * Exports from ssh.c. + */ +extern Backend ssh_backend; + +/* + * Exports from ldisc.c. + */ +void *ldisc_create(Config *, Terminal *, Backend *, void *, void *); +void ldisc_free(void *); +void ldisc_send(void *handle, char *buf, int len, int interactive); + +/* + * Exports from ldiscucs.c. + */ +void lpage_send(void *, int codepage, char *buf, int len, int interactive); +void luni_send(void *, wchar_t * widebuf, int len, int interactive); + +/* + * Exports from sshrand.c. + */ + +void random_add_noise(void *noise, int length); +int random_byte(void); +void random_get_savedata(void **data, int *len); +extern int random_active; +/* The random number subsystem is activated if at least one other entity + * within the program expresses an interest in it. So each SSH session + * calls random_ref on startup and random_unref on shutdown. */ +void random_ref(void); +void random_unref(void); + +/* + * Exports from pinger.c. + */ +typedef struct pinger_tag *Pinger; +Pinger pinger_new(Config *cfg, Backend *back, void *backhandle); +void pinger_reconfig(Pinger, Config *oldcfg, Config *newcfg); +void pinger_free(Pinger); + +/* + * Exports from misc.c. + */ + +#include "misc.h" +int cfg_launchable(const Config *cfg); +char const *cfg_dest(const Config *cfg); + +/* + * Exports from sercfg.c. + */ +void ser_setup_config_box(struct controlbox *b, int midsession, + int parity_mask, int flow_mask); + +/* + * Exports from version.c. + */ +extern char ver[]; + +/* + * Exports from unicode.c. + */ +#ifndef CP_UTF8 +#define CP_UTF8 65001 +#endif +/* void init_ucs(void); -- this is now in platform-specific headers */ +int is_dbcs_leadbyte(int codepage, char byte); +int mb_to_wc(int codepage, int flags, char *mbstr, int mblen, + wchar_t *wcstr, int wclen); +int wc_to_mb(int codepage, int flags, wchar_t *wcstr, int wclen, + char *mbstr, int mblen, char *defchr, int *defused, + struct unicode_data *ucsdata); +wchar_t xlat_uskbd2cyrllic(int ch); +int check_compose(int first, int second); +int decode_codepage(char *cp_name); +const char *cp_enumerate (int index); +const char *cp_name(int codepage); +void get_unitab(int codepage, wchar_t * unitab, int ftype); + +/* + * Exports from wcwidth.c + */ +int mk_wcwidth(wchar_t ucs); +int mk_wcswidth(const wchar_t *pwcs, size_t n); +int mk_wcwidth_cjk(wchar_t ucs); +int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n); + +/* + * Exports from mscrypto.c + */ +#ifdef MSCRYPTOAPI +int crypto_startup(); +void crypto_wrapup(); +#endif + +/* + * Exports from pageantc.c. + * + * agent_query returns 1 for here's-a-response, and 0 for query-in- + * progress. In the latter case there will be a call to `callback' + * at some future point, passing callback_ctx as the first + * parameter and the actual reply data as the second and third. + * + * The response may be a NULL pointer (in either of the synchronous + * or asynchronous cases), which indicates failure to receive a + * response. + */ +int agent_query(void *in, int inlen, void **out, int *outlen, + void (*callback)(void *, void *, int), void *callback_ctx); +int agent_exists(void); + +/* + * Exports from wildcard.c + */ +const char *wc_error(int value); +int wc_match(const char *wildcard, const char *target); +int wc_unescape(char *output, const char *wildcard); + +/* + * Exports from frontend (windlg.c etc) + */ +void logevent(void *frontend, const char *); +void pgp_fingerprints(void); +/* + * verify_ssh_host_key() can return one of three values: + * + * - +1 means `key was OK' (either already known or the user just + * approved it) `so continue with the connection' + * + * - 0 means `key was not OK, abandon the connection' + * + * - -1 means `I've initiated enquiries, please wait to be called + * back via the provided function with a result that's either 0 + * or +1'. + */ +int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, + char *keystr, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx); +/* + * askalg has the same set of return values as verify_ssh_host_key. + */ +int askalg(void *frontend, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx); +/* + * askappend can return four values: + * + * - 2 means overwrite the log file + * - 1 means append to the log file + * - 0 means cancel logging for this session + * - -1 means please wait. + */ +int askappend(void *frontend, Filename filename, + void (*callback)(void *ctx, int result), void *ctx); + +/* + * Exports from console frontends (wincons.c, uxcons.c) + * that aren't equivalents to things in windlg.c et al. + */ +extern int console_batch_mode; +int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen); +void console_provide_logctx(void *logctx); +int is_interactive(void); + +/* + * Exports from printing.c. + */ +typedef struct printer_enum_tag printer_enum; +typedef struct printer_job_tag printer_job; +printer_enum *printer_start_enum(int *nprinters); +char *printer_get_name(printer_enum *, int); +void printer_finish_enum(printer_enum *); +printer_job *printer_start_job(char *printer); +void printer_job_data(printer_job *, void *, int); +void printer_finish_job(printer_job *); + +/* + * Exports from cmdline.c (and also cmdline_error(), which is + * defined differently in various places and required _by_ + * cmdline.c). + */ +int cmdline_process_param(char *, char *, int, Config *); +void cmdline_run_saved(Config *); +void cmdline_cleanup(void); +int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen); +#define TOOLTYPE_FILETRANSFER 1 +#define TOOLTYPE_NONNETWORK 2 +extern int cmdline_tooltype; + +void cmdline_error(char *, ...); + +/* + * Exports from config.c. + */ +struct controlbox; +void setup_config_box(struct controlbox *b, int midsession, + int protocol, int protcfginfo); + +/* + * Exports from minibidi.c. + */ +typedef struct bidi_char { + wchar_t origwc, wc; + unsigned short index; +} bidi_char; +int do_bidi(bidi_char *line, int count); +int do_shape(bidi_char *line, bidi_char *to, int count); +int is_rtl(int c); + +/* + * X11 auth mechanisms we know about. + */ +enum { + X11_NO_AUTH, + X11_MIT, /* MIT-MAGIC-COOKIE-1 */ + X11_XDM, /* XDM-AUTHORIZATION-1 */ + X11_NAUTHS +}; +extern const char *const x11_authnames[]; /* declared in x11fwd.c */ + +/* + * Miscellaneous exports from the platform-specific code. + */ +Filename filename_from_str(const char *string); +const char *filename_to_str(const Filename *fn); +int filename_equal(Filename f1, Filename f2); +int filename_is_null(Filename fn); +char *get_username(void); /* return value needs freeing */ +char *get_random_data(int bytes); /* used in cmdgen.c */ + +/* + * Exports and imports from timing.c. + * + * schedule_timer() asks the front end to schedule a callback to a + * timer function in a given number of ticks. The returned value is + * the time (in ticks since an arbitrary offset) at which the + * callback can be expected. This value will also be passed as the + * `now' parameter to the callback function. Hence, you can (for + * example) schedule an event at a particular time by calling + * schedule_timer() and storing the return value in your context + * structure as the time when that event is due. The first time a + * callback function gives you that value or more as `now', you do + * the thing. + * + * expire_timer_context() drops all current timers associated with + * a given value of ctx (for when you're about to free ctx). + * + * run_timers() is called from the front end when it has reason to + * think some timers have reached their moment, or when it simply + * needs to know how long to wait next. We pass it the time we + * think it is. It returns TRUE and places the time when the next + * timer needs to go off in `next', or alternatively it returns + * FALSE if there are no timers at all pending. + * + * timer_change_notify() must be supplied by the front end; it + * notifies the front end that a new timer has been added to the + * list which is sooner than any existing ones. It provides the + * time when that timer needs to go off. + * + * *** FRONT END IMPLEMENTORS NOTE: + * + * There's an important subtlety in the front-end implementation of + * the timer interface. When a front end is given a `next' value, + * either returned from run_timers() or via timer_change_notify(), + * it should ensure that it really passes _that value_ as the `now' + * parameter to its next run_timers call. It should _not_ simply + * call GETTICKCOUNT() to get the `now' parameter when invoking + * run_timers(). + * + * The reason for this is that an OS's system clock might not agree + * exactly with the timing mechanisms it supplies to wait for a + * given interval. I'll illustrate this by the simple example of + * Unix Plink, which uses timeouts to select() in a way which for + * these purposes can simply be considered to be a wait() function. + * Suppose, for the sake of argument, that this wait() function + * tends to return early by 1%. Then a possible sequence of actions + * is: + * + * - run_timers() tells the front end that the next timer firing + * is 10000ms from now. + * - Front end calls wait(10000ms), but according to + * GETTICKCOUNT() it has only waited for 9900ms. + * - Front end calls run_timers() again, passing time T-100ms as + * `now'. + * - run_timers() does nothing, and says the next timer firing is + * still 100ms from now. + * - Front end calls wait(100ms), which only waits for 99ms. + * - Front end calls run_timers() yet again, passing time T-1ms. + * - run_timers() says there's still 1ms to wait. + * - Front end calls wait(1ms). + * + * If you're _lucky_ at this point, wait(1ms) will actually wait + * for 1ms and you'll only have woken the program up three times. + * If you're unlucky, wait(1ms) might do nothing at all due to + * being below some minimum threshold, and you might find your + * program spends the whole of the last millisecond tight-looping + * between wait() and run_timers(). + * + * Instead, what you should do is to _save_ the precise `next' + * value provided by run_timers() or via timer_change_notify(), and + * use that precise value as the input to the next run_timers() + * call. So: + * + * - run_timers() tells the front end that the next timer firing + * is at time T, 10000ms from now. + * - Front end calls wait(10000ms). + * - Front end then immediately calls run_timers() and passes it + * time T, without stopping to check GETTICKCOUNT() at all. + * + * This guarantees that the program wakes up only as many times as + * there are actual timer actions to be taken, and that the timing + * mechanism will never send it into a tight loop. + * + * (It does also mean that the timer action in the above example + * will occur 100ms early, but this is not generally critical. And + * the hypothetical 1% error in wait() will be partially corrected + * for anyway when, _after_ run_timers() returns, you call + * GETTICKCOUNT() and compare the result with the returned `next' + * value to find out how long you have to make your next wait().) + */ +typedef void (*timer_fn_t)(void *ctx, long now); +long schedule_timer(int ticks, timer_fn_t fn, void *ctx); +void expire_timer_context(void *ctx); +int run_timers(long now, long *next); +void timer_change_notify(long next); + +/* + * Define no-op macros for the jump list functions, on platforms that + * don't support them. (This is a bit of a hack, and it'd be nicer to + * localise even the calls to those functions into the Windows front + * end, but it'll do for the moment.) + */ +#ifndef JUMPLIST_SUPPORTED +#define add_session_to_jumplist(x) ((void)0) +#define remove_session_from_jumplist(x) ((void)0) +#endif + +#endif diff --git a/putty/PUTTYMEM.H b/putty/PUTTYMEM.H new file mode 100644 index 0000000..d478f79 --- /dev/null +++ b/putty/PUTTYMEM.H @@ -0,0 +1,42 @@ +/* + * PuTTY memory-handling header. + */ + +#ifndef PUTTY_PUTTYMEM_H +#define PUTTY_PUTTYMEM_H + +#include /* for size_t */ +#include /* for memcpy() */ + + +/* #define MALLOC_LOG do this if you suspect putty of leaking memory */ +#ifdef MALLOC_LOG +#define smalloc(z) (mlog(__FILE__,__LINE__), safemalloc(z,1)) +#define snmalloc(z,s) (mlog(__FILE__,__LINE__), safemalloc(z,s)) +#define srealloc(y,z) (mlog(__FILE__,__LINE__), saferealloc(y,z,1)) +#define snrealloc(y,z,s) (mlog(__FILE__,__LINE__), saferealloc(y,z,s)) +#define sfree(z) (mlog(__FILE__,__LINE__), safefree(z)) +void mlog(char *, int); +#else +#define smalloc(z) safemalloc(z,1) +#define snmalloc safemalloc +#define srealloc(y,z) saferealloc(y,z,1) +#define snrealloc saferealloc +#define sfree safefree +#endif + +void *safemalloc(size_t, size_t); +void *saferealloc(void *, size_t, size_t); +void safefree(void *); + +/* + * Direct use of smalloc within the code should be avoided where + * possible, in favour of these type-casting macros which ensure + * you don't mistakenly allocate enough space for one sort of + * structure and assign it to a different sort of pointer. + */ +#define snew(type) ((type *)snmalloc(1, sizeof(type))) +#define snewn(n, type) ((type *)snmalloc((n), sizeof(type))) +#define sresize(ptr, n, type) ((type *)snrealloc((ptr), (n), sizeof(type))) + +#endif diff --git a/putty/PUTTYPS.H b/putty/PUTTYPS.H new file mode 100644 index 0000000..454f605 --- /dev/null +++ b/putty/PUTTYPS.H @@ -0,0 +1,22 @@ +/* + * Find the platform-specific header for this platform. + */ + +#ifndef PUTTY_PUTTYPS_H +#define PUTTY_PUTTYPS_H + +#ifdef _WINDOWS + +#include "winstuff.h" + +#elif defined(MACOSX) + +#include "osx.h" + +#else + +#include "unix.h" + +#endif + +#endif diff --git a/putty/PuTTY.vc90.vcproj b/putty/PuTTY.vc90.vcproj new file mode 100644 index 0000000..78e5d54 --- /dev/null +++ b/putty/PuTTY.vc90.vcproj @@ -0,0 +1,394 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/putty/PuTTY.vcproj b/putty/PuTTY.vcproj new file mode 100644 index 0000000..76d679b --- /dev/null +++ b/putty/PuTTY.vcproj @@ -0,0 +1,393 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/putty/RAW.C b/putty/RAW.C new file mode 100644 index 0000000..ea51d74 --- /dev/null +++ b/putty/RAW.C @@ -0,0 +1,300 @@ +/* + * "Raw" backend. + */ + +#include +#include + +#include "putty.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#define RAW_MAX_BACKLOG 4096 + +typedef struct raw_backend_data { + const struct plug_function_table *fn; + /* the above field _must_ be first in the structure */ + + Socket s; + int bufsize; + void *frontend; +} *Raw; + +static void raw_size(void *handle, int width, int height); + +static void c_write(Raw raw, char *buf, int len) +{ + int backlog = from_backend(raw->frontend, 0, buf, len); + sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); +} + +static void raw_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + Raw raw = (Raw) plug; + char addrbuf[256], *msg; + + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + + if (type == 0) + msg = dupprintf("Connecting to %s port %d", addrbuf, port); + else + msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); + + logevent(raw->frontend, msg); +} + +static int raw_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + Raw raw = (Raw) plug; + + if (raw->s) { + sk_close(raw->s); + raw->s = NULL; + notify_remote_exit(raw->frontend); + } + if (error_msg) { + /* A socket error has occurred. */ + logevent(raw->frontend, error_msg); + connection_fatal(raw->frontend, "%s", error_msg); + } /* Otherwise, the remote side closed the connection normally. */ + return 0; +} + +static int raw_receive(Plug plug, int urgent, char *data, int len) +{ + Raw raw = (Raw) plug; + c_write(raw, data, len); + return 1; +} + +static void raw_sent(Plug plug, int bufsize) +{ + Raw raw = (Raw) plug; + raw->bufsize = bufsize; +} + +/* + * Called to set up the raw connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *raw_init(void *frontend_handle, void **backend_handle, + Config *cfg, + char *host, int port, char **realhost, int nodelay, + int keepalive) +{ + static const struct plug_function_table fn_table = { + raw_log, + raw_closing, + raw_receive, + raw_sent + }; + SockAddr addr; + const char *err; + Raw raw; + + raw = snew(struct raw_backend_data); + raw->fn = &fn_table; + raw->s = NULL; + *backend_handle = raw; + + raw->frontend = frontend_handle; + + /* + * Try to find host. + */ + { + char *buf; + buf = dupprintf("Looking up host \"%s\"%s", host, + (cfg->addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (cfg->addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + ""))); + logevent(raw->frontend, buf); + sfree(buf); + } + addr = name_lookup(host, port, realhost, cfg, cfg->addressfamily); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + + if (port < 0) + port = 23; /* default telnet port */ + + /* + * Open socket. + */ + raw->s = new_connection(addr, *realhost, port, 0, 1, nodelay, keepalive, + (Plug) raw, cfg); + if ((err = sk_socket_error(raw->s)) != NULL) + return err; + + if (*cfg->loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(cfg->loghost); + colon = strrchr(*realhost, ':'); + if (colon) { + /* + * FIXME: if we ever update this aspect of ssh.c for + * IPv6 literal management, this should change in line + * with it. + */ + *colon++ = '\0'; + } + } + + return NULL; +} + +static void raw_free(void *handle) +{ + Raw raw = (Raw) handle; + + if (raw->s) + sk_close(raw->s); + sfree(raw); +} + +/* + * Stub routine (we don't have any need to reconfigure this backend). + */ +static void raw_reconfig(void *handle, Config *cfg) +{ +} + +/* + * Called to send data down the raw connection. + */ +static int raw_send(void *handle, char *buf, int len) +{ + Raw raw = (Raw) handle; + + if (raw->s == NULL) + return 0; + + raw->bufsize = sk_write(raw->s, buf, len); + + return raw->bufsize; +} + +/* + * Called to query the current socket sendability status. + */ +static int raw_sendbuffer(void *handle) +{ + Raw raw = (Raw) handle; + return raw->bufsize; +} + +/* + * Called to set the size of the window + */ +static void raw_size(void *handle, int width, int height) +{ + /* Do nothing! */ + return; +} + +/* + * Send raw special codes. + */ +static void raw_special(void *handle, Telnet_Special code) +{ + /* Do nothing! */ + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *raw_get_specials(void *handle) +{ + return NULL; +} + +static int raw_connected(void *handle) +{ + Raw raw = (Raw) handle; + return raw->s != NULL; +} + +static int raw_sendok(void *handle) +{ + return 1; +} + +static void raw_unthrottle(void *handle, int backlog) +{ + Raw raw = (Raw) handle; + sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); +} + +static int raw_ldisc(void *handle, int option) +{ + if (option == LD_EDIT || option == LD_ECHO) + return 1; + return 0; +} + +static void raw_provide_ldisc(void *handle, void *ldisc) +{ + /* This is a stub. */ +} + +static void raw_provide_logctx(void *handle, void *logctx) +{ + /* This is a stub. */ +} + +static int raw_exitcode(void *handle) +{ + Raw raw = (Raw) handle; + if (raw->s != NULL) + return -1; /* still connected */ + else + /* Exit codes are a meaningless concept in the Raw protocol */ + return 0; +} + +/* + * cfg_info for Raw does nothing at all. + */ +static int raw_cfg_info(void *handle) +{ + return 0; +} + +Backend raw_backend = { + raw_init, + raw_free, + raw_reconfig, + raw_send, + raw_sendbuffer, + raw_size, + raw_special, + raw_get_specials, + raw_connected, + raw_exitcode, + raw_sendok, + raw_ldisc, + raw_provide_ldisc, + raw_provide_logctx, + raw_unthrottle, + raw_cfg_info, + "raw", + PROT_RAW, + 0 +}; diff --git a/putty/README b/putty/README new file mode 100644 index 0000000..9cca5c0 --- /dev/null +++ b/putty/README @@ -0,0 +1,115 @@ +This is the README for the source archive of PuTTY, a free Win32 +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 +Subversion, you'll have to generate the Makefiles yourself -- see +below.) + +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. + +For building on Windows: + + - 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. + + Last time we checked, PuTTY built with vanilla VC7, or VC6 with + an up-to-date Platform SDK. (It might still be possible to build + with vanilla VC6, but you'll certainly have to remove some + functionality with directives such as NO_IPV6.) + + (We've also had reports of success building with the + OpenWatcom compiler -- www.openwatcom.org -- using Makefile.vc + with `wmake -ms -f makefile.vc' and NO_MULTIMON, although we + haven't tried this ourselves. Version 1.3 is reported to work.) + + - 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 6. + + 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.bor is for the Borland C compiler. Type `make -f + Makefile.bor' while in the `windows' subdirectory to build all + the PuTTY binaries. + + - windows/Makefile.cyg is for Cygwin / mingw32 installations. Type + `make -f Makefile.cyg' while in the `windows' subdirectory to + build all the PuTTY binaries. + + You'll probably need quite a recent version of the w32api package. + Note that by default the multiple monitor and HTML Help support are + excluded from the Cygwin build, since at the time of writing Cygwin + doesn't include the necessary headers. + + - 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) and Makefile.cyg +(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'. + + Note that Unix PuTTY has mostly only been tested on Linux so far; + portability problems such as BSD-style ptys or different header file + requirements are expected. + + - 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, Gtk+-1.2 and Gtk+-2.0 should both be + supported. + + - Both Unix Makefiles have an `install' target. Note that by default + it tries to install `man' pages, which you may need to have built + using Halibut first -- see below. + +All of the Makefiles are generated automatically from the file +`Recipe' by the Perl script `mkfiles.pl'. Additions and corrections +to Recipe and the mkfiles.pl are much more useful than additions and +corrections to the alternative Makefiles themselves. + +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 Subversion rather +than using one of our source snapshots, you'll need to run this +yourself. + +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 . + +The PuTTY home web site is + + http://www.chiark.greenend.org.uk/~sgtatham/putty/ + +If you want to send bug reports or feature requests, please read the +Feedback section of the web site before doing so. Sending one-line +reports saying `it doesn't work' will waste your time as much as +ours. + +See the file LICENCE for the licence conditions. diff --git a/putty/RECIPE b/putty/RECIPE new file mode 100644 index 0000000..ce59a75 --- /dev/null +++ b/putty/RECIPE @@ -0,0 +1,347 @@ +# -*- 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 vc windows/Makefile.vc +!makefile vcproj windows/MSVC +!makefile cygwin windows/Makefile.cyg +!makefile borland windows/Makefile.bor +!makefile lcc windows/Makefile.lcc +!makefile gtk unix/Makefile.gtk +!makefile unix unix/Makefile.ux +!makefile ac unix/Makefile.in +!makefile osx macosx/Makefile +!makefile devcppproj windows/DEVCPP +# Source directories. +!srcdir charset/ +!srcdir windows/ +!srcdir unix/ +!srcdir macosx/ + +# 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: +# +# - VER="/DSNAPSHOT=1999-01-25 /DSVN_REV=1234" +# Generates executables whose About box report them as being a +# development snapshot. SVN_REV is a Subversion revision number. +# +# - VER=/DRELEASE=0.43 +# Generates executables whose About box report them as being a +# release version. +# +# - COMPAT=/DAUTO_WINSOCK (Windows only) +# Causes PuTTY to assume that includes its own WinSock +# header file, so that it won't try to include . +# +# - COMPAT=/DWINSOCK_TWO (Windows only) +# Causes the PuTTY utilities to include instead of +# , except Plink which _needs_ WinSock 2 so it already +# does this. +# +# - COMPAT=/DNO_SECURITY (Windows only) +# Disables Pageant's use of , which is not available +# with some development environments (such as older versions of +# the Cygwin/mingw GNU toolchain). This means that 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). +# +# - COMPAT=/DNO_MULTIMON (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. This means that PuTTY's +# full-screen mode (configurable to work on Alt-Enter) will +# not behave usefully in a multi-monitor environment. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin. +# +# - COMPAT=/DNO_HTMLHELP (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. The resulting binary +# will only look for an old-style WinHelp file (.HLP/.CNT), and +# will ignore any .CHM file. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin (although you can use +# the htmlhelp.h 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. +# +# - RCFL=/DASCIICTLS (Windows only) +# Uses ASCII rather than Unicode to specify the tab control in +# the resource file. Probably most useful when compiling with +# Cygnus/mingw32, whose resource compiler may have less of a +# problem with it. +# +# - XFLAGS=/DTELNET_DEFAULT +# Causes PuTTY to default to the Telnet protocol (in the absence +# of Default Settings and so on to the contrary). Normally PuTTY +# will default to SSH. +# +# - 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. +# +!end + +# ------------------------------------------------------------ +# Additional text added verbatim to each individual Makefile. + +# Hack to force version.o to be rebuilt always. +!begin vc +version.obj: *.c *.h *.rc + cl $(VER) $(CFLAGS) /c ..\version.c +!end +!specialobj vc version +!begin cygwin +version.o: FORCE + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) $(VER) -c ../version.c +!end +!specialobj cygwin version +!begin borland +version.obj: FORCE + bcc32 $(VER) $(CFLAGS) /c ..\version.c +!end +!specialobj borland version +!begin lcc +version.obj: FORCE + lcc $(VER) $(CFLAGS) /c ..\version.c +!end +!specialobj lcc version +# For Unix, we also need the gross MD5 hack that causes automatic +# version number selection in release source archives. +!begin gtk +version.o: FORCE + if test -z "$(VER)" && (cd ..; md5sum -c manifest); then \ + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) `cat ../version.def` -c ../version.c; \ + else \ + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) $(VER) -c ../version.c; \ + fi +!end +!specialobj gtk version + +# Add VER to Windows resource targets, and force them to be rebuilt every +# time, on the assumption that they will contain version information. +!begin vc vars +CFLAGS = $(CFLAGS) /DHAS_GSSAPI /DSECURITY_WIN32 +RCFLAGS = $(RCFLAGS) $(VER) +!end +!begin cygwin vars +# XXX GNU-ism, but it's probably all right for a Cygwin/MinGW Makefile. +RCFLAGS += $(patsubst -D%,--define %,$(VER)) +!end +!begin borland vars +# Borland doesn't support +=. This probably shouldn't work, but seems to. +RCFLAGS = $(RCFLAGS) $(VER) +!end +!begin lcc vars +RCFLAGS += $(VER) +!end +!forceobj putty.res +!forceobj puttytel.res +!forceobj plink.res +!forceobj pscp.res +!forceobj psftp.res +!forceobj pageant.res +!forceobj puttygen.res + +# `make install' target for Unix. +!begin gtk +install: + mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir) + $(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/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 +!begin osx vars +CFLAGS += -DMACOSX +!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. + +# Terminal emulator and its (platform-independent) dependencies. +TERMINAL = terminal wcwidth ldiscucs logging tree234 minibidi + + config dialog + +# GUI front end and terminal emulator (putty, puttytel). +GUITERM = TERMINAL window windlg winctrls sizetip winucs winprint + + winutils wincfg sercfg winhelp winjump + +# Same thing on Unix. +UXTERM = TERMINAL uxcfg sercfg uxucs uxprint timing +GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols xkeysym +OSXTERM = UXTERM osxwin osxdlg osxctrls + +# Non-SSH back ends (putty, puttytel, plink). +NONSSH = telnet raw rlogin ldisc pinger + +# SSH back end (putty, plink, pscp, psftp). +SSH = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf + + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd + + sshaes sshsh256 sshsh512 sshbn wildcard pinger ssharcf + + sshgssc pgssapi +WINSSH = SSH winnoise winpgntc wingss +UXSSH = SSH uxnoise uxagentc uxgss + +# SFTP implementation (pscp, psftp). +SFTP = sftp int64 logging + +# Miscellaneous objects appearing in all the network utilities (not +# Pageant or PuTTYgen). +MISC = timing misc version settings tree234 proxy +WINMISC = MISC winstore winnet winhandl cmdline windefs winmisc winproxy + + wintime +UXMISC = MISC uxstore uxsel uxnet cmdline uxmisc uxproxy time +OSXMISC = MISC uxstore uxsel osxsel uxnet uxmisc uxproxy time + +# 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 comctl32.lib comdlg32.lib + + shell32.lib winmm.lib imm32.lib winspool.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 nocproxy +BE_SSH = be_none 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 winser nocproxy +# And with the Unix serial-port module. +U_BE_ALL = be_all_s uxser cproxy +U_BE_NOSSH = be_nos_s uxser nocproxy + +# ------------------------------------------------------------ +# 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 NONSSH WINSSH W_BE_ALL logging WINMISC + + winx11 plink.res winnojmp LIBS +pscp : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC + + pscp.res winnojmp LIBS +psftp : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC + + psftp.res winnojmp LIBS + +pageant : [G] winpgnt sshrsa sshpubk sshdes sshbn sshmd5 version tree234 + + misc sshaes sshsha winpgntc sshdss sshsh256 sshsh512 winutils + + winmisc winhelp pageant.res LIBS + +puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + + sshrand winnoise sshsha winstore misc winctrls sshrsa sshdss winmisc + + sshpubk sshaes sshsh256 sshsh512 import winutils puttygen.res + + tree234 notiming winhelp winnojmp LIBS wintime + +pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore + + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg + + nogss +putty : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore + + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty + + xpmpucfg +puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH + + uxstore uxsignal CHARSET uxputty NONSSH UXMISC xpmputty xpmpucfg + + nogss + +plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal + + ux_x11 + +puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc + + sshpubk sshaes sshsh256 sshsh512 import puttygen.res time tree234 + + uxgen notiming + +pscp : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC +psftp : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC + +PuTTY : [MX] osxmain OSXTERM OSXMISC CHARSET U_BE_ALL NONSSH UXSSH + + ux_x11 uxpty uxsignal testback putty.icns info.plist diff --git a/putty/RESOURCE.H b/putty/RESOURCE.H new file mode 100644 index 0000000..188cc33 --- /dev/null +++ b/putty/RESOURCE.H @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by win_res.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/putty/RLOGIN.C b/putty/RLOGIN.C new file mode 100644 index 0000000..0abf8cd --- /dev/null +++ b/putty/RLOGIN.C @@ -0,0 +1,416 @@ +/* + * Rlogin backend. + */ + +#include +#include +#include + +#include "putty.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#define RLOGIN_MAX_BACKLOG 4096 + +typedef struct rlogin_tag { + const struct plug_function_table *fn; + /* the above field _must_ be first in the structure */ + + Socket s; + int bufsize; + int firstbyte; + int cansize; + int term_width, term_height; + void *frontend; + + Config cfg; + + /* In case we need to read a username from the terminal before starting */ + prompts_t *prompt; +} *Rlogin; + +static void rlogin_size(void *handle, int width, int height); + +static void c_write(Rlogin rlogin, char *buf, int len) +{ + int backlog = from_backend(rlogin->frontend, 0, buf, len); + sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); +} + +static void rlogin_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + Rlogin rlogin = (Rlogin) plug; + char addrbuf[256], *msg; + + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + + if (type == 0) + msg = dupprintf("Connecting to %s port %d", addrbuf, port); + else + msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); + + logevent(rlogin->frontend, msg); +} + +static int rlogin_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + Rlogin rlogin = (Rlogin) plug; + if (rlogin->s) { + sk_close(rlogin->s); + rlogin->s = NULL; + notify_remote_exit(rlogin->frontend); + } + if (error_msg) { + /* A socket error has occurred. */ + logevent(rlogin->frontend, error_msg); + connection_fatal(rlogin->frontend, "%s", error_msg); + } /* Otherwise, the remote side closed the connection normally. */ + return 0; +} + +static int rlogin_receive(Plug plug, int urgent, char *data, int len) +{ + Rlogin rlogin = (Rlogin) plug; + if (urgent == 2) { + char c; + + c = *data++; + len--; + if (c == '\x80') { + rlogin->cansize = 1; + rlogin_size(rlogin, rlogin->term_width, rlogin->term_height); + } + /* + * We should flush everything (aka Telnet SYNCH) if we see + * 0x02, and we should turn off and on _local_ flow control + * on 0x10 and 0x20 respectively. I'm not convinced it's + * worth it... + */ + } else { + /* + * Main rlogin protocol. This is really simple: the first + * byte is expected to be NULL and is ignored, and the rest + * is printed. + */ + if (rlogin->firstbyte) { + if (data[0] == '\0') { + data++; + len--; + } + rlogin->firstbyte = 0; + } + if (len > 0) + c_write(rlogin, data, len); + } + return 1; +} + +static void rlogin_sent(Plug plug, int bufsize) +{ + Rlogin rlogin = (Rlogin) plug; + rlogin->bufsize = bufsize; +} + +static void rlogin_startup(Rlogin rlogin, const char *ruser) +{ + char z = 0; + char *p; + sk_write(rlogin->s, &z, 1); + sk_write(rlogin->s, rlogin->cfg.localusername, + strlen(rlogin->cfg.localusername)); + sk_write(rlogin->s, &z, 1); + sk_write(rlogin->s, ruser, + strlen(ruser)); + sk_write(rlogin->s, &z, 1); + sk_write(rlogin->s, rlogin->cfg.termtype, + strlen(rlogin->cfg.termtype)); + sk_write(rlogin->s, "/", 1); + for (p = rlogin->cfg.termspeed; isdigit((unsigned char)*p); p++) continue; + sk_write(rlogin->s, rlogin->cfg.termspeed, p - rlogin->cfg.termspeed); + rlogin->bufsize = sk_write(rlogin->s, &z, 1); + + rlogin->prompt = NULL; +} + +/* + * Called to set up the rlogin connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *rlogin_init(void *frontend_handle, void **backend_handle, + Config *cfg, + char *host, int port, char **realhost, + int nodelay, int keepalive) +{ + static const struct plug_function_table fn_table = { + rlogin_log, + rlogin_closing, + rlogin_receive, + rlogin_sent + }; + SockAddr addr; + const char *err; + Rlogin rlogin; + char ruser[sizeof(cfg->username)]; + + rlogin = snew(struct rlogin_tag); + rlogin->fn = &fn_table; + rlogin->s = NULL; + rlogin->frontend = frontend_handle; + rlogin->term_width = cfg->width; + rlogin->term_height = cfg->height; + rlogin->firstbyte = 1; + rlogin->cansize = 0; + rlogin->prompt = NULL; + rlogin->cfg = *cfg; /* STRUCTURE COPY */ + *backend_handle = rlogin; + + /* + * Try to find host. + */ + { + char *buf; + buf = dupprintf("Looking up host \"%s\"%s", host, + (cfg->addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (cfg->addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + ""))); + logevent(rlogin->frontend, buf); + sfree(buf); + } + addr = name_lookup(host, port, realhost, cfg, cfg->addressfamily); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + + if (port < 0) + port = 513; /* default rlogin port */ + + /* + * Open socket. + */ + rlogin->s = new_connection(addr, *realhost, port, 1, 0, + nodelay, keepalive, (Plug) rlogin, cfg); + if ((err = sk_socket_error(rlogin->s)) != NULL) + return err; + + if (*cfg->loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(cfg->loghost); + colon = strrchr(*realhost, ':'); + if (colon) { + /* + * FIXME: if we ever update this aspect of ssh.c for + * IPv6 literal management, this should change in line + * with it. + */ + *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 (get_remote_username(cfg, ruser, sizeof(ruser))) { + rlogin_startup(rlogin, ruser); + } else { + int ret; + + rlogin->prompt = new_prompts(rlogin->frontend); + rlogin->prompt->to_server = TRUE; + rlogin->prompt->name = dupstr("Rlogin login name"); + add_prompt(rlogin->prompt, dupstr("rlogin username: "), TRUE, + sizeof(cfg->username)); + ret = get_userpass_input(rlogin->prompt, NULL, 0); + if (ret >= 0) { + rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result); + } + } + + return NULL; +} + +static void rlogin_free(void *handle) +{ + Rlogin rlogin = (Rlogin) handle; + + if (rlogin->prompt) + free_prompts(rlogin->prompt); + if (rlogin->s) + sk_close(rlogin->s); + sfree(rlogin); +} + +/* + * Stub routine (we don't have any need to reconfigure this backend). + */ +static void rlogin_reconfig(void *handle, Config *cfg) +{ +} + +/* + * Called to send data down the rlogin connection. + */ +static int rlogin_send(void *handle, char *buf, int len) +{ + Rlogin rlogin = (Rlogin) handle; + + if (rlogin->s == NULL) + return 0; + + if (rlogin->prompt) { + /* + * We're still prompting for a username, and aren't talking + * directly to the network connection yet. + */ + int ret = get_userpass_input(rlogin->prompt, + (unsigned char *)buf, len); + if (ret >= 0) { + rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result); + /* that nulls out rlogin->prompt, so then we'll start sending + * data down the wire in the obvious way */ + } + } else { + rlogin->bufsize = sk_write(rlogin->s, buf, len); + } + + return rlogin->bufsize; +} + +/* + * Called to query the current socket sendability status. + */ +static int rlogin_sendbuffer(void *handle) +{ + Rlogin rlogin = (Rlogin) handle; + return rlogin->bufsize; +} + +/* + * Called to set the size of the window + */ +static void rlogin_size(void *handle, int width, int height) +{ + Rlogin rlogin = (Rlogin) handle; + char b[12] = { '\xFF', '\xFF', 0x73, 0x73, 0, 0, 0, 0, 0, 0, 0, 0 }; + + rlogin->term_width = width; + rlogin->term_height = height; + + if (rlogin->s == NULL || !rlogin->cansize) + return; + + b[6] = rlogin->term_width >> 8; + b[7] = rlogin->term_width & 0xFF; + b[4] = rlogin->term_height >> 8; + b[5] = rlogin->term_height & 0xFF; + rlogin->bufsize = sk_write(rlogin->s, b, 12); + return; +} + +/* + * Send rlogin special codes. + */ +static void rlogin_special(void *handle, Telnet_Special code) +{ + /* Do nothing! */ + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *rlogin_get_specials(void *handle) +{ + return NULL; +} + +static int rlogin_connected(void *handle) +{ + Rlogin rlogin = (Rlogin) handle; + return rlogin->s != NULL; +} + +static int rlogin_sendok(void *handle) +{ + /* Rlogin rlogin = (Rlogin) handle; */ + return 1; +} + +static void rlogin_unthrottle(void *handle, int backlog) +{ + Rlogin rlogin = (Rlogin) handle; + sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); +} + +static int rlogin_ldisc(void *handle, int option) +{ + /* Rlogin rlogin = (Rlogin) handle; */ + return 0; +} + +static void rlogin_provide_ldisc(void *handle, void *ldisc) +{ + /* This is a stub. */ +} + +static void rlogin_provide_logctx(void *handle, void *logctx) +{ + /* This is a stub. */ +} + +static int rlogin_exitcode(void *handle) +{ + Rlogin rlogin = (Rlogin) handle; + if (rlogin->s != NULL) + return -1; /* still connected */ + else + /* If we ever implement RSH, we'll probably need to do this properly */ + return 0; +} + +/* + * cfg_info for rlogin does nothing at all. + */ +static int rlogin_cfg_info(void *handle) +{ + return 0; +} + +Backend rlogin_backend = { + rlogin_init, + rlogin_free, + rlogin_reconfig, + rlogin_send, + rlogin_sendbuffer, + rlogin_size, + rlogin_special, + rlogin_get_specials, + rlogin_connected, + rlogin_exitcode, + rlogin_sendok, + rlogin_ldisc, + rlogin_provide_ldisc, + rlogin_provide_logctx, + rlogin_unthrottle, + rlogin_cfg_info, + "rlogin", + PROT_RLOGIN, + 513 +}; diff --git a/putty/Release/PuTTY.dll b/putty/Release/PuTTY.dll new file mode 100644 index 0000000..9e8b5bb Binary files /dev/null and b/putty/Release/PuTTY.dll differ diff --git a/putty/SERCFG.C b/putty/SERCFG.C new file mode 100644 index 0000000..86a8a0b --- /dev/null +++ b/putty/SERCFG.C @@ -0,0 +1,199 @@ +/* + * sercfg.c - the serial-port specific parts of the PuTTY + * configuration box. Centralised as cross-platform code because + * more than one platform will want to use it, but not part of the + * main configuration. The expectation is that each platform's + * local config function will call out to ser_setup_config_box() if + * it needs to set up the standard serial stuff. (Of course, it can + * then apply local tweaks after ser_setup_config_box() returns, if + * it needs to.) + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +static void serial_parity_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + static const struct { + const char *name; + int val; + } parities[] = { + {"None", SER_PAR_NONE}, + {"Odd", SER_PAR_ODD}, + {"Even", SER_PAR_EVEN}, + {"Mark", SER_PAR_MARK}, + {"Space", SER_PAR_SPACE}, + }; + int mask = ctrl->listbox.context.i; + int i, j; + Config *cfg = (Config *)data; + + if (event == EVENT_REFRESH) { + int oldparity = cfg->serparity;/* preserve past reentrant calls */ + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < lenof(parities); i++) { + if (mask & (1 << i)) + dlg_listbox_addwithid(ctrl, dlg, parities[i].name, + parities[i].val); + } + for (i = j = 0; i < lenof(parities); i++) { + if (mask & (1 << i)) { + if (oldparity == parities[i].val) { + dlg_listbox_select(ctrl, dlg, j); + break; + } + j++; + } + } + if (i == lenof(parities)) { /* an unsupported setting was chosen */ + dlg_listbox_select(ctrl, dlg, 0); + oldparity = SER_PAR_NONE; + } + dlg_update_done(ctrl, dlg); + cfg->serparity = oldparity; /* restore */ + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = SER_PAR_NONE; + else + i = dlg_listbox_getid(ctrl, dlg, i); + cfg->serparity = i; + } +} + +static void serial_flow_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + static const struct { + const char *name; + int val; + } flows[] = { + {"None", SER_FLOW_NONE}, + {"XON/XOFF", SER_FLOW_XONXOFF}, + {"RTS/CTS", SER_FLOW_RTSCTS}, + {"DSR/DTR", SER_FLOW_DSRDTR}, + }; + int mask = ctrl->listbox.context.i; + int i, j; + Config *cfg = (Config *)data; + + if (event == EVENT_REFRESH) { + int oldflow = cfg->serflow; /* preserve past reentrant calls */ + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < lenof(flows); i++) { + if (mask & (1 << i)) + dlg_listbox_addwithid(ctrl, dlg, flows[i].name, flows[i].val); + } + for (i = j = 0; i < lenof(flows); i++) { + if (mask & (1 << i)) { + if (oldflow == flows[i].val) { + dlg_listbox_select(ctrl, dlg, j); + break; + } + j++; + } + } + if (i == lenof(flows)) { /* an unsupported setting was chosen */ + dlg_listbox_select(ctrl, dlg, 0); + oldflow = SER_FLOW_NONE; + } + dlg_update_done(ctrl, dlg); + cfg->serflow = oldflow; /* restore */ + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = SER_FLOW_NONE; + else + i = dlg_listbox_getid(ctrl, dlg, i); + cfg->serflow = i; + } +} + +void ser_setup_config_box(struct controlbox *b, int midsession, + int parity_mask, int flow_mask) +{ + struct controlset *s; + union control *c; + + if (!midsession) { + int i; + extern void config_protocolbuttons_handler(union control *, void *, + void *, int); + + /* + * Add the serial back end to the protocols list at the + * top of the config box. + */ + s = ctrl_getset(b, "Session", "hostport", + "Specify the destination you want to connect to"); + + for (i = 0; i < s->ncontrols; i++) { + c = s->ctrls[i]; + if (c->generic.type == CTRL_RADIO && + c->generic.handler == config_protocolbuttons_handler) { + c->radio.nbuttons++; + c->radio.ncolumns++; + c->radio.buttons = + sresize(c->radio.buttons, c->radio.nbuttons, char *); + c->radio.buttons[c->radio.nbuttons-1] = + dupstr("Serial"); + c->radio.buttondata = + sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); + c->radio.buttondata[c->radio.nbuttons-1] = I(PROT_SERIAL); + if (c->radio.shortcuts) { + c->radio.shortcuts = + sresize(c->radio.shortcuts, c->radio.nbuttons, char); + c->radio.shortcuts[c->radio.nbuttons-1] = 'r'; + } + } + } + } + + /* + * Entirely new Connection/Serial panel for serial port + * configuration. + */ + ctrl_settitle(b, "Connection/Serial", + "Options controlling local serial lines"); + + if (!midsession) { + /* + * We don't permit switching to a different serial port in + * midflight, although we do allow all other + * reconfiguration. + */ + s = ctrl_getset(b, "Connection/Serial", "serline", + "Select a serial line"); + ctrl_editbox(s, "Serial line to connect to", 'l', 40, + HELPCTX(serial_line), + dlg_stdeditbox_handler, I(offsetof(Config,serline)), + I(sizeof(((Config *)0)->serline))); + } + + s = ctrl_getset(b, "Connection/Serial", "sercfg", "Configure the serial line"); + ctrl_editbox(s, "Speed (baud)", 's', 40, + HELPCTX(serial_speed), + dlg_stdeditbox_handler, I(offsetof(Config,serspeed)), I(-1)); + ctrl_editbox(s, "Data bits", 'b', 40, + HELPCTX(serial_databits), + dlg_stdeditbox_handler,I(offsetof(Config,serdatabits)),I(-1)); + /* + * Stop bits come in units of one half. + */ + ctrl_editbox(s, "Stop bits", 't', 40, + HELPCTX(serial_stopbits), + dlg_stdeditbox_handler,I(offsetof(Config,serstopbits)),I(-2)); + ctrl_droplist(s, "Parity", 'p', 40, + HELPCTX(serial_parity), + serial_parity_handler, I(parity_mask)); + ctrl_droplist(s, "Flow control", 'f', 40, + HELPCTX(serial_flow), + serial_flow_handler, I(flow_mask)); +} diff --git a/putty/SETTINGS.C b/putty/SETTINGS.C new file mode 100644 index 0000000..b2f3ddc --- /dev/null +++ b/putty/SETTINGS.C @@ -0,0 +1,1005 @@ +/* + * settings.c: read and write saved sessions. (platform-independent) + */ + +#include +#include +#include +#include "putty.h" +#include "storage.h" + +/* The cipher order given here is the default order. */ +static const struct keyvalwhere ciphernames[] = { + { "aes", CIPHER_AES, -1, -1 }, + { "blowfish", CIPHER_BLOWFISH, -1, -1 }, + { "3des", CIPHER_3DES, -1, -1 }, + { "WARN", CIPHER_WARN, -1, -1 }, + { "arcfour", CIPHER_ARCFOUR, -1, -1 }, + { "des", CIPHER_DES, -1, -1 } +}; + +static const struct keyvalwhere kexnames[] = { + { "dh-gex-sha1", KEX_DHGEX, -1, -1 }, + { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 }, + { "dh-group1-sha1", KEX_DHGROUP1, -1, -1 }, + { "rsa", KEX_RSA, KEX_WARN, -1 }, + { "WARN", KEX_WARN, -1, -1 } +}; + +/* + * All the terminal modes that we know about for the "TerminalModes" + * setting. (Also used by config.c for the drop-down list.) + * This is currently precisely the same as the set in ssh.c, but could + * in principle differ if other backends started to support tty modes + * (e.g., the pty backend). + */ +const char *const ttymodes[] = { + "INTR", "QUIT", "ERASE", "KILL", "EOF", + "EOL", "EOL2", "START", "STOP", "SUSP", + "DSUSP", "REPRINT", "WERASE", "LNEXT", "FLUSH", + "SWTCH", "STATUS", "DISCARD", "IGNPAR", "PARMRK", + "INPCK", "ISTRIP", "INLCR", "IGNCR", "ICRNL", + "IUCLC", "IXON", "IXANY", "IXOFF", "IMAXBEL", + "ISIG", "ICANON", "XCASE", "ECHO", "ECHOE", + "ECHOK", "ECHONL", "NOFLSH", "TOSTOP", "IEXTEN", + "ECHOCTL", "ECHOKE", "PENDIN", "OPOST", "OLCUC", + "ONLCR", "OCRNL", "ONOCR", "ONLRET", "CS7", + "CS8", "PARENB", "PARODD", NULL +}; + +/* + * Convenience functions to access the backends[] array + * (which is only present in tools that manage settings). + */ + +Backend *backend_from_name(const char *name) +{ + Backend **p; + for (p = backends; *p != NULL; p++) + if (!strcmp((*p)->name, name)) + return *p; + return NULL; +} + +Backend *backend_from_proto(int proto) +{ + Backend **p; + for (p = backends; *p != NULL; p++) + if ((*p)->protocol == proto) + return *p; + return NULL; +} + +int get_remote_username(Config *cfg, char *user, size_t len) +{ + if (*cfg->username) { + strncpy(user, cfg->username, len); + user[len-1] = '\0'; + } else { + if (cfg->username_from_env) { + /* Use local username. */ + char *luser = get_username(); + if (luser) { + strncpy(user, luser, len); + user[len-1] = '\0'; + sfree(luser); + } else { + *user = '\0'; + } + } else { + *user = '\0'; + } + } + return (*user != '\0'); +} + +static void gpps(void *handle, const char *name, const char *def, + char *val, int len) +{ + if (!read_setting_s(handle, name, val, len)) { + char *pdef; + + pdef = platform_default_s(name); + if (pdef) { + strncpy(val, pdef, len); + sfree(pdef); + } else { + strncpy(val, def, len); + } + + val[len - 1] = '\0'; + } +} + +/* + * gppfont and gppfile cannot have local defaults, since the very + * format of a Filename or Font is platform-dependent. So the + * platform-dependent functions MUST return some sort of value. + */ +static void gppfont(void *handle, const char *name, FontSpec *result) +{ + if (!read_setting_fontspec(handle, name, result)) + *result = platform_default_fontspec(name); +} +static void gppfile(void *handle, const char *name, Filename *result) +{ + if (!read_setting_filename(handle, name, result)) + *result = platform_default_filename(name); +} + +static void gppi(void *handle, char *name, int def, int *i) +{ + def = platform_default_i(name, def); + *i = read_setting_i(handle, name, def); +} + +/* + * Read a set of name-value pairs in the format we occasionally use: + * NAME\tVALUE\0NAME\tVALUE\0\0 in memory + * NAME=VALUE,NAME=VALUE, in storage + * `def' is in the storage format. + */ +static void gppmap(void *handle, char *name, char *def, char *val, int len) +{ + char *buf = snewn(2*len, char), *p, *q; + gpps(handle, name, def, buf, 2*len); + p = buf; + q = val; + while (*p) { + while (*p && *p != ',') { + int c = *p++; + if (c == '=') + c = '\t'; + if (c == '\\') + c = *p++; + *q++ = c; + } + if (*p == ',') + p++; + *q++ = '\0'; + } + *q = '\0'; + sfree(buf); +} + +/* + * Write a set of name/value pairs in the above format. + */ +static void wmap(void *handle, char const *key, char const *value, int len) +{ + char *buf = snewn(2*len, char), *p; + const char *q; + p = buf; + q = value; + while (*q) { + while (*q) { + int c = *q++; + if (c == '=' || c == ',' || c == '\\') + *p++ = '\\'; + if (c == '\t') + c = '='; + *p++ = c; + } + *p++ = ','; + q++; + } + *p = '\0'; + write_setting_s(handle, key, buf); + sfree(buf); +} + +static int key2val(const struct keyvalwhere *mapping, + int nmaps, char *key) +{ + int i; + for (i = 0; i < nmaps; i++) + if (!strcmp(mapping[i].s, key)) return mapping[i].v; + return -1; +} + +static const char *val2key(const struct keyvalwhere *mapping, + int nmaps, int val) +{ + int i; + for (i = 0; i < nmaps; i++) + if (mapping[i].v == val) return mapping[i].s; + return NULL; +} + +/* + * Helper function to parse a comma-separated list of strings into + * a preference list array of values. Any missing values are added + * to the end and duplicates are weeded. + * XXX: assumes vals in 'mapping' are small +ve integers + */ +static void gprefs(void *sesskey, char *name, char *def, + const struct keyvalwhere *mapping, int nvals, + int *array) +{ + char commalist[256]; + char *p, *q; + int i, j, n, v, pos; + unsigned long seen = 0; /* bitmap for weeding dups etc */ + + /* + * Fetch the string which we'll parse as a comma-separated list. + */ + gpps(sesskey, name, def, commalist, sizeof(commalist)); + + /* + * Go through that list and convert it into values. + */ + n = 0; + p = commalist; + while (1) { + while (*p && *p == ',') p++; + if (!*p) + break; /* no more words */ + + q = p; + while (*p && *p != ',') p++; + if (*p) *p++ = '\0'; + + v = key2val(mapping, nvals, q); + if (v != -1 && !(seen & (1 << v))) { + seen |= (1 << v); + array[n++] = v; + } + } + + /* + * Now go through 'mapping' and add values that weren't mentioned + * in the list we fetched. We may have to loop over it multiple + * times so that we add values before other values whose default + * positions depend on them. + */ + while (n < nvals) { + for (i = 0; i < nvals; i++) { + assert(mapping[i].v < 32); + + if (!(seen & (1 << mapping[i].v))) { + /* + * This element needs adding. But can we add it yet? + */ + if (mapping[i].vrel != -1 && !(seen & (1 << mapping[i].vrel))) + continue; /* nope */ + + /* + * OK, we can work out where to add this element, so + * do so. + */ + if (mapping[i].vrel == -1) { + pos = (mapping[i].where < 0 ? n : 0); + } else { + for (j = 0; j < n; j++) + if (array[j] == mapping[i].vrel) + break; + assert(j < n); /* implied by (seen & (1<= pos; j--) + array[j+1] = array[j]; + array[pos] = mapping[i].v; + n++; + } + } + } +} + +/* + * Write out a preference list. + */ +static void wprefs(void *sesskey, char *name, + const struct keyvalwhere *mapping, int nvals, + int *array) +{ + char *buf, *p; + int i, maxlen; + + for (maxlen = i = 0; i < nvals; i++) { + const char *s = val2key(mapping, nvals, array[i]); + if (s) { + maxlen += 1 + strlen(s); + } + } + + buf = snewn(maxlen, char); + p = buf; + + for (i = 0; i < nvals; i++) { + const char *s = val2key(mapping, nvals, array[i]); + if (s) { + p += sprintf(p, "%s%s", (p > buf ? "," : ""), s); + } + } + + assert(p - buf == maxlen - 1); /* maxlen counted the NUL */ + + write_setting_s(sesskey, name, buf); + + sfree(buf); +} + +char *save_settings(char *section, Config * cfg) +{ + void *sesskey; + char *errmsg; + + sesskey = open_settings_w(section, &errmsg); + if (!sesskey) + return errmsg; + save_open_settings(sesskey, cfg); + close_settings_w(sesskey); + return NULL; +} + +void save_open_settings(void *sesskey, Config *cfg) +{ + int i; + char *p; + + write_setting_i(sesskey, "Present", 1); + write_setting_s(sesskey, "HostName", cfg->host); + write_setting_filename(sesskey, "LogFileName", cfg->logfilename); + write_setting_i(sesskey, "LogType", cfg->logtype); + write_setting_i(sesskey, "LogFileClash", cfg->logxfovr); + write_setting_i(sesskey, "LogFlush", cfg->logflush); + write_setting_i(sesskey, "SSHLogOmitPasswords", cfg->logomitpass); + write_setting_i(sesskey, "SSHLogOmitData", cfg->logomitdata); + p = "raw"; + { + const Backend *b = backend_from_proto(cfg->protocol); + if (b) + p = b->name; + } + write_setting_s(sesskey, "Protocol", p); + write_setting_i(sesskey, "PortNumber", cfg->port); + /* The CloseOnExit numbers are arranged in a different order from + * the standard FORCE_ON / FORCE_OFF / AUTO. */ + write_setting_i(sesskey, "CloseOnExit", (cfg->close_on_exit+2)%3); + write_setting_i(sesskey, "WarnOnClose", !!cfg->warn_on_close); + write_setting_i(sesskey, "PingInterval", cfg->ping_interval / 60); /* minutes */ + write_setting_i(sesskey, "PingIntervalSecs", cfg->ping_interval % 60); /* seconds */ + write_setting_i(sesskey, "TCPNoDelay", cfg->tcp_nodelay); + write_setting_i(sesskey, "TCPKeepalives", cfg->tcp_keepalives); + write_setting_s(sesskey, "TerminalType", cfg->termtype); + write_setting_s(sesskey, "TerminalSpeed", cfg->termspeed); + wmap(sesskey, "TerminalModes", cfg->ttymodes, lenof(cfg->ttymodes)); + + /* Address family selection */ + write_setting_i(sesskey, "AddressFamily", cfg->addressfamily); + + /* proxy settings */ + write_setting_s(sesskey, "ProxyExcludeList", cfg->proxy_exclude_list); + write_setting_i(sesskey, "ProxyDNS", (cfg->proxy_dns+2)%3); + write_setting_i(sesskey, "ProxyLocalhost", cfg->even_proxy_localhost); + write_setting_i(sesskey, "ProxyMethod", cfg->proxy_type); + write_setting_s(sesskey, "ProxyHost", cfg->proxy_host); + write_setting_i(sesskey, "ProxyPort", cfg->proxy_port); + write_setting_s(sesskey, "ProxyUsername", cfg->proxy_username); + write_setting_s(sesskey, "ProxyPassword", cfg->proxy_password); + write_setting_s(sesskey, "ProxyTelnetCommand", cfg->proxy_telnet_command); + wmap(sesskey, "Environment", cfg->environmt, lenof(cfg->environmt)); + write_setting_s(sesskey, "UserName", cfg->username); + write_setting_i(sesskey, "UserNameFromEnvironment", cfg->username_from_env); + write_setting_s(sesskey, "LocalUserName", cfg->localusername); + write_setting_i(sesskey, "NoPTY", cfg->nopty); + write_setting_i(sesskey, "Compression", cfg->compression); + write_setting_i(sesskey, "TryAgent", cfg->tryagent); + write_setting_i(sesskey, "AgentFwd", cfg->agentfwd); + write_setting_i(sesskey, "GssapiFwd", cfg->gssapifwd); + write_setting_i(sesskey, "ChangeUsername", cfg->change_username); + wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, + cfg->ssh_cipherlist); + wprefs(sesskey, "KEX", kexnames, KEX_MAX, cfg->ssh_kexlist); + write_setting_i(sesskey, "RekeyTime", cfg->ssh_rekey_time); + write_setting_s(sesskey, "RekeyBytes", cfg->ssh_rekey_data); + write_setting_i(sesskey, "SshNoAuth", cfg->ssh_no_userauth); + write_setting_i(sesskey, "SshBanner", cfg->ssh_show_banner); + write_setting_i(sesskey, "AuthTIS", cfg->try_tis_auth); + write_setting_i(sesskey, "AuthKI", cfg->try_ki_auth); + write_setting_i(sesskey, "AuthGSSAPI", cfg->try_gssapi_auth); +#ifndef NO_GSSAPI + wprefs(sesskey, "GSSLibs", gsslibkeywords, ngsslibs, + cfg->ssh_gsslist); + write_setting_filename(sesskey, "GSSCustom", cfg->ssh_gss_custom); +#endif + write_setting_i(sesskey, "SshNoShell", cfg->ssh_no_shell); + write_setting_i(sesskey, "SshProt", cfg->sshprot); + write_setting_s(sesskey, "LogHost", cfg->loghost); + write_setting_i(sesskey, "SSH2DES", cfg->ssh2_des_cbc); + write_setting_filename(sesskey, "PublicKeyFile", cfg->keyfile); + write_setting_s(sesskey, "RemoteCommand", cfg->remote_cmd); + write_setting_i(sesskey, "RFCEnviron", cfg->rfc_environ); + write_setting_i(sesskey, "PassiveTelnet", cfg->passive_telnet); + write_setting_i(sesskey, "BackspaceIsDelete", cfg->bksp_is_delete); + write_setting_i(sesskey, "RXVTHomeEnd", cfg->rxvt_homeend); + write_setting_i(sesskey, "LinuxFunctionKeys", cfg->funky_type); + write_setting_i(sesskey, "NoApplicationKeys", cfg->no_applic_k); + write_setting_i(sesskey, "NoApplicationCursors", cfg->no_applic_c); + write_setting_i(sesskey, "NoMouseReporting", cfg->no_mouse_rep); + write_setting_i(sesskey, "NoRemoteResize", cfg->no_remote_resize); + write_setting_i(sesskey, "NoAltScreen", cfg->no_alt_screen); + write_setting_i(sesskey, "NoRemoteWinTitle", cfg->no_remote_wintitle); + write_setting_i(sesskey, "RemoteQTitleAction", cfg->remote_qtitle_action); + write_setting_i(sesskey, "NoDBackspace", cfg->no_dbackspace); + write_setting_i(sesskey, "NoRemoteCharset", cfg->no_remote_charset); + write_setting_i(sesskey, "ApplicationCursorKeys", cfg->app_cursor); + write_setting_i(sesskey, "ApplicationKeypad", cfg->app_keypad); + write_setting_i(sesskey, "NetHackKeypad", cfg->nethack_keypad); + write_setting_i(sesskey, "AltF4", cfg->alt_f4); + write_setting_i(sesskey, "AltSpace", cfg->alt_space); + write_setting_i(sesskey, "AltOnly", cfg->alt_only); + write_setting_i(sesskey, "ComposeKey", cfg->compose_key); + write_setting_i(sesskey, "CtrlAltKeys", cfg->ctrlaltkeys); + write_setting_i(sesskey, "TelnetKey", cfg->telnet_keyboard); + write_setting_i(sesskey, "TelnetRet", cfg->telnet_newline); + write_setting_i(sesskey, "LocalEcho", cfg->localecho); + write_setting_i(sesskey, "LocalEdit", cfg->localedit); + write_setting_s(sesskey, "Answerback", cfg->answerback); + write_setting_i(sesskey, "AlwaysOnTop", cfg->alwaysontop); + write_setting_i(sesskey, "FullScreenOnAltEnter", cfg->fullscreenonaltenter); + write_setting_i(sesskey, "HideMousePtr", cfg->hide_mouseptr); + write_setting_i(sesskey, "SunkenEdge", cfg->sunken_edge); + write_setting_i(sesskey, "WindowBorder", cfg->window_border); + write_setting_i(sesskey, "CurType", cfg->cursor_type); + write_setting_i(sesskey, "BlinkCur", cfg->blink_cur); + write_setting_i(sesskey, "Beep", cfg->beep); + write_setting_i(sesskey, "BeepInd", cfg->beep_ind); + write_setting_filename(sesskey, "BellWaveFile", cfg->bell_wavefile); + write_setting_i(sesskey, "BellOverload", cfg->bellovl); + write_setting_i(sesskey, "BellOverloadN", cfg->bellovl_n); + write_setting_i(sesskey, "BellOverloadT", cfg->bellovl_t +#ifdef PUTTY_UNIX_H + * 1000 +#endif + ); + write_setting_i(sesskey, "BellOverloadS", cfg->bellovl_s +#ifdef PUTTY_UNIX_H + * 1000 +#endif + ); + write_setting_i(sesskey, "ScrollbackLines", cfg->savelines); + write_setting_i(sesskey, "DECOriginMode", cfg->dec_om); + write_setting_i(sesskey, "AutoWrapMode", cfg->wrap_mode); + write_setting_i(sesskey, "LFImpliesCR", cfg->lfhascr); + write_setting_i(sesskey, "CRImpliesLF", cfg->crhaslf); + write_setting_i(sesskey, "DisableArabicShaping", cfg->arabicshaping); + write_setting_i(sesskey, "DisableBidi", cfg->bidi); + write_setting_i(sesskey, "WinNameAlways", cfg->win_name_always); + write_setting_s(sesskey, "WinTitle", cfg->wintitle); + write_setting_i(sesskey, "TermWidth", cfg->width); + write_setting_i(sesskey, "TermHeight", cfg->height); + write_setting_fontspec(sesskey, "Font", cfg->font); + write_setting_i(sesskey, "FontQuality", cfg->font_quality); + write_setting_i(sesskey, "FontVTMode", cfg->vtmode); + write_setting_i(sesskey, "UseSystemColours", cfg->system_colour); + write_setting_i(sesskey, "TryPalette", cfg->try_palette); + write_setting_i(sesskey, "ANSIColour", cfg->ansi_colour); + write_setting_i(sesskey, "Xterm256Colour", cfg->xterm_256_colour); + write_setting_i(sesskey, "BoldAsColour", cfg->bold_colour); + + for (i = 0; i < 22; i++) { + char buf[20], buf2[30]; + sprintf(buf, "Colour%d", i); + sprintf(buf2, "%d,%d,%d", cfg->colours[i][0], + cfg->colours[i][1], cfg->colours[i][2]); + write_setting_s(sesskey, buf, buf2); + } + write_setting_i(sesskey, "RawCNP", cfg->rawcnp); + write_setting_i(sesskey, "PasteRTF", cfg->rtf_paste); + write_setting_i(sesskey, "MouseIsXterm", cfg->mouse_is_xterm); + write_setting_i(sesskey, "RectSelect", cfg->rect_select); + write_setting_i(sesskey, "MouseOverride", cfg->mouse_override); + for (i = 0; i < 256; i += 32) { + char buf[20], buf2[256]; + int j; + sprintf(buf, "Wordness%d", i); + *buf2 = '\0'; + for (j = i; j < i + 32; j++) { + sprintf(buf2 + strlen(buf2), "%s%d", + (*buf2 ? "," : ""), cfg->wordness[j]); + } + write_setting_s(sesskey, buf, buf2); + } + write_setting_s(sesskey, "LineCodePage", cfg->line_codepage); + write_setting_i(sesskey, "CJKAmbigWide", cfg->cjk_ambig_wide); + write_setting_i(sesskey, "UTF8Override", cfg->utf8_override); + write_setting_s(sesskey, "Printer", cfg->printer); + write_setting_i(sesskey, "CapsLockCyr", cfg->xlat_capslockcyr); + write_setting_i(sesskey, "ScrollBar", cfg->scrollbar); + write_setting_i(sesskey, "ScrollBarFullScreen", cfg->scrollbar_in_fullscreen); + write_setting_i(sesskey, "ScrollOnKey", cfg->scroll_on_key); + write_setting_i(sesskey, "ScrollOnDisp", cfg->scroll_on_disp); + write_setting_i(sesskey, "EraseToScrollback", cfg->erase_to_scrollback); + write_setting_i(sesskey, "LockSize", cfg->resize_action); + write_setting_i(sesskey, "BCE", cfg->bce); + write_setting_i(sesskey, "BlinkText", cfg->blinktext); + write_setting_i(sesskey, "X11Forward", cfg->x11_forward); + write_setting_s(sesskey, "X11Display", cfg->x11_display); + write_setting_i(sesskey, "X11AuthType", cfg->x11_auth); + write_setting_filename(sesskey, "X11AuthFile", cfg->xauthfile); + write_setting_i(sesskey, "LocalPortAcceptAll", cfg->lport_acceptall); + write_setting_i(sesskey, "RemotePortAcceptAll", cfg->rport_acceptall); + wmap(sesskey, "PortForwardings", cfg->portfwd, lenof(cfg->portfwd)); + write_setting_i(sesskey, "BugIgnore1", 2-cfg->sshbug_ignore1); + write_setting_i(sesskey, "BugPlainPW1", 2-cfg->sshbug_plainpw1); + write_setting_i(sesskey, "BugRSA1", 2-cfg->sshbug_rsa1); + write_setting_i(sesskey, "BugIgnore2", 2-cfg->sshbug_ignore2); + write_setting_i(sesskey, "BugHMAC2", 2-cfg->sshbug_hmac2); + write_setting_i(sesskey, "BugDeriveKey2", 2-cfg->sshbug_derivekey2); + write_setting_i(sesskey, "BugRSAPad2", 2-cfg->sshbug_rsapad2); + write_setting_i(sesskey, "BugPKSessID2", 2-cfg->sshbug_pksessid2); + write_setting_i(sesskey, "BugRekey2", 2-cfg->sshbug_rekey2); + write_setting_i(sesskey, "BugMaxPkt2", 2-cfg->sshbug_maxpkt2); + write_setting_i(sesskey, "StampUtmp", cfg->stamp_utmp); + write_setting_i(sesskey, "LoginShell", cfg->login_shell); + write_setting_i(sesskey, "ScrollbarOnLeft", cfg->scrollbar_on_left); + write_setting_fontspec(sesskey, "BoldFont", cfg->boldfont); + write_setting_fontspec(sesskey, "WideFont", cfg->widefont); + write_setting_fontspec(sesskey, "WideBoldFont", cfg->wideboldfont); + write_setting_i(sesskey, "ShadowBold", cfg->shadowbold); + write_setting_i(sesskey, "ShadowBoldOffset", cfg->shadowboldoffset); + write_setting_s(sesskey, "SerialLine", cfg->serline); + write_setting_i(sesskey, "SerialSpeed", cfg->serspeed); + write_setting_i(sesskey, "SerialDataBits", cfg->serdatabits); + write_setting_i(sesskey, "SerialStopHalfbits", cfg->serstopbits); + write_setting_i(sesskey, "SerialParity", cfg->serparity); + write_setting_i(sesskey, "SerialFlowControl", cfg->serflow); + write_setting_s(sesskey, "WindowClass", cfg->winclass); +} + +void load_settings(char *section, Config * cfg) +{ + void *sesskey; + + sesskey = open_settings_r(section); + load_open_settings(sesskey, cfg); + close_settings_r(sesskey); + + if (cfg_launchable(cfg)) + add_session_to_jumplist(section); +} + +void load_open_settings(void *sesskey, Config *cfg) +{ + int i; + char prot[10]; + + cfg->ssh_subsys = 0; /* FIXME: load this properly */ + cfg->remote_cmd_ptr = NULL; + cfg->remote_cmd_ptr2 = NULL; + cfg->ssh_nc_host[0] = '\0'; + + gpps(sesskey, "HostName", "", cfg->host, sizeof(cfg->host)); + gppfile(sesskey, "LogFileName", &cfg->logfilename); + gppi(sesskey, "LogType", 0, &cfg->logtype); + gppi(sesskey, "LogFileClash", LGXF_ASK, &cfg->logxfovr); + gppi(sesskey, "LogFlush", 1, &cfg->logflush); + gppi(sesskey, "SSHLogOmitPasswords", 1, &cfg->logomitpass); + gppi(sesskey, "SSHLogOmitData", 0, &cfg->logomitdata); + + gpps(sesskey, "Protocol", "default", prot, 10); + cfg->protocol = default_protocol; + cfg->port = default_port; + { + const Backend *b = backend_from_name(prot); + if (b) { + cfg->protocol = b->protocol; + gppi(sesskey, "PortNumber", default_port, &cfg->port); + } + } + + /* Address family selection */ + gppi(sesskey, "AddressFamily", ADDRTYPE_UNSPEC, &cfg->addressfamily); + + /* The CloseOnExit numbers are arranged in a different order from + * the standard FORCE_ON / FORCE_OFF / AUTO. */ + gppi(sesskey, "CloseOnExit", 1, &i); cfg->close_on_exit = (i+1)%3; + gppi(sesskey, "WarnOnClose", 1, &cfg->warn_on_close); + { + /* This is two values for backward compatibility with 0.50/0.51 */ + int pingmin, pingsec; + gppi(sesskey, "PingInterval", 0, &pingmin); + gppi(sesskey, "PingIntervalSecs", 0, &pingsec); + cfg->ping_interval = pingmin * 60 + pingsec; + } + gppi(sesskey, "TCPNoDelay", 1, &cfg->tcp_nodelay); + gppi(sesskey, "TCPKeepalives", 0, &cfg->tcp_keepalives); + gpps(sesskey, "TerminalType", "xterm", cfg->termtype, + sizeof(cfg->termtype)); + gpps(sesskey, "TerminalSpeed", "38400,38400", cfg->termspeed, + sizeof(cfg->termspeed)); + { + /* This hardcodes a big set of defaults in any new saved + * sessions. Let's hope we don't change our mind. */ + int i; + char *def = dupstr(""); + /* Default: all set to "auto" */ + for (i = 0; ttymodes[i]; i++) { + char *def2 = dupprintf("%s%s=A,", def, ttymodes[i]); + sfree(def); + def = def2; + } + gppmap(sesskey, "TerminalModes", def, + cfg->ttymodes, lenof(cfg->ttymodes)); + sfree(def); + } + + /* proxy settings */ + gpps(sesskey, "ProxyExcludeList", "", cfg->proxy_exclude_list, + sizeof(cfg->proxy_exclude_list)); + gppi(sesskey, "ProxyDNS", 1, &i); cfg->proxy_dns = (i+1)%3; + gppi(sesskey, "ProxyLocalhost", 0, &cfg->even_proxy_localhost); + gppi(sesskey, "ProxyMethod", -1, &cfg->proxy_type); + if (cfg->proxy_type == -1) { + int i; + gppi(sesskey, "ProxyType", 0, &i); + if (i == 0) + cfg->proxy_type = PROXY_NONE; + else if (i == 1) + cfg->proxy_type = PROXY_HTTP; + else if (i == 3) + cfg->proxy_type = PROXY_TELNET; + else if (i == 4) + cfg->proxy_type = PROXY_CMD; + else { + gppi(sesskey, "ProxySOCKSVersion", 5, &i); + if (i == 5) + cfg->proxy_type = PROXY_SOCKS5; + else + cfg->proxy_type = PROXY_SOCKS4; + } + } + gpps(sesskey, "ProxyHost", "proxy", cfg->proxy_host, + sizeof(cfg->proxy_host)); + gppi(sesskey, "ProxyPort", 80, &cfg->proxy_port); + gpps(sesskey, "ProxyUsername", "", cfg->proxy_username, + sizeof(cfg->proxy_username)); + gpps(sesskey, "ProxyPassword", "", cfg->proxy_password, + sizeof(cfg->proxy_password)); + gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n", + cfg->proxy_telnet_command, sizeof(cfg->proxy_telnet_command)); + gppmap(sesskey, "Environment", "", cfg->environmt, lenof(cfg->environmt)); + gpps(sesskey, "UserName", "", cfg->username, sizeof(cfg->username)); + gppi(sesskey, "UserNameFromEnvironment", 0, &cfg->username_from_env); + gpps(sesskey, "LocalUserName", "", cfg->localusername, + sizeof(cfg->localusername)); + gppi(sesskey, "NoPTY", 0, &cfg->nopty); + gppi(sesskey, "Compression", 0, &cfg->compression); + gppi(sesskey, "TryAgent", 1, &cfg->tryagent); + gppi(sesskey, "AgentFwd", 0, &cfg->agentfwd); + gppi(sesskey, "ChangeUsername", 0, &cfg->change_username); + gppi(sesskey, "GssapiFwd", 0, &cfg->gssapifwd); + gprefs(sesskey, "Cipher", "\0", + ciphernames, CIPHER_MAX, cfg->ssh_cipherlist); + { + /* Backward-compatibility: we used to have an option to + * disable gex under the "bugs" panel after one report of + * a server which offered it then choked, but we never got + * a server version string or any other reports. */ + char *default_kexes; + gppi(sesskey, "BugDHGEx2", 0, &i); i = 2-i; + if (i == FORCE_ON) + default_kexes = "dh-group14-sha1,dh-group1-sha1,rsa,WARN,dh-gex-sha1"; + else + default_kexes = "dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN"; + gprefs(sesskey, "KEX", default_kexes, + kexnames, KEX_MAX, cfg->ssh_kexlist); + } + gppi(sesskey, "RekeyTime", 60, &cfg->ssh_rekey_time); + gpps(sesskey, "RekeyBytes", "1G", cfg->ssh_rekey_data, + sizeof(cfg->ssh_rekey_data)); + gppi(sesskey, "SshProt", 2, &cfg->sshprot); + gpps(sesskey, "LogHost", "", cfg->loghost, sizeof(cfg->loghost)); + gppi(sesskey, "SSH2DES", 0, &cfg->ssh2_des_cbc); + gppi(sesskey, "SshNoAuth", 0, &cfg->ssh_no_userauth); + gppi(sesskey, "SshBanner", 1, &cfg->ssh_show_banner); + gppi(sesskey, "AuthTIS", 0, &cfg->try_tis_auth); + gppi(sesskey, "AuthKI", 1, &cfg->try_ki_auth); + gppi(sesskey, "AuthGSSAPI", 1, &cfg->try_gssapi_auth); +#ifndef NO_GSSAPI + gprefs(sesskey, "GSSLibs", "\0", + gsslibkeywords, ngsslibs, cfg->ssh_gsslist); + gppfile(sesskey, "GSSCustom", &cfg->ssh_gss_custom); +#endif + gppi(sesskey, "SshNoShell", 0, &cfg->ssh_no_shell); + gppfile(sesskey, "PublicKeyFile", &cfg->keyfile); + gpps(sesskey, "RemoteCommand", "", cfg->remote_cmd, + sizeof(cfg->remote_cmd)); + gppi(sesskey, "RFCEnviron", 0, &cfg->rfc_environ); + gppi(sesskey, "PassiveTelnet", 0, &cfg->passive_telnet); + gppi(sesskey, "BackspaceIsDelete", 1, &cfg->bksp_is_delete); + gppi(sesskey, "RXVTHomeEnd", 0, &cfg->rxvt_homeend); + gppi(sesskey, "LinuxFunctionKeys", 0, &cfg->funky_type); + gppi(sesskey, "NoApplicationKeys", 0, &cfg->no_applic_k); + gppi(sesskey, "NoApplicationCursors", 0, &cfg->no_applic_c); + gppi(sesskey, "NoMouseReporting", 0, &cfg->no_mouse_rep); + gppi(sesskey, "NoRemoteResize", 0, &cfg->no_remote_resize); + gppi(sesskey, "NoAltScreen", 0, &cfg->no_alt_screen); + gppi(sesskey, "NoRemoteWinTitle", 0, &cfg->no_remote_wintitle); + { + /* Backward compatibility */ + int no_remote_qtitle; + gppi(sesskey, "NoRemoteQTitle", 1, &no_remote_qtitle); + /* We deliberately interpret the old setting of "no response" as + * "empty string". This changes the behaviour, but hopefully for + * the better; the user can always recover the old behaviour. */ + gppi(sesskey, "RemoteQTitleAction", + no_remote_qtitle ? TITLE_EMPTY : TITLE_REAL, + &cfg->remote_qtitle_action); + } + gppi(sesskey, "NoDBackspace", 0, &cfg->no_dbackspace); + gppi(sesskey, "NoRemoteCharset", 0, &cfg->no_remote_charset); + gppi(sesskey, "ApplicationCursorKeys", 0, &cfg->app_cursor); + gppi(sesskey, "ApplicationKeypad", 0, &cfg->app_keypad); + gppi(sesskey, "NetHackKeypad", 0, &cfg->nethack_keypad); + gppi(sesskey, "AltF4", 1, &cfg->alt_f4); + gppi(sesskey, "AltSpace", 0, &cfg->alt_space); + gppi(sesskey, "AltOnly", 0, &cfg->alt_only); + gppi(sesskey, "ComposeKey", 0, &cfg->compose_key); + gppi(sesskey, "CtrlAltKeys", 1, &cfg->ctrlaltkeys); + gppi(sesskey, "TelnetKey", 0, &cfg->telnet_keyboard); + gppi(sesskey, "TelnetRet", 1, &cfg->telnet_newline); + gppi(sesskey, "LocalEcho", AUTO, &cfg->localecho); + gppi(sesskey, "LocalEdit", AUTO, &cfg->localedit); + gpps(sesskey, "Answerback", "PuTTY", cfg->answerback, + sizeof(cfg->answerback)); + gppi(sesskey, "AlwaysOnTop", 0, &cfg->alwaysontop); + gppi(sesskey, "FullScreenOnAltEnter", 0, &cfg->fullscreenonaltenter); + gppi(sesskey, "HideMousePtr", 0, &cfg->hide_mouseptr); + gppi(sesskey, "SunkenEdge", 0, &cfg->sunken_edge); + gppi(sesskey, "WindowBorder", 1, &cfg->window_border); + gppi(sesskey, "CurType", 0, &cfg->cursor_type); + gppi(sesskey, "BlinkCur", 0, &cfg->blink_cur); + /* pedantic compiler tells me I can't use &cfg->beep as an int * :-) */ + gppi(sesskey, "Beep", 1, &cfg->beep); + gppi(sesskey, "BeepInd", 0, &cfg->beep_ind); + gppfile(sesskey, "BellWaveFile", &cfg->bell_wavefile); + gppi(sesskey, "BellOverload", 1, &cfg->bellovl); + gppi(sesskey, "BellOverloadN", 5, &cfg->bellovl_n); + gppi(sesskey, "BellOverloadT", 2*TICKSPERSEC +#ifdef PUTTY_UNIX_H + *1000 +#endif + , &i); + cfg->bellovl_t = i +#ifdef PUTTY_UNIX_H + / 1000 +#endif + ; + gppi(sesskey, "BellOverloadS", 5*TICKSPERSEC +#ifdef PUTTY_UNIX_H + *1000 +#endif + , &i); + cfg->bellovl_s = i +#ifdef PUTTY_UNIX_H + / 1000 +#endif + ; + gppi(sesskey, "ScrollbackLines", 200, &cfg->savelines); + gppi(sesskey, "DECOriginMode", 0, &cfg->dec_om); + gppi(sesskey, "AutoWrapMode", 1, &cfg->wrap_mode); + gppi(sesskey, "LFImpliesCR", 0, &cfg->lfhascr); + gppi(sesskey, "CRImpliesLF", 0, &cfg->crhaslf); + gppi(sesskey, "DisableArabicShaping", 0, &cfg->arabicshaping); + gppi(sesskey, "DisableBidi", 0, &cfg->bidi); + gppi(sesskey, "WinNameAlways", 1, &cfg->win_name_always); + gpps(sesskey, "WinTitle", "", cfg->wintitle, sizeof(cfg->wintitle)); + gppi(sesskey, "TermWidth", 80, &cfg->width); + gppi(sesskey, "TermHeight", 24, &cfg->height); + gppfont(sesskey, "Font", &cfg->font); + gppi(sesskey, "FontQuality", FQ_DEFAULT, &cfg->font_quality); + gppi(sesskey, "FontVTMode", VT_UNICODE, (int *) &cfg->vtmode); + gppi(sesskey, "UseSystemColours", 0, &cfg->system_colour); + gppi(sesskey, "TryPalette", 0, &cfg->try_palette); + gppi(sesskey, "ANSIColour", 1, &cfg->ansi_colour); + gppi(sesskey, "Xterm256Colour", 1, &cfg->xterm_256_colour); + gppi(sesskey, "BoldAsColour", 1, &cfg->bold_colour); + + for (i = 0; i < 22; i++) { + static const char *const defaults[] = { + "187,187,187", "255,255,255", "0,0,0", "85,85,85", "0,0,0", + "0,255,0", "0,0,0", "85,85,85", "187,0,0", "255,85,85", + "0,187,0", "85,255,85", "187,187,0", "255,255,85", "0,0,187", + "85,85,255", "187,0,187", "255,85,255", "0,187,187", + "85,255,255", "187,187,187", "255,255,255" + }; + char buf[20], buf2[30]; + int c0, c1, c2; + sprintf(buf, "Colour%d", i); + gpps(sesskey, buf, defaults[i], buf2, sizeof(buf2)); + if (sscanf(buf2, "%d,%d,%d", &c0, &c1, &c2) == 3) { + cfg->colours[i][0] = c0; + cfg->colours[i][1] = c1; + cfg->colours[i][2] = c2; + } + } + gppi(sesskey, "RawCNP", 0, &cfg->rawcnp); + gppi(sesskey, "PasteRTF", 0, &cfg->rtf_paste); + gppi(sesskey, "MouseIsXterm", 0, &cfg->mouse_is_xterm); + gppi(sesskey, "RectSelect", 0, &cfg->rect_select); + gppi(sesskey, "MouseOverride", 1, &cfg->mouse_override); + for (i = 0; i < 256; i += 32) { + static const char *const defaults[] = { + "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0", + "0,1,2,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1", + "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2", + "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1", + "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", + "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", + "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2", + "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2" + }; + char buf[20], buf2[256], *p; + int j; + sprintf(buf, "Wordness%d", i); + gpps(sesskey, buf, defaults[i / 32], buf2, sizeof(buf2)); + p = buf2; + for (j = i; j < i + 32; j++) { + char *q = p; + while (*p && *p != ',') + p++; + if (*p == ',') + *p++ = '\0'; + cfg->wordness[j] = atoi(q); + } + } + /* + * The empty default for LineCodePage will be converted later + * into a plausible default for the locale. + */ + gpps(sesskey, "LineCodePage", "", cfg->line_codepage, + sizeof(cfg->line_codepage)); + gppi(sesskey, "CJKAmbigWide", 0, &cfg->cjk_ambig_wide); + gppi(sesskey, "UTF8Override", 1, &cfg->utf8_override); + gpps(sesskey, "Printer", "", cfg->printer, sizeof(cfg->printer)); + gppi (sesskey, "CapsLockCyr", 0, &cfg->xlat_capslockcyr); + gppi(sesskey, "ScrollBar", 1, &cfg->scrollbar); + gppi(sesskey, "ScrollBarFullScreen", 0, &cfg->scrollbar_in_fullscreen); + gppi(sesskey, "ScrollOnKey", 0, &cfg->scroll_on_key); + gppi(sesskey, "ScrollOnDisp", 1, &cfg->scroll_on_disp); + gppi(sesskey, "EraseToScrollback", 1, &cfg->erase_to_scrollback); + gppi(sesskey, "LockSize", 0, &cfg->resize_action); + gppi(sesskey, "BCE", 1, &cfg->bce); + gppi(sesskey, "BlinkText", 0, &cfg->blinktext); + gppi(sesskey, "X11Forward", 0, &cfg->x11_forward); + gpps(sesskey, "X11Display", "", cfg->x11_display, + sizeof(cfg->x11_display)); + gppi(sesskey, "X11AuthType", X11_MIT, &cfg->x11_auth); + gppfile(sesskey, "X11AuthFile", &cfg->xauthfile); + + gppi(sesskey, "LocalPortAcceptAll", 0, &cfg->lport_acceptall); + gppi(sesskey, "RemotePortAcceptAll", 0, &cfg->rport_acceptall); + gppmap(sesskey, "PortForwardings", "", cfg->portfwd, lenof(cfg->portfwd)); + gppi(sesskey, "BugIgnore1", 0, &i); cfg->sshbug_ignore1 = 2-i; + gppi(sesskey, "BugPlainPW1", 0, &i); cfg->sshbug_plainpw1 = 2-i; + gppi(sesskey, "BugRSA1", 0, &i); cfg->sshbug_rsa1 = 2-i; + gppi(sesskey, "BugIgnore2", 0, &i); cfg->sshbug_ignore2 = 2-i; + { + int i; + gppi(sesskey, "BugHMAC2", 0, &i); cfg->sshbug_hmac2 = 2-i; + if (cfg->sshbug_hmac2 == AUTO) { + gppi(sesskey, "BuggyMAC", 0, &i); + if (i == 1) + cfg->sshbug_hmac2 = FORCE_ON; + } + } + gppi(sesskey, "BugDeriveKey2", 0, &i); cfg->sshbug_derivekey2 = 2-i; + gppi(sesskey, "BugRSAPad2", 0, &i); cfg->sshbug_rsapad2 = 2-i; + gppi(sesskey, "BugPKSessID2", 0, &i); cfg->sshbug_pksessid2 = 2-i; + gppi(sesskey, "BugRekey2", 0, &i); cfg->sshbug_rekey2 = 2-i; + gppi(sesskey, "BugMaxPkt2", 0, &i); cfg->sshbug_maxpkt2 = 2-i; + cfg->ssh_simple = FALSE; + gppi(sesskey, "StampUtmp", 1, &cfg->stamp_utmp); + gppi(sesskey, "LoginShell", 1, &cfg->login_shell); + gppi(sesskey, "ScrollbarOnLeft", 0, &cfg->scrollbar_on_left); + gppi(sesskey, "ShadowBold", 0, &cfg->shadowbold); + gppfont(sesskey, "BoldFont", &cfg->boldfont); + gppfont(sesskey, "WideFont", &cfg->widefont); + gppfont(sesskey, "WideBoldFont", &cfg->wideboldfont); + gppi(sesskey, "ShadowBoldOffset", 1, &cfg->shadowboldoffset); + gpps(sesskey, "SerialLine", "", cfg->serline, sizeof(cfg->serline)); + gppi(sesskey, "SerialSpeed", 9600, &cfg->serspeed); + gppi(sesskey, "SerialDataBits", 8, &cfg->serdatabits); + gppi(sesskey, "SerialStopHalfbits", 2, &cfg->serstopbits); + gppi(sesskey, "SerialParity", SER_PAR_NONE, &cfg->serparity); + gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, &cfg->serflow); + gpps(sesskey, "WindowClass", "", cfg->winclass, sizeof(cfg->winclass)); +} + +void do_defaults(char *session, Config * cfg) +{ + load_settings(session, cfg); +} + +static int sessioncmp(const void *av, const void *bv) +{ + const char *a = *(const char *const *) av; + const char *b = *(const char *const *) bv; + + /* + * Alphabetical order, except that "Default Settings" is a + * special case and comes first. + */ + if (!strcmp(a, "Default Settings")) + return -1; /* a comes first */ + if (!strcmp(b, "Default Settings")) + return +1; /* b comes first */ + /* + * FIXME: perhaps we should ignore the first & in determining + * sort order. + */ + return strcmp(a, b); /* otherwise, compare normally */ +} + +void get_sesslist(struct sesslist *list, int allocate) +{ + char otherbuf[2048]; + int buflen, bufsize, i; + char *p, *ret; + void *handle; + + if (allocate) { + + buflen = bufsize = 0; + list->buffer = NULL; + if ((handle = enum_settings_start()) != NULL) { + do { + ret = enum_settings_next(handle, otherbuf, sizeof(otherbuf)); + if (ret) { + int len = strlen(otherbuf) + 1; + if (bufsize < buflen + len) { + bufsize = buflen + len + 2048; + list->buffer = sresize(list->buffer, bufsize, char); + } + strcpy(list->buffer + buflen, otherbuf); + buflen += strlen(list->buffer + buflen) + 1; + } + } while (ret); + enum_settings_finish(handle); + } + list->buffer = sresize(list->buffer, buflen + 1, char); + list->buffer[buflen] = '\0'; + + /* + * Now set up the list of sessions. Note that "Default + * Settings" must always be claimed to exist, even if it + * doesn't really. + */ + + p = list->buffer; + list->nsessions = 1; /* "Default Settings" counts as one */ + while (*p) { + if (strcmp(p, "Default Settings")) + list->nsessions++; + while (*p) + p++; + p++; + } + + list->sessions = snewn(list->nsessions + 1, char *); + list->sessions[0] = "Default Settings"; + p = list->buffer; + i = 1; + while (*p) { + if (strcmp(p, "Default Settings")) + list->sessions[i++] = p; + while (*p) + p++; + p++; + } + + qsort(list->sessions, i, sizeof(char *), sessioncmp); + } else { + sfree(list->buffer); + sfree(list->sessions); + list->buffer = NULL; + list->sessions = NULL; + } +} diff --git a/putty/SFTP.C b/putty/SFTP.C new file mode 100644 index 0000000..e665dfb --- /dev/null +++ b/putty/SFTP.C @@ -0,0 +1,1422 @@ +/* + * sftp.c: SFTP generic client code. + */ + +#include +#include +#include +#include +#include + +#include "misc.h" +#include "int64.h" +#include "tree234.h" +#include "sftp.h" + +struct sftp_packet { + char *data; + unsigned length, maxlen; + unsigned savedpos; + int type; +}; + +static const char *fxp_error_message; +static int fxp_errtype; + +static void fxp_internal_error(char *msg); + +/* ---------------------------------------------------------------------- + * SFTP packet construction functions. + */ +static void sftp_pkt_ensure(struct sftp_packet *pkt, int length) +{ + if ((int)pkt->maxlen < length) { + pkt->maxlen = length + 256; + pkt->data = sresize(pkt->data, pkt->maxlen, char); + } +} +static void sftp_pkt_adddata(struct sftp_packet *pkt, void *data, int len) +{ + pkt->length += len; + sftp_pkt_ensure(pkt, pkt->length); + memcpy(pkt->data + pkt->length - len, data, len); +} +static void sftp_pkt_addbyte(struct sftp_packet *pkt, unsigned char byte) +{ + sftp_pkt_adddata(pkt, &byte, 1); +} +static struct sftp_packet *sftp_pkt_init(int pkt_type) +{ + struct sftp_packet *pkt; + pkt = snew(struct sftp_packet); + pkt->data = NULL; + pkt->savedpos = -1; + pkt->length = 0; + pkt->maxlen = 0; + sftp_pkt_addbyte(pkt, (unsigned char) pkt_type); + return pkt; +} +/* +static void sftp_pkt_addbool(struct sftp_packet *pkt, unsigned char value) +{ + sftp_pkt_adddata(pkt, &value, 1); +} +*/ +static void sftp_pkt_adduint32(struct sftp_packet *pkt, + unsigned long value) +{ + unsigned char x[4]; + PUT_32BIT(x, value); + sftp_pkt_adddata(pkt, x, 4); +} +static void sftp_pkt_adduint64(struct sftp_packet *pkt, uint64 value) +{ + unsigned char x[8]; + PUT_32BIT(x, value.hi); + PUT_32BIT(x + 4, value.lo); + sftp_pkt_adddata(pkt, x, 8); +} +static void sftp_pkt_addstring_start(struct sftp_packet *pkt) +{ + sftp_pkt_adduint32(pkt, 0); + pkt->savedpos = pkt->length; +} +static void sftp_pkt_addstring_str(struct sftp_packet *pkt, char *data) +{ + sftp_pkt_adddata(pkt, data, strlen(data)); + PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); +} +static void sftp_pkt_addstring_data(struct sftp_packet *pkt, + char *data, int len) +{ + sftp_pkt_adddata(pkt, data, len); + PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); +} +static void sftp_pkt_addstring(struct sftp_packet *pkt, char *data) +{ + sftp_pkt_addstring_start(pkt); + sftp_pkt_addstring_str(pkt, data); +} +static void sftp_pkt_addattrs(struct sftp_packet *pkt, struct fxp_attrs attrs) +{ + sftp_pkt_adduint32(pkt, attrs.flags); + if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) { + sftp_pkt_adduint32(pkt, attrs.size.hi); + sftp_pkt_adduint32(pkt, attrs.size.lo); + } + if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) { + sftp_pkt_adduint32(pkt, attrs.uid); + sftp_pkt_adduint32(pkt, attrs.gid); + } + if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + sftp_pkt_adduint32(pkt, attrs.permissions); + } + if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { + sftp_pkt_adduint32(pkt, attrs.atime); + sftp_pkt_adduint32(pkt, attrs.mtime); + } + if (attrs.flags & SSH_FILEXFER_ATTR_EXTENDED) { + /* + * We currently don't support sending any extended + * attributes. + */ + } +} + +/* ---------------------------------------------------------------------- + * SFTP packet decode functions. + */ + +static int sftp_pkt_getbyte(struct sftp_packet *pkt, unsigned char *ret) +{ + if (pkt->length - pkt->savedpos < 1) + return 0; + *ret = (unsigned char) pkt->data[pkt->savedpos]; + pkt->savedpos++; + return 1; +} +static int sftp_pkt_getuint32(struct sftp_packet *pkt, unsigned long *ret) +{ + if (pkt->length - pkt->savedpos < 4) + return 0; + *ret = GET_32BIT(pkt->data + pkt->savedpos); + pkt->savedpos += 4; + return 1; +} +static int sftp_pkt_getstring(struct sftp_packet *pkt, + char **p, int *length) +{ + *p = NULL; + if (pkt->length - pkt->savedpos < 4) + return 0; + *length = GET_32BIT(pkt->data + pkt->savedpos); + pkt->savedpos += 4; + if ((int)(pkt->length - pkt->savedpos) < *length || *length < 0) { + *length = 0; + return 0; + } + *p = pkt->data + pkt->savedpos; + pkt->savedpos += *length; + return 1; +} +static int sftp_pkt_getattrs(struct sftp_packet *pkt, struct fxp_attrs *ret) +{ + if (!sftp_pkt_getuint32(pkt, &ret->flags)) + return 0; + if (ret->flags & SSH_FILEXFER_ATTR_SIZE) { + unsigned long hi, lo; + if (!sftp_pkt_getuint32(pkt, &hi) || + !sftp_pkt_getuint32(pkt, &lo)) + return 0; + ret->size = uint64_make(hi, lo); + } + if (ret->flags & SSH_FILEXFER_ATTR_UIDGID) { + if (!sftp_pkt_getuint32(pkt, &ret->uid) || + !sftp_pkt_getuint32(pkt, &ret->gid)) + return 0; + } + if (ret->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (!sftp_pkt_getuint32(pkt, &ret->permissions)) + return 0; + } + if (ret->flags & SSH_FILEXFER_ATTR_ACMODTIME) { + if (!sftp_pkt_getuint32(pkt, &ret->atime) || + !sftp_pkt_getuint32(pkt, &ret->mtime)) + return 0; + } + if (ret->flags & SSH_FILEXFER_ATTR_EXTENDED) { + unsigned long count; + if (!sftp_pkt_getuint32(pkt, &count)) + return 0; + while (count--) { + char *str; + int len; + /* + * We should try to analyse these, if we ever find one + * we recognise. + */ + if (!sftp_pkt_getstring(pkt, &str, &len) || + !sftp_pkt_getstring(pkt, &str, &len)) + return 0; + } + } + return 1; +} +static void sftp_pkt_free(struct sftp_packet *pkt) +{ + if (pkt->data) + sfree(pkt->data); + sfree(pkt); +} + +/* ---------------------------------------------------------------------- + * Send and receive packet functions. + */ +int sftp_send(struct sftp_packet *pkt) +{ + int ret; + char x[4]; + PUT_32BIT(x, pkt->length); + ret = (sftp_senddata(x, 4) && sftp_senddata(pkt->data, pkt->length)); + sftp_pkt_free(pkt); + return ret; +} +struct sftp_packet *sftp_recv(void) +{ + struct sftp_packet *pkt; + char x[4]; + unsigned char uc; + + if (!sftp_recvdata(x, 4)) + return NULL; + + pkt = snew(struct sftp_packet); + pkt->savedpos = 0; + pkt->length = pkt->maxlen = GET_32BIT(x); + pkt->data = snewn(pkt->length, char); + + if (!sftp_recvdata(pkt->data, pkt->length)) { + sftp_pkt_free(pkt); + return NULL; + } + + if (!sftp_pkt_getbyte(pkt, &uc)) { + sftp_pkt_free(pkt); + return NULL; + } else { + pkt->type = uc; + } + + return pkt; +} + +/* ---------------------------------------------------------------------- + * Request ID allocation and temporary dispatch routines. + */ + +#define REQUEST_ID_OFFSET 256 + +struct sftp_request { + unsigned id; + int registered; + void *userdata; +}; + +static int sftp_reqcmp(void *av, void *bv) +{ + struct sftp_request *a = (struct sftp_request *)av; + struct sftp_request *b = (struct sftp_request *)bv; + if (a->id < b->id) + return -1; + if (a->id > b->id) + return +1; + return 0; +} +static int sftp_reqfind(void *av, void *bv) +{ + unsigned *a = (unsigned *) av; + struct sftp_request *b = (struct sftp_request *)bv; + if (*a < b->id) + return -1; + if (*a > b->id) + return +1; + return 0; +} + +static tree234 *sftp_requests; + +static struct sftp_request *sftp_alloc_request(void) +{ + unsigned low, high, mid; + int tsize; + struct sftp_request *r; + + if (sftp_requests == NULL) + sftp_requests = newtree234(sftp_reqcmp); + + /* + * First-fit allocation of request IDs: always pick the lowest + * unused one. To do this, binary-search using the counted + * B-tree to find the largest ID which is in a contiguous + * sequence from the beginning. (Precisely everything in that + * sequence must have ID equal to its tree index plus + * REQUEST_ID_OFFSET.) + */ + tsize = count234(sftp_requests); + + low = -1; + high = tsize; + while (high - low > 1) { + mid = (high + low) / 2; + r = index234(sftp_requests, mid); + if (r->id == mid + REQUEST_ID_OFFSET) + low = mid; /* this one is fine */ + else + high = mid; /* this one is past it */ + } + /* + * Now low points to either -1, or the tree index of the + * largest ID in the initial sequence. + */ + { + unsigned i = low + 1 + REQUEST_ID_OFFSET; + assert(NULL == find234(sftp_requests, &i, sftp_reqfind)); + } + + /* + * So the request ID we need to create is + * low + 1 + REQUEST_ID_OFFSET. + */ + r = snew(struct sftp_request); + r->id = low + 1 + REQUEST_ID_OFFSET; + r->registered = 0; + r->userdata = NULL; + add234(sftp_requests, r); + return r; +} + +void sftp_cleanup_request(void) +{ + if (sftp_requests != NULL) { + freetree234(sftp_requests); + sftp_requests = NULL; + } +} + +void sftp_register(struct sftp_request *req) +{ + req->registered = 1; +} + +struct sftp_request *sftp_find_request(struct sftp_packet *pktin) +{ + unsigned long id; + struct sftp_request *req; + + if (!pktin) { + fxp_internal_error("did not receive a valid SFTP packet\n"); + return NULL; + } + + if (!sftp_pkt_getuint32(pktin, &id)) { + fxp_internal_error("did not receive a valid SFTP packet\n"); + return NULL; + } + req = find234(sftp_requests, &id, sftp_reqfind); + + if (!req || !req->registered) { + fxp_internal_error("request ID mismatch\n"); + sftp_pkt_free(pktin); + return NULL; + } + + del234(sftp_requests, req); + + return req; +} + +/* ---------------------------------------------------------------------- + * String handling routines. + */ + +static char *mkstr(char *s, int len) +{ + char *p = snewn(len + 1, char); + memcpy(p, s, len); + p[len] = '\0'; + return p; +} + +/* ---------------------------------------------------------------------- + * SFTP primitives. + */ + +/* + * Deal with (and free) an FXP_STATUS packet. Return 1 if + * SSH_FX_OK, 0 if SSH_FX_EOF, and -1 for anything else (error). + * Also place the status into fxp_errtype. + */ +static int fxp_got_status(struct sftp_packet *pktin) +{ + static const char *const messages[] = { + /* SSH_FX_OK. The only time we will display a _message_ for this + * is if we were expecting something other than FXP_STATUS on + * success, so this is actually an error message! */ + "unexpected OK response", + "end of file", + "no such file or directory", + "permission denied", + "failure", + "bad message", + "no connection", + "connection lost", + "operation unsupported", + }; + + if (pktin->type != SSH_FXP_STATUS) { + fxp_error_message = "expected FXP_STATUS packet"; + fxp_errtype = -1; + } else { + unsigned long ul; + if (!sftp_pkt_getuint32(pktin, &ul)) { + fxp_error_message = "malformed FXP_STATUS packet"; + fxp_errtype = -1; + } else { + fxp_errtype = ul; + if (fxp_errtype < 0 || + fxp_errtype >= sizeof(messages) / sizeof(*messages)) + fxp_error_message = "unknown error code"; + else + fxp_error_message = messages[fxp_errtype]; + } + } + + if (fxp_errtype == SSH_FX_OK) + return 1; + else if (fxp_errtype == SSH_FX_EOF) + return 0; + else + return -1; +} + +static void fxp_internal_error(char *msg) +{ + fxp_error_message = msg; + fxp_errtype = -1; +} + +const char *fxp_error(void) +{ + return fxp_error_message; +} + +int fxp_error_type(void) +{ + return fxp_errtype; +} + +/* + * Perform exchange of init/version packets. Return 0 on failure. + */ +int fxp_init(void) +{ + struct sftp_packet *pktout, *pktin; + unsigned long remotever; + + pktout = sftp_pkt_init(SSH_FXP_INIT); + sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION); + sftp_send(pktout); + + pktin = sftp_recv(); + if (!pktin) { + fxp_internal_error("could not connect"); + return 0; + } + if (pktin->type != SSH_FXP_VERSION) { + fxp_internal_error("did not receive FXP_VERSION"); + sftp_pkt_free(pktin); + return 0; + } + if (!sftp_pkt_getuint32(pktin, &remotever)) { + fxp_internal_error("malformed FXP_VERSION packet"); + sftp_pkt_free(pktin); + return 0; + } + if (remotever > SFTP_PROTO_VERSION) { + fxp_internal_error + ("remote protocol is more advanced than we support"); + sftp_pkt_free(pktin); + return 0; + } + /* + * In principle, this packet might also contain extension- + * string pairs. We should work through them and look for any + * we recognise. In practice we don't currently do so because + * we know we don't recognise _any_. + */ + sftp_pkt_free(pktin); + + return 1; +} + +/* + * Canonify a pathname. + */ +struct sftp_request *fxp_realpath_send(char *path) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_REALPATH); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_str(pktout, path); + sftp_send(pktout); + + return req; +} + +char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + sfree(req); + + if (pktin->type == SSH_FXP_NAME) { + unsigned long count; + char *path; + int len; + + if (!sftp_pkt_getuint32(pktin, &count) || count != 1) { + fxp_internal_error("REALPATH did not return name count of 1\n"); + sftp_pkt_free(pktin); + return NULL; + } + if (!sftp_pkt_getstring(pktin, &path, &len)) { + fxp_internal_error("REALPATH returned malformed FXP_NAME\n"); + sftp_pkt_free(pktin); + return NULL; + } + path = mkstr(path, len); + sftp_pkt_free(pktin); + return path; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return NULL; + } +} + +/* + * Open a file. + */ +struct sftp_request *fxp_open_send(char *path, int type) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_OPEN); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, path); + sftp_pkt_adduint32(pktout, type); + sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */ + sftp_send(pktout); + + return req; +} + +struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin, + struct sftp_request *req) +{ + sfree(req); + + if (pktin->type == SSH_FXP_HANDLE) { + char *hstring; + struct fxp_handle *handle; + int len; + + if (!sftp_pkt_getstring(pktin, &hstring, &len)) { + fxp_internal_error("OPEN returned malformed FXP_HANDLE\n"); + sftp_pkt_free(pktin); + return NULL; + } + handle = snew(struct fxp_handle); + handle->hstring = mkstr(hstring, len); + handle->hlen = len; + sftp_pkt_free(pktin); + return handle; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return NULL; + } +} + +/* + * Open a directory. + */ +struct sftp_request *fxp_opendir_send(char *path) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_OPENDIR); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, path); + sftp_send(pktout); + + return req; +} + +struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin, + struct sftp_request *req) +{ + sfree(req); + if (pktin->type == SSH_FXP_HANDLE) { + char *hstring; + struct fxp_handle *handle; + int len; + + if (!sftp_pkt_getstring(pktin, &hstring, &len)) { + fxp_internal_error("OPENDIR returned malformed FXP_HANDLE\n"); + sftp_pkt_free(pktin); + return NULL; + } + handle = snew(struct fxp_handle); + handle->hstring = mkstr(hstring, len); + handle->hlen = len; + sftp_pkt_free(pktin); + return handle; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return NULL; + } +} + +/* + * Close a file/dir. + */ +struct sftp_request *fxp_close_send(struct fxp_handle *handle) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_CLOSE); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_send(pktout); + + sfree(handle->hstring); + sfree(handle); + + return req; +} + +void fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + sfree(req); + fxp_got_status(pktin); + sftp_pkt_free(pktin); +} + +struct sftp_request *fxp_mkdir_send(char *path) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_MKDIR); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, path); + sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */ + sftp_send(pktout); + + return req; +} + +int fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +struct sftp_request *fxp_rmdir_send(char *path) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_RMDIR); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, path); + sftp_send(pktout); + + return req; +} + +int fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +struct sftp_request *fxp_remove_send(char *fname) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_REMOVE); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, fname); + sftp_send(pktout); + + return req; +} + +int fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +struct sftp_request *fxp_rename_send(char *srcfname, char *dstfname) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_RENAME); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, srcfname); + sftp_pkt_addstring(pktout, dstfname); + sftp_send(pktout); + + return req; +} + +int fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +/* + * Retrieve the attributes of a file. We have fxp_stat which works + * on filenames, and fxp_fstat which works on open file handles. + */ +struct sftp_request *fxp_stat_send(char *fname) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_STAT); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, fname); + sftp_send(pktout); + + return req; +} + +int fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs) +{ + sfree(req); + if (pktin->type == SSH_FXP_ATTRS) { + if (!sftp_pkt_getattrs(pktin, attrs)) { + fxp_internal_error("malformed SSH_FXP_ATTRS packet"); + sftp_pkt_free(pktin); + return 0; + } + sftp_pkt_free(pktin); + return 1; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return 0; + } +} + +struct sftp_request *fxp_fstat_send(struct fxp_handle *handle) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_FSTAT); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_send(pktout); + + return req; +} + +int fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs) +{ + sfree(req); + if (pktin->type == SSH_FXP_ATTRS) { + if (!sftp_pkt_getattrs(pktin, attrs)) { + fxp_internal_error("malformed SSH_FXP_ATTRS packet"); + sftp_pkt_free(pktin); + return 0; + } + sftp_pkt_free(pktin); + return 1; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return 0; + } +} + +/* + * Set the attributes of a file. + */ +struct sftp_request *fxp_setstat_send(char *fname, struct fxp_attrs attrs) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_SETSTAT); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring(pktout, fname); + sftp_pkt_addattrs(pktout, attrs); + sftp_send(pktout); + + return req; +} + +int fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle, + struct fxp_attrs attrs) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_FSETSTAT); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_pkt_addattrs(pktout, attrs); + sftp_send(pktout); + + return req; +} + +int fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + if (id != 1) { + return 0; + } + return 1; +} + +/* + * Read from a file. Returns the number of bytes read, or -1 on an + * error, or possibly 0 if EOF. (I'm not entirely sure whether it + * will return 0 on EOF, or return -1 and store SSH_FX_EOF in the + * error indicator. It might even depend on the SFTP server.) + */ +struct sftp_request *fxp_read_send(struct fxp_handle *handle, + uint64 offset, int len) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_READ); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_pkt_adduint64(pktout, offset); + sftp_pkt_adduint32(pktout, len); + sftp_send(pktout); + + return req; +} + +int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req, + char *buffer, int len) +{ + sfree(req); + if (pktin->type == SSH_FXP_DATA) { + char *str; + int rlen; + + if (!sftp_pkt_getstring(pktin, &str, &rlen)) { + fxp_internal_error("READ returned malformed SSH_FXP_DATA packet"); + sftp_pkt_free(pktin); + return -1; + } + + if (rlen > len || rlen < 0) { + fxp_internal_error("READ returned more bytes than requested"); + sftp_pkt_free(pktin); + return -1; + } + + memcpy(buffer, str, rlen); + sftp_pkt_free(pktin); + return rlen; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return -1; + } +} + +/* + * Read from a directory. + */ +struct sftp_request *fxp_readdir_send(struct fxp_handle *handle) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_READDIR); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_send(pktout); + + return req; +} + +struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin, + struct sftp_request *req) +{ + sfree(req); + if (pktin->type == SSH_FXP_NAME) { + struct fxp_names *ret; + unsigned long i; + + /* + * Sanity-check the number of names. Minimum is obviously + * zero. Maximum is the remaining space in the packet + * divided by the very minimum length of a name, which is + * 12 bytes (4 for an empty filename, 4 for an empty + * longname, 4 for a set of attribute flags indicating that + * no other attributes are supplied). + */ + if (!sftp_pkt_getuint32(pktin, &i) || + i > (pktin->length-pktin->savedpos)/12) { + fxp_internal_error("malformed FXP_NAME packet"); + sftp_pkt_free(pktin); + return NULL; + } + + /* + * Ensure the implicit multiplication in the snewn() call + * doesn't suffer integer overflow and cause us to malloc + * too little space. + */ + if (i > INT_MAX / sizeof(struct fxp_name)) { + fxp_internal_error("unreasonably large FXP_NAME packet"); + sftp_pkt_free(pktin); + return NULL; + } + + ret = snew(struct fxp_names); + ret->nnames = i; + ret->names = snewn(ret->nnames, struct fxp_name); + for (i = 0; i < (unsigned long)ret->nnames; i++) { + char *str1, *str2; + int len1, len2; + if (!sftp_pkt_getstring(pktin, &str1, &len1) || + !sftp_pkt_getstring(pktin, &str2, &len2) || + !sftp_pkt_getattrs(pktin, &ret->names[i].attrs)) { + fxp_internal_error("malformed FXP_NAME packet"); + while (i--) { + sfree(ret->names[i].filename); + sfree(ret->names[i].longname); + } + sfree(ret->names); + sfree(ret); + sfree(pktin); + return NULL; + } + ret->names[i].filename = mkstr(str1, len1); + ret->names[i].longname = mkstr(str2, len2); + } + sftp_pkt_free(pktin); + return ret; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return NULL; + } +} + +/* + * Write to a file. Returns 0 on error, 1 on OK. + */ +struct sftp_request *fxp_write_send(struct fxp_handle *handle, + char *buffer, uint64 offset, int len) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_WRITE); + sftp_pkt_adduint32(pktout, req->id); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + sftp_pkt_adduint64(pktout, offset); + sftp_pkt_addstring_start(pktout); + sftp_pkt_addstring_data(pktout, buffer, len); + sftp_send(pktout); + + return req; +} + +int fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + sfree(req); + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return fxp_errtype == SSH_FX_OK; +} + +/* + * Free up an fxp_names structure. + */ +void fxp_free_names(struct fxp_names *names) +{ + int i; + + for (i = 0; i < names->nnames; i++) { + sfree(names->names[i].filename); + sfree(names->names[i].longname); + } + sfree(names->names); + sfree(names); +} + +/* + * Duplicate an fxp_name structure. + */ +struct fxp_name *fxp_dup_name(struct fxp_name *name) +{ + struct fxp_name *ret; + ret = snew(struct fxp_name); + ret->filename = dupstr(name->filename); + ret->longname = dupstr(name->longname); + ret->attrs = name->attrs; /* structure copy */ + return ret; +} + +/* + * Free up an fxp_name structure. + */ +void fxp_free_name(struct fxp_name *name) +{ + sfree(name->filename); + sfree(name->longname); + sfree(name); +} + +/* + * Store user data in an sftp_request structure. + */ +void *fxp_get_userdata(struct sftp_request *req) +{ + return req->userdata; +} + +void fxp_set_userdata(struct sftp_request *req, void *data) +{ + req->userdata = data; +} + +/* + * A wrapper to go round fxp_read_* and fxp_write_*, which manages + * the queueing of multiple read/write requests. + */ + +struct req { + char *buffer; + int len, retlen, complete; + uint64 offset; + struct req *next, *prev; +}; + +struct fxp_xfer { + uint64 offset, furthestdata, filesize; + int req_totalsize, req_maxsize, eof, err; + struct fxp_handle *fh; + struct req *head, *tail; +}; + +static struct fxp_xfer *xfer_init(struct fxp_handle *fh, uint64 offset) +{ + struct fxp_xfer *xfer = snew(struct fxp_xfer); + + xfer->fh = fh; + xfer->offset = offset; + xfer->head = xfer->tail = NULL; + xfer->req_totalsize = 0; + xfer->req_maxsize = 1048576; + xfer->err = 0; + xfer->filesize = uint64_make(ULONG_MAX, ULONG_MAX); + xfer->furthestdata = uint64_make(0, 0); + + return xfer; +} + +int xfer_done(struct fxp_xfer *xfer) +{ + /* + * We're finished if we've seen EOF _and_ there are no + * outstanding requests. + */ + return (xfer->eof || xfer->err) && !xfer->head; +} + +void xfer_download_queue(struct fxp_xfer *xfer) +{ + while (xfer->req_totalsize < xfer->req_maxsize && + !xfer->eof && !xfer->err) { + /* + * Queue a new read request. + */ + struct req *rr; + struct sftp_request *req; + + rr = snew(struct req); + rr->offset = xfer->offset; + rr->complete = 0; + if (xfer->tail) { + xfer->tail->next = rr; + rr->prev = xfer->tail; + } else { + xfer->head = rr; + rr->prev = NULL; + } + xfer->tail = rr; + rr->next = NULL; + + rr->len = 32768; + rr->buffer = snewn(rr->len, char); + sftp_register(req = fxp_read_send(xfer->fh, rr->offset, rr->len)); + fxp_set_userdata(req, rr); + + xfer->offset = uint64_add32(xfer->offset, rr->len); + xfer->req_totalsize += rr->len; + +#ifdef DEBUG_DOWNLOAD + { char buf[40]; uint64_decimal(rr->offset, buf); printf("queueing read request %p at %s\n", rr, buf); } +#endif + } +} + +struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64 offset) +{ + struct fxp_xfer *xfer = xfer_init(fh, offset); + + xfer->eof = FALSE; + xfer_download_queue(xfer); + + return xfer; +} + +int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) +{ + struct sftp_request *rreq; + struct req *rr; + + rreq = sftp_find_request(pktin); + rr = (struct req *)fxp_get_userdata(rreq); + if (!rr) + return 0; /* this packet isn't ours */ + rr->retlen = fxp_read_recv(pktin, rreq, rr->buffer, rr->len); +#ifdef DEBUG_DOWNLOAD + printf("read request %p has returned [%d]\n", rr, rr->retlen); +#endif + + if ((rr->retlen < 0 && fxp_error_type()==SSH_FX_EOF) || rr->retlen == 0) { + xfer->eof = TRUE; + rr->complete = -1; +#ifdef DEBUG_DOWNLOAD + printf("setting eof\n"); +#endif + } else if (rr->retlen < 0) { + /* some error other than EOF; signal it back to caller */ + xfer_set_error(xfer); + rr->complete = -1; + return -1; + } + + rr->complete = 1; + + /* + * Special case: if we have received fewer bytes than we + * actually read, we should do something. For the moment I'll + * just throw an ersatz FXP error to signal this; the SFTP + * draft I've got says that it can't happen except on special + * files, in which case seeking probably has very little + * meaning and so queueing an additional read request to fill + * up the gap sounds like the wrong answer. I'm not sure what I + * should be doing here - if it _was_ a special file, I suspect + * I simply shouldn't have been queueing multiple requests in + * the first place... + */ + if (rr->retlen > 0 && uint64_compare(xfer->furthestdata, rr->offset) < 0) { + xfer->furthestdata = rr->offset; +#ifdef DEBUG_DOWNLOAD + { char buf[40]; + uint64_decimal(xfer->furthestdata, buf); + printf("setting furthestdata = %s\n", buf); } +#endif + } + + if (rr->retlen < rr->len) { + uint64 filesize = uint64_add32(rr->offset, + (rr->retlen < 0 ? 0 : rr->retlen)); +#ifdef DEBUG_DOWNLOAD + { char buf[40]; + uint64_decimal(filesize, buf); + printf("short block! trying filesize = %s\n", buf); } +#endif + if (uint64_compare(xfer->filesize, filesize) > 0) { + xfer->filesize = filesize; +#ifdef DEBUG_DOWNLOAD + printf("actually changing filesize\n"); +#endif + } + } + + if (uint64_compare(xfer->furthestdata, xfer->filesize) > 0) { + fxp_error_message = "received a short buffer from FXP_READ, but not" + " at EOF"; + fxp_errtype = -1; + xfer_set_error(xfer); + return -1; + } + + return 1; +} + +void xfer_set_error(struct fxp_xfer *xfer) +{ + xfer->err = 1; +} + +int xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len) +{ + void *retbuf = NULL; + int retlen = 0; + + /* + * Discard anything at the head of the rr queue with complete < + * 0; return the first thing with complete > 0. + */ + while (xfer->head && xfer->head->complete && !retbuf) { + struct req *rr = xfer->head; + + if (rr->complete > 0) { + retbuf = rr->buffer; + retlen = rr->retlen; +#ifdef DEBUG_DOWNLOAD + printf("handing back data from read request %p\n", rr); +#endif + } +#ifdef DEBUG_DOWNLOAD + else + printf("skipping failed read request %p\n", rr); +#endif + + xfer->head = xfer->head->next; + if (xfer->head) + xfer->head->prev = NULL; + else + xfer->tail = NULL; + xfer->req_totalsize -= rr->len; + sfree(rr); + } + + if (retbuf) { + *buf = retbuf; + *len = retlen; + return 1; + } else + return 0; +} + +struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64 offset) +{ + struct fxp_xfer *xfer = xfer_init(fh, offset); + + /* + * We set `eof' to 1 because this will cause xfer_done() to + * return true iff there are no outstanding requests. During an + * upload, our caller will be responsible for working out + * whether all the data has been sent, so all it needs to know + * from us is whether the outstanding requests have been + * handled once that's done. + */ + xfer->eof = 1; + + return xfer; +} + +int xfer_upload_ready(struct fxp_xfer *xfer) +{ + if (xfer->req_totalsize < xfer->req_maxsize) + return 1; + else + return 0; +} + +void xfer_upload_data(struct fxp_xfer *xfer, char *buffer, int len) +{ + struct req *rr; + struct sftp_request *req; + + rr = snew(struct req); + rr->offset = xfer->offset; + rr->complete = 0; + if (xfer->tail) { + xfer->tail->next = rr; + rr->prev = xfer->tail; + } else { + xfer->head = rr; + rr->prev = NULL; + } + xfer->tail = rr; + rr->next = NULL; + + rr->len = len; + rr->buffer = NULL; + sftp_register(req = fxp_write_send(xfer->fh, buffer, rr->offset, len)); + fxp_set_userdata(req, rr); + + xfer->offset = uint64_add32(xfer->offset, rr->len); + xfer->req_totalsize += rr->len; + +#ifdef DEBUG_UPLOAD + { char buf[40]; uint64_decimal(rr->offset, buf); printf("queueing write request %p at %s [len %d]\n", rr, buf, len); } +#endif +} + +int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) +{ + struct sftp_request *rreq; + struct req *rr, *prev, *next; + int ret; + + rreq = sftp_find_request(pktin); + rr = (struct req *)fxp_get_userdata(rreq); + if (!rr) + return 0; /* this packet isn't ours */ + ret = fxp_write_recv(pktin, rreq); +#ifdef DEBUG_UPLOAD + printf("write request %p has returned [%d]\n", rr, ret); +#endif + + /* + * Remove this one from the queue. + */ + prev = rr->prev; + next = rr->next; + if (prev) + prev->next = next; + else + xfer->head = next; + if (next) + next->prev = prev; + else + xfer->tail = prev; + xfer->req_totalsize -= rr->len; + sfree(rr); + + if (!ret) + return -1; + + return 1; +} + +void xfer_cleanup(struct fxp_xfer *xfer) +{ + struct req *rr; + while (xfer->head) { + rr = xfer->head; + xfer->head = xfer->head->next; + sfree(rr->buffer); + sfree(rr); + } + sfree(xfer); +} diff --git a/putty/SFTP.H b/putty/SFTP.H new file mode 100644 index 0000000..98368b3 --- /dev/null +++ b/putty/SFTP.H @@ -0,0 +1,248 @@ +/* + * sftp.h: definitions for SFTP and the sftp.c routines. + */ + +#include "int64.h" + +#define SSH_FXP_INIT 1 /* 0x1 */ +#define SSH_FXP_VERSION 2 /* 0x2 */ +#define SSH_FXP_OPEN 3 /* 0x3 */ +#define SSH_FXP_CLOSE 4 /* 0x4 */ +#define SSH_FXP_READ 5 /* 0x5 */ +#define SSH_FXP_WRITE 6 /* 0x6 */ +#define SSH_FXP_LSTAT 7 /* 0x7 */ +#define SSH_FXP_FSTAT 8 /* 0x8 */ +#define SSH_FXP_SETSTAT 9 /* 0x9 */ +#define SSH_FXP_FSETSTAT 10 /* 0xa */ +#define SSH_FXP_OPENDIR 11 /* 0xb */ +#define SSH_FXP_READDIR 12 /* 0xc */ +#define SSH_FXP_REMOVE 13 /* 0xd */ +#define SSH_FXP_MKDIR 14 /* 0xe */ +#define SSH_FXP_RMDIR 15 /* 0xf */ +#define SSH_FXP_REALPATH 16 /* 0x10 */ +#define SSH_FXP_STAT 17 /* 0x11 */ +#define SSH_FXP_RENAME 18 /* 0x12 */ +#define SSH_FXP_STATUS 101 /* 0x65 */ +#define SSH_FXP_HANDLE 102 /* 0x66 */ +#define SSH_FXP_DATA 103 /* 0x67 */ +#define SSH_FXP_NAME 104 /* 0x68 */ +#define SSH_FXP_ATTRS 105 /* 0x69 */ +#define SSH_FXP_EXTENDED 200 /* 0xc8 */ +#define SSH_FXP_EXTENDED_REPLY 201 /* 0xc9 */ + +#define SSH_FX_OK 0 +#define SSH_FX_EOF 1 +#define SSH_FX_NO_SUCH_FILE 2 +#define SSH_FX_PERMISSION_DENIED 3 +#define SSH_FX_FAILURE 4 +#define SSH_FX_BAD_MESSAGE 5 +#define SSH_FX_NO_CONNECTION 6 +#define SSH_FX_CONNECTION_LOST 7 +#define SSH_FX_OP_UNSUPPORTED 8 + +#define SSH_FILEXFER_ATTR_SIZE 0x00000001 +#define SSH_FILEXFER_ATTR_UIDGID 0x00000002 +#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 +#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 +#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000 + +#define SSH_FXF_READ 0x00000001 +#define SSH_FXF_WRITE 0x00000002 +#define SSH_FXF_APPEND 0x00000004 +#define SSH_FXF_CREAT 0x00000008 +#define SSH_FXF_TRUNC 0x00000010 +#define SSH_FXF_EXCL 0x00000020 + +#define SFTP_PROTO_VERSION 3 + +/* + * External references. The sftp client module sftp.c expects to be + * able to get at these functions. + * + * sftp_recvdata must never return less than len. It either blocks + * until len is available, or it returns failure. + * + * Both functions return 1 on success, 0 on failure. + */ +int sftp_senddata(char *data, int len); +int sftp_recvdata(char *data, int len); + +/* + * Free sftp_requests + */ +void sftp_cleanup_request(void); + +struct fxp_attrs { + unsigned long flags; + uint64 size; + unsigned long uid; + unsigned long gid; + unsigned long permissions; + unsigned long atime; + unsigned long mtime; +}; + +struct fxp_handle { + char *hstring; + int hlen; +}; + +struct fxp_name { + char *filename, *longname; + struct fxp_attrs attrs; +}; + +struct fxp_names { + int nnames; + struct fxp_name *names; +}; + +struct sftp_request; +struct sftp_packet; + +const char *fxp_error(void); +int fxp_error_type(void); + +/* + * Perform exchange of init/version packets. Return 0 on failure. + */ +int fxp_init(void); + +/* + * Canonify a pathname. Concatenate the two given path elements + * with a separating slash, unless the second is NULL. + */ +struct sftp_request *fxp_realpath_send(char *path); +char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Open a file. + */ +struct sftp_request *fxp_open_send(char *path, int type); +struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin, + struct sftp_request *req); + +/* + * Open a directory. + */ +struct sftp_request *fxp_opendir_send(char *path); +struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin, + struct sftp_request *req); + +/* + * Close a file/dir. + */ +struct sftp_request *fxp_close_send(struct fxp_handle *handle); +void fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Make a directory. + */ +struct sftp_request *fxp_mkdir_send(char *path); +int fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Remove a directory. + */ +struct sftp_request *fxp_rmdir_send(char *path); +int fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Remove a file. + */ +struct sftp_request *fxp_remove_send(char *fname); +int fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Rename a file. + */ +struct sftp_request *fxp_rename_send(char *srcfname, char *dstfname); +int fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Return file attributes. + */ +struct sftp_request *fxp_stat_send(char *fname); +int fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs); +struct sftp_request *fxp_fstat_send(struct fxp_handle *handle); +int fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs); + +/* + * Set file attributes. + */ +struct sftp_request *fxp_setstat_send(char *fname, struct fxp_attrs attrs); +int fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req); +struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle, + struct fxp_attrs attrs); +int fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Read from a file. + */ +struct sftp_request *fxp_read_send(struct fxp_handle *handle, + uint64 offset, int len); +int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req, + char *buffer, int len); + +/* + * Write to a file. Returns 0 on error, 1 on OK. + */ +struct sftp_request *fxp_write_send(struct fxp_handle *handle, + char *buffer, uint64 offset, int len); +int fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Read from a directory. + */ +struct sftp_request *fxp_readdir_send(struct fxp_handle *handle); +struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin, + struct sftp_request *req); + +/* + * Free up an fxp_names structure. + */ +void fxp_free_names(struct fxp_names *names); + +/* + * Duplicate and free fxp_name structures. + */ +struct fxp_name *fxp_dup_name(struct fxp_name *name); +void fxp_free_name(struct fxp_name *name); + +/* + * Store user data in an sftp_request structure. + */ +void *fxp_get_userdata(struct sftp_request *req); +void fxp_set_userdata(struct sftp_request *req, void *data); + +/* + * These functions might well be temporary placeholders to be + * replaced with more useful similar functions later. They form the + * main dispatch loop for processing incoming SFTP responses. + */ +void sftp_register(struct sftp_request *req); +struct sftp_request *sftp_find_request(struct sftp_packet *pktin); +struct sftp_packet *sftp_recv(void); + +/* + * A wrapper to go round fxp_read_* and fxp_write_*, which manages + * the queueing of multiple read/write requests. + */ + +struct fxp_xfer; + +struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64 offset); +void xfer_download_queue(struct fxp_xfer *xfer); +int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin); +int xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len); + +struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64 offset); +int xfer_upload_ready(struct fxp_xfer *xfer); +void xfer_upload_data(struct fxp_xfer *xfer, char *buffer, int len); +int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin); + +int xfer_done(struct fxp_xfer *xfer); +void xfer_set_error(struct fxp_xfer *xfer); +void xfer_cleanup(struct fxp_xfer *xfer); diff --git a/putty/SIGN.SH b/putty/SIGN.SH new file mode 100644 index 0000000..dfa9fbc --- /dev/null +++ b/putty/SIGN.SH @@ -0,0 +1,31 @@ +#!/bin/sh + +# Generate GPG signatures on a PuTTY release/snapshot directory as +# delivered by Buildscr. + +# Usage: sh sign.sh +# e.g. sh sign.sh putty Snapshots (probably in the build.out directory) +# or sh sign.sh 0.60 Releases + +set -e + +sign() { + # Check for the prior existence of the signature, so we can + # re-run this script if it encounters an error part way + # through. + echo "----- Signing $2 with '$keyname'" + test -f "$3" || \ + gpg --load-extension=idea "$1" -u "$keyname" -o "$3" "$2" +} + +cd "$1" +for t in DSA RSA; do + keyname="$2 ($t)" + echo "===== Signing with '$keyname'" + for i in putty*src.zip putty*.tar.gz x86/*.exe x86/*.zip; do + sign --detach-sign "$i" "$i.$t" + done + for i in md5sums sha1sums sha256sums sha512sums; do + sign --clearsign $i ${i}.$t + done +done diff --git a/putty/SSH.C b/putty/SSH.C new file mode 100644 index 0000000..950af14 --- /dev/null +++ b/putty/SSH.C @@ -0,0 +1,9972 @@ +/* + * SSH backend. + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" +#include "ssh.h" +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.h" +#endif + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#define SSH1_MSG_DISCONNECT 1 /* 0x1 */ +#define SSH1_SMSG_PUBLIC_KEY 2 /* 0x2 */ +#define SSH1_CMSG_SESSION_KEY 3 /* 0x3 */ +#define SSH1_CMSG_USER 4 /* 0x4 */ +#define SSH1_CMSG_AUTH_RSA 6 /* 0x6 */ +#define SSH1_SMSG_AUTH_RSA_CHALLENGE 7 /* 0x7 */ +#define SSH1_CMSG_AUTH_RSA_RESPONSE 8 /* 0x8 */ +#define SSH1_CMSG_AUTH_PASSWORD 9 /* 0x9 */ +#define SSH1_CMSG_REQUEST_PTY 10 /* 0xa */ +#define SSH1_CMSG_WINDOW_SIZE 11 /* 0xb */ +#define SSH1_CMSG_EXEC_SHELL 12 /* 0xc */ +#define SSH1_CMSG_EXEC_CMD 13 /* 0xd */ +#define SSH1_SMSG_SUCCESS 14 /* 0xe */ +#define SSH1_SMSG_FAILURE 15 /* 0xf */ +#define SSH1_CMSG_STDIN_DATA 16 /* 0x10 */ +#define SSH1_SMSG_STDOUT_DATA 17 /* 0x11 */ +#define SSH1_SMSG_STDERR_DATA 18 /* 0x12 */ +#define SSH1_CMSG_EOF 19 /* 0x13 */ +#define SSH1_SMSG_EXIT_STATUS 20 /* 0x14 */ +#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION 21 /* 0x15 */ +#define SSH1_MSG_CHANNEL_OPEN_FAILURE 22 /* 0x16 */ +#define SSH1_MSG_CHANNEL_DATA 23 /* 0x17 */ +#define SSH1_MSG_CHANNEL_CLOSE 24 /* 0x18 */ +#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION 25 /* 0x19 */ +#define SSH1_SMSG_X11_OPEN 27 /* 0x1b */ +#define SSH1_CMSG_PORT_FORWARD_REQUEST 28 /* 0x1c */ +#define SSH1_MSG_PORT_OPEN 29 /* 0x1d */ +#define SSH1_CMSG_AGENT_REQUEST_FORWARDING 30 /* 0x1e */ +#define SSH1_SMSG_AGENT_OPEN 31 /* 0x1f */ +#define SSH1_MSG_IGNORE 32 /* 0x20 */ +#define SSH1_CMSG_EXIT_CONFIRMATION 33 /* 0x21 */ +#define SSH1_CMSG_X11_REQUEST_FORWARDING 34 /* 0x22 */ +#define SSH1_CMSG_AUTH_RHOSTS_RSA 35 /* 0x23 */ +#define SSH1_MSG_DEBUG 36 /* 0x24 */ +#define SSH1_CMSG_REQUEST_COMPRESSION 37 /* 0x25 */ +#define SSH1_CMSG_AUTH_TIS 39 /* 0x27 */ +#define SSH1_SMSG_AUTH_TIS_CHALLENGE 40 /* 0x28 */ +#define SSH1_CMSG_AUTH_TIS_RESPONSE 41 /* 0x29 */ +#define SSH1_CMSG_AUTH_CCARD 70 /* 0x46 */ +#define SSH1_SMSG_AUTH_CCARD_CHALLENGE 71 /* 0x47 */ +#define SSH1_CMSG_AUTH_CCARD_RESPONSE 72 /* 0x48 */ + +#define SSH1_AUTH_RHOSTS 1 /* 0x1 */ +#define SSH1_AUTH_RSA 2 /* 0x2 */ +#define SSH1_AUTH_PASSWORD 3 /* 0x3 */ +#define SSH1_AUTH_RHOSTS_RSA 4 /* 0x4 */ +#define SSH1_AUTH_TIS 5 /* 0x5 */ +#define SSH1_AUTH_CCARD 16 /* 0x10 */ + +#define SSH1_PROTOFLAG_SCREEN_NUMBER 1 /* 0x1 */ +/* Mask for protoflags we will echo back to server if seen */ +#define SSH1_PROTOFLAGS_SUPPORTED 0 /* 0x1 */ + +#define SSH2_MSG_DISCONNECT 1 /* 0x1 */ +#define SSH2_MSG_IGNORE 2 /* 0x2 */ +#define SSH2_MSG_UNIMPLEMENTED 3 /* 0x3 */ +#define SSH2_MSG_DEBUG 4 /* 0x4 */ +#define SSH2_MSG_SERVICE_REQUEST 5 /* 0x5 */ +#define SSH2_MSG_SERVICE_ACCEPT 6 /* 0x6 */ +#define SSH2_MSG_KEXINIT 20 /* 0x14 */ +#define SSH2_MSG_NEWKEYS 21 /* 0x15 */ +#define SSH2_MSG_KEXDH_INIT 30 /* 0x1e */ +#define SSH2_MSG_KEXDH_REPLY 31 /* 0x1f */ +#define SSH2_MSG_KEX_DH_GEX_REQUEST 30 /* 0x1e */ +#define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */ +#define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */ +#define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */ +#define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */ +#define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */ +#define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */ +#define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */ +#define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */ +#define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */ +#define SSH2_MSG_USERAUTH_BANNER 53 /* 0x35 */ +#define SSH2_MSG_USERAUTH_PK_OK 60 /* 0x3c */ +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* 0x3c */ +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 /* 0x3c */ +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* 0x3d */ +#define SSH2_MSG_GLOBAL_REQUEST 80 /* 0x50 */ +#define SSH2_MSG_REQUEST_SUCCESS 81 /* 0x51 */ +#define SSH2_MSG_REQUEST_FAILURE 82 /* 0x52 */ +#define SSH2_MSG_CHANNEL_OPEN 90 /* 0x5a */ +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 /* 0x5b */ +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 /* 0x5c */ +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 /* 0x5d */ +#define SSH2_MSG_CHANNEL_DATA 94 /* 0x5e */ +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 /* 0x5f */ +#define SSH2_MSG_CHANNEL_EOF 96 /* 0x60 */ +#define SSH2_MSG_CHANNEL_CLOSE 97 /* 0x61 */ +#define SSH2_MSG_CHANNEL_REQUEST 98 /* 0x62 */ +#define SSH2_MSG_CHANNEL_SUCCESS 99 /* 0x63 */ +#define SSH2_MSG_CHANNEL_FAILURE 100 /* 0x64 */ +#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 +#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 +#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 +#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 +#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 +#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 + +/* + * Packet type contexts, so that ssh2_pkt_type can correctly decode + * the ambiguous type numbers back into the correct type strings. + */ +typedef enum { + SSH2_PKTCTX_NOKEX, + SSH2_PKTCTX_DHGROUP, + SSH2_PKTCTX_DHGEX, + SSH2_PKTCTX_RSAKEX +} Pkt_KCtx; +typedef enum { + SSH2_PKTCTX_NOAUTH, + SSH2_PKTCTX_PUBLICKEY, + SSH2_PKTCTX_PASSWORD, + SSH2_PKTCTX_GSSAPI, + SSH2_PKTCTX_KBDINTER +} Pkt_ACtx; + +#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 /* 0x1 */ +#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 /* 0x2 */ +#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 /* 0x3 */ +#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 /* 0x4 */ +#define SSH2_DISCONNECT_MAC_ERROR 5 /* 0x5 */ +#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 /* 0x6 */ +#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 /* 0x7 */ +#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 /* 0x8 */ +#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 /* 0x9 */ +#define SSH2_DISCONNECT_CONNECTION_LOST 10 /* 0xa */ +#define SSH2_DISCONNECT_BY_APPLICATION 11 /* 0xb */ +#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 /* 0xc */ +#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 /* 0xd */ +#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 /* 0xe */ +#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 /* 0xf */ + +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", +}; + +#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 /* 0x1 */ +#define SSH2_OPEN_CONNECT_FAILED 2 /* 0x2 */ +#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 /* 0x3 */ +#define SSH2_OPEN_RESOURCE_SHORTAGE 4 /* 0x4 */ + +#define SSH2_EXTENDED_DATA_STDERR 1 /* 0x1 */ + +/* + * Various remote-bug flags. + */ +#define BUG_CHOKES_ON_SSH1_IGNORE 1 +#define BUG_SSH2_HMAC 2 +#define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4 +#define BUG_CHOKES_ON_RSA 8 +#define BUG_SSH2_RSA_PADDING 16 +#define BUG_SSH2_DERIVEKEY 32 +#define BUG_SSH2_REKEY 64 +#define BUG_SSH2_PK_SESSIONID 128 +#define BUG_SSH2_MAXPKT 256 +#define BUG_CHOKES_ON_SSH2_IGNORE 512 + +/* + * Codes for terminal modes. + * Most of these are the same in SSH-1 and SSH-2. + * This list is derived from RFC 4254 and + * SSH-1 RFC-1.2.31. + */ +static const struct { + const char* const mode; + int opcode; + enum { TTY_OP_CHAR, TTY_OP_BOOL } type; +} ssh_ttymodes[] = { + /* "V" prefix discarded for special characters relative to SSH specs */ + { "INTR", 1, TTY_OP_CHAR }, + { "QUIT", 2, TTY_OP_CHAR }, + { "ERASE", 3, TTY_OP_CHAR }, + { "KILL", 4, TTY_OP_CHAR }, + { "EOF", 5, TTY_OP_CHAR }, + { "EOL", 6, TTY_OP_CHAR }, + { "EOL2", 7, TTY_OP_CHAR }, + { "START", 8, TTY_OP_CHAR }, + { "STOP", 9, TTY_OP_CHAR }, + { "SUSP", 10, TTY_OP_CHAR }, + { "DSUSP", 11, TTY_OP_CHAR }, + { "REPRINT", 12, TTY_OP_CHAR }, + { "WERASE", 13, TTY_OP_CHAR }, + { "LNEXT", 14, TTY_OP_CHAR }, + { "FLUSH", 15, TTY_OP_CHAR }, + { "SWTCH", 16, TTY_OP_CHAR }, + { "STATUS", 17, TTY_OP_CHAR }, + { "DISCARD", 18, TTY_OP_CHAR }, + { "IGNPAR", 30, TTY_OP_BOOL }, + { "PARMRK", 31, TTY_OP_BOOL }, + { "INPCK", 32, TTY_OP_BOOL }, + { "ISTRIP", 33, TTY_OP_BOOL }, + { "INLCR", 34, TTY_OP_BOOL }, + { "IGNCR", 35, TTY_OP_BOOL }, + { "ICRNL", 36, TTY_OP_BOOL }, + { "IUCLC", 37, TTY_OP_BOOL }, + { "IXON", 38, TTY_OP_BOOL }, + { "IXANY", 39, TTY_OP_BOOL }, + { "IXOFF", 40, TTY_OP_BOOL }, + { "IMAXBEL", 41, TTY_OP_BOOL }, + { "ISIG", 50, TTY_OP_BOOL }, + { "ICANON", 51, TTY_OP_BOOL }, + { "XCASE", 52, TTY_OP_BOOL }, + { "ECHO", 53, TTY_OP_BOOL }, + { "ECHOE", 54, TTY_OP_BOOL }, + { "ECHOK", 55, TTY_OP_BOOL }, + { "ECHONL", 56, TTY_OP_BOOL }, + { "NOFLSH", 57, TTY_OP_BOOL }, + { "TOSTOP", 58, TTY_OP_BOOL }, + { "IEXTEN", 59, TTY_OP_BOOL }, + { "ECHOCTL", 60, TTY_OP_BOOL }, + { "ECHOKE", 61, TTY_OP_BOOL }, + { "PENDIN", 62, TTY_OP_BOOL }, /* XXX is this a real mode? */ + { "OPOST", 70, TTY_OP_BOOL }, + { "OLCUC", 71, TTY_OP_BOOL }, + { "ONLCR", 72, TTY_OP_BOOL }, + { "OCRNL", 73, TTY_OP_BOOL }, + { "ONOCR", 74, TTY_OP_BOOL }, + { "ONLRET", 75, TTY_OP_BOOL }, + { "CS7", 90, TTY_OP_BOOL }, + { "CS8", 91, TTY_OP_BOOL }, + { "PARENB", 92, TTY_OP_BOOL }, + { "PARODD", 93, TTY_OP_BOOL } +}; + +/* Miscellaneous other tty-related constants. */ +#define SSH_TTY_OP_END 0 +/* The opcodes for ISPEED/OSPEED differ between SSH-1 and SSH-2. */ +#define SSH1_TTY_OP_ISPEED 192 +#define SSH1_TTY_OP_OSPEED 193 +#define SSH2_TTY_OP_ISPEED 128 +#define SSH2_TTY_OP_OSPEED 129 + +/* Helper functions for parsing tty-related config. */ +static unsigned int ssh_tty_parse_specchar(char *s) +{ + unsigned int ret; + if (*s) { + char *next = NULL; + ret = ctrlparse(s, &next); + if (!next) ret = s[0]; + } else { + ret = 255; /* special value meaning "don't set" */ + } + return ret; +} +static unsigned int ssh_tty_parse_boolean(char *s) +{ + if (stricmp(s, "yes") == 0 || + stricmp(s, "on") == 0 || + stricmp(s, "true") == 0 || + stricmp(s, "+") == 0) + return 1; /* true */ + else if (stricmp(s, "no") == 0 || + stricmp(s, "off") == 0 || + stricmp(s, "false") == 0 || + stricmp(s, "-") == 0) + return 0; /* false */ + else + return (atoi(s) != 0); +} + +#define translate(x) if (type == x) return #x +#define translatek(x,ctx) if (type == x && (pkt_kctx == ctx)) return #x +#define translatea(x,ctx) if (type == x && (pkt_actx == ctx)) return #x +static char *ssh1_pkt_type(int type) +{ + translate(SSH1_MSG_DISCONNECT); + translate(SSH1_SMSG_PUBLIC_KEY); + translate(SSH1_CMSG_SESSION_KEY); + translate(SSH1_CMSG_USER); + translate(SSH1_CMSG_AUTH_RSA); + translate(SSH1_SMSG_AUTH_RSA_CHALLENGE); + translate(SSH1_CMSG_AUTH_RSA_RESPONSE); + translate(SSH1_CMSG_AUTH_PASSWORD); + translate(SSH1_CMSG_REQUEST_PTY); + translate(SSH1_CMSG_WINDOW_SIZE); + translate(SSH1_CMSG_EXEC_SHELL); + translate(SSH1_CMSG_EXEC_CMD); + translate(SSH1_SMSG_SUCCESS); + translate(SSH1_SMSG_FAILURE); + translate(SSH1_CMSG_STDIN_DATA); + translate(SSH1_SMSG_STDOUT_DATA); + translate(SSH1_SMSG_STDERR_DATA); + translate(SSH1_CMSG_EOF); + translate(SSH1_SMSG_EXIT_STATUS); + translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); + translate(SSH1_MSG_CHANNEL_OPEN_FAILURE); + translate(SSH1_MSG_CHANNEL_DATA); + translate(SSH1_MSG_CHANNEL_CLOSE); + translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION); + translate(SSH1_SMSG_X11_OPEN); + translate(SSH1_CMSG_PORT_FORWARD_REQUEST); + translate(SSH1_MSG_PORT_OPEN); + translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING); + translate(SSH1_SMSG_AGENT_OPEN); + translate(SSH1_MSG_IGNORE); + translate(SSH1_CMSG_EXIT_CONFIRMATION); + translate(SSH1_CMSG_X11_REQUEST_FORWARDING); + translate(SSH1_CMSG_AUTH_RHOSTS_RSA); + translate(SSH1_MSG_DEBUG); + translate(SSH1_CMSG_REQUEST_COMPRESSION); + translate(SSH1_CMSG_AUTH_TIS); + translate(SSH1_SMSG_AUTH_TIS_CHALLENGE); + translate(SSH1_CMSG_AUTH_TIS_RESPONSE); + translate(SSH1_CMSG_AUTH_CCARD); + translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE); + translate(SSH1_CMSG_AUTH_CCARD_RESPONSE); + return "unknown"; +} +static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type) +{ + translatea(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,SSH2_PKTCTX_GSSAPI); + translatea(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,SSH2_PKTCTX_GSSAPI); + translatea(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE,SSH2_PKTCTX_GSSAPI); + translatea(SSH2_MSG_USERAUTH_GSSAPI_ERROR,SSH2_PKTCTX_GSSAPI); + translatea(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK,SSH2_PKTCTX_GSSAPI); + translatea(SSH2_MSG_USERAUTH_GSSAPI_MIC, SSH2_PKTCTX_GSSAPI); + translate(SSH2_MSG_DISCONNECT); + translate(SSH2_MSG_IGNORE); + translate(SSH2_MSG_UNIMPLEMENTED); + translate(SSH2_MSG_DEBUG); + translate(SSH2_MSG_SERVICE_REQUEST); + translate(SSH2_MSG_SERVICE_ACCEPT); + translate(SSH2_MSG_KEXINIT); + translate(SSH2_MSG_NEWKEYS); + translatek(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP); + translatek(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP); + translatek(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX); + translatek(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX); + translatek(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX); + translatek(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX); + translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX); + translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX); + translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX); + translate(SSH2_MSG_USERAUTH_REQUEST); + translate(SSH2_MSG_USERAUTH_FAILURE); + translate(SSH2_MSG_USERAUTH_SUCCESS); + translate(SSH2_MSG_USERAUTH_BANNER); + translatea(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY); + translatea(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD); + translatea(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER); + translatea(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER); + translate(SSH2_MSG_GLOBAL_REQUEST); + translate(SSH2_MSG_REQUEST_SUCCESS); + translate(SSH2_MSG_REQUEST_FAILURE); + translate(SSH2_MSG_CHANNEL_OPEN); + translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + translate(SSH2_MSG_CHANNEL_OPEN_FAILURE); + translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST); + translate(SSH2_MSG_CHANNEL_DATA); + translate(SSH2_MSG_CHANNEL_EXTENDED_DATA); + translate(SSH2_MSG_CHANNEL_EOF); + translate(SSH2_MSG_CHANNEL_CLOSE); + translate(SSH2_MSG_CHANNEL_REQUEST); + translate(SSH2_MSG_CHANNEL_SUCCESS); + translate(SSH2_MSG_CHANNEL_FAILURE); + return "unknown"; +} +#undef translate +#undef translatec + +/* Enumeration values for fields in SSH-1 packets */ +enum { + PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM, + /* These values are for communicating relevant semantics of + * fields to the packet logging code. */ + PKTT_OTHER, PKTT_PASSWORD, PKTT_DATA +}; + +/* + * Coroutine mechanics for the sillier bits of the code. If these + * macros look impenetrable to you, you might find it helpful to + * read + * + * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + * + * which explains the theory behind these macros. + * + * In particular, if you are getting `case expression not constant' + * errors when building with MS Visual Studio, this is because MS's + * Edit and Continue debugging feature causes their compiler to + * violate ANSI C. To disable Edit and Continue debugging: + * + * - right-click ssh.c in the FileView + * - click Settings + * - select the C/C++ tab and the General category + * - under `Debug info:', select anything _other_ than `Program + * Database for Edit and Continue'. + */ +#define crBegin(v) { int *crLine = &v; switch(v) { case 0:; +#define crState(t) \ + struct t *s; \ + if (!ssh->t) ssh->t = snew(struct t); \ + s = ssh->t; +#define crFinish(z) } *crLine = 0; return (z); } +#define crFinishV } *crLine = 0; return; } +#define crReturn(z) \ + do {\ + *crLine =__LINE__; return (z); case __LINE__:;\ + } while (0) +#define crReturnV \ + do {\ + *crLine=__LINE__; return; case __LINE__:;\ + } while (0) +#define crStop(z) do{ *crLine = 0; return (z); }while(0) +#define crStopV do{ *crLine = 0; return; }while(0) +#define crWaitUntil(c) do { crReturn(0); } while (!(c)) +#define crWaitUntilV(c) do { crReturnV; } while (!(c)) + +typedef struct ssh_tag *Ssh; +struct Packet; + +static struct Packet *ssh1_pkt_init(int pkt_type); +static struct Packet *ssh2_pkt_init(int pkt_type); +static void ssh_pkt_ensure(struct Packet *, int length); +static void ssh_pkt_adddata(struct Packet *, void *data, int len); +static void ssh_pkt_addbyte(struct Packet *, unsigned char value); +static void ssh2_pkt_addbool(struct Packet *, unsigned char value); +static void ssh_pkt_adduint32(struct Packet *, unsigned long value); +static void ssh_pkt_addstring_start(struct Packet *); +static void ssh_pkt_addstring_str(struct Packet *, char *data); +static void ssh_pkt_addstring_data(struct Packet *, char *data, int len); +static void ssh_pkt_addstring(struct Packet *, char *data); +static unsigned char *ssh2_mpint_fmt(Bignum b, int *len); +static void ssh1_pkt_addmp(struct Packet *, Bignum b); +static void ssh2_pkt_addmp(struct Packet *, Bignum b); +static int ssh2_pkt_construct(Ssh, struct Packet *); +static void ssh2_pkt_send(Ssh, struct Packet *); +static void ssh2_pkt_send_noqueue(Ssh, struct Packet *); +static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin); +static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin); + +/* + * Buffer management constants. There are several of these for + * various different purposes: + * + * - SSH1_BUFFER_LIMIT is the amount of backlog that must build up + * on a local data stream before we throttle the whole SSH + * connection (in SSH-1 only). Throttling the whole connection is + * pretty drastic so we set this high in the hope it won't + * happen very often. + * + * - SSH_MAX_BACKLOG is the amount of backlog that must build up + * on the SSH connection itself before we defensively throttle + * _all_ local data streams. This is pretty drastic too (though + * thankfully unlikely in SSH-2 since the window mechanism should + * ensure that the server never has any need to throttle its end + * of the connection), so we set this high as well. + * + * - OUR_V2_WINSIZE is the maximum window size we present on SSH-2 + * channels. + * + * - OUR_V2_BIGWIN is the window size we advertise for the only + * channel in a simple connection. It must be <= INT_MAX. + * + * - OUR_V2_MAXPKT is the official "maximum packet size" we send + * to the remote side. This actually has nothing to do with the + * size of the _packet_, but is instead a limit on the amount + * of data we're willing to receive in a single SSH2 channel + * data message. + * + * - OUR_V2_PACKETLIMIT is actually the maximum size of SSH + * _packet_ we're prepared to cope with. It must be a multiple + * of the cipher block size, and must be at least 35000. + */ + +#define SSH1_BUFFER_LIMIT 32768 +#define SSH_MAX_BACKLOG 32768 +#define OUR_V2_WINSIZE 16384 +#define OUR_V2_BIGWIN 0x7fffffff +#define OUR_V2_MAXPKT 0x4000UL +#define OUR_V2_PACKETLIMIT 0x9000UL + +/* Maximum length of passwords/passphrases (arbitrary) */ +#define SSH_MAX_PASSWORD_LEN 100 + +const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss }; + +const static struct ssh_mac *macs[] = { + &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 +}; +const static struct ssh_mac *buggymacs[] = { + &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5 +}; + +static void *ssh_comp_none_init(void) +{ + return NULL; +} +static void ssh_comp_none_cleanup(void *handle) +{ +} +static int ssh_comp_none_block(void *handle, unsigned char *block, int len, + unsigned char **outblock, int *outlen) +{ + return 0; +} +static int ssh_comp_none_disable(void *handle) +{ + return 0; +} +const static struct ssh_compress ssh_comp_none = { + "none", NULL, + ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block, + ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block, + ssh_comp_none_disable, NULL +}; +extern const struct ssh_compress ssh_zlib; +const static struct ssh_compress *compressions[] = { + &ssh_zlib, &ssh_comp_none +}; + +enum { /* channel types */ + CHAN_MAINSESSION, + CHAN_X11, + CHAN_AGENT, + CHAN_SOCKDATA, + CHAN_SOCKDATA_DORMANT /* one the remote hasn't confirmed */ +}; + +/* + * little structure to keep track of outstanding WINDOW_ADJUSTs + */ +struct winadj { + struct winadj *next; + unsigned size; +}; + +/* + * 2-3-4 tree storing channels. + */ +struct ssh_channel { + Ssh ssh; /* pointer back to main context */ + unsigned remoteid, localid; + int type; + /* True if we opened this channel but server hasn't confirmed. */ + int halfopen; + /* + * In SSH-1, this value contains four bits: + * + * 1 We have sent SSH1_MSG_CHANNEL_CLOSE. + * 2 We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. + * 4 We have received SSH1_MSG_CHANNEL_CLOSE. + * 8 We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. + * + * A channel is completely finished with when all four bits are set. + */ + int closes; + + /* + * This flag indicates that a close 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 close the channel until we've finished sending + * the data, so we set this flag instead to remind us to + * initiate the closing process once our buffer is clear. + */ + int pending_close; + + /* + * True if this channel is causing the underlying connection to be + * throttled. + */ + int throttling_conn; + union { + struct ssh2_data_channel { + bufchain outbuffer; + unsigned remwindow, remmaxpkt; + /* locwindow is signed so we can cope with excess data. */ + int locwindow, locmaxwin; + /* + * remlocwin is the amount of local window that we think + * the remote end had available to it after it sent the + * last data packet or window adjust ack. + */ + int remlocwin; + /* + * These store the list of window adjusts that haven't + * been acked. + */ + struct winadj *winadj_head, *winadj_tail; + enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state; + } v2; + } v; + union { + struct ssh_agent_channel { + unsigned char *message; + unsigned char msglen[4]; + unsigned lensofar, totallen; + } a; + struct ssh_x11_channel { + Socket s; + } x11; + struct ssh_pfd_channel { + Socket s; + } pfd; + } u; +}; + +/* + * 2-3-4 tree storing remote->local port forwardings. SSH-1 and SSH-2 + * use this structure in different ways, reflecting SSH-2's + * altogether saner approach to port forwarding. + * + * In SSH-1, you arrange a remote forwarding by sending the server + * the remote port number, and the local destination host:port. + * When a connection comes in, the server sends you back that + * host:port pair, and you connect to it. This is a ready-made + * security hole if you're not on the ball: a malicious server + * could send you back _any_ host:port pair, so if you trustingly + * connect to the address it gives you then you've just opened the + * entire inside of your corporate network just by connecting + * through it to a dodgy SSH server. Hence, we must store a list of + * host:port pairs we _are_ trying to forward to, and reject a + * connection request from the server if it's not in the list. + * + * In SSH-2, each side of the connection minds its own business and + * doesn't send unnecessary information to the other. You arrange a + * remote forwarding by sending the server just the remote port + * number. When a connection comes in, the server tells you which + * of its ports was connected to; and _you_ have to remember what + * local host:port pair went with that port number. + * + * Hence, in SSH-1 this structure is indexed by destination + * host:port pair, whereas in SSH-2 it is indexed by source port. + */ +struct ssh_portfwd; /* forward declaration */ + +struct ssh_rportfwd { + unsigned sport, dport; + char dhost[256]; + char *sportdesc; + struct ssh_portfwd *pfrec; +}; +#define free_rportfwd(pf) ( \ + ((pf) ? (sfree((pf)->sportdesc)) : (void)0 ), sfree(pf) ) + +/* + * Separately to the rportfwd tree (which is for looking up port + * open requests from the server), a tree of _these_ structures is + * used to keep track of all the currently open port forwardings, + * so that we can reconfigure in mid-session if the user requests + * it. + */ +struct ssh_portfwd { + enum { DESTROY, KEEP, CREATE } status; + int type; + unsigned sport, dport; + char *saddr, *daddr; + char *sserv, *dserv; + struct ssh_rportfwd *remote; + int addressfamily; + void *local; +}; +#define free_portfwd(pf) ( \ + ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr), \ + sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) ) + +struct Packet { + long length; /* length of `data' actually used */ + long forcepad; /* SSH-2: force padding to at least this length */ + int type; /* only used for incoming packets */ + unsigned long sequence; /* SSH-2 incoming sequence number */ + unsigned char *data; /* allocated storage */ + unsigned char *body; /* offset of payload within `data' */ + long savedpos; /* temporary index into `data' (for strings) */ + long maxlen; /* amount of storage allocated for `data' */ + long encrypted_len; /* for SSH-2 total-size counting */ + + /* + * State associated with packet logging + */ + int logmode; + int nblanks; + struct logblank_t *blanks; +}; + +static void ssh1_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin); +static void ssh2_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin); +static void ssh1_protocol_setup(Ssh ssh); +static void ssh2_protocol_setup(Ssh ssh); +static void ssh_size(void *handle, int width, int height); +static void ssh_special(void *handle, Telnet_Special); +static int ssh2_try_send(struct ssh_channel *c); +static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len); +static void ssh_throttle_all(Ssh ssh, int enable, int bufsize); +static void ssh2_set_window(struct ssh_channel *c, int newwin); +static int ssh_sendbuffer(void *handle); +static int ssh_do_close(Ssh ssh, int notify_exit); +static unsigned long ssh_pkt_getuint32(struct Packet *pkt); +static int ssh2_pkt_getbool(struct Packet *pkt); +static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length); +static void ssh2_timer(void *ctx, long now); +static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, + struct Packet *pktin); + +struct rdpkt1_state_tag { + long len, pad, biglen, to_read; + unsigned long realcrc, gotcrc; + unsigned char *p; + int i; + int chunk; + struct Packet *pktin; +}; + +struct rdpkt2_state_tag { + long len, pad, payload, packetlen, maclen; + int i; + int cipherblk; + unsigned long incoming_sequence; + struct Packet *pktin; +}; + +typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin); +typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx); + +struct queued_handler; +struct queued_handler { + int msg1, msg2; + chandler_fn_t handler; + void *ctx; + struct queued_handler *next; +}; + +struct ssh_tag { + const struct plug_function_table *fn; + /* the above field _must_ be first in the structure */ + + char *v_c, *v_s; + void *exhash; + + Socket s; + + void *ldisc; + void *logctx; + + unsigned char session_key[32]; + int v1_compressing; + int v1_remote_protoflags; + int v1_local_protoflags; + int agentfwd_enabled; + int X11_fwd_enabled; + int remote_bugs; + const struct ssh_cipher *cipher; + void *v1_cipher_ctx; + void *crcda_ctx; + const struct ssh2_cipher *cscipher, *sccipher; + void *cs_cipher_ctx, *sc_cipher_ctx; + const struct ssh_mac *csmac, *scmac; + void *cs_mac_ctx, *sc_mac_ctx; + const struct ssh_compress *cscomp, *sccomp; + void *cs_comp_ctx, *sc_comp_ctx; + const struct ssh_kex *kex; + const struct ssh_signkey *hostkey; + unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN]; + int v2_session_id_len; + void *kex_ctx; + + char *savedhost; + int savedport; + int send_ok; + int echoing, editing; + + void *frontend; + + int ospeed, ispeed; /* temporaries */ + int term_width, term_height; + + tree234 *channels; /* indexed by local id */ + struct ssh_channel *mainchan; /* primary session channel */ + int ncmode; /* is primary channel direct-tcpip? */ + int exitcode; + int close_expected; + int clean_exit; + + tree234 *rportfwds, *portfwds; + + enum { + SSH_STATE_PREPACKET, + SSH_STATE_BEFORE_SIZE, + SSH_STATE_INTERMED, + SSH_STATE_SESSION, + SSH_STATE_CLOSED + } state; + + int size_needed, eof_needed; + + struct Packet **queue; + int queuelen, queuesize; + int queueing; + unsigned char *deferred_send_data; + int deferred_len, deferred_size; + + /* + * Gross hack: pscp will try to start SFTP but fall back to + * scp1 if that fails. This variable is the means by which + * scp.c can reach into the SSH code and find out which one it + * got. + */ + int fallback_cmd; + + bufchain banner; /* accumulates banners during do_ssh2_authconn */ + + Pkt_KCtx pkt_kctx; + Pkt_ACtx pkt_actx; + + struct X11Display *x11disp; + + int version; + int conn_throttle_count; + int overall_bufsize; + int throttled_all; + int v1_stdout_throttling; + unsigned long v2_outgoing_sequence; + + int ssh1_rdpkt_crstate; + int ssh2_rdpkt_crstate; + int do_ssh_init_crstate; + int ssh_gotdata_crstate; + int do_ssh1_login_crstate; + int do_ssh1_connection_crstate; + int do_ssh2_transport_crstate; + int do_ssh2_authconn_crstate; + + void *do_ssh_init_state; + void *do_ssh1_login_state; + void *do_ssh2_transport_state; + void *do_ssh2_authconn_state; + + struct rdpkt1_state_tag rdpkt1_state; + struct rdpkt2_state_tag rdpkt2_state; + + /* SSH-1 and SSH-2 use this for different things, but both use it */ + int protocol_initial_phase_done; + + void (*protocol) (Ssh ssh, void *vin, int inlen, + struct Packet *pkt); + struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen); + + /* + * We maintain a full _copy_ of a Config structure here, not + * merely a pointer to it. That way, when we're passed a new + * one for reconfiguration, we can check the differences and + * potentially reconfigure port forwardings etc in mid-session. + */ + Config cfg; + + /* + * Used to transfer data back from async callbacks. + */ + void *agent_response; + int agent_response_len; + int user_response; + + /* + * The SSH connection can be set as `frozen', meaning we are + * not currently accepting incoming data from the network. This + * is slightly more serious than setting the _socket_ as + * frozen, because we may already have had data passed to us + * from the network which we need to delay processing until + * after the freeze is lifted, so we also need a bufchain to + * store that data. + */ + int frozen; + bufchain queued_incoming_data; + + /* + * Dispatch table for packet types that we may have to deal + * with at any time. + */ + handler_fn_t packet_dispatch[256]; + + /* + * Queues of one-off handler functions for success/failure + * indications from a request. + */ + struct queued_handler *qhead, *qtail; + + /* + * This module deals with sending keepalives. + */ + Pinger pinger; + + /* + * Track incoming and outgoing data sizes and time, for + * size-based rekeys. + */ + unsigned long incoming_data_size, outgoing_data_size, deferred_data_size; + unsigned long max_data_size; + int kex_in_progress; + long next_rekey, last_rekey; + char *deferred_rekey_reason; /* points to STATIC string; don't free */ + + /* + * Fully qualified host name, which we need if doing GSSAPI. + */ + char *fullhostname; + +#ifndef NO_GSSAPI + /* + * GSSAPI libraries for this session. + */ + struct ssh_gss_liblist *gsslibs; +#endif +}; + +#define logevent(s) logevent(ssh->frontend, s) + +/* logevent, only printf-formatted. */ +static void logeventf(Ssh ssh, const char *fmt, ...) +{ + va_list ap; + char *buf; + + va_start(ap, fmt); + buf = dupvprintf(fmt, ap); + va_end(ap); + logevent(buf); + sfree(buf); +} + +#define bombout(msg) \ + do { \ + char *text = dupprintf msg; \ + ssh_do_close(ssh, FALSE); \ + logevent(text); \ + connection_fatal(ssh->frontend, "%s", text); \ + sfree(text); \ + } while (0) + +/* Functions to leave bits out of the SSH packet log file. */ + +static void dont_log_password(Ssh ssh, struct Packet *pkt, int blanktype) +{ + if (ssh->cfg.logomitpass) + pkt->logmode = blanktype; +} + +static void dont_log_data(Ssh ssh, struct Packet *pkt, int blanktype) +{ + if (ssh->cfg.logomitdata) + pkt->logmode = blanktype; +} + +static void end_log_omission(Ssh ssh, struct Packet *pkt) +{ + pkt->logmode = PKTLOG_EMIT; +} + +/* Helper function for common bits of parsing cfg.ttymodes. */ +static void parse_ttymodes(Ssh ssh, char *modes, + void (*do_mode)(void *data, char *mode, char *val), + void *data) +{ + while (*modes) { + char *t = strchr(modes, '\t'); + char *m = snewn(t-modes+1, char); + char *val; + strncpy(m, modes, t-modes); + m[t-modes] = '\0'; + if (*(t+1) == 'A') + val = get_ttymode(ssh->frontend, m); + else + val = dupstr(t+2); + if (val) + do_mode(data, m, val); + sfree(m); + sfree(val); + modes += strlen(modes) + 1; + } +} + +static int ssh_channelcmp(void *av, void *bv) +{ + struct ssh_channel *a = (struct ssh_channel *) av; + struct ssh_channel *b = (struct ssh_channel *) bv; + if (a->localid < b->localid) + return -1; + if (a->localid > b->localid) + return +1; + return 0; +} +static int ssh_channelfind(void *av, void *bv) +{ + unsigned *a = (unsigned *) av; + struct ssh_channel *b = (struct ssh_channel *) bv; + if (*a < b->localid) + return -1; + if (*a > b->localid) + return +1; + return 0; +} + +static int ssh_rportcmp_ssh1(void *av, void *bv) +{ + struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; + struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; + int i; + if ( (i = strcmp(a->dhost, b->dhost)) != 0) + return i < 0 ? -1 : +1; + if (a->dport > b->dport) + return +1; + if (a->dport < b->dport) + return -1; + return 0; +} + +static int ssh_rportcmp_ssh2(void *av, void *bv) +{ + struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; + struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; + + if (a->sport > b->sport) + return +1; + if (a->sport < b->sport) + return -1; + return 0; +} + +/* + * Special form of strcmp which can cope with NULL inputs. NULL is + * defined to sort before even the empty string. + */ +static int nullstrcmp(const char *a, const char *b) +{ + if (a == NULL && b == NULL) + return 0; + if (a == NULL) + return -1; + if (b == NULL) + return +1; + return strcmp(a, b); +} + +static int ssh_portcmp(void *av, void *bv) +{ + struct ssh_portfwd *a = (struct ssh_portfwd *) av; + struct ssh_portfwd *b = (struct ssh_portfwd *) bv; + int i; + if (a->type > b->type) + return +1; + if (a->type < b->type) + return -1; + if (a->addressfamily > b->addressfamily) + return +1; + if (a->addressfamily < b->addressfamily) + return -1; + if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0) + return i < 0 ? -1 : +1; + if (a->sport > b->sport) + return +1; + if (a->sport < b->sport) + return -1; + if (a->type != 'D') { + if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0) + return i < 0 ? -1 : +1; + if (a->dport > b->dport) + return +1; + if (a->dport < b->dport) + return -1; + } + return 0; +} + +static int alloc_channel_id(Ssh ssh) +{ + const unsigned CHANNEL_NUMBER_OFFSET = 256; + unsigned low, high, mid; + int tsize; + struct ssh_channel *c; + + /* + * First-fit allocation of channel numbers: always pick the + * lowest unused one. To do this, binary-search using the + * counted B-tree to find the largest channel ID which is in a + * contiguous sequence from the beginning. (Precisely + * everything in that sequence must have ID equal to its tree + * index plus CHANNEL_NUMBER_OFFSET.) + */ + tsize = count234(ssh->channels); + + low = -1; + high = tsize; + while (high - low > 1) { + mid = (high + low) / 2; + c = index234(ssh->channels, mid); + if (c->localid == mid + CHANNEL_NUMBER_OFFSET) + low = mid; /* this one is fine */ + else + high = mid; /* this one is past it */ + } + /* + * Now low points to either -1, or the tree index of the + * largest ID in the initial sequence. + */ + { + unsigned i = low + 1 + CHANNEL_NUMBER_OFFSET; + assert(NULL == find234(ssh->channels, &i, ssh_channelfind)); + } + return low + 1 + CHANNEL_NUMBER_OFFSET; +} + +static void c_write_stderr(int trusted, const char *buf, int len) +{ + int i; + for (i = 0; i < len; i++) + if (buf[i] != '\r' && (trusted || buf[i] == '\n' || (buf[i] & 0x60))) + fputc(buf[i], stderr); +} + +static void c_write(Ssh ssh, const char *buf, int len) +{ + if (flags & FLAG_STDERR) + c_write_stderr(1, buf, len); + else + from_backend(ssh->frontend, 1, buf, len); +} + +static void c_write_untrusted(Ssh ssh, const char *buf, int len) +{ + if (flags & FLAG_STDERR) + c_write_stderr(0, buf, len); + else + from_backend_untrusted(ssh->frontend, buf, len); +} + +static void c_write_str(Ssh ssh, const char *buf) +{ + c_write(ssh, buf, strlen(buf)); +} + +static void ssh_free_packet(struct Packet *pkt) +{ + sfree(pkt->data); + sfree(pkt); +} +static struct Packet *ssh_new_packet(void) +{ + struct Packet *pkt = snew(struct Packet); + + pkt->body = pkt->data = NULL; + pkt->maxlen = 0; + pkt->logmode = PKTLOG_EMIT; + pkt->nblanks = 0; + pkt->blanks = NULL; + + return pkt; +} + +/* + * Collect incoming data in the incoming packet buffer. + * Decipher and verify the packet when it is completely read. + * Drop SSH1_MSG_DEBUG and SSH1_MSG_IGNORE packets. + * Update the *data and *datalen variables. + * Return a Packet structure when a packet is completed. + */ +static struct Packet *ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen) +{ + struct rdpkt1_state_tag *st = &ssh->rdpkt1_state; + + crBegin(ssh->ssh1_rdpkt_crstate); + + st->pktin = ssh_new_packet(); + + st->pktin->type = 0; + st->pktin->length = 0; + + for (st->i = st->len = 0; st->i < 4; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->len = (st->len << 8) + **data; + (*data)++, (*datalen)--; + } + + st->pad = 8 - (st->len % 8); + st->biglen = st->len + st->pad; + st->pktin->length = st->len - 5; + + if (st->biglen < 0) { + bombout(("Extremely large packet length from server suggests" + " data stream corruption")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + st->pktin->maxlen = st->biglen; + st->pktin->data = snewn(st->biglen + APIEXTRA, unsigned char); + + st->to_read = st->biglen; + st->p = st->pktin->data; + while (st->to_read > 0) { + st->chunk = st->to_read; + while ((*datalen) == 0) + crReturn(NULL); + if (st->chunk > (*datalen)) + st->chunk = (*datalen); + memcpy(st->p, *data, st->chunk); + *data += st->chunk; + *datalen -= st->chunk; + st->p += st->chunk; + st->to_read -= st->chunk; + } + + if (ssh->cipher && detect_attack(ssh->crcda_ctx, st->pktin->data, + st->biglen, NULL)) { + bombout(("Network attack (CRC compensation) detected!")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + if (ssh->cipher) + ssh->cipher->decrypt(ssh->v1_cipher_ctx, st->pktin->data, st->biglen); + + st->realcrc = crc32_compute(st->pktin->data, st->biglen - 4); + st->gotcrc = GET_32BIT(st->pktin->data + st->biglen - 4); + if (st->gotcrc != st->realcrc) { + bombout(("Incorrect CRC received on packet")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + st->pktin->body = st->pktin->data + st->pad + 1; + st->pktin->savedpos = 0; + + if (ssh->v1_compressing) { + unsigned char *decompblk; + int decomplen; + if (!zlib_decompress_block(ssh->sc_comp_ctx, + st->pktin->body - 1, st->pktin->length + 1, + &decompblk, &decomplen)) { + bombout(("Zlib decompression encountered invalid data")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + if (st->pktin->maxlen < st->pad + decomplen) { + st->pktin->maxlen = st->pad + decomplen; + st->pktin->data = sresize(st->pktin->data, + st->pktin->maxlen + APIEXTRA, + unsigned char); + st->pktin->body = st->pktin->data + st->pad + 1; + } + + memcpy(st->pktin->body - 1, decompblk, decomplen); + sfree(decompblk); + st->pktin->length = decomplen - 1; + } + + st->pktin->type = st->pktin->body[-1]; + + /* + * Log incoming packet, possibly omitting sensitive fields. + */ + if (ssh->logctx) { + int nblanks = 0; + struct logblank_t blank; + if (ssh->cfg.logomitdata) { + int do_blank = FALSE, blank_prefix = 0; + /* "Session data" packets - omit the data field */ + if ((st->pktin->type == SSH1_SMSG_STDOUT_DATA) || + (st->pktin->type == SSH1_SMSG_STDERR_DATA)) { + do_blank = TRUE; blank_prefix = 4; + } else if (st->pktin->type == SSH1_MSG_CHANNEL_DATA) { + do_blank = TRUE; blank_prefix = 8; + } + if (do_blank) { + blank.offset = blank_prefix; + blank.len = st->pktin->length; + blank.type = PKTLOG_OMIT; + nblanks = 1; + } + } + log_packet(ssh->logctx, + PKT_INCOMING, st->pktin->type, + ssh1_pkt_type(st->pktin->type), + st->pktin->body, st->pktin->length, + nblanks, &blank, NULL); + } + + crFinish(st->pktin); +} + +static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) +{ + struct rdpkt2_state_tag *st = &ssh->rdpkt2_state; + + crBegin(ssh->ssh2_rdpkt_crstate); + + st->pktin = ssh_new_packet(); + + st->pktin->type = 0; + st->pktin->length = 0; + if (ssh->sccipher) + st->cipherblk = ssh->sccipher->blksize; + else + st->cipherblk = 8; + if (st->cipherblk < 8) + st->cipherblk = 8; + st->maclen = ssh->scmac ? ssh->scmac->len : 0; + + if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) && + ssh->scmac) { + /* + * When dealing with a CBC-mode cipher, we want to avoid the + * possibility of an attacker's tweaking the ciphertext stream + * so as to cause us to feed the same block to the block + * cipher more than once and thus leak information + * (VU#958563). The way we do this is not to take any + * decisions on the basis of anything we've decrypted until + * we've verified it with a MAC. That includes the packet + * length, so we just read data and check the MAC repeatedly, + * and when the MAC passes, see if the length we've got is + * plausible. + */ + + /* May as well allocate the whole lot now. */ + st->pktin->data = snewn(OUR_V2_PACKETLIMIT + st->maclen + APIEXTRA, + unsigned char); + + /* Read an amount corresponding to the MAC. */ + for (st->i = 0; st->i < st->maclen; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->i] = *(*data)++; + (*datalen)--; + } + + st->packetlen = 0; + { + unsigned char seq[4]; + ssh->scmac->start(ssh->sc_mac_ctx); + PUT_32BIT(seq, st->incoming_sequence); + ssh->scmac->bytes(ssh->sc_mac_ctx, seq, 4); + } + + for (;;) { /* Once around this loop per cipher block. */ + /* Read another cipher-block's worth, and tack it onto the end. */ + for (st->i = 0; st->i < st->cipherblk; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->packetlen+st->maclen+st->i] = *(*data)++; + (*datalen)--; + } + /* Decrypt one more block (a little further back in the stream). */ + ssh->sccipher->decrypt(ssh->sc_cipher_ctx, + st->pktin->data + st->packetlen, + st->cipherblk); + /* Feed that block to the MAC. */ + ssh->scmac->bytes(ssh->sc_mac_ctx, + st->pktin->data + st->packetlen, st->cipherblk); + st->packetlen += st->cipherblk; + /* See if that gives us a valid packet. */ + if (ssh->scmac->verresult(ssh->sc_mac_ctx, + st->pktin->data + st->packetlen) && + (st->len = GET_32BIT(st->pktin->data)) + 4 == st->packetlen) + break; + if (st->packetlen >= OUR_V2_PACKETLIMIT) { + bombout(("No valid incoming packet found")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + } + st->pktin->maxlen = st->packetlen + st->maclen; + st->pktin->data = sresize(st->pktin->data, + st->pktin->maxlen + APIEXTRA, + unsigned char); + } else { + st->pktin->data = snewn(st->cipherblk + APIEXTRA, unsigned char); + + /* + * Acquire and decrypt the first block of the packet. This will + * contain the length and padding details. + */ + for (st->i = st->len = 0; st->i < st->cipherblk; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->i] = *(*data)++; + (*datalen)--; + } + + if (ssh->sccipher) + ssh->sccipher->decrypt(ssh->sc_cipher_ctx, + st->pktin->data, st->cipherblk); + + /* + * Now get the length figure. + */ + st->len = GET_32BIT(st->pktin->data); + + /* + * _Completely_ silly lengths should be stomped on before they + * do us any more damage. + */ + if (st->len < 0 || st->len > OUR_V2_PACKETLIMIT || + (st->len + 4) % st->cipherblk != 0) { + bombout(("Incoming packet was garbled on decryption")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + /* + * So now we can work out the total packet length. + */ + st->packetlen = st->len + 4; + + /* + * Allocate memory for the rest of the packet. + */ + st->pktin->maxlen = st->packetlen + st->maclen; + st->pktin->data = sresize(st->pktin->data, + st->pktin->maxlen + APIEXTRA, + unsigned char); + + /* + * Read and decrypt the remainder of the packet. + */ + for (st->i = st->cipherblk; st->i < st->packetlen + st->maclen; + st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->i] = *(*data)++; + (*datalen)--; + } + /* Decrypt everything _except_ the MAC. */ + if (ssh->sccipher) + ssh->sccipher->decrypt(ssh->sc_cipher_ctx, + st->pktin->data + st->cipherblk, + st->packetlen - st->cipherblk); + + /* + * Check the MAC. + */ + if (ssh->scmac + && !ssh->scmac->verify(ssh->sc_mac_ctx, st->pktin->data, + st->len + 4, st->incoming_sequence)) { + bombout(("Incorrect MAC received on packet")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + } + /* Get and sanity-check the amount of random padding. */ + st->pad = st->pktin->data[4]; + if (st->pad < 4 || st->len - st->pad < 1) { + bombout(("Invalid padding length on received packet")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + /* + * This enables us to deduce the payload length. + */ + st->payload = st->len - st->pad - 1; + + st->pktin->length = st->payload + 5; + st->pktin->encrypted_len = st->packetlen; + + st->pktin->sequence = st->incoming_sequence++; + + /* + * Decompress packet payload. + */ + { + unsigned char *newpayload; + int newlen; + if (ssh->sccomp && + ssh->sccomp->decompress(ssh->sc_comp_ctx, + st->pktin->data + 5, st->pktin->length - 5, + &newpayload, &newlen)) { + if (st->pktin->maxlen < newlen + 5) { + st->pktin->maxlen = newlen + 5; + st->pktin->data = sresize(st->pktin->data, + st->pktin->maxlen + APIEXTRA, + unsigned char); + } + st->pktin->length = 5 + newlen; + memcpy(st->pktin->data + 5, newpayload, newlen); + sfree(newpayload); + } + } + + st->pktin->savedpos = 6; + st->pktin->body = st->pktin->data; + st->pktin->type = st->pktin->data[5]; + + /* + * Log incoming packet, possibly omitting sensitive fields. + */ + if (ssh->logctx) { + int nblanks = 0; + struct logblank_t blank; + if (ssh->cfg.logomitdata) { + int do_blank = FALSE, blank_prefix = 0; + /* "Session data" packets - omit the data field */ + if (st->pktin->type == SSH2_MSG_CHANNEL_DATA) { + do_blank = TRUE; blank_prefix = 8; + } else if (st->pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) { + do_blank = TRUE; blank_prefix = 12; + } + if (do_blank) { + blank.offset = blank_prefix; + blank.len = (st->pktin->length-6) - blank_prefix; + blank.type = PKTLOG_OMIT; + nblanks = 1; + } + } + log_packet(ssh->logctx, PKT_INCOMING, st->pktin->type, + ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, + st->pktin->type), + st->pktin->data+6, st->pktin->length-6, + nblanks, &blank, &st->pktin->sequence); + } + + crFinish(st->pktin); +} + +static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p) +{ + int pad, biglen, i, pktoffs; + unsigned long crc; +#ifdef __SC__ + /* + * XXX various versions of SC (including 8.8.4) screw up the + * register allocation in this function and use the same register + * (D6) for len and as a temporary, with predictable results. The + * following sledgehammer prevents this. + */ + volatile +#endif + int len; + + if (ssh->logctx) + log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12], + ssh1_pkt_type(pkt->data[12]), + pkt->body, pkt->length - (pkt->body - pkt->data), + pkt->nblanks, pkt->blanks, NULL); + sfree(pkt->blanks); pkt->blanks = NULL; + pkt->nblanks = 0; + + if (ssh->v1_compressing) { + unsigned char *compblk; + int complen; + zlib_compress_block(ssh->cs_comp_ctx, + pkt->data + 12, pkt->length - 12, + &compblk, &complen); + ssh_pkt_ensure(pkt, complen + 2); /* just in case it's got bigger */ + memcpy(pkt->data + 12, compblk, complen); + sfree(compblk); + pkt->length = complen + 12; + } + + ssh_pkt_ensure(pkt, pkt->length + 4); /* space for CRC */ + pkt->length += 4; + len = pkt->length - 4 - 8; /* len(type+data+CRC) */ + pad = 8 - (len % 8); + pktoffs = 8 - pad; + biglen = len + pad; /* len(padding+type+data+CRC) */ + + for (i = pktoffs; i < 4+8; i++) + pkt->data[i] = random_byte(); + crc = crc32_compute(pkt->data + pktoffs + 4, biglen - 4); /* all ex len */ + PUT_32BIT(pkt->data + pktoffs + 4 + biglen - 4, crc); + PUT_32BIT(pkt->data + pktoffs, len); + + if (ssh->cipher) + ssh->cipher->encrypt(ssh->v1_cipher_ctx, + pkt->data + pktoffs + 4, biglen); + + if (offset_p) *offset_p = pktoffs; + return biglen + 4; /* len(length+padding+type+data+CRC) */ +} + +static int s_write(Ssh ssh, void *data, int len) +{ + if (ssh->logctx) + log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len, + 0, NULL, NULL); + return sk_write(ssh->s, (char *)data, len); +} + +static void s_wrpkt(Ssh ssh, struct Packet *pkt) +{ + int len, backlog, offset; + len = s_wrpkt_prepare(ssh, pkt, &offset); + backlog = s_write(ssh, pkt->data + offset, len); + if (backlog > SSH_MAX_BACKLOG) + ssh_throttle_all(ssh, 1, backlog); + ssh_free_packet(pkt); +} + +static void s_wrpkt_defer(Ssh ssh, struct Packet *pkt) +{ + int len, offset; + len = s_wrpkt_prepare(ssh, pkt, &offset); + if (ssh->deferred_len + len > ssh->deferred_size) { + ssh->deferred_size = ssh->deferred_len + len + 128; + ssh->deferred_send_data = sresize(ssh->deferred_send_data, + ssh->deferred_size, + unsigned char); + } + memcpy(ssh->deferred_send_data + ssh->deferred_len, + pkt->data + offset, len); + ssh->deferred_len += len; + ssh_free_packet(pkt); +} + +/* + * Construct a SSH-1 packet with the specified contents. + * (This all-at-once interface used to be the only one, but now SSH-1 + * packets can also be constructed incrementally.) + */ +static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap) +{ + int argtype; + Bignum bn; + struct Packet *pkt; + + pkt = ssh1_pkt_init(pkttype); + + while ((argtype = va_arg(ap, int)) != PKT_END) { + unsigned char *argp, argchar; + char *sargp; + unsigned long argint; + int arglen; + switch (argtype) { + /* Actual fields in the packet */ + case PKT_INT: + argint = va_arg(ap, int); + ssh_pkt_adduint32(pkt, argint); + break; + case PKT_CHAR: + argchar = (unsigned char) va_arg(ap, int); + ssh_pkt_addbyte(pkt, argchar); + break; + case PKT_DATA: + argp = va_arg(ap, unsigned char *); + arglen = va_arg(ap, int); + ssh_pkt_adddata(pkt, argp, arglen); + break; + case PKT_STR: + sargp = va_arg(ap, char *); + ssh_pkt_addstring(pkt, sargp); + break; + case PKT_BIGNUM: + bn = va_arg(ap, Bignum); + ssh1_pkt_addmp(pkt, bn); + break; + /* Tokens for modifications to packet logging */ + case PKTT_PASSWORD: + dont_log_password(ssh, pkt, PKTLOG_BLANK); + break; + case PKTT_DATA: + dont_log_data(ssh, pkt, PKTLOG_OMIT); + break; + case PKTT_OTHER: + end_log_omission(ssh, pkt); + break; + } + } + + return pkt; +} + +static void send_packet(Ssh ssh, int pkttype, ...) +{ + struct Packet *pkt; + va_list ap; + va_start(ap, pkttype); + pkt = construct_packet(ssh, pkttype, ap); + va_end(ap); + s_wrpkt(ssh, pkt); +} + +static void defer_packet(Ssh ssh, int pkttype, ...) +{ + struct Packet *pkt; + va_list ap; + va_start(ap, pkttype); + pkt = construct_packet(ssh, pkttype, ap); + va_end(ap); + s_wrpkt_defer(ssh, pkt); +} + +static int ssh_versioncmp(char *a, char *b) +{ + char *ae, *be; + unsigned long av, bv; + + av = strtoul(a, &ae, 10); + bv = strtoul(b, &be, 10); + if (av != bv) + return (av < bv ? -1 : +1); + if (*ae == '.') + ae++; + if (*be == '.') + be++; + av = strtoul(ae, &ae, 10); + bv = strtoul(be, &be, 10); + if (av != bv) + return (av < bv ? -1 : +1); + return 0; +} + +/* + * Utility routines for putting an SSH-protocol `string' and + * `uint32' into a hash state. + */ +static void hash_string(const struct ssh_hash *h, void *s, void *str, int len) +{ + unsigned char lenblk[4]; + PUT_32BIT(lenblk, len); + h->bytes(s, lenblk, 4); + h->bytes(s, str, len); +} + +static void hash_uint32(const struct ssh_hash *h, void *s, unsigned i) +{ + unsigned char intblk[4]; + PUT_32BIT(intblk, i); + h->bytes(s, intblk, 4); +} + +/* + * Packet construction functions. Mostly shared between SSH-1 and SSH-2. + */ +static void ssh_pkt_ensure(struct Packet *pkt, int length) +{ + if (pkt->maxlen < length) { + unsigned char *body = pkt->body; + int offset = body ? body - pkt->data : 0; + pkt->maxlen = length + 256; + pkt->data = sresize(pkt->data, pkt->maxlen + APIEXTRA, unsigned char); + if (body) pkt->body = pkt->data + offset; + } +} +static void ssh_pkt_adddata(struct Packet *pkt, void *data, int len) +{ + if (pkt->logmode != PKTLOG_EMIT) { + pkt->nblanks++; + pkt->blanks = sresize(pkt->blanks, pkt->nblanks, struct logblank_t); + assert(pkt->body); + pkt->blanks[pkt->nblanks-1].offset = pkt->length - + (pkt->body - pkt->data); + pkt->blanks[pkt->nblanks-1].len = len; + pkt->blanks[pkt->nblanks-1].type = pkt->logmode; + } + pkt->length += len; + ssh_pkt_ensure(pkt, pkt->length); + memcpy(pkt->data + pkt->length - len, data, len); +} +static void ssh_pkt_addbyte(struct Packet *pkt, unsigned char byte) +{ + ssh_pkt_adddata(pkt, &byte, 1); +} +static void ssh2_pkt_addbool(struct Packet *pkt, unsigned char value) +{ + ssh_pkt_adddata(pkt, &value, 1); +} +static void ssh_pkt_adduint32(struct Packet *pkt, unsigned long value) +{ + unsigned char x[4]; + PUT_32BIT(x, value); + ssh_pkt_adddata(pkt, x, 4); +} +static void ssh_pkt_addstring_start(struct Packet *pkt) +{ + ssh_pkt_adduint32(pkt, 0); + pkt->savedpos = pkt->length; +} +static void ssh_pkt_addstring_str(struct Packet *pkt, char *data) +{ + ssh_pkt_adddata(pkt, data, strlen(data)); + PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); +} +static void ssh_pkt_addstring_data(struct Packet *pkt, char *data, int len) +{ + ssh_pkt_adddata(pkt, data, len); + PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); +} +static void ssh_pkt_addstring(struct Packet *pkt, char *data) +{ + ssh_pkt_addstring_start(pkt); + ssh_pkt_addstring_str(pkt, data); +} +static void ssh1_pkt_addmp(struct Packet *pkt, Bignum b) +{ + int len = ssh1_bignum_length(b); + unsigned char *data = snewn(len, unsigned char); + (void) ssh1_write_bignum(data, b); + ssh_pkt_adddata(pkt, data, len); + sfree(data); +} +static unsigned char *ssh2_mpint_fmt(Bignum b, int *len) +{ + unsigned char *p; + int i, n = (bignum_bitcount(b) + 7) / 8; + p = snewn(n + 1, unsigned char); + p[0] = 0; + for (i = 1; i <= n; i++) + p[i] = bignum_byte(b, n - i); + i = 0; + while (i <= n && p[i] == 0 && (p[i + 1] & 0x80) == 0) + i++; + memmove(p, p + i, n + 1 - i); + *len = n + 1 - i; + return p; +} +static void ssh2_pkt_addmp(struct Packet *pkt, Bignum b) +{ + unsigned char *p; + int len; + p = ssh2_mpint_fmt(b, &len); + ssh_pkt_addstring_start(pkt); + ssh_pkt_addstring_data(pkt, (char *)p, len); + sfree(p); +} + +static struct Packet *ssh1_pkt_init(int pkt_type) +{ + struct Packet *pkt = ssh_new_packet(); + pkt->length = 4 + 8; /* space for length + max padding */ + ssh_pkt_addbyte(pkt, pkt_type); + pkt->body = pkt->data + pkt->length; + return pkt; +} + +/* For legacy code (SSH-1 and -2 packet construction used to be separate) */ +#define ssh2_pkt_ensure(pkt, length) ssh_pkt_ensure(pkt, length) +#define ssh2_pkt_adddata(pkt, data, len) ssh_pkt_adddata(pkt, data, len) +#define ssh2_pkt_addbyte(pkt, byte) ssh_pkt_addbyte(pkt, byte) +#define ssh2_pkt_adduint32(pkt, value) ssh_pkt_adduint32(pkt, value) +#define ssh2_pkt_addstring_start(pkt) ssh_pkt_addstring_start(pkt) +#define ssh2_pkt_addstring_str(pkt, data) ssh_pkt_addstring_str(pkt, data) +#define ssh2_pkt_addstring_data(pkt, data, len) ssh_pkt_addstring_data(pkt, data, len) +#define ssh2_pkt_addstring(pkt, data) ssh_pkt_addstring(pkt, data) + +static struct Packet *ssh2_pkt_init(int pkt_type) +{ + struct Packet *pkt = ssh_new_packet(); + pkt->length = 5; /* space for packet length + padding length */ + pkt->forcepad = 0; + ssh_pkt_addbyte(pkt, (unsigned char) pkt_type); + pkt->body = pkt->data + pkt->length; /* after packet type */ + return pkt; +} + +/* + * Construct an SSH-2 final-form packet: compress it, encrypt it, + * put the MAC on it. Final packet, ready to be sent, is stored in + * pkt->data. Total length is returned. + */ +static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt) +{ + int cipherblk, maclen, padding, i; + + if (ssh->logctx) + log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5], + ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->data[5]), + pkt->body, pkt->length - (pkt->body - pkt->data), + pkt->nblanks, pkt->blanks, &ssh->v2_outgoing_sequence); + sfree(pkt->blanks); pkt->blanks = NULL; + pkt->nblanks = 0; + + /* + * Compress packet payload. + */ + { + unsigned char *newpayload; + int newlen; + if (ssh->cscomp && + ssh->cscomp->compress(ssh->cs_comp_ctx, pkt->data + 5, + pkt->length - 5, + &newpayload, &newlen)) { + pkt->length = 5; + ssh2_pkt_adddata(pkt, newpayload, newlen); + sfree(newpayload); + } + } + + /* + * Add padding. At least four bytes, and must also bring total + * length (minus MAC) up to a multiple of the block size. + * If pkt->forcepad is set, make sure the packet is at least that size + * after padding. + */ + cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8; /* block size */ + cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ + padding = 4; + if (pkt->length + padding < pkt->forcepad) + padding = pkt->forcepad - pkt->length; + padding += + (cipherblk - (pkt->length + padding) % cipherblk) % cipherblk; + assert(padding <= 255); + maclen = ssh->csmac ? ssh->csmac->len : 0; + ssh2_pkt_ensure(pkt, pkt->length + padding + maclen); + pkt->data[4] = padding; + for (i = 0; i < padding; i++) + pkt->data[pkt->length + i] = random_byte(); + PUT_32BIT(pkt->data, pkt->length + padding - 4); + if (ssh->csmac) + ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data, + pkt->length + padding, + ssh->v2_outgoing_sequence); + ssh->v2_outgoing_sequence++; /* whether or not we MACed */ + + if (ssh->cscipher) + ssh->cscipher->encrypt(ssh->cs_cipher_ctx, + pkt->data, pkt->length + padding); + + pkt->encrypted_len = pkt->length + padding; + + /* Ready-to-send packet starts at pkt->data. We return length. */ + return pkt->length + padding + maclen; +} + +/* + * Routines called from the main SSH code to send packets. There + * are quite a few of these, because we have two separate + * mechanisms for delaying the sending of packets: + * + * - In order to send an IGNORE message and a password message in + * a single fixed-length blob, we require the ability to + * concatenate the encrypted forms of those two packets _into_ a + * single blob and then pass it to our transport + * layer in one go. Hence, there's a deferment mechanism which + * works after packet encryption. + * + * - In order to avoid sending any connection-layer messages + * during repeat key exchange, we have to queue up any such + * outgoing messages _before_ they are encrypted (and in + * particular before they're allocated sequence numbers), and + * then send them once we've finished. + * + * I call these mechanisms `defer' and `queue' respectively, so as + * to distinguish them reasonably easily. + * + * The functions send_noqueue() and defer_noqueue() free the packet + * structure they are passed. Every outgoing packet goes through + * precisely one of these functions in its life; packets passed to + * ssh2_pkt_send() or ssh2_pkt_defer() either go straight to one of + * these or get queued, and then when the queue is later emptied + * the packets are all passed to defer_noqueue(). + * + * When using a CBC-mode cipher, it's necessary to ensure that an + * attacker can't provide data to be encrypted using an IV that they + * know. We ensure this by prefixing each packet that might contain + * user data with an SSH_MSG_IGNORE. This is done using the deferral + * mechanism, so in this case send_noqueue() ends up redirecting to + * defer_noqueue(). If you don't like this inefficiency, don't use + * CBC. + */ + +static void ssh2_pkt_defer_noqueue(Ssh, struct Packet *, int); +static void ssh_pkt_defersend(Ssh); + +/* + * Send an SSH-2 packet immediately, without queuing or deferring. + */ +static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt) +{ + int len; + int backlog; + if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC)) { + /* We need to send two packets, so use the deferral mechanism. */ + ssh2_pkt_defer_noqueue(ssh, pkt, FALSE); + ssh_pkt_defersend(ssh); + return; + } + len = ssh2_pkt_construct(ssh, pkt); + backlog = s_write(ssh, pkt->data, len); + if (backlog > SSH_MAX_BACKLOG) + ssh_throttle_all(ssh, 1, backlog); + + ssh->outgoing_data_size += pkt->encrypted_len; + if (!ssh->kex_in_progress && + ssh->max_data_size != 0 && + ssh->outgoing_data_size > ssh->max_data_size) + do_ssh2_transport(ssh, "too much data sent", -1, NULL); + + ssh_free_packet(pkt); +} + +/* + * Defer an SSH-2 packet. + */ +static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore) +{ + int len; + if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC) && + ssh->deferred_len == 0 && !noignore && + !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { + /* + * Interpose an SSH_MSG_IGNORE to ensure that user data don't + * get encrypted with a known IV. + */ + struct Packet *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE); + ssh2_pkt_addstring_start(ipkt); + ssh2_pkt_defer_noqueue(ssh, ipkt, TRUE); + } + len = ssh2_pkt_construct(ssh, pkt); + if (ssh->deferred_len + len > ssh->deferred_size) { + ssh->deferred_size = ssh->deferred_len + len + 128; + ssh->deferred_send_data = sresize(ssh->deferred_send_data, + ssh->deferred_size, + unsigned char); + } + memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->data, len); + ssh->deferred_len += len; + ssh->deferred_data_size += pkt->encrypted_len; + ssh_free_packet(pkt); +} + +/* + * Queue an SSH-2 packet. + */ +static void ssh2_pkt_queue(Ssh ssh, struct Packet *pkt) +{ + assert(ssh->queueing); + + if (ssh->queuelen >= ssh->queuesize) { + ssh->queuesize = ssh->queuelen + 32; + ssh->queue = sresize(ssh->queue, ssh->queuesize, struct Packet *); + } + + ssh->queue[ssh->queuelen++] = pkt; +} + +/* + * Either queue or send a packet, depending on whether queueing is + * set. + */ +static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt) +{ + if (ssh->queueing) + ssh2_pkt_queue(ssh, pkt); + else + ssh2_pkt_send_noqueue(ssh, pkt); +} + +/* + * Either queue or defer a packet, depending on whether queueing is + * set. + */ +static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt) +{ + if (ssh->queueing) + ssh2_pkt_queue(ssh, pkt); + else + ssh2_pkt_defer_noqueue(ssh, pkt, FALSE); +} + +/* + * Send the whole deferred data block constructed by + * ssh2_pkt_defer() or SSH-1's defer_packet(). + * + * The expected use of the defer mechanism is that you call + * ssh2_pkt_defer() a few times, then call ssh_pkt_defersend(). If + * not currently queueing, this simply sets up deferred_send_data + * and then sends it. If we _are_ currently queueing, the calls to + * ssh2_pkt_defer() put the deferred packets on to the queue + * instead, and therefore ssh_pkt_defersend() has no deferred data + * to send. Hence, there's no need to make it conditional on + * ssh->queueing. + */ +static void ssh_pkt_defersend(Ssh ssh) +{ + int backlog; + backlog = s_write(ssh, ssh->deferred_send_data, ssh->deferred_len); + ssh->deferred_len = ssh->deferred_size = 0; + sfree(ssh->deferred_send_data); + ssh->deferred_send_data = NULL; + if (backlog > SSH_MAX_BACKLOG) + ssh_throttle_all(ssh, 1, backlog); + + ssh->outgoing_data_size += ssh->deferred_data_size; + if (!ssh->kex_in_progress && + ssh->max_data_size != 0 && + ssh->outgoing_data_size > ssh->max_data_size) + do_ssh2_transport(ssh, "too much data sent", -1, NULL); + ssh->deferred_data_size = 0; +} + +/* + * Send a packet whose length needs to be disguised (typically + * passwords or keyboard-interactive responses). + */ +static void ssh2_pkt_send_with_padding(Ssh ssh, struct Packet *pkt, + int padsize) +{ +#if 0 + if (0) { + /* + * The simplest way to do this is to adjust the + * variable-length padding field in the outgoing packet. + * + * Currently compiled out, because some Cisco SSH servers + * don't like excessively padded packets (bah, why's it + * always Cisco?) + */ + pkt->forcepad = padsize; + ssh2_pkt_send(ssh, pkt); + } else +#endif + { + /* + * If we can't do that, however, an alternative approach is + * to use the pkt_defer mechanism to bundle the packet + * tightly together with an SSH_MSG_IGNORE such that their + * combined length is a constant. So first we construct the + * final form of this packet and defer its sending. + */ + ssh2_pkt_defer(ssh, pkt); + + /* + * Now construct an SSH_MSG_IGNORE which includes a string + * that's an exact multiple of the cipher block size. (If + * the cipher is NULL so that the block size is + * unavailable, we don't do this trick at all, because we + * gain nothing by it.) + */ + if (ssh->cscipher && + !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { + int stringlen, i; + + stringlen = (256 - ssh->deferred_len); + stringlen += ssh->cscipher->blksize - 1; + stringlen -= (stringlen % ssh->cscipher->blksize); + if (ssh->cscomp) { + /* + * Temporarily disable actual compression, so we + * can guarantee to get this string exactly the + * length we want it. The compression-disabling + * routine should return an integer indicating how + * many bytes we should adjust our string length + * by. + */ + stringlen -= + ssh->cscomp->disable_compression(ssh->cs_comp_ctx); + } + pkt = ssh2_pkt_init(SSH2_MSG_IGNORE); + ssh2_pkt_addstring_start(pkt); + for (i = 0; i < stringlen; i++) { + char c = (char) random_byte(); + ssh2_pkt_addstring_data(pkt, &c, 1); + } + ssh2_pkt_defer(ssh, pkt); + } + ssh_pkt_defersend(ssh); + } +} + +/* + * Send all queued SSH-2 packets. We send them by means of + * ssh2_pkt_defer_noqueue(), in case they included a pair of + * packets that needed to be lumped together. + */ +static void ssh2_pkt_queuesend(Ssh ssh) +{ + int i; + + assert(!ssh->queueing); + + for (i = 0; i < ssh->queuelen; i++) + ssh2_pkt_defer_noqueue(ssh, ssh->queue[i], FALSE); + ssh->queuelen = 0; + + ssh_pkt_defersend(ssh); +} + +#if 0 +void bndebug(char *string, Bignum b) +{ + unsigned char *p; + int i, len; + p = ssh2_mpint_fmt(b, &len); + debug(("%s", string)); + for (i = 0; i < len; i++) + debug((" %02x", p[i])); + debug(("\n")); + sfree(p); +} +#endif + +static void hash_mpint(const struct ssh_hash *h, void *s, Bignum b) +{ + unsigned char *p; + int len; + p = ssh2_mpint_fmt(b, &len); + hash_string(h, s, p, len); + sfree(p); +} + +/* + * Packet decode functions for both SSH-1 and SSH-2. + */ +static unsigned long ssh_pkt_getuint32(struct Packet *pkt) +{ + unsigned long value; + if (pkt->length - pkt->savedpos < 4) + return 0; /* arrgh, no way to decline (FIXME?) */ + value = GET_32BIT(pkt->body + pkt->savedpos); + pkt->savedpos += 4; + return value; +} +static int ssh2_pkt_getbool(struct Packet *pkt) +{ + unsigned long value; + if (pkt->length - pkt->savedpos < 1) + return 0; /* arrgh, no way to decline (FIXME?) */ + value = pkt->body[pkt->savedpos] != 0; + pkt->savedpos++; + return value; +} +static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length) +{ + int len; + *p = NULL; + *length = 0; + if (pkt->length - pkt->savedpos < 4) + return; + len = GET_32BIT(pkt->body + pkt->savedpos); + if (len < 0) + return; + *length = len; + pkt->savedpos += 4; + if (pkt->length - pkt->savedpos < *length) + return; + *p = (char *)(pkt->body + pkt->savedpos); + pkt->savedpos += *length; +} +static void *ssh_pkt_getdata(struct Packet *pkt, int length) +{ + if (pkt->length - pkt->savedpos < length) + return NULL; + pkt->savedpos += length; + return pkt->body + (pkt->savedpos - length); +} +static int ssh1_pkt_getrsakey(struct Packet *pkt, struct RSAKey *key, + unsigned char **keystr) +{ + int j; + + j = makekey(pkt->body + pkt->savedpos, + pkt->length - pkt->savedpos, + key, keystr, 0); + + if (j < 0) + return FALSE; + + pkt->savedpos += j; + assert(pkt->savedpos < pkt->length); + + return TRUE; +} +static Bignum ssh1_pkt_getmp(struct Packet *pkt) +{ + int j; + Bignum b; + + j = ssh1_read_bignum(pkt->body + pkt->savedpos, + pkt->length - pkt->savedpos, &b); + + if (j < 0) + return NULL; + + pkt->savedpos += j; + return b; +} +static Bignum ssh2_pkt_getmp(struct Packet *pkt) +{ + char *p; + int length; + Bignum b; + + ssh_pkt_getstring(pkt, &p, &length); + if (!p) + return NULL; + if (p[0] & 0x80) + return NULL; + b = bignum_from_bytes((unsigned char *)p, length); + return b; +} + +/* + * Helper function to add an SSH-2 signature blob to a packet. + * Expects to be shown the public key blob as well as the signature + * blob. Normally works just like ssh2_pkt_addstring, but will + * fiddle with the signature packet if necessary for + * BUG_SSH2_RSA_PADDING. + */ +static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt, + void *pkblob_v, int pkblob_len, + void *sigblob_v, int sigblob_len) +{ + unsigned char *pkblob = (unsigned char *)pkblob_v; + unsigned char *sigblob = (unsigned char *)sigblob_v; + + /* dmemdump(pkblob, pkblob_len); */ + /* dmemdump(sigblob, sigblob_len); */ + + /* + * See if this is in fact an ssh-rsa signature and a buggy + * server; otherwise we can just do this the easy way. + */ + if ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) && + (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) { + int pos, len, siglen; + + /* + * Find the byte length of the modulus. + */ + + pos = 4+7; /* skip over "ssh-rsa" */ + pos += 4 + GET_32BIT(pkblob+pos); /* skip over exponent */ + len = GET_32BIT(pkblob+pos); /* find length of modulus */ + pos += 4; /* find modulus itself */ + while (len > 0 && pkblob[pos] == 0) + len--, pos++; + /* debug(("modulus length is %d\n", len)); */ + + /* + * Now find the signature integer. + */ + pos = 4+7; /* skip over "ssh-rsa" */ + siglen = GET_32BIT(sigblob+pos); + /* debug(("signature length is %d\n", siglen)); */ + + if (len != siglen) { + unsigned char newlen[4]; + ssh2_pkt_addstring_start(pkt); + ssh2_pkt_addstring_data(pkt, (char *)sigblob, pos); + /* dmemdump(sigblob, pos); */ + pos += 4; /* point to start of actual sig */ + PUT_32BIT(newlen, len); + ssh2_pkt_addstring_data(pkt, (char *)newlen, 4); + /* dmemdump(newlen, 4); */ + newlen[0] = 0; + while (len-- > siglen) { + ssh2_pkt_addstring_data(pkt, (char *)newlen, 1); + /* dmemdump(newlen, 1); */ + } + ssh2_pkt_addstring_data(pkt, (char *)(sigblob+pos), siglen); + /* dmemdump(sigblob+pos, siglen); */ + return; + } + + /* Otherwise fall through and do it the easy way. */ + } + + ssh2_pkt_addstring_start(pkt); + ssh2_pkt_addstring_data(pkt, (char *)sigblob, sigblob_len); +} + +/* + * Examine the remote side's version string and compare it against + * a list of known buggy implementations. + */ +static void ssh_detect_bugs(Ssh ssh, char *vstring) +{ + char *imp; /* pointer to implementation part */ + imp = vstring; + imp += strcspn(imp, "-"); + if (*imp) imp++; + imp += strcspn(imp, "-"); + if (*imp) imp++; + + ssh->remote_bugs = 0; + + /* + * General notes on server version strings: + * - Not all servers reporting "Cisco-1.25" have all the bugs listed + * here -- in particular, we've heard of one that's perfectly happy + * with SSH1_MSG_IGNOREs -- but this string never seems to change, + * so we can't distinguish them. + */ + if (ssh->cfg.sshbug_ignore1 == FORCE_ON || + (ssh->cfg.sshbug_ignore1 == AUTO && + (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") || + !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") || + !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") || + !strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) { + /* + * These versions don't support SSH1_MSG_IGNORE, so we have + * to use a different defence against password length + * sniffing. + */ + ssh->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE; + logevent("We believe remote version has SSH-1 ignore bug"); + } + + if (ssh->cfg.sshbug_plainpw1 == FORCE_ON || + (ssh->cfg.sshbug_plainpw1 == AUTO && + (!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) { + /* + * These versions need a plain password sent; they can't + * handle having a null and a random length of data after + * the password. + */ + ssh->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD; + logevent("We believe remote version needs a plain SSH-1 password"); + } + + if (ssh->cfg.sshbug_rsa1 == FORCE_ON || + (ssh->cfg.sshbug_rsa1 == AUTO && + (!strcmp(imp, "Cisco-1.25")))) { + /* + * These versions apparently have no clue whatever about + * RSA authentication and will panic and die if they see + * an AUTH_RSA message. + */ + ssh->remote_bugs |= BUG_CHOKES_ON_RSA; + logevent("We believe remote version can't handle SSH-1 RSA authentication"); + } + + if (ssh->cfg.sshbug_hmac2 == FORCE_ON || + (ssh->cfg.sshbug_hmac2 == AUTO && + !wc_match("* VShell", imp) && + (wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) || + wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) || + wc_match("2.1 *", imp)))) { + /* + * These versions have the HMAC bug. + */ + ssh->remote_bugs |= BUG_SSH2_HMAC; + logevent("We believe remote version has SSH-2 HMAC bug"); + } + + if (ssh->cfg.sshbug_derivekey2 == FORCE_ON || + (ssh->cfg.sshbug_derivekey2 == AUTO && + !wc_match("* VShell", imp) && + (wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) { + /* + * These versions have the key-derivation bug (failing to + * include the literal shared secret in the hashes that + * generate the keys). + */ + ssh->remote_bugs |= BUG_SSH2_DERIVEKEY; + logevent("We believe remote version has SSH-2 key-derivation bug"); + } + + if (ssh->cfg.sshbug_rsapad2 == FORCE_ON || + (ssh->cfg.sshbug_rsapad2 == AUTO && + (wc_match("OpenSSH_2.[5-9]*", imp) || + wc_match("OpenSSH_3.[0-2]*", imp)))) { + /* + * These versions have the SSH-2 RSA padding bug. + */ + ssh->remote_bugs |= BUG_SSH2_RSA_PADDING; + logevent("We believe remote version has SSH-2 RSA padding bug"); + } + + if (ssh->cfg.sshbug_pksessid2 == FORCE_ON || + (ssh->cfg.sshbug_pksessid2 == AUTO && + wc_match("OpenSSH_2.[0-2]*", imp))) { + /* + * These versions have the SSH-2 session-ID bug in + * public-key authentication. + */ + ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID; + logevent("We believe remote version has SSH-2 public-key-session-ID bug"); + } + + if (ssh->cfg.sshbug_rekey2 == FORCE_ON || + (ssh->cfg.sshbug_rekey2 == AUTO && + (wc_match("DigiSSH_2.0", imp) || + wc_match("OpenSSH_2.[0-4]*", imp) || + wc_match("OpenSSH_2.5.[0-3]*", imp) || + wc_match("Sun_SSH_1.0", imp) || + wc_match("Sun_SSH_1.0.1", imp) || + /* All versions <= 1.2.6 (they changed their format in 1.2.7) */ + wc_match("WeOnlyDo-*", imp)))) { + /* + * These versions have the SSH-2 rekey bug. + */ + ssh->remote_bugs |= BUG_SSH2_REKEY; + logevent("We believe remote version has SSH-2 rekey bug"); + } + + if (ssh->cfg.sshbug_maxpkt2 == FORCE_ON || + (ssh->cfg.sshbug_maxpkt2 == AUTO && + (wc_match("1.36_sshlib GlobalSCAPE", imp) || + wc_match("1.36 sshlib: GlobalScape", imp)))) { + /* + * This version ignores our makpkt and needs to be throttled. + */ + ssh->remote_bugs |= BUG_SSH2_MAXPKT; + logevent("We believe remote version ignores SSH-2 maximum packet size"); + } + + if (ssh->cfg.sshbug_ignore2 == FORCE_ON) { + /* + * Servers that don't support SSH2_MSG_IGNORE. Currently, + * none detected automatically. + */ + ssh->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE; + logevent("We believe remote version has SSH-2 ignore bug"); + } +} + +/* + * The `software version' part of an SSH version string is required + * to contain no spaces or minus signs. + */ +static void ssh_fix_verstring(char *str) +{ + /* Eat "SSH--". */ + assert(*str == 'S'); str++; + assert(*str == 'S'); str++; + assert(*str == 'H'); str++; + assert(*str == '-'); str++; + while (*str && *str != '-') str++; + assert(*str == '-'); str++; + + /* Convert minus signs and spaces in the remaining string into + * underscores. */ + while (*str) { + if (*str == '-' || *str == ' ') + *str = '_'; + str++; + } +} + +/* + * Send an appropriate SSH version string. + */ +static void ssh_send_verstring(Ssh ssh, char *svers) +{ + char *verstring; + + if (ssh->version == 2) { + /* + * Construct a v2 version string. + */ + verstring = dupprintf("SSH-2.0-%s\015\012", sshver); + } else { + /* + * Construct a v1 version string. + */ + verstring = dupprintf("SSH-%s-%s\012", + (ssh_versioncmp(svers, "1.5") <= 0 ? + svers : "1.5"), + sshver); + } + + ssh_fix_verstring(verstring); + + if (ssh->version == 2) { + size_t len; + /* + * Record our version string. + */ + len = strcspn(verstring, "\015\012"); + ssh->v_c = snewn(len + 1, char); + memcpy(ssh->v_c, verstring, len); + ssh->v_c[len] = 0; + } + + logeventf(ssh, "We claim version: %.*s", + strcspn(verstring, "\015\012"), verstring); + s_write(ssh, verstring, strlen(verstring)); + sfree(verstring); +} + +static int do_ssh_init(Ssh ssh, unsigned char c) +{ + struct do_ssh_init_state { + int vslen; + char version[10]; + char *vstring; + int vstrsize; + int i; + int proto1, proto2; + }; + crState(do_ssh_init_state); + + crBegin(ssh->do_ssh_init_crstate); + + /* Search for a line beginning with the string "SSH-" in the input. */ + for (;;) { + if (c != 'S') goto no; + crReturn(1); + if (c != 'S') goto no; + crReturn(1); + if (c != 'H') goto no; + crReturn(1); + if (c != '-') goto no; + break; + no: + while (c != '\012') + crReturn(1); + crReturn(1); + } + + s->vstrsize = 16; + s->vstring = snewn(s->vstrsize, char); + strcpy(s->vstring, "SSH-"); + s->vslen = 4; + s->i = 0; + while (1) { + crReturn(1); /* get another char */ + if (s->vslen >= s->vstrsize - 1) { + s->vstrsize += 16; + s->vstring = sresize(s->vstring, s->vstrsize, char); + } + s->vstring[s->vslen++] = c; + if (s->i >= 0) { + if (c == '-') { + s->version[s->i] = '\0'; + s->i = -1; + } else if (s->i < sizeof(s->version) - 1) + s->version[s->i++] = c; + } else if (c == '\012') + break; + } + + ssh->agentfwd_enabled = FALSE; + ssh->rdpkt2_state.incoming_sequence = 0; + + s->vstring[s->vslen] = 0; + s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */ + logeventf(ssh, "Server version: %s", s->vstring); + ssh_detect_bugs(ssh, s->vstring); + + /* + * Decide which SSH protocol version to support. + */ + + /* Anything strictly below "2.0" means protocol 1 is supported. */ + s->proto1 = ssh_versioncmp(s->version, "2.0") < 0; + /* Anything greater or equal to "1.99" means protocol 2 is supported. */ + s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0; + + if (ssh->cfg.sshprot == 0 && !s->proto1) { + bombout(("SSH protocol version 1 required by user but not provided by server")); + crStop(0); + } + if (ssh->cfg.sshprot == 3 && !s->proto2) { + bombout(("SSH protocol version 2 required by user but not provided by server")); + crStop(0); + } + + if (s->proto2 && (ssh->cfg.sshprot >= 2 || !s->proto1)) + ssh->version = 2; + else + ssh->version = 1; + + logeventf(ssh, "Using SSH protocol version %d", ssh->version); + + /* Send the version string, if we haven't already */ + if (ssh->cfg.sshprot != 3) + ssh_send_verstring(ssh, s->version); + + if (ssh->version == 2) { + size_t len; + /* + * Record their version string. + */ + len = strcspn(s->vstring, "\015\012"); + ssh->v_s = snewn(len + 1, char); + memcpy(ssh->v_s, s->vstring, len); + ssh->v_s[len] = 0; + + /* + * Initialise SSH-2 protocol. + */ + ssh->protocol = ssh2_protocol; + ssh2_protocol_setup(ssh); + ssh->s_rdpkt = ssh2_rdpkt; + } else { + /* + * Initialise SSH-1 protocol. + */ + ssh->protocol = ssh1_protocol; + ssh1_protocol_setup(ssh); + ssh->s_rdpkt = ssh1_rdpkt; + } + if (ssh->version == 2) + do_ssh2_transport(ssh, NULL, -1, NULL); + + update_specials_menu(ssh->frontend); + ssh->state = SSH_STATE_BEFORE_SIZE; + ssh->pinger = pinger_new(&ssh->cfg, &ssh_backend, ssh); + + sfree(s->vstring); + + crFinish(0); +} + +static void ssh_process_incoming_data(Ssh ssh, + unsigned char **data, int *datalen) +{ + struct Packet *pktin; + + pktin = ssh->s_rdpkt(ssh, data, datalen); + if (pktin) { + ssh->protocol(ssh, NULL, 0, pktin); + ssh_free_packet(pktin); + } +} + +static void ssh_queue_incoming_data(Ssh ssh, + unsigned char **data, int *datalen) +{ + bufchain_add(&ssh->queued_incoming_data, *data, *datalen); + *data += *datalen; + *datalen = 0; +} + +static void ssh_process_queued_incoming_data(Ssh ssh) +{ + void *vdata; + unsigned char *data; + int len, origlen; + + while (!ssh->frozen && bufchain_size(&ssh->queued_incoming_data)) { + bufchain_prefix(&ssh->queued_incoming_data, &vdata, &len); + data = vdata; + origlen = len; + + while (!ssh->frozen && len > 0) + ssh_process_incoming_data(ssh, &data, &len); + + if (origlen > len) + bufchain_consume(&ssh->queued_incoming_data, origlen - len); + } +} + +static void ssh_set_frozen(Ssh ssh, int frozen) +{ + if (ssh->s) + sk_set_frozen(ssh->s, frozen); + ssh->frozen = frozen; +} + +static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen) +{ + /* Log raw data, if we're in that mode. */ + if (ssh->logctx) + log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, datalen, + 0, NULL, NULL); + + crBegin(ssh->ssh_gotdata_crstate); + + /* + * To begin with, feed the characters one by one to the + * protocol initialisation / selection function do_ssh_init(). + * When that returns 0, we're done with the initial greeting + * exchange and can move on to packet discipline. + */ + while (1) { + int ret; /* need not be kept across crReturn */ + if (datalen == 0) + crReturnV; /* more data please */ + ret = do_ssh_init(ssh, *data); + data++; + datalen--; + if (ret == 0) + break; + } + + /* + * We emerge from that loop when the initial negotiation is + * over and we have selected an s_rdpkt function. Now pass + * everything to s_rdpkt, and then pass the resulting packets + * to the proper protocol handler. + */ + + while (1) { + while (bufchain_size(&ssh->queued_incoming_data) > 0 || datalen > 0) { + if (ssh->frozen) { + ssh_queue_incoming_data(ssh, &data, &datalen); + /* This uses up all data and cannot cause anything interesting + * to happen; indeed, for anything to happen at all, we must + * return, so break out. */ + break; + } else if (bufchain_size(&ssh->queued_incoming_data) > 0) { + /* This uses up some or all data, and may freeze the + * session. */ + ssh_process_queued_incoming_data(ssh); + } else { + /* This uses up some or all data, and may freeze the + * session. */ + ssh_process_incoming_data(ssh, &data, &datalen); + } + /* FIXME this is probably EBW. */ + if (ssh->state == SSH_STATE_CLOSED) + return; + } + /* We're out of data. Go and get some more. */ + crReturnV; + } + crFinishV; +} + +static int ssh_do_close(Ssh ssh, int notify_exit) +{ + int ret = 0; + struct ssh_channel *c; + + ssh->state = SSH_STATE_CLOSED; + expire_timer_context(ssh); + if (ssh->s) { + sk_close(ssh->s); + ssh->s = NULL; + if (notify_exit) + notify_remote_exit(ssh->frontend); + else + ret = 1; + } + /* + * Now we must shut down any port- and X-forwarded channels going + * through this connection. + */ + if (ssh->channels) { + while (NULL != (c = index234(ssh->channels, 0))) { + switch (c->type) { + case CHAN_X11: + x11_close(c->u.x11.s); + break; + case CHAN_SOCKDATA: + case CHAN_SOCKDATA_DORMANT: + pfd_close(c->u.pfd.s); + break; + } + del234(ssh->channels, c); /* moving next one to index 0 */ + if (ssh->version == 2) + bufchain_clear(&c->v.v2.outbuffer); + sfree(c); + } + } + /* + * Go through port-forwardings, and close any associated + * listening sockets. + */ + if (ssh->portfwds) { + struct ssh_portfwd *pf; + while (NULL != (pf = index234(ssh->portfwds, 0))) { + /* Dispose of any listening socket. */ + if (pf->local) + pfd_terminate(pf->local); + del234(ssh->portfwds, pf); /* moving next one to index 0 */ + free_portfwd(pf); + } + freetree234(ssh->portfwds); + ssh->portfwds = NULL; + } + + return ret; +} + +static void ssh_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + Ssh ssh = (Ssh) plug; + char addrbuf[256], *msg; + + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + + if (type == 0) + msg = dupprintf("Connecting to %s port %d", addrbuf, port); + else + msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); + + logevent(msg); + sfree(msg); +} + +static int ssh_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + Ssh ssh = (Ssh) plug; + int need_notify = ssh_do_close(ssh, FALSE); + + if (!error_msg) { + if (!ssh->close_expected) + error_msg = "Server unexpectedly closed network connection"; + else + error_msg = "Server closed network connection"; + } + + if (ssh->close_expected && ssh->clean_exit && ssh->exitcode < 0) + ssh->exitcode = 0; + + if (need_notify) + notify_remote_exit(ssh->frontend); + + if (error_msg) + logevent(error_msg); + if (!ssh->close_expected || !ssh->clean_exit) + connection_fatal(ssh->frontend, "%s", error_msg); + return 0; +} + +static int ssh_receive(Plug plug, int urgent, char *data, int len) +{ + Ssh ssh = (Ssh) plug; + ssh_gotdata(ssh, (unsigned char *)data, len); + if (ssh->state == SSH_STATE_CLOSED) { + ssh_do_close(ssh, TRUE); + return 0; + } + return 1; +} + +static void ssh_sent(Plug plug, int bufsize) +{ + Ssh ssh = (Ssh) plug; + /* + * If the send backlog on the SSH socket itself clears, we + * should unthrottle the whole world if it was throttled. + */ + if (bufsize < SSH_MAX_BACKLOG) + ssh_throttle_all(ssh, 0, bufsize); +} + +/* + * Connect to specified host and port. + * Returns an error message, or NULL on success. + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *connect_to_host(Ssh ssh, char *host, int port, + char **realhost, int nodelay, int keepalive) +{ + static const struct plug_function_table fn_table = { + ssh_log, + ssh_closing, + ssh_receive, + ssh_sent, + NULL + }; + + SockAddr addr; + const char *err; + + if (*ssh->cfg.loghost) { + char *colon; + + ssh->savedhost = dupstr(ssh->cfg.loghost); + ssh->savedport = 22; /* default ssh port */ + + /* + * A colon suffix on savedhost also lets us affect + * savedport. + * + * (FIXME: do something about IPv6 address literals here.) + */ + colon = strrchr(ssh->savedhost, ':'); + if (colon) { + *colon++ = '\0'; + if (*colon) + ssh->savedport = atoi(colon); + } + } else { + ssh->savedhost = dupstr(host); + if (port < 0) + port = 22; /* default ssh port */ + ssh->savedport = port; + } + + /* + * Try to find host. + */ + logeventf(ssh, "Looking up host \"%s\"%s", host, + (ssh->cfg.addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (ssh->cfg.addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : ""))); + addr = name_lookup(host, port, realhost, &ssh->cfg, + ssh->cfg.addressfamily); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */ + + /* + * Open socket. + */ + ssh->fn = &fn_table; + ssh->s = new_connection(addr, *realhost, port, + 0, 1, nodelay, keepalive, (Plug) ssh, &ssh->cfg); + if ((err = sk_socket_error(ssh->s)) != NULL) { + ssh->s = NULL; + notify_remote_exit(ssh->frontend); + return err; + } + + /* + * If the SSH version number's fixed, set it now, and if it's SSH-2, + * send the version string too. + */ + if (ssh->cfg.sshprot == 0) + ssh->version = 1; + if (ssh->cfg.sshprot == 3) { + ssh->version = 2; + ssh_send_verstring(ssh, NULL); + } + + /* + * loghost, if configured, overrides realhost. + */ + if (*ssh->cfg.loghost) { + sfree(*realhost); + *realhost = dupstr(ssh->cfg.loghost); + } + + return NULL; +} + +/* + * Throttle or unthrottle the SSH connection. + */ +static void ssh_throttle_conn(Ssh ssh, int adjust) +{ + int old_count = ssh->conn_throttle_count; + ssh->conn_throttle_count += adjust; + assert(ssh->conn_throttle_count >= 0); + if (ssh->conn_throttle_count && !old_count) { + ssh_set_frozen(ssh, 1); + } else if (!ssh->conn_throttle_count && old_count) { + ssh_set_frozen(ssh, 0); + } +} + +/* + * Throttle or unthrottle _all_ local data streams (for when sends + * on the SSH connection itself back up). + */ +static void ssh_throttle_all(Ssh ssh, int enable, int bufsize) +{ + int i; + struct ssh_channel *c; + + if (enable == ssh->throttled_all) + return; + ssh->throttled_all = enable; + ssh->overall_bufsize = bufsize; + if (!ssh->channels) + return; + for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) { + switch (c->type) { + case CHAN_MAINSESSION: + /* + * This is treated separately, outside the switch. + */ + break; + case CHAN_X11: + x11_override_throttle(c->u.x11.s, enable); + break; + case CHAN_AGENT: + /* Agent channels require no buffer management. */ + break; + case CHAN_SOCKDATA: + pfd_override_throttle(c->u.pfd.s, enable); + break; + } + } +} + +static void ssh_agent_callback(void *sshv, void *reply, int replylen) +{ + Ssh ssh = (Ssh) sshv; + + ssh->agent_response = reply; + ssh->agent_response_len = replylen; + + if (ssh->version == 1) + do_ssh1_login(ssh, NULL, -1, NULL); + else + do_ssh2_authconn(ssh, NULL, -1, NULL); +} + +static void ssh_dialog_callback(void *sshv, int ret) +{ + Ssh ssh = (Ssh) sshv; + + ssh->user_response = ret; + + if (ssh->version == 1) + do_ssh1_login(ssh, NULL, -1, NULL); + else + do_ssh2_transport(ssh, NULL, -1, NULL); + + /* + * This may have unfrozen the SSH connection, so do a + * queued-data run. + */ + ssh_process_queued_incoming_data(ssh); +} + +static void ssh_agentf_callback(void *cv, void *reply, int replylen) +{ + struct ssh_channel *c = (struct ssh_channel *)cv; + Ssh ssh = c->ssh; + void *sentreply = reply; + + if (!sentreply) { + /* Fake SSH_AGENT_FAILURE. */ + sentreply = "\0\0\0\1\5"; + replylen = 5; + } + if (ssh->version == 2) { + ssh2_add_channel_data(c, sentreply, replylen); + ssh2_try_send(c); + } else { + send_packet(ssh, SSH1_MSG_CHANNEL_DATA, + PKT_INT, c->remoteid, + PKT_INT, replylen, + PKTT_DATA, + PKT_DATA, sentreply, replylen, + PKTT_OTHER, + PKT_END); + } + if (reply) + sfree(reply); +} + +/* + * Client-initiated disconnection. Send a DISCONNECT if `wire_reason' + * non-NULL, otherwise just close the connection. `client_reason' == NULL + * => log `wire_reason'. + */ +static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason, + int code, int clean_exit) +{ + char *error; + if (!client_reason) + client_reason = wire_reason; + if (client_reason) + error = dupprintf("Disconnected: %s", client_reason); + else + error = dupstr("Disconnected"); + if (wire_reason) { + if (ssh->version == 1) { + send_packet(ssh, SSH1_MSG_DISCONNECT, PKT_STR, wire_reason, + PKT_END); + } else if (ssh->version == 2) { + struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT); + ssh2_pkt_adduint32(pktout, code); + ssh2_pkt_addstring(pktout, wire_reason); + ssh2_pkt_addstring(pktout, "en"); /* language tag */ + ssh2_pkt_send_noqueue(ssh, pktout); + } + } + ssh->close_expected = TRUE; + ssh->clean_exit = clean_exit; + ssh_closing((Plug)ssh, error, 0, 0); + sfree(error); +} + +/* + * Handle the key exchange and user authentication phases. + */ +static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin) +{ + int i, j, ret; + unsigned char cookie[8], *ptr; + struct RSAKey servkey, hostkey; + struct MD5Context md5c; + struct do_ssh1_login_state { + int len; + unsigned char *rsabuf, *keystr1, *keystr2; + unsigned long supported_ciphers_mask, supported_auths_mask; + int tried_publickey, tried_agent; + int tis_auth_refused, ccard_auth_refused; + unsigned char session_id[16]; + int cipher_type; + char username[100]; + void *publickey_blob; + int publickey_bloblen; + char *publickey_comment; + int publickey_encrypted; + prompts_t *cur_prompt; + char c; + int pwpkt_type; + unsigned char request[5], *response, *p; + int responselen; + int keyi, nkeys; + int authed; + struct RSAKey key; + Bignum challenge; + char *commentp; + int commentlen; + int dlgret; + }; + crState(do_ssh1_login_state); + + crBegin(ssh->do_ssh1_login_crstate); + + if (!pktin) + crWaitUntil(pktin); + + if (pktin->type != SSH1_SMSG_PUBLIC_KEY) { + bombout(("Public key packet not received")); + crStop(0); + } + + logevent("Received public keys"); + + ptr = ssh_pkt_getdata(pktin, 8); + if (!ptr) { + bombout(("SSH-1 public key packet stopped before random cookie")); + crStop(0); + } + memcpy(cookie, ptr, 8); + + if (!ssh1_pkt_getrsakey(pktin, &servkey, &s->keystr1) || + !ssh1_pkt_getrsakey(pktin, &hostkey, &s->keystr2)) { + bombout(("Failed to read SSH-1 public keys from public key packet")); + crStop(0); + } + + /* + * Log the host key fingerprint. + */ + { + char logmsg[80]; + logevent("Host key fingerprint is:"); + strcpy(logmsg, " "); + hostkey.comment = NULL; + rsa_fingerprint(logmsg + strlen(logmsg), + sizeof(logmsg) - strlen(logmsg), &hostkey); + logevent(logmsg); + } + + ssh->v1_remote_protoflags = ssh_pkt_getuint32(pktin); + s->supported_ciphers_mask = ssh_pkt_getuint32(pktin); + s->supported_auths_mask = ssh_pkt_getuint32(pktin); + if ((ssh->remote_bugs & BUG_CHOKES_ON_RSA)) + s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA); + + ssh->v1_local_protoflags = + ssh->v1_remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED; + ssh->v1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER; + + MD5Init(&md5c); + MD5Update(&md5c, s->keystr2, hostkey.bytes); + MD5Update(&md5c, s->keystr1, servkey.bytes); + MD5Update(&md5c, cookie, 8); + MD5Final(s->session_id, &md5c); + + for (i = 0; i < 32; i++) + ssh->session_key[i] = random_byte(); + + /* + * Verify that the `bits' and `bytes' parameters match. + */ + if (hostkey.bits > hostkey.bytes * 8 || + servkey.bits > servkey.bytes * 8) { + bombout(("SSH-1 public keys were badly formatted")); + crStop(0); + } + + s->len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes); + + s->rsabuf = snewn(s->len, unsigned char); + + /* + * Verify the host key. + */ + { + /* + * First format the key into a string. + */ + int len = rsastr_len(&hostkey); + char fingerprint[100]; + char *keystr = snewn(len, char); + rsastr_fmt(keystr, &hostkey); + rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey); + + ssh_set_frozen(ssh, 1); + s->dlgret = verify_ssh_host_key(ssh->frontend, + ssh->savedhost, ssh->savedport, + "rsa", keystr, fingerprint, + ssh_dialog_callback, ssh); + sfree(keystr); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user host key response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at host key verification", + NULL, 0, TRUE); + crStop(0); + } + } + + for (i = 0; i < 32; i++) { + s->rsabuf[i] = ssh->session_key[i]; + if (i < 16) + s->rsabuf[i] ^= s->session_id[i]; + } + + if (hostkey.bytes > servkey.bytes) { + ret = rsaencrypt(s->rsabuf, 32, &servkey); + if (ret) + ret = rsaencrypt(s->rsabuf, servkey.bytes, &hostkey); + } else { + ret = rsaencrypt(s->rsabuf, 32, &hostkey); + if (ret) + ret = rsaencrypt(s->rsabuf, hostkey.bytes, &servkey); + } + if (!ret) { + bombout(("SSH-1 public key encryptions failed due to bad formatting")); + crStop(0); + } + + logevent("Encrypted session key"); + + { + int cipher_chosen = 0, warn = 0; + char *cipher_string = NULL; + int i; + for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { + int next_cipher = ssh->cfg.ssh_cipherlist[i]; + if (next_cipher == CIPHER_WARN) { + /* If/when we choose a cipher, warn about it */ + warn = 1; + } else if (next_cipher == CIPHER_AES) { + /* XXX Probably don't need to mention this. */ + logevent("AES not supported in SSH-1, skipping"); + } else { + switch (next_cipher) { + case CIPHER_3DES: s->cipher_type = SSH_CIPHER_3DES; + cipher_string = "3DES"; break; + case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH; + cipher_string = "Blowfish"; break; + case CIPHER_DES: s->cipher_type = SSH_CIPHER_DES; + cipher_string = "single-DES"; break; + } + if (s->supported_ciphers_mask & (1 << s->cipher_type)) + cipher_chosen = 1; + } + } + if (!cipher_chosen) { + if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0) + bombout(("Server violates SSH-1 protocol by not " + "supporting 3DES encryption")); + else + /* shouldn't happen */ + bombout(("No supported ciphers found")); + crStop(0); + } + + /* Warn about chosen cipher if necessary. */ + if (warn) { + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, "cipher", cipher_string, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at cipher warning", NULL, + 0, TRUE); + crStop(0); + } + } + } + + switch (s->cipher_type) { + case SSH_CIPHER_3DES: + logevent("Using 3DES encryption"); + break; + case SSH_CIPHER_DES: + logevent("Using single-DES encryption"); + break; + case SSH_CIPHER_BLOWFISH: + logevent("Using Blowfish encryption"); + break; + } + + send_packet(ssh, SSH1_CMSG_SESSION_KEY, + PKT_CHAR, s->cipher_type, + PKT_DATA, cookie, 8, + PKT_CHAR, (s->len * 8) >> 8, PKT_CHAR, (s->len * 8) & 0xFF, + PKT_DATA, s->rsabuf, s->len, + PKT_INT, ssh->v1_local_protoflags, PKT_END); + + logevent("Trying to enable encryption..."); + + sfree(s->rsabuf); + + ssh->cipher = (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : + s->cipher_type == SSH_CIPHER_DES ? &ssh_des : + &ssh_3des); + ssh->v1_cipher_ctx = ssh->cipher->make_context(); + ssh->cipher->sesskey(ssh->v1_cipher_ctx, ssh->session_key); + logeventf(ssh, "Initialised %s encryption", ssh->cipher->text_name); + + ssh->crcda_ctx = crcda_make_context(); + logevent("Installing CRC compensation attack detector"); + + if (servkey.modulus) { + sfree(servkey.modulus); + servkey.modulus = NULL; + } + if (servkey.exponent) { + sfree(servkey.exponent); + servkey.exponent = NULL; + } + if (hostkey.modulus) { + sfree(hostkey.modulus); + hostkey.modulus = NULL; + } + if (hostkey.exponent) { + sfree(hostkey.exponent); + hostkey.exponent = NULL; + } + crWaitUntil(pktin); + + if (pktin->type != SSH1_SMSG_SUCCESS) { + bombout(("Encryption not successfully enabled")); + crStop(0); + } + + logevent("Successfully started encryption"); + + fflush(stdout); /* FIXME eh? */ + { + if (!get_remote_username(&ssh->cfg, s->username, + sizeof(s->username))) { + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH login name"); + add_prompt(s->cur_prompt, dupstr("login as: "), TRUE, + lenof(s->username)); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntil(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get a username. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); + crStop(0); + } + memcpy(s->username, s->cur_prompt->prompts[0]->result, + lenof(s->username)); + free_prompts(s->cur_prompt); + } + + send_packet(ssh, SSH1_CMSG_USER, PKT_STR, s->username, PKT_END); + { + char *userlog = dupprintf("Sent username \"%s\"", s->username); + logevent(userlog); + if (flags & FLAG_INTERACTIVE && + (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) { + c_write_str(ssh, userlog); + c_write_str(ssh, "\r\n"); + } + sfree(userlog); + } + } + + crWaitUntil(pktin); + + if ((s->supported_auths_mask & (1 << SSH1_AUTH_RSA)) == 0) { + /* We must not attempt PK auth. Pretend we've already tried it. */ + s->tried_publickey = s->tried_agent = 1; + } else { + s->tried_publickey = s->tried_agent = 0; + } + s->tis_auth_refused = s->ccard_auth_refused = 0; + /* + * Load the public half of any configured keyfile for later use. + */ + if (!filename_is_null(ssh->cfg.keyfile)) { + int keytype; + logeventf(ssh, "Reading private key file \"%.150s\"", + filename_to_str(&ssh->cfg.keyfile)); + keytype = key_type(&ssh->cfg.keyfile); + if (keytype == SSH_KEYTYPE_SSH1) { + const char *error; + if (rsakey_pubblob(&ssh->cfg.keyfile, + &s->publickey_blob, &s->publickey_bloblen, + &s->publickey_comment, &error)) { + s->publickey_encrypted = rsakey_encrypted(&ssh->cfg.keyfile, + NULL); + } else { + char *msgbuf; + logeventf(ssh, "Unable to load private key (%s)", error); + msgbuf = dupprintf("Unable to load private key file " + "\"%.150s\" (%s)\r\n", + filename_to_str(&ssh->cfg.keyfile), + error); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + s->publickey_blob = NULL; + } + } else { + char *msgbuf; + logeventf(ssh, "Unable to use this key file (%s)", + key_type_to_str(keytype)); + msgbuf = dupprintf("Unable to use key file \"%.150s\"" + " (%s)\r\n", + filename_to_str(&ssh->cfg.keyfile), + key_type_to_str(keytype)); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + s->publickey_blob = NULL; + } + } else + s->publickey_blob = NULL; + + while (pktin->type == SSH1_SMSG_FAILURE) { + s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; + + if (ssh->cfg.tryagent && agent_exists() && !s->tried_agent) { + /* + * Attempt RSA authentication using Pageant. + */ + void *r; + + s->authed = FALSE; + s->tried_agent = 1; + logevent("Pageant is running. Requesting keys."); + + /* Request the keys held by the agent. */ + PUT_32BIT(s->request, 1); + s->request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES; + if (!agent_query(s->request, 5, &r, &s->responselen, + ssh_agent_callback, ssh)) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for agent response")); + crStop(0); + } + } while (pktin || inlen > 0); + r = ssh->agent_response; + s->responselen = ssh->agent_response_len; + } + s->response = (unsigned char *) r; + if (s->response && s->responselen >= 5 && + s->response[4] == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { + s->p = s->response + 5; + s->nkeys = GET_32BIT(s->p); + s->p += 4; + logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys); + for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { + unsigned char *pkblob = s->p; + s->p += 4; + { + int n, ok = FALSE; + do { /* do while (0) to make breaking easy */ + n = ssh1_read_bignum + (s->p, s->responselen-(s->p-s->response), + &s->key.exponent); + if (n < 0) + break; + s->p += n; + n = ssh1_read_bignum + (s->p, s->responselen-(s->p-s->response), + &s->key.modulus); + if (n < 0) + break; + s->p += n; + if (s->responselen - (s->p-s->response) < 4) + break; + s->commentlen = GET_32BIT(s->p); + s->p += 4; + if (s->responselen - (s->p-s->response) < + s->commentlen) + break; + s->commentp = (char *)s->p; + s->p += s->commentlen; + ok = TRUE; + } while (0); + if (!ok) { + logevent("Pageant key list packet was truncated"); + break; + } + } + if (s->publickey_blob) { + if (!memcmp(pkblob, s->publickey_blob, + s->publickey_bloblen)) { + logeventf(ssh, "Pageant key #%d matches " + "configured key file", s->keyi); + s->tried_publickey = 1; + } else + /* Skip non-configured key */ + continue; + } + logeventf(ssh, "Trying Pageant key #%d", s->keyi); + send_packet(ssh, SSH1_CMSG_AUTH_RSA, + PKT_BIGNUM, s->key.modulus, PKT_END); + crWaitUntil(pktin); + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + logevent("Key refused"); + continue; + } + logevent("Received RSA challenge"); + if ((s->challenge = ssh1_pkt_getmp(pktin)) == NULL) { + bombout(("Server's RSA challenge was badly formatted")); + crStop(0); + } + + { + char *agentreq, *q, *ret; + void *vret; + int len, retlen; + len = 1 + 4; /* message type, bit count */ + len += ssh1_bignum_length(s->key.exponent); + len += ssh1_bignum_length(s->key.modulus); + len += ssh1_bignum_length(s->challenge); + len += 16; /* session id */ + len += 4; /* response format */ + agentreq = snewn(4 + len, char); + PUT_32BIT(agentreq, len); + q = agentreq + 4; + *q++ = SSH1_AGENTC_RSA_CHALLENGE; + PUT_32BIT(q, bignum_bitcount(s->key.modulus)); + q += 4; + q += ssh1_write_bignum(q, s->key.exponent); + q += ssh1_write_bignum(q, s->key.modulus); + q += ssh1_write_bignum(q, s->challenge); + memcpy(q, s->session_id, 16); + q += 16; + PUT_32BIT(q, 1); /* response format */ + if (!agent_query(agentreq, len + 4, &vret, &retlen, + ssh_agent_callback, ssh)) { + sfree(agentreq); + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server" + " while waiting for agent" + " response")); + crStop(0); + } + } while (pktin || inlen > 0); + vret = ssh->agent_response; + retlen = ssh->agent_response_len; + } else + sfree(agentreq); + ret = vret; + if (ret) { + if (ret[4] == SSH1_AGENT_RSA_RESPONSE) { + logevent("Sending Pageant's response"); + send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE, + PKT_DATA, ret + 5, 16, + PKT_END); + sfree(ret); + crWaitUntil(pktin); + if (pktin->type == SSH1_SMSG_SUCCESS) { + logevent + ("Pageant's response accepted"); + if (flags & FLAG_VERBOSE) { + c_write_str(ssh, "Authenticated using" + " RSA key \""); + c_write(ssh, s->commentp, + s->commentlen); + c_write_str(ssh, "\" from agent\r\n"); + } + s->authed = TRUE; + } else + logevent + ("Pageant's response not accepted"); + } else { + logevent + ("Pageant failed to answer challenge"); + sfree(ret); + } + } else { + logevent("No reply received from Pageant"); + } + } + freebn(s->key.exponent); + freebn(s->key.modulus); + freebn(s->challenge); + if (s->authed) + break; + } + sfree(s->response); + if (s->publickey_blob && !s->tried_publickey) + logevent("Configured key file not in Pageant"); + } else { + logevent("Failed to get reply from Pageant"); + } + if (s->authed) + break; + } + if (s->publickey_blob && !s->tried_publickey) { + /* + * Try public key authentication with the specified + * key file. + */ + int got_passphrase; /* need not be kept over crReturn */ + if (flags & FLAG_VERBOSE) + c_write_str(ssh, "Trying public key authentication.\r\n"); + logeventf(ssh, "Trying public key \"%s\"", + filename_to_str(&ssh->cfg.keyfile)); + s->tried_publickey = 1; + got_passphrase = FALSE; + while (!got_passphrase) { + /* + * Get a passphrase, if necessary. + */ + char *passphrase = NULL; /* only written after crReturn */ + const char *error; + if (!s->publickey_encrypted) { + if (flags & FLAG_VERBOSE) + c_write_str(ssh, "No passphrase required.\r\n"); + passphrase = NULL; + } else { + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = FALSE; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%.100s\": ", + s->publickey_comment), + FALSE, SSH_MAX_PASSWORD_LEN); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntil(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* Failed to get a passphrase. Terminate. */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + 0, TRUE); + crStop(0); + } + passphrase = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + } + /* + * Try decrypting key with passphrase. + */ + ret = loadrsakey(&ssh->cfg.keyfile, &s->key, passphrase, + &error); + if (passphrase) { + memset(passphrase, 0, strlen(passphrase)); + sfree(passphrase); + } + if (ret == 1) { + /* Correct passphrase. */ + got_passphrase = TRUE; + } else if (ret == 0) { + c_write_str(ssh, "Couldn't load private key from "); + c_write_str(ssh, filename_to_str(&ssh->cfg.keyfile)); + c_write_str(ssh, " ("); + c_write_str(ssh, error); + c_write_str(ssh, ").\r\n"); + got_passphrase = FALSE; + break; /* go and try something else */ + } else if (ret == -1) { + c_write_str(ssh, "Wrong passphrase.\r\n"); /* FIXME */ + got_passphrase = FALSE; + /* and try again */ + } else { + assert(0 && "unexpected return from loadrsakey()"); + got_passphrase = FALSE; /* placate optimisers */ + } + } + + if (got_passphrase) { + + /* + * Send a public key attempt. + */ + send_packet(ssh, SSH1_CMSG_AUTH_RSA, + PKT_BIGNUM, s->key.modulus, PKT_END); + + crWaitUntil(pktin); + if (pktin->type == SSH1_SMSG_FAILURE) { + c_write_str(ssh, "Server refused our public key.\r\n"); + continue; /* go and try something else */ + } + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + bombout(("Bizarre response to offer of public key")); + crStop(0); + } + + { + int i; + unsigned char buffer[32]; + Bignum challenge, response; + + if ((challenge = ssh1_pkt_getmp(pktin)) == NULL) { + bombout(("Server's RSA challenge was badly formatted")); + crStop(0); + } + response = rsadecrypt(challenge, &s->key); + freebn(s->key.private_exponent);/* burn the evidence */ + + for (i = 0; i < 32; i++) { + buffer[i] = bignum_byte(response, 31 - i); + } + + MD5Init(&md5c); + MD5Update(&md5c, buffer, 32); + MD5Update(&md5c, s->session_id, 16); + MD5Final(buffer, &md5c); + + send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE, + PKT_DATA, buffer, 16, PKT_END); + + freebn(challenge); + freebn(response); + } + + crWaitUntil(pktin); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (flags & FLAG_VERBOSE) + c_write_str(ssh, "Failed to authenticate with" + " our public key.\r\n"); + continue; /* go and try something else */ + } else if (pktin->type != SSH1_SMSG_SUCCESS) { + bombout(("Bizarre response to RSA authentication response")); + crStop(0); + } + + break; /* we're through! */ + } + + } + + /* + * Otherwise, try various forms of password-like authentication. + */ + s->cur_prompt = new_prompts(ssh->frontend); + + if (ssh->cfg.try_tis_auth && + (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && + !s->tis_auth_refused) { + s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; + logevent("Requested TIS authentication"); + send_packet(ssh, SSH1_CMSG_AUTH_TIS, PKT_END); + crWaitUntil(pktin); + if (pktin->type != SSH1_SMSG_AUTH_TIS_CHALLENGE) { + logevent("TIS authentication declined"); + if (flags & FLAG_INTERACTIVE) + c_write_str(ssh, "TIS authentication refused.\r\n"); + s->tis_auth_refused = 1; + continue; + } else { + char *challenge; + int challengelen; + char *instr_suf, *prompt; + + ssh_pkt_getstring(pktin, &challenge, &challengelen); + if (!challenge) { + bombout(("TIS challenge packet was badly formed")); + crStop(0); + } + logevent("Received TIS challenge"); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH TIS authentication"); + /* Prompt heuristic comes from OpenSSH */ + if (memchr(challenge, '\n', challengelen)) { + instr_suf = dupstr(""); + prompt = dupprintf("%.*s", challengelen, challenge); + } else { + instr_suf = dupprintf("%.*s", challengelen, challenge); + prompt = dupstr("Response: "); + } + s->cur_prompt->instruction = + dupprintf("Using TIS authentication.%s%s", + (*instr_suf) ? "\n" : "", + instr_suf); + s->cur_prompt->instr_reqd = TRUE; + add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN); + sfree(instr_suf); + } + } + if (ssh->cfg.try_tis_auth && + (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && + !s->ccard_auth_refused) { + s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; + logevent("Requested CryptoCard authentication"); + send_packet(ssh, SSH1_CMSG_AUTH_CCARD, PKT_END); + crWaitUntil(pktin); + if (pktin->type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) { + logevent("CryptoCard authentication declined"); + c_write_str(ssh, "CryptoCard authentication refused.\r\n"); + s->ccard_auth_refused = 1; + continue; + } else { + char *challenge; + int challengelen; + char *instr_suf, *prompt; + + ssh_pkt_getstring(pktin, &challenge, &challengelen); + if (!challenge) { + bombout(("CryptoCard challenge packet was badly formed")); + crStop(0); + } + logevent("Received CryptoCard challenge"); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH CryptoCard authentication"); + s->cur_prompt->name_reqd = FALSE; + /* Prompt heuristic comes from OpenSSH */ + if (memchr(challenge, '\n', challengelen)) { + instr_suf = dupstr(""); + prompt = dupprintf("%.*s", challengelen, challenge); + } else { + instr_suf = dupprintf("%.*s", challengelen, challenge); + prompt = dupstr("Response: "); + } + s->cur_prompt->instruction = + dupprintf("Using CryptoCard authentication.%s%s", + (*instr_suf) ? "\n" : "", + instr_suf); + s->cur_prompt->instr_reqd = TRUE; + add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN); + sfree(instr_suf); + } + } + if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { + if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) { + bombout(("No supported authentication methods available")); + crStop(0); + } + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%.90s@%.90s's password: ", + s->username, ssh->savedhost), + FALSE, SSH_MAX_PASSWORD_LEN); + } + + /* + * Show password prompt, having first obtained it via a TIS + * or CryptoCard exchange if we're doing TIS or CryptoCard + * authentication. + */ + { + int ret; /* need not be kept over crReturn */ + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntil(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get a password (for example + * because one was supplied on the command line + * which has already failed to work). Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, TRUE); + crStop(0); + } + } + + if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { + /* + * Defence against traffic analysis: we send a + * whole bunch of packets containing strings of + * different lengths. One of these strings is the + * password, in a SSH1_CMSG_AUTH_PASSWORD packet. + * The others are all random data in + * SSH1_MSG_IGNORE packets. This way a passive + * listener can't tell which is the password, and + * hence can't deduce the password length. + * + * Anybody with a password length greater than 16 + * bytes is going to have enough entropy in their + * password that a listener won't find it _that_ + * much help to know how long it is. So what we'll + * do is: + * + * - if password length < 16, we send 15 packets + * containing string lengths 1 through 15 + * + * - otherwise, we let N be the nearest multiple + * of 8 below the password length, and send 8 + * packets containing string lengths N through + * N+7. This won't obscure the order of + * magnitude of the password length, but it will + * introduce a bit of extra uncertainty. + * + * A few servers can't deal with SSH1_MSG_IGNORE, at + * least in this context. For these servers, we need + * an alternative defence. We make use of the fact + * that the password is interpreted as a C string: + * so we can append a NUL, then some random data. + * + * A few servers can deal with neither SSH1_MSG_IGNORE + * here _nor_ a padded password string. + * For these servers we are left with no defences + * against password length sniffing. + */ + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) && + !(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { + /* + * The server can deal with SSH1_MSG_IGNORE, so + * we can use the primary defence. + */ + int bottom, top, pwlen, i; + char *randomstr; + + pwlen = strlen(s->cur_prompt->prompts[0]->result); + if (pwlen < 16) { + bottom = 0; /* zero length passwords are OK! :-) */ + top = 15; + } else { + bottom = pwlen & ~7; + top = bottom + 7; + } + + assert(pwlen >= bottom && pwlen <= top); + + randomstr = snewn(top + 1, char); + + for (i = bottom; i <= top; i++) { + if (i == pwlen) { + defer_packet(ssh, s->pwpkt_type, + PKTT_PASSWORD, PKT_STR, + s->cur_prompt->prompts[0]->result, + PKTT_OTHER, PKT_END); + } else { + for (j = 0; j < i; j++) { + do { + randomstr[j] = random_byte(); + } while (randomstr[j] == '\0'); + } + randomstr[i] = '\0'; + defer_packet(ssh, SSH1_MSG_IGNORE, + PKT_STR, randomstr, PKT_END); + } + } + logevent("Sending password with camouflage packets"); + ssh_pkt_defersend(ssh); + sfree(randomstr); + } + else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { + /* + * The server can't deal with SSH1_MSG_IGNORE + * but can deal with padded passwords, so we + * can use the secondary defence. + */ + char string[64]; + char *ss; + int len; + + len = strlen(s->cur_prompt->prompts[0]->result); + if (len < sizeof(string)) { + ss = string; + strcpy(string, s->cur_prompt->prompts[0]->result); + len++; /* cover the zero byte */ + while (len < sizeof(string)) { + string[len++] = (char) random_byte(); + } + } else { + ss = s->cur_prompt->prompts[0]->result; + } + logevent("Sending length-padded password"); + send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD, + PKT_INT, len, PKT_DATA, ss, len, + PKTT_OTHER, PKT_END); + } else { + /* + * The server is believed unable to cope with + * any of our password camouflage methods. + */ + int len; + len = strlen(s->cur_prompt->prompts[0]->result); + logevent("Sending unpadded password"); + send_packet(ssh, s->pwpkt_type, + PKTT_PASSWORD, PKT_INT, len, + PKT_DATA, s->cur_prompt->prompts[0]->result, len, + PKTT_OTHER, PKT_END); + } + } else { + send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD, + PKT_STR, s->cur_prompt->prompts[0]->result, + PKTT_OTHER, PKT_END); + } + logevent("Sent password"); + free_prompts(s->cur_prompt); + crWaitUntil(pktin); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (flags & FLAG_VERBOSE) + c_write_str(ssh, "Access denied\r\n"); + logevent("Authentication refused"); + } else if (pktin->type != SSH1_SMSG_SUCCESS) { + bombout(("Strange packet received, type %d", pktin->type)); + crStop(0); + } + } + + /* Clear up */ + if (s->publickey_blob) { + sfree(s->publickey_blob); + sfree(s->publickey_comment); + } + + logevent("Authentication successful"); + + crFinish(1); +} + +void sshfwd_close(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (!c->closes) { + /* + * If halfopen is true, we have sent + * CHANNEL_OPEN for this channel, but it hasn't even been + * acknowledged by the server. So we must set a close flag + * on it now, and then when the server acks the channel + * open, we can close it then. + */ + if (!c->halfopen) { + if (ssh->version == 1) { + send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, + PKT_END); + c->closes = 1; /* sent MSG_CLOSE */ + } else { + int bytes_to_send = bufchain_size(&c->v.v2.outbuffer); + if (bytes_to_send > 0) { + /* + * If we still have unsent data in our outgoing + * buffer for this channel, we can't actually + * initiate a close operation yet or that data + * will be lost. Instead, set the pending_close + * flag so that when we do clear the buffer + * we'll start closing the channel. + */ + char logmsg[160] = {'\0'}; + sprintf( + logmsg, + "Forwarded port pending to be closed : " + "%d bytes remaining", + bytes_to_send); + logevent(logmsg); + + c->pending_close = TRUE; + } else { + /* + * No locally buffered data, so we can send the + * close message immediately. + */ + struct Packet *pktout; + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + c->closes = 1; /* sent MSG_CLOSE */ + logevent("Nothing left to send, closing channel"); + } + } + } + + if (c->type == CHAN_X11) { + c->u.x11.s = NULL; + logevent("Forwarded X11 connection terminated"); + } else if (c->type == CHAN_SOCKDATA || + c->type == CHAN_SOCKDATA_DORMANT) { + c->u.pfd.s = NULL; + logevent("Forwarded port closed"); + } + } +} + +int sshfwd_write(struct ssh_channel *c, char *buf, int len) +{ + Ssh ssh = c->ssh; + + if (ssh->state == SSH_STATE_CLOSED) + return 0; + + if (ssh->version == 1) { + send_packet(ssh, SSH1_MSG_CHANNEL_DATA, + PKT_INT, c->remoteid, + PKT_INT, len, PKTT_DATA, PKT_DATA, buf, len, + PKTT_OTHER, PKT_END); + /* + * In SSH-1 we can return 0 here - implying that forwarded + * connections are never individually throttled - because + * the only circumstance that can cause throttling will be + * the whole SSH connection backing up, in which case + * _everything_ will be throttled as a whole. + */ + return 0; + } else { + ssh2_add_channel_data(c, buf, len); + return ssh2_try_send(c); + } +} + +void sshfwd_unthrottle(struct ssh_channel *c, int bufsize) +{ + Ssh ssh = c->ssh; + int buflimit; + + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (ssh->version == 1) { + buflimit = SSH1_BUFFER_LIMIT; + } else { + buflimit = c->v.v2.locmaxwin; + ssh2_set_window(c, bufsize < buflimit ? buflimit - bufsize : 0); + } + if (c->throttling_conn && bufsize <= buflimit) { + c->throttling_conn = 0; + ssh_throttle_conn(ssh, -1); + } +} + +static void ssh_queueing_handler(Ssh ssh, struct Packet *pktin) +{ + struct queued_handler *qh = ssh->qhead; + + assert(qh != NULL); + + assert(pktin->type == qh->msg1 || pktin->type == qh->msg2); + + if (qh->msg1 > 0) { + assert(ssh->packet_dispatch[qh->msg1] == ssh_queueing_handler); + ssh->packet_dispatch[qh->msg1] = NULL; + } + if (qh->msg2 > 0) { + assert(ssh->packet_dispatch[qh->msg2] == ssh_queueing_handler); + ssh->packet_dispatch[qh->msg2] = NULL; + } + + if (qh->next) { + ssh->qhead = qh->next; + + if (ssh->qhead->msg1 > 0) { + assert(ssh->packet_dispatch[ssh->qhead->msg1] == NULL); + ssh->packet_dispatch[ssh->qhead->msg1] = ssh_queueing_handler; + } + if (ssh->qhead->msg2 > 0) { + assert(ssh->packet_dispatch[ssh->qhead->msg2] == NULL); + ssh->packet_dispatch[ssh->qhead->msg2] = ssh_queueing_handler; + } + } else { + ssh->qhead = ssh->qtail = NULL; + ssh->packet_dispatch[pktin->type] = NULL; + } + + qh->handler(ssh, pktin, qh->ctx); + + sfree(qh); +} + +static void ssh_queue_handler(Ssh ssh, int msg1, int msg2, + chandler_fn_t handler, void *ctx) +{ + struct queued_handler *qh; + + qh = snew(struct queued_handler); + qh->msg1 = msg1; + qh->msg2 = msg2; + qh->handler = handler; + qh->ctx = ctx; + qh->next = NULL; + + if (ssh->qtail == NULL) { + ssh->qhead = qh; + + if (qh->msg1 > 0) { + assert(ssh->packet_dispatch[qh->msg1] == NULL); + ssh->packet_dispatch[qh->msg1] = ssh_queueing_handler; + } + if (qh->msg2 > 0) { + assert(ssh->packet_dispatch[qh->msg2] == NULL); + ssh->packet_dispatch[qh->msg2] = ssh_queueing_handler; + } + } else { + ssh->qtail->next = qh; + } + ssh->qtail = qh; +} + +static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx) +{ + struct ssh_rportfwd *rpf, *pf = (struct ssh_rportfwd *)ctx; + + if (pktin->type == (ssh->version == 1 ? SSH1_SMSG_SUCCESS : + SSH2_MSG_REQUEST_SUCCESS)) { + logeventf(ssh, "Remote port forwarding from %s enabled", + pf->sportdesc); + } else { + logeventf(ssh, "Remote port forwarding from %s refused", + pf->sportdesc); + + rpf = del234(ssh->rportfwds, pf); + assert(rpf == pf); + pf->pfrec->remote = NULL; + free_rportfwd(pf); + } +} + +static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) +{ + const char *portfwd_strptr = cfg->portfwd; + struct ssh_portfwd *epf; + int i; + + if (!ssh->portfwds) { + ssh->portfwds = newtree234(ssh_portcmp); + } else { + /* + * Go through the existing port forwardings and tag them + * with status==DESTROY. Any that we want to keep will be + * re-enabled (status==KEEP) as we go through the + * configuration and find out which bits are the same as + * they were before. + */ + struct ssh_portfwd *epf; + int i; + for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) + epf->status = DESTROY; + } + + while (*portfwd_strptr) { + char address_family, type; + int sport,dport,sserv,dserv; + char sports[256], dports[256], saddr[256], host[256]; + int n; + + address_family = 'A'; + type = 'L'; + if (*portfwd_strptr == 'A' || + *portfwd_strptr == '4' || + *portfwd_strptr == '6') + address_family = *portfwd_strptr++; + if (*portfwd_strptr == 'L' || + *portfwd_strptr == 'R' || + *portfwd_strptr == 'D') + type = *portfwd_strptr++; + + saddr[0] = '\0'; + + n = 0; + while (*portfwd_strptr && *portfwd_strptr != '\t') { + if (*portfwd_strptr == ':') { + /* + * We've seen a colon in the middle of the + * source port number. This means that + * everything we've seen until now is the + * source _address_, so we'll move it into + * saddr and start sports from the beginning + * again. + */ + portfwd_strptr++; + sports[n] = '\0'; + if (ssh->version == 1 && type == 'R') { + logeventf(ssh, "SSH-1 cannot handle remote source address " + "spec \"%s\"; ignoring", sports); + } else + strcpy(saddr, sports); + n = 0; + } + if (n < lenof(sports)-1) sports[n++] = *portfwd_strptr++; + } + sports[n] = 0; + if (type != 'D') { + if (*portfwd_strptr == '\t') + portfwd_strptr++; + n = 0; + while (*portfwd_strptr && *portfwd_strptr != ':') { + if (n < lenof(host)-1) host[n++] = *portfwd_strptr++; + } + host[n] = 0; + if (*portfwd_strptr == ':') + portfwd_strptr++; + n = 0; + while (*portfwd_strptr) { + if (n < lenof(dports)-1) dports[n++] = *portfwd_strptr++; + } + dports[n] = 0; + portfwd_strptr++; + dport = atoi(dports); + dserv = 0; + if (dport == 0) { + dserv = 1; + dport = net_service_lookup(dports); + if (!dport) { + logeventf(ssh, "Service lookup failed for destination" + " port \"%s\"", dports); + } + } + } else { + while (*portfwd_strptr) portfwd_strptr++; + host[0] = 0; + dports[0] = 0; + dport = dserv = -1; + portfwd_strptr++; /* eat the NUL and move to next one */ + } + sport = atoi(sports); + sserv = 0; + if (sport == 0) { + sserv = 1; + sport = net_service_lookup(sports); + if (!sport) { + logeventf(ssh, "Service lookup failed for source" + " port \"%s\"", sports); + } + } + if (sport && dport) { + /* Set up a description of the source port. */ + struct ssh_portfwd *pfrec, *epfrec; + + pfrec = snew(struct ssh_portfwd); + pfrec->type = type; + pfrec->saddr = *saddr ? dupstr(saddr) : NULL; + pfrec->sserv = sserv ? dupstr(sports) : NULL; + pfrec->sport = sport; + pfrec->daddr = *host ? dupstr(host) : NULL; + pfrec->dserv = dserv ? dupstr(dports) : NULL; + pfrec->dport = dport; + pfrec->local = NULL; + pfrec->remote = NULL; + pfrec->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 : + address_family == '6' ? ADDRTYPE_IPV6 : + ADDRTYPE_UNSPEC); + + epfrec = add234(ssh->portfwds, pfrec); + if (epfrec != pfrec) { + if (epfrec->status == DESTROY) { + /* + * We already have a port forwarding up and running + * with precisely these parameters. Hence, no need + * to do anything; simply re-tag the existing one + * as KEEP. + */ + epfrec->status = KEEP; + } + /* + * Anything else indicates that there was a duplicate + * in our input, which we'll silently ignore. + */ + free_portfwd(pfrec); + } else { + pfrec->status = CREATE; + } + } + } + + /* + * Now go through and destroy any port forwardings which were + * not re-enabled. + */ + for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) + if (epf->status == DESTROY) { + char *message; + + message = dupprintf("%s port forwarding from %s%s%d", + epf->type == 'L' ? "local" : + epf->type == 'R' ? "remote" : "dynamic", + epf->saddr ? epf->saddr : "", + epf->saddr ? ":" : "", + epf->sport); + + if (epf->type != 'D') { + char *msg2 = dupprintf("%s to %s:%d", message, + epf->daddr, epf->dport); + sfree(message); + message = msg2; + } + + logeventf(ssh, "Cancelling %s", message); + sfree(message); + + /* epf->remote or epf->local may be NULL if setting up a + * forwarding failed. */ + if (epf->remote) { + struct ssh_rportfwd *rpf = epf->remote; + struct Packet *pktout; + + /* + * Cancel the port forwarding at the server + * end. + */ + if (ssh->version == 1) { + /* + * We cannot cancel listening ports on the + * server side in SSH-1! There's no message + * to support it. Instead, we simply remove + * the rportfwd record from the local end + * so that any connections the server tries + * to make on it are rejected. + */ + } else { + pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); + ssh2_pkt_addstring(pktout, "cancel-tcpip-forward"); + ssh2_pkt_addbool(pktout, 0);/* _don't_ want reply */ + if (epf->saddr) { + ssh2_pkt_addstring(pktout, epf->saddr); + } else if (ssh->cfg.rport_acceptall) { + /* XXX: ssh->cfg.rport_acceptall may not represent + * what was used to open the original connection, + * since it's reconfigurable. */ + ssh2_pkt_addstring(pktout, "0.0.0.0"); + } else { + ssh2_pkt_addstring(pktout, "127.0.0.1"); + } + ssh2_pkt_adduint32(pktout, epf->sport); + ssh2_pkt_send(ssh, pktout); + } + + del234(ssh->rportfwds, rpf); + free_rportfwd(rpf); + } else if (epf->local) { + pfd_terminate(epf->local); + } + + delpos234(ssh->portfwds, i); + free_portfwd(epf); + i--; /* so we don't skip one in the list */ + } + + /* + * And finally, set up any new port forwardings (status==CREATE). + */ + for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) + if (epf->status == CREATE) { + char *sportdesc, *dportdesc; + sportdesc = dupprintf("%s%s%s%s%d%s", + epf->saddr ? epf->saddr : "", + epf->saddr ? ":" : "", + epf->sserv ? epf->sserv : "", + epf->sserv ? "(" : "", + epf->sport, + epf->sserv ? ")" : ""); + if (epf->type == 'D') { + dportdesc = NULL; + } else { + dportdesc = dupprintf("%s:%s%s%d%s", + epf->daddr, + epf->dserv ? epf->dserv : "", + epf->dserv ? "(" : "", + epf->dport, + epf->dserv ? ")" : ""); + } + + if (epf->type == 'L') { + const char *err = pfd_addforward(epf->daddr, epf->dport, + epf->saddr, epf->sport, + ssh, cfg, + &epf->local, + epf->addressfamily); + + logeventf(ssh, "Local %sport %s forwarding to %s%s%s", + epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : + epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", + sportdesc, dportdesc, + err ? " failed: " : "", err ? err : ""); + } else if (epf->type == 'D') { + const char *err = pfd_addforward(NULL, -1, + epf->saddr, epf->sport, + ssh, cfg, + &epf->local, + epf->addressfamily); + + logeventf(ssh, "Local %sport %s SOCKS dynamic forwarding%s%s", + epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : + epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", + sportdesc, + err ? " failed: " : "", err ? err : ""); + } else { + struct ssh_rportfwd *pf; + + /* + * Ensure the remote port forwardings tree exists. + */ + if (!ssh->rportfwds) { + if (ssh->version == 1) + ssh->rportfwds = newtree234(ssh_rportcmp_ssh1); + else + ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); + } + + pf = snew(struct ssh_rportfwd); + strncpy(pf->dhost, epf->daddr, lenof(pf->dhost)-1); + pf->dhost[lenof(pf->dhost)-1] = '\0'; + pf->dport = epf->dport; + pf->sport = epf->sport; + if (add234(ssh->rportfwds, pf) != pf) { + logeventf(ssh, "Duplicate remote port forwarding to %s:%d", + epf->daddr, epf->dport); + sfree(pf); + } else { + logeventf(ssh, "Requesting remote port %s" + " forward to %s", sportdesc, dportdesc); + + pf->sportdesc = sportdesc; + sportdesc = NULL; + epf->remote = pf; + pf->pfrec = epf; + + if (ssh->version == 1) { + send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST, + PKT_INT, epf->sport, + PKT_STR, epf->daddr, + PKT_INT, epf->dport, + PKT_END); + ssh_queue_handler(ssh, SSH1_SMSG_SUCCESS, + SSH1_SMSG_FAILURE, + ssh_rportfwd_succfail, pf); + } else { + struct Packet *pktout; + pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); + ssh2_pkt_addstring(pktout, "tcpip-forward"); + ssh2_pkt_addbool(pktout, 1);/* want reply */ + if (epf->saddr) { + ssh2_pkt_addstring(pktout, epf->saddr); + } else if (cfg->rport_acceptall) { + ssh2_pkt_addstring(pktout, "0.0.0.0"); + } else { + ssh2_pkt_addstring(pktout, "127.0.0.1"); + } + ssh2_pkt_adduint32(pktout, epf->sport); + ssh2_pkt_send(ssh, pktout); + + ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, + SSH2_MSG_REQUEST_FAILURE, + ssh_rportfwd_succfail, pf); + } + } + } + sfree(sportdesc); + sfree(dportdesc); + } +} + +static void ssh1_smsg_stdout_stderr_data(Ssh ssh, struct Packet *pktin) +{ + char *string; + int stringlen, bufsize; + + ssh_pkt_getstring(pktin, &string, &stringlen); + if (string == NULL) { + bombout(("Incoming terminal data packet was badly formed")); + return; + } + + bufsize = from_backend(ssh->frontend, pktin->type == SSH1_SMSG_STDERR_DATA, + string, stringlen); + if (!ssh->v1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) { + ssh->v1_stdout_throttling = 1; + ssh_throttle_conn(ssh, +1); + } +} + +static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin) +{ + /* Remote side is trying to open a channel to talk to our + * X-Server. Give them back a local channel number. */ + struct ssh_channel *c; + int remoteid = ssh_pkt_getuint32(pktin); + + logevent("Received X11 connect request"); + /* Refuse if X11 forwarding is disabled. */ + if (!ssh->X11_fwd_enabled) { + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, remoteid, PKT_END); + logevent("Rejected X11 connect request"); + } else { + c = snew(struct ssh_channel); + c->ssh = ssh; + + if (x11_init(&c->u.x11.s, ssh->x11disp, c, + NULL, -1, &ssh->cfg) != NULL) { + logevent("Opening X11 forward connection failed"); + sfree(c); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, remoteid, PKT_END); + } else { + logevent + ("Opening X11 forward connection succeeded"); + c->remoteid = remoteid; + c->halfopen = FALSE; + c->localid = alloc_channel_id(ssh); + c->closes = 0; + c->pending_close = FALSE; + c->throttling_conn = 0; + c->type = CHAN_X11; /* identify channel type */ + add234(ssh->channels, c); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, + PKT_INT, c->remoteid, PKT_INT, + c->localid, PKT_END); + logevent("Opened X11 forward channel"); + } + } +} + +static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin) +{ + /* Remote side is trying to open a channel to talk to our + * agent. Give them back a local channel number. */ + struct ssh_channel *c; + int remoteid = ssh_pkt_getuint32(pktin); + + /* Refuse if agent forwarding is disabled. */ + if (!ssh->agentfwd_enabled) { + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, remoteid, PKT_END); + } else { + c = snew(struct ssh_channel); + c->ssh = ssh; + c->remoteid = remoteid; + c->halfopen = FALSE; + c->localid = alloc_channel_id(ssh); + c->closes = 0; + c->pending_close = FALSE; + c->throttling_conn = 0; + c->type = CHAN_AGENT; /* identify channel type */ + c->u.a.lensofar = 0; + add234(ssh->channels, c); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, + PKT_INT, c->remoteid, PKT_INT, c->localid, + PKT_END); + } +} + +static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin) +{ + /* Remote side is trying to open a channel to talk to a + * forwarded port. Give them back a local channel number. */ + struct ssh_channel *c; + struct ssh_rportfwd pf, *pfp; + int remoteid; + int hostsize, port; + char *host; + const char *e; + c = snew(struct ssh_channel); + c->ssh = ssh; + + remoteid = ssh_pkt_getuint32(pktin); + ssh_pkt_getstring(pktin, &host, &hostsize); + port = ssh_pkt_getuint32(pktin); + + if (hostsize >= lenof(pf.dhost)) + hostsize = lenof(pf.dhost)-1; + memcpy(pf.dhost, host, hostsize); + pf.dhost[hostsize] = '\0'; + pf.dport = port; + pfp = find234(ssh->rportfwds, &pf, NULL); + + if (pfp == NULL) { + logeventf(ssh, "Rejected remote port open request for %s:%d", + pf.dhost, port); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, remoteid, PKT_END); + } else { + logeventf(ssh, "Received remote port open request for %s:%d", + pf.dhost, port); + e = pfd_newconnect(&c->u.pfd.s, pf.dhost, port, + c, &ssh->cfg, pfp->pfrec->addressfamily); + if (e != NULL) { + logeventf(ssh, "Port open failed: %s", e); + sfree(c); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, remoteid, PKT_END); + } else { + c->remoteid = remoteid; + c->halfopen = FALSE; + c->localid = alloc_channel_id(ssh); + c->closes = 0; + c->pending_close = FALSE; + c->throttling_conn = 0; + c->type = CHAN_SOCKDATA; /* identify channel type */ + add234(ssh->channels, c); + send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, + PKT_INT, c->remoteid, PKT_INT, + c->localid, PKT_END); + logevent("Forwarded port opened successfully"); + } + } +} + +static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) +{ + unsigned int remoteid = ssh_pkt_getuint32(pktin); + unsigned int localid = ssh_pkt_getuint32(pktin); + struct ssh_channel *c; + + c = find234(ssh->channels, &remoteid, ssh_channelfind); + if (c && c->type == CHAN_SOCKDATA_DORMANT) { + c->remoteid = localid; + c->halfopen = FALSE; + c->type = CHAN_SOCKDATA; + c->throttling_conn = 0; + pfd_confirm(c->u.pfd.s); + } + + if (c && c->closes) { + /* + * We have a pending close on this channel, + * which we decided on before the server acked + * the channel open. So now we know the + * remoteid, we can close it again. + */ + send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, + PKT_INT, c->remoteid, PKT_END); + } +} + +static void ssh1_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) +{ + unsigned int remoteid = ssh_pkt_getuint32(pktin); + struct ssh_channel *c; + + c = find234(ssh->channels, &remoteid, ssh_channelfind); + if (c && c->type == CHAN_SOCKDATA_DORMANT) { + logevent("Forwarded connection refused by server"); + pfd_close(c->u.pfd.s); + del234(ssh->channels, c); + sfree(c); + } +} + +static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin) +{ + /* Remote side closes a channel. */ + unsigned i = ssh_pkt_getuint32(pktin); + struct ssh_channel *c; + c = find234(ssh->channels, &i, ssh_channelfind); + if (c && !c->halfopen) { + int closetype; + closetype = + (pktin->type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2); + + if ((c->closes == 0) && (c->type == CHAN_X11)) { + logevent("Forwarded X11 connection terminated"); + assert(c->u.x11.s != NULL); + x11_close(c->u.x11.s); + c->u.x11.s = NULL; + } + if ((c->closes == 0) && (c->type == CHAN_SOCKDATA)) { + logevent("Forwarded port closed"); + assert(c->u.pfd.s != NULL); + pfd_close(c->u.pfd.s); + c->u.pfd.s = NULL; + } + + c->closes |= (closetype << 2); /* seen this message */ + if (!(c->closes & closetype)) { + send_packet(ssh, pktin->type, PKT_INT, c->remoteid, + PKT_END); + c->closes |= closetype; /* sent it too */ + } + + if (c->closes == 15) { + del234(ssh->channels, c); + sfree(c); + } + } else { + bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n", + pktin->type == SSH1_MSG_CHANNEL_CLOSE ? "" : + "_CONFIRMATION", c ? "half-open" : "nonexistent", + i)); + } +} + +static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin) +{ + /* Data sent down one of our channels. */ + int i = ssh_pkt_getuint32(pktin); + char *p; + int len; + struct ssh_channel *c; + + ssh_pkt_getstring(pktin, &p, &len); + + c = find234(ssh->channels, &i, ssh_channelfind); + if (c) { + int bufsize = 0; + switch (c->type) { + case CHAN_X11: + bufsize = x11_send(c->u.x11.s, p, len); + break; + case CHAN_SOCKDATA: + bufsize = pfd_send(c->u.pfd.s, p, len); + break; + case CHAN_AGENT: + /* Data for an agent message. Buffer it. */ + while (len > 0) { + if (c->u.a.lensofar < 4) { + unsigned int l = min(4 - c->u.a.lensofar, (unsigned)len); + memcpy(c->u.a.msglen + c->u.a.lensofar, p, + l); + p += l; + len -= l; + c->u.a.lensofar += l; + } + if (c->u.a.lensofar == 4) { + c->u.a.totallen = + 4 + GET_32BIT(c->u.a.msglen); + c->u.a.message = snewn(c->u.a.totallen, + unsigned char); + memcpy(c->u.a.message, c->u.a.msglen, 4); + } + if (c->u.a.lensofar >= 4 && len > 0) { + unsigned int l = + min(c->u.a.totallen - c->u.a.lensofar, + (unsigned)len); + memcpy(c->u.a.message + c->u.a.lensofar, p, + l); + p += l; + len -= l; + c->u.a.lensofar += l; + } + if (c->u.a.lensofar == c->u.a.totallen) { + void *reply; + int replylen; + if (agent_query(c->u.a.message, + c->u.a.totallen, + &reply, &replylen, + ssh_agentf_callback, c)) + ssh_agentf_callback(c, reply, replylen); + sfree(c->u.a.message); + c->u.a.lensofar = 0; + } + } + bufsize = 0; /* agent channels never back up */ + break; + } + if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) { + c->throttling_conn = 1; + ssh_throttle_conn(ssh, +1); + } + } +} + +static void ssh1_smsg_exit_status(Ssh ssh, struct Packet *pktin) +{ + ssh->exitcode = ssh_pkt_getuint32(pktin); + logeventf(ssh, "Server sent command exit status %d", ssh->exitcode); + send_packet(ssh, SSH1_CMSG_EXIT_CONFIRMATION, PKT_END); + /* + * In case `helpful' firewalls or proxies tack + * extra human-readable text on the end of the + * session which we might mistake for another + * encrypted packet, we close the session once + * we've sent EXIT_CONFIRMATION. + */ + ssh_disconnect(ssh, NULL, NULL, 0, TRUE); +} + +/* Helper function to deal with sending tty modes for REQUEST_PTY */ +static void ssh1_send_ttymode(void *data, char *mode, char *val) +{ + struct Packet *pktout = (struct Packet *)data; + int i = 0; + unsigned int arg = 0; + while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++; + if (i == lenof(ssh_ttymodes)) return; + switch (ssh_ttymodes[i].type) { + case TTY_OP_CHAR: + arg = ssh_tty_parse_specchar(val); + break; + case TTY_OP_BOOL: + arg = ssh_tty_parse_boolean(val); + break; + } + ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode); + ssh2_pkt_addbyte(pktout, arg); +} + + +static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin) +{ + crBegin(ssh->do_ssh1_connection_crstate); + + ssh->packet_dispatch[SSH1_SMSG_STDOUT_DATA] = + ssh->packet_dispatch[SSH1_SMSG_STDERR_DATA] = + ssh1_smsg_stdout_stderr_data; + + ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_CONFIRMATION] = + ssh1_msg_channel_open_confirmation; + ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_FAILURE] = + ssh1_msg_channel_open_failure; + ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE] = + ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION] = + ssh1_msg_channel_close; + ssh->packet_dispatch[SSH1_MSG_CHANNEL_DATA] = ssh1_msg_channel_data; + ssh->packet_dispatch[SSH1_SMSG_EXIT_STATUS] = ssh1_smsg_exit_status; + + if (ssh->cfg.agentfwd && agent_exists()) { + logevent("Requesting agent forwarding"); + send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END); + do { + crReturnV; + } while (!pktin); + if (pktin->type != SSH1_SMSG_SUCCESS + && pktin->type != SSH1_SMSG_FAILURE) { + bombout(("Protocol confusion")); + crStopV; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + logevent("Agent forwarding refused"); + } else { + logevent("Agent forwarding enabled"); + ssh->agentfwd_enabled = TRUE; + ssh->packet_dispatch[SSH1_SMSG_AGENT_OPEN] = ssh1_smsg_agent_open; + } + } + + if (ssh->cfg.x11_forward && + (ssh->x11disp = x11_setup_display(ssh->cfg.x11_display, + ssh->cfg.x11_auth, &ssh->cfg))) { + logevent("Requesting X11 forwarding"); + /* + * Note that while we blank the X authentication data here, we don't + * take any special action to blank the start of an X11 channel, + * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection + * without having session blanking enabled is likely to leak your + * cookie into the log. + */ + if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) { + send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, + PKT_STR, ssh->x11disp->remoteauthprotoname, + PKTT_PASSWORD, + PKT_STR, ssh->x11disp->remoteauthdatastring, + PKTT_OTHER, + PKT_INT, ssh->x11disp->screennum, + PKT_END); + } else { + send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, + PKT_STR, ssh->x11disp->remoteauthprotoname, + PKTT_PASSWORD, + PKT_STR, ssh->x11disp->remoteauthdatastring, + PKTT_OTHER, + PKT_END); + } + do { + crReturnV; + } while (!pktin); + if (pktin->type != SSH1_SMSG_SUCCESS + && pktin->type != SSH1_SMSG_FAILURE) { + bombout(("Protocol confusion")); + crStopV; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + logevent("X11 forwarding refused"); + } else { + logevent("X11 forwarding enabled"); + ssh->X11_fwd_enabled = TRUE; + ssh->packet_dispatch[SSH1_SMSG_X11_OPEN] = ssh1_smsg_x11_open; + } + } + + ssh_setup_portfwd(ssh, &ssh->cfg); + ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open; + + if (!ssh->cfg.nopty) { + struct Packet *pkt; + /* Unpick the terminal-speed string. */ + /* XXX perhaps we should allow no speeds to be sent. */ + ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ + sscanf(ssh->cfg.termspeed, "%d,%d", &ssh->ospeed, &ssh->ispeed); + /* Send the pty request. */ + pkt = ssh1_pkt_init(SSH1_CMSG_REQUEST_PTY); + ssh_pkt_addstring(pkt, ssh->cfg.termtype); + ssh_pkt_adduint32(pkt, ssh->term_height); + ssh_pkt_adduint32(pkt, ssh->term_width); + ssh_pkt_adduint32(pkt, 0); /* width in pixels */ + ssh_pkt_adduint32(pkt, 0); /* height in pixels */ + parse_ttymodes(ssh, ssh->cfg.ttymodes, + ssh1_send_ttymode, (void *)pkt); + ssh_pkt_addbyte(pkt, SSH1_TTY_OP_ISPEED); + ssh_pkt_adduint32(pkt, ssh->ispeed); + ssh_pkt_addbyte(pkt, SSH1_TTY_OP_OSPEED); + ssh_pkt_adduint32(pkt, ssh->ospeed); + ssh_pkt_addbyte(pkt, SSH_TTY_OP_END); + s_wrpkt(ssh, pkt); + ssh->state = SSH_STATE_INTERMED; + do { + crReturnV; + } while (!pktin); + if (pktin->type != SSH1_SMSG_SUCCESS + && pktin->type != SSH1_SMSG_FAILURE) { + bombout(("Protocol confusion")); + crStopV; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + c_write_str(ssh, "Server refused to allocate pty\r\n"); + ssh->editing = ssh->echoing = 1; + } + logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", + ssh->ospeed, ssh->ispeed); + } else { + ssh->editing = ssh->echoing = 1; + } + + if (ssh->cfg.compression) { + send_packet(ssh, SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, PKT_END); + do { + crReturnV; + } while (!pktin); + if (pktin->type != SSH1_SMSG_SUCCESS + && pktin->type != SSH1_SMSG_FAILURE) { + bombout(("Protocol confusion")); + crStopV; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + c_write_str(ssh, "Server refused to compress\r\n"); + } + logevent("Started compression"); + ssh->v1_compressing = TRUE; + ssh->cs_comp_ctx = zlib_compress_init(); + logevent("Initialised zlib (RFC1950) compression"); + ssh->sc_comp_ctx = zlib_decompress_init(); + logevent("Initialised zlib (RFC1950) decompression"); + } + + /* + * Start the shell or command. + * + * Special case: if the first-choice command is an SSH-2 + * subsystem (hence not usable here) and the second choice + * exists, we fall straight back to that. + */ + { + char *cmd = ssh->cfg.remote_cmd_ptr; + + if (!cmd) cmd = ssh->cfg.remote_cmd; + + if (ssh->cfg.ssh_subsys && ssh->cfg.remote_cmd_ptr2) { + cmd = ssh->cfg.remote_cmd_ptr2; + ssh->fallback_cmd = TRUE; + } + if (*cmd) + send_packet(ssh, SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END); + else + send_packet(ssh, SSH1_CMSG_EXEC_SHELL, PKT_END); + logevent("Started session"); + } + + ssh->state = SSH_STATE_SESSION; + if (ssh->size_needed) + ssh_size(ssh, ssh->term_width, ssh->term_height); + if (ssh->eof_needed) + ssh_special(ssh, TS_EOF); + + if (ssh->ldisc) + ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */ + ssh->send_ok = 1; + ssh->channels = newtree234(ssh_channelcmp); + while (1) { + + /* + * By this point, most incoming packets are already being + * handled by the dispatch table, and we need only pay + * attention to the unusual ones. + */ + + crReturnV; + if (pktin) { + if (pktin->type == SSH1_SMSG_SUCCESS) { + /* may be from EXEC_SHELL on some servers */ + } else if (pktin->type == SSH1_SMSG_FAILURE) { + /* may be from EXEC_SHELL on some servers + * if no pty is available or in other odd cases. Ignore */ + } else { + bombout(("Strange packet received: type %d", pktin->type)); + crStopV; + } + } else { + while (inlen > 0) { + int len = min(inlen, 512); + send_packet(ssh, SSH1_CMSG_STDIN_DATA, + PKT_INT, len, PKTT_DATA, PKT_DATA, in, len, + PKTT_OTHER, PKT_END); + in += len; + inlen -= len; + } + } + } + + crFinishV; +} + +/* + * Handle the top-level SSH-2 protocol. + */ +static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin) +{ + char *msg; + int msglen; + + ssh_pkt_getstring(pktin, &msg, &msglen); + logeventf(ssh, "Remote debug message: %.*s", msglen, msg); +} + +static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin) +{ + /* log reason code in disconnect message */ + char *msg; + int msglen; + + ssh_pkt_getstring(pktin, &msg, &msglen); + bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg)); +} + +static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin) +{ + /* Do nothing, because we're ignoring it! Duhh. */ +} + +static void ssh1_protocol_setup(Ssh ssh) +{ + int i; + + /* + * Most messages are handled by the coroutines. + */ + for (i = 0; i < 256; i++) + ssh->packet_dispatch[i] = NULL; + + /* + * These special message types we install handlers for. + */ + ssh->packet_dispatch[SSH1_MSG_DISCONNECT] = ssh1_msg_disconnect; + ssh->packet_dispatch[SSH1_MSG_IGNORE] = ssh_msg_ignore; + ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug; +} + +static void ssh1_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin) +{ + unsigned char *in=(unsigned char*)vin; + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (pktin && ssh->packet_dispatch[pktin->type]) { + ssh->packet_dispatch[pktin->type](ssh, pktin); + return; + } + + if (!ssh->protocol_initial_phase_done) { + if (do_ssh1_login(ssh, in, inlen, pktin)) + ssh->protocol_initial_phase_done = TRUE; + else + return; + } + + do_ssh1_connection(ssh, in, inlen, pktin); +} + +/* + * Utility routine for decoding comma-separated strings in KEXINIT. + */ +static int in_commasep_string(char *needle, char *haystack, int haylen) +{ + int needlen; + if (!needle || !haystack) /* protect against null pointers */ + return 0; + needlen = strlen(needle); + while (1) { + /* + * Is it at the start of the string? + */ + if (haylen >= needlen && /* haystack is long enough */ + !memcmp(needle, haystack, needlen) && /* initial match */ + (haylen == needlen || haystack[needlen] == ',') + /* either , or EOS follows */ + ) + return 1; + /* + * If not, search for the next comma and resume after that. + * If no comma found, terminate. + */ + while (haylen > 0 && *haystack != ',') + haylen--, haystack++; + if (haylen == 0) + return 0; + haylen--, haystack++; /* skip over comma itself */ + } +} + +/* + * Similar routine for checking whether we have the first string in a list. + */ +static int first_in_commasep_string(char *needle, char *haystack, int haylen) +{ + int needlen; + if (!needle || !haystack) /* protect against null pointers */ + return 0; + needlen = strlen(needle); + /* + * Is it at the start of the string? + */ + if (haylen >= needlen && /* haystack is long enough */ + !memcmp(needle, haystack, needlen) && /* initial match */ + (haylen == needlen || haystack[needlen] == ',') + /* either , or EOS follows */ + ) + return 1; + return 0; +} + + +/* + * SSH-2 key creation method. + * (Currently assumes 2 lots of any hash are sufficient to generate + * keys/IVs for any cipher/MAC. SSH2_MKKEY_ITERS documents this assumption.) + */ +#define SSH2_MKKEY_ITERS (2) +static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr, + unsigned char *keyspace) +{ + const struct ssh_hash *h = ssh->kex->hash; + void *s; + /* First hlen bytes. */ + s = h->init(); + if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY)) + hash_mpint(h, s, K); + h->bytes(s, H, h->hlen); + h->bytes(s, &chr, 1); + h->bytes(s, ssh->v2_session_id, ssh->v2_session_id_len); + h->final(s, keyspace); + /* Next hlen bytes. */ + s = h->init(); + if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY)) + hash_mpint(h, s, K); + h->bytes(s, H, h->hlen); + h->bytes(s, keyspace, h->hlen); + h->final(s, keyspace + h->hlen); +} + +/* + * Handle the SSH-2 transport layer. + */ +static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, + struct Packet *pktin) +{ + unsigned char *in = (unsigned char *)vin; + struct do_ssh2_transport_state { + int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher; + Bignum p, g, e, f, K; + void *our_kexinit; + int our_kexinitlen; + int kex_init_value, kex_reply_value; + const struct ssh_mac **maclist; + int nmacs; + const struct ssh2_cipher *cscipher_tobe; + const struct ssh2_cipher *sccipher_tobe; + const struct ssh_mac *csmac_tobe; + const struct ssh_mac *scmac_tobe; + const struct ssh_compress *cscomp_tobe; + const struct ssh_compress *sccomp_tobe; + char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint; + int hostkeylen, siglen, rsakeylen; + void *hkey; /* actual host key */ + void *rsakey; /* for RSA kex */ + unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN]; + int n_preferred_kex; + const struct ssh_kexes *preferred_kex[KEX_MAX]; + int n_preferred_ciphers; + const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; + const struct ssh_compress *preferred_comp; + int userauth_succeeded; /* for delayed compression */ + int pending_compression; + int got_session_id, activated_authconn; + struct Packet *pktout; + int dlgret; + int guessok; + int ignorepkt; + }; + crState(do_ssh2_transport_state); + + crBegin(ssh->do_ssh2_transport_crstate); + + s->cscipher_tobe = s->sccipher_tobe = NULL; + s->csmac_tobe = s->scmac_tobe = NULL; + s->cscomp_tobe = s->sccomp_tobe = NULL; + + s->got_session_id = s->activated_authconn = FALSE; + s->userauth_succeeded = FALSE; + s->pending_compression = FALSE; + + /* + * Be prepared to work around the buggy MAC problem. + */ + if (ssh->remote_bugs & BUG_SSH2_HMAC) + s->maclist = buggymacs, s->nmacs = lenof(buggymacs); + else + s->maclist = macs, s->nmacs = lenof(macs); + + begin_key_exchange: + ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; + { + int i, j, commalist_started; + + /* + * Set up the preferred key exchange. (NULL => warn below here) + */ + s->n_preferred_kex = 0; + for (i = 0; i < KEX_MAX; i++) { + switch (ssh->cfg.ssh_kexlist[i]) { + case KEX_DHGEX: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_diffiehellman_gex; + break; + case KEX_DHGROUP14: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_diffiehellman_group14; + break; + case KEX_DHGROUP1: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_diffiehellman_group1; + break; + case KEX_RSA: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_rsa_kex; + break; + case KEX_WARN: + /* Flag for later. Don't bother if it's the last in + * the list. */ + if (i < KEX_MAX - 1) { + s->preferred_kex[s->n_preferred_kex++] = NULL; + } + break; + } + } + + /* + * Set up the preferred ciphers. (NULL => warn below here) + */ + s->n_preferred_ciphers = 0; + for (i = 0; i < CIPHER_MAX; i++) { + switch (ssh->cfg.ssh_cipherlist[i]) { + case CIPHER_BLOWFISH: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish; + break; + case CIPHER_DES: + if (ssh->cfg.ssh2_des_cbc) { + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_des; + } + break; + case CIPHER_3DES: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_3des; + break; + case CIPHER_AES: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_aes; + break; + case CIPHER_ARCFOUR: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour; + break; + case CIPHER_WARN: + /* Flag for later. Don't bother if it's the last in + * the list. */ + if (i < CIPHER_MAX - 1) { + s->preferred_ciphers[s->n_preferred_ciphers++] = NULL; + } + break; + } + } + + /* + * Set up preferred compression. + */ + if (ssh->cfg.compression) + s->preferred_comp = &ssh_zlib; + else + s->preferred_comp = &ssh_comp_none; + + /* + * Enable queueing of outgoing auth- or connection-layer + * packets while we are in the middle of a key exchange. + */ + ssh->queueing = TRUE; + + /* + * Flag that KEX is in progress. + */ + ssh->kex_in_progress = TRUE; + + /* + * Construct and send our key exchange packet. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_KEXINIT); + for (i = 0; i < 16; i++) + ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte()); + /* List key exchange algorithms. */ + ssh2_pkt_addstring_start(s->pktout); + commalist_started = 0; + for (i = 0; i < s->n_preferred_kex; i++) { + const struct ssh_kexes *k = s->preferred_kex[i]; + if (!k) continue; /* warning flag */ + for (j = 0; j < k->nkexes; j++) { + if (commalist_started) + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, k->list[j]->name); + commalist_started = 1; + } + } + /* List server host key algorithms. */ + ssh2_pkt_addstring_start(s->pktout); + for (i = 0; i < lenof(hostkey_algs); i++) { + ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name); + if (i < lenof(hostkey_algs) - 1) + ssh2_pkt_addstring_str(s->pktout, ","); + } + /* List client->server encryption algorithms. */ + ssh2_pkt_addstring_start(s->pktout); + commalist_started = 0; + for (i = 0; i < s->n_preferred_ciphers; i++) { + const struct ssh2_ciphers *c = s->preferred_ciphers[i]; + if (!c) continue; /* warning flag */ + for (j = 0; j < c->nciphers; j++) { + if (commalist_started) + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, c->list[j]->name); + commalist_started = 1; + } + } + /* List server->client encryption algorithms. */ + ssh2_pkt_addstring_start(s->pktout); + commalist_started = 0; + for (i = 0; i < s->n_preferred_ciphers; i++) { + const struct ssh2_ciphers *c = s->preferred_ciphers[i]; + if (!c) continue; /* warning flag */ + for (j = 0; j < c->nciphers; j++) { + if (commalist_started) + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, c->list[j]->name); + commalist_started = 1; + } + } + /* List client->server MAC algorithms. */ + ssh2_pkt_addstring_start(s->pktout); + for (i = 0; i < s->nmacs; i++) { + ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name); + if (i < s->nmacs - 1) + ssh2_pkt_addstring_str(s->pktout, ","); + } + /* List server->client MAC algorithms. */ + ssh2_pkt_addstring_start(s->pktout); + for (i = 0; i < s->nmacs; i++) { + ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name); + if (i < s->nmacs - 1) + ssh2_pkt_addstring_str(s->pktout, ","); + } + /* List client->server compression algorithms, + * then server->client compression algorithms. (We use the + * same set twice.) */ + for (j = 0; j < 2; j++) { + ssh2_pkt_addstring_start(s->pktout); + assert(lenof(compressions) > 1); + /* Prefer non-delayed versions */ + ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name); + /* We don't even list delayed versions of algorithms until + * they're allowed to be used, to avoid a race. See the end of + * this function. */ + if (s->userauth_succeeded && s->preferred_comp->delayed_name) { + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, + s->preferred_comp->delayed_name); + } + for (i = 0; i < lenof(compressions); i++) { + const struct ssh_compress *c = compressions[i]; + if (c != s->preferred_comp) { + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, c->name); + if (s->userauth_succeeded && c->delayed_name) { + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, c->delayed_name); + } + } + } + } + /* List client->server languages. Empty list. */ + ssh2_pkt_addstring_start(s->pktout); + /* List server->client languages. Empty list. */ + ssh2_pkt_addstring_start(s->pktout); + /* First KEX packet does _not_ follow, because we're not that brave. */ + ssh2_pkt_addbool(s->pktout, FALSE); + /* Reserved. */ + ssh2_pkt_adduint32(s->pktout, 0); + } + + s->our_kexinitlen = s->pktout->length - 5; + s->our_kexinit = snewn(s->our_kexinitlen, unsigned char); + memcpy(s->our_kexinit, s->pktout->data + 5, s->our_kexinitlen); + + ssh2_pkt_send_noqueue(ssh, s->pktout); + + if (!pktin) + crWaitUntil(pktin); + + /* + * Now examine the other side's KEXINIT to see what we're up + * to. + */ + { + char *str, *preferred; + int i, j, len; + + if (pktin->type != SSH2_MSG_KEXINIT) { + bombout(("expected key exchange packet from server")); + crStop(0); + } + ssh->kex = NULL; + ssh->hostkey = NULL; + s->cscipher_tobe = NULL; + s->sccipher_tobe = NULL; + s->csmac_tobe = NULL; + s->scmac_tobe = NULL; + s->cscomp_tobe = NULL; + s->sccomp_tobe = NULL; + s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE; + + pktin->savedpos += 16; /* skip garbage cookie */ + ssh_pkt_getstring(pktin, &str, &len); /* key exchange algorithms */ + + preferred = NULL; + for (i = 0; i < s->n_preferred_kex; i++) { + const struct ssh_kexes *k = s->preferred_kex[i]; + if (!k) { + s->warn_kex = TRUE; + } else { + for (j = 0; j < k->nkexes; j++) { + if (!preferred) preferred = k->list[j]->name; + if (in_commasep_string(k->list[j]->name, str, len)) { + ssh->kex = k->list[j]; + break; + } + } + } + if (ssh->kex) + break; + } + if (!ssh->kex) { + bombout(("Couldn't agree a key exchange algorithm (available: %s)", + str ? str : "(null)")); + crStop(0); + } + /* + * Note that the server's guess is considered wrong if it doesn't match + * the first algorithm in our list, even if it's still the algorithm + * we end up using. + */ + s->guessok = first_in_commasep_string(preferred, str, len); + ssh_pkt_getstring(pktin, &str, &len); /* host key algorithms */ + for (i = 0; i < lenof(hostkey_algs); i++) { + if (in_commasep_string(hostkey_algs[i]->name, str, len)) { + ssh->hostkey = hostkey_algs[i]; + break; + } + } + s->guessok = s->guessok && + first_in_commasep_string(hostkey_algs[0]->name, str, len); + ssh_pkt_getstring(pktin, &str, &len); /* client->server cipher */ + for (i = 0; i < s->n_preferred_ciphers; i++) { + const struct ssh2_ciphers *c = s->preferred_ciphers[i]; + if (!c) { + s->warn_cscipher = TRUE; + } else { + for (j = 0; j < c->nciphers; j++) { + if (in_commasep_string(c->list[j]->name, str, len)) { + s->cscipher_tobe = c->list[j]; + break; + } + } + } + if (s->cscipher_tobe) + break; + } + if (!s->cscipher_tobe) { + bombout(("Couldn't agree a client-to-server cipher (available: %s)", + str ? str : "(null)")); + crStop(0); + } + + ssh_pkt_getstring(pktin, &str, &len); /* server->client cipher */ + for (i = 0; i < s->n_preferred_ciphers; i++) { + const struct ssh2_ciphers *c = s->preferred_ciphers[i]; + if (!c) { + s->warn_sccipher = TRUE; + } else { + for (j = 0; j < c->nciphers; j++) { + if (in_commasep_string(c->list[j]->name, str, len)) { + s->sccipher_tobe = c->list[j]; + break; + } + } + } + if (s->sccipher_tobe) + break; + } + if (!s->sccipher_tobe) { + bombout(("Couldn't agree a server-to-client cipher (available: %s)", + str ? str : "(null)")); + crStop(0); + } + + ssh_pkt_getstring(pktin, &str, &len); /* client->server mac */ + for (i = 0; i < s->nmacs; i++) { + if (in_commasep_string(s->maclist[i]->name, str, len)) { + s->csmac_tobe = s->maclist[i]; + break; + } + } + ssh_pkt_getstring(pktin, &str, &len); /* server->client mac */ + for (i = 0; i < s->nmacs; i++) { + if (in_commasep_string(s->maclist[i]->name, str, len)) { + s->scmac_tobe = s->maclist[i]; + break; + } + } + ssh_pkt_getstring(pktin, &str, &len); /* client->server compression */ + for (i = 0; i < lenof(compressions) + 1; i++) { + const struct ssh_compress *c = + i == 0 ? s->preferred_comp : compressions[i - 1]; + if (in_commasep_string(c->name, str, len)) { + s->cscomp_tobe = c; + break; + } else if (in_commasep_string(c->delayed_name, str, len)) { + if (s->userauth_succeeded) { + s->cscomp_tobe = c; + break; + } else { + s->pending_compression = TRUE; /* try this later */ + } + } + } + ssh_pkt_getstring(pktin, &str, &len); /* server->client compression */ + for (i = 0; i < lenof(compressions) + 1; i++) { + const struct ssh_compress *c = + i == 0 ? s->preferred_comp : compressions[i - 1]; + if (in_commasep_string(c->name, str, len)) { + s->sccomp_tobe = c; + break; + } else if (in_commasep_string(c->delayed_name, str, len)) { + if (s->userauth_succeeded) { + s->sccomp_tobe = c; + break; + } else { + s->pending_compression = TRUE; /* try this later */ + } + } + } + if (s->pending_compression) { + logevent("Server supports delayed compression; " + "will try this later"); + } + ssh_pkt_getstring(pktin, &str, &len); /* client->server language */ + ssh_pkt_getstring(pktin, &str, &len); /* server->client language */ + s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok; + + if (s->warn_kex) { + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, "key-exchange algorithm", + ssh->kex->name, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for user response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at kex warning", NULL, + 0, TRUE); + crStop(0); + } + } + + if (s->warn_cscipher) { + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, + "client-to-server cipher", + s->cscipher_tobe->name, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for user response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at cipher warning", NULL, + 0, TRUE); + crStop(0); + } + } + + if (s->warn_sccipher) { + ssh_set_frozen(ssh, 1); + s->dlgret = askalg(ssh->frontend, + "server-to-client cipher", + s->sccipher_tobe->name, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for user response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at cipher warning", NULL, + 0, TRUE); + crStop(0); + } + } + + ssh->exhash = ssh->kex->hash->init(); + hash_string(ssh->kex->hash, ssh->exhash, ssh->v_c, strlen(ssh->v_c)); + hash_string(ssh->kex->hash, ssh->exhash, ssh->v_s, strlen(ssh->v_s)); + hash_string(ssh->kex->hash, ssh->exhash, + s->our_kexinit, s->our_kexinitlen); + sfree(s->our_kexinit); + if (pktin->length > 5) + hash_string(ssh->kex->hash, ssh->exhash, + pktin->data + 5, pktin->length - 5); + + if (s->ignorepkt) /* first_kex_packet_follows */ + crWaitUntil(pktin); /* Ignore packet */ + } + + if (ssh->kex->main_type == KEXTYPE_DH) { + /* + * Work out the number of bits of key we will need from the + * key exchange. We start with the maximum key length of + * either cipher... + */ + { + int csbits, scbits; + + csbits = s->cscipher_tobe->keylen; + scbits = s->sccipher_tobe->keylen; + s->nbits = (csbits > scbits ? csbits : scbits); + } + /* The keys only have hlen-bit entropy, since they're based on + * a hash. So cap the key size at hlen bits. */ + if (s->nbits > ssh->kex->hash->hlen * 8) + s->nbits = ssh->kex->hash->hlen * 8; + + /* + * If we're doing Diffie-Hellman group exchange, start by + * requesting a group. + */ + if (!ssh->kex->pdata) { + logevent("Doing Diffie-Hellman group exchange"); + ssh->pkt_kctx = SSH2_PKTCTX_DHGEX; + /* + * Work out how big a DH group we will need to allow that + * much data. + */ + s->pbits = 512 << ((s->nbits - 1) / 64); + s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST); + ssh2_pkt_adduint32(s->pktout, s->pbits); + ssh2_pkt_send_noqueue(ssh, s->pktout); + + crWaitUntil(pktin); + if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) { + bombout(("expected key exchange group packet from server")); + crStop(0); + } + s->p = ssh2_pkt_getmp(pktin); + s->g = ssh2_pkt_getmp(pktin); + if (!s->p || !s->g) { + bombout(("unable to read mp-ints from incoming group packet")); + crStop(0); + } + ssh->kex_ctx = dh_setup_gex(s->p, s->g); + s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT; + s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY; + } else { + ssh->pkt_kctx = SSH2_PKTCTX_DHGROUP; + ssh->kex_ctx = dh_setup_group(ssh->kex); + s->kex_init_value = SSH2_MSG_KEXDH_INIT; + s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; + logeventf(ssh, "Using Diffie-Hellman with standard group \"%s\"", + ssh->kex->groupname); + } + + logeventf(ssh, "Doing Diffie-Hellman key exchange with hash %s", + ssh->kex->hash->text_name); + /* + * Now generate and send e for Diffie-Hellman. + */ + set_busy_status(ssh->frontend, BUSY_CPU); /* this can take a while */ + s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2); + s->pktout = ssh2_pkt_init(s->kex_init_value); + ssh2_pkt_addmp(s->pktout, s->e); + ssh2_pkt_send_noqueue(ssh, s->pktout); + + set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */ + crWaitUntil(pktin); + if (pktin->type != s->kex_reply_value) { + bombout(("expected key exchange reply packet from server")); + crStop(0); + } + set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */ + ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); + s->f = ssh2_pkt_getmp(pktin); + if (!s->f) { + bombout(("unable to parse key exchange reply packet")); + crStop(0); + } + ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); + + s->K = dh_find_K(ssh->kex_ctx, s->f); + + /* We assume everything from now on will be quick, and it might + * involve user interaction. */ + set_busy_status(ssh->frontend, BUSY_NOT); + + hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen); + if (!ssh->kex->pdata) { + hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits); + hash_mpint(ssh->kex->hash, ssh->exhash, s->p); + hash_mpint(ssh->kex->hash, ssh->exhash, s->g); + } + hash_mpint(ssh->kex->hash, ssh->exhash, s->e); + hash_mpint(ssh->kex->hash, ssh->exhash, s->f); + + dh_cleanup(ssh->kex_ctx); + freebn(s->f); + if (!ssh->kex->pdata) { + freebn(s->g); + freebn(s->p); + } + } else { + logeventf(ssh, "Doing RSA key exchange with hash %s", + ssh->kex->hash->text_name); + ssh->pkt_kctx = SSH2_PKTCTX_RSAKEX; + /* + * RSA key exchange. First expect a KEXRSA_PUBKEY packet + * from the server. + */ + crWaitUntil(pktin); + if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) { + bombout(("expected RSA public key packet from server")); + crStop(0); + } + + ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + hash_string(ssh->kex->hash, ssh->exhash, + s->hostkeydata, s->hostkeylen); + s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); + + { + char *keydata; + ssh_pkt_getstring(pktin, &keydata, &s->rsakeylen); + s->rsakeydata = snewn(s->rsakeylen, char); + memcpy(s->rsakeydata, keydata, s->rsakeylen); + } + + s->rsakey = ssh_rsakex_newkey(s->rsakeydata, s->rsakeylen); + if (!s->rsakey) { + sfree(s->rsakeydata); + bombout(("unable to parse RSA public key from server")); + crStop(0); + } + + hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen); + + /* + * Next, set up a shared secret K, of precisely KLEN - + * 2*HLEN - 49 bits, where KLEN is the bit length of the + * RSA key modulus and HLEN is the bit length of the hash + * we're using. + */ + { + int klen = ssh_rsakex_klen(s->rsakey); + int nbits = klen - (2*ssh->kex->hash->hlen*8 + 49); + int i, byte = 0; + unsigned char *kstr1, *kstr2, *outstr; + int kstr1len, kstr2len, outstrlen; + + s->K = bn_power_2(nbits - 1); + + for (i = 0; i < nbits; i++) { + if ((i & 7) == 0) { + byte = random_byte(); + } + bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1); + } + + /* + * Encode this as an mpint. + */ + kstr1 = ssh2_mpint_fmt(s->K, &kstr1len); + kstr2 = snewn(kstr2len = 4 + kstr1len, unsigned char); + PUT_32BIT(kstr2, kstr1len); + memcpy(kstr2 + 4, kstr1, kstr1len); + + /* + * Encrypt it with the given RSA key. + */ + outstrlen = (klen + 7) / 8; + outstr = snewn(outstrlen, unsigned char); + ssh_rsakex_encrypt(ssh->kex->hash, kstr2, kstr2len, + outstr, outstrlen, s->rsakey); + + /* + * And send it off in a return packet. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_KEXRSA_SECRET); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, (char *)outstr, outstrlen); + ssh2_pkt_send_noqueue(ssh, s->pktout); + + hash_string(ssh->kex->hash, ssh->exhash, outstr, outstrlen); + + sfree(kstr2); + sfree(kstr1); + sfree(outstr); + } + + ssh_rsakex_freekey(s->rsakey); + + crWaitUntil(pktin); + if (pktin->type != SSH2_MSG_KEXRSA_DONE) { + sfree(s->rsakeydata); + bombout(("expected signature packet from server")); + crStop(0); + } + + ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); + + sfree(s->rsakeydata); + } + + hash_mpint(ssh->kex->hash, ssh->exhash, s->K); + assert(ssh->kex->hash->hlen <= sizeof(s->exchange_hash)); + ssh->kex->hash->final(ssh->exhash, s->exchange_hash); + + ssh->kex_ctx = NULL; + +#if 0 + debug(("Exchange hash is:\n")); + dmemdump(s->exchange_hash, ssh->kex->hash->hlen); +#endif + + if (!s->hkey || + !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen, + (char *)s->exchange_hash, + ssh->kex->hash->hlen)) { + bombout(("Server's host key did not match the signature supplied")); + crStop(0); + } + + /* + * Authenticate remote host: verify host key. (We've already + * checked the signature of the exchange hash.) + */ + s->keystr = ssh->hostkey->fmtkey(s->hkey); + s->fingerprint = ssh->hostkey->fingerprint(s->hkey); + ssh_set_frozen(ssh, 1); + s->dlgret = verify_ssh_host_key(ssh->frontend, + ssh->savedhost, ssh->savedport, + ssh->hostkey->keytype, s->keystr, + s->fingerprint, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user host key response")); + crStop(0); + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at host key verification", NULL, + 0, TRUE); + crStop(0); + } + if (!s->got_session_id) { /* don't bother logging this in rekeys */ + logevent("Host key fingerprint is:"); + logevent(s->fingerprint); + } + sfree(s->fingerprint); + sfree(s->keystr); + ssh->hostkey->freekey(s->hkey); + + /* + * The exchange hash from the very first key exchange is also + * the session id, used in session key construction and + * authentication. + */ + if (!s->got_session_id) { + assert(sizeof(s->exchange_hash) <= sizeof(ssh->v2_session_id)); + memcpy(ssh->v2_session_id, s->exchange_hash, + sizeof(s->exchange_hash)); + ssh->v2_session_id_len = ssh->kex->hash->hlen; + assert(ssh->v2_session_id_len <= sizeof(ssh->v2_session_id)); + s->got_session_id = TRUE; + } + + /* + * Send SSH2_MSG_NEWKEYS. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_NEWKEYS); + ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh->outgoing_data_size = 0; /* start counting from here */ + + /* + * We've sent client NEWKEYS, so create and initialise + * client-to-server session keys. + */ + if (ssh->cs_cipher_ctx) + ssh->cscipher->free_context(ssh->cs_cipher_ctx); + ssh->cscipher = s->cscipher_tobe; + ssh->cs_cipher_ctx = ssh->cscipher->make_context(); + + if (ssh->cs_mac_ctx) + ssh->csmac->free_context(ssh->cs_mac_ctx); + ssh->csmac = s->csmac_tobe; + ssh->cs_mac_ctx = ssh->csmac->make_context(); + + if (ssh->cs_comp_ctx) + ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx); + ssh->cscomp = s->cscomp_tobe; + ssh->cs_comp_ctx = ssh->cscomp->compress_init(); + + /* + * Set IVs on client-to-server keys. Here we use the exchange + * hash from the _first_ key exchange. + */ + { + unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS]; + assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'C',keyspace); + assert((ssh->cscipher->keylen+7) / 8 <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'A',keyspace); + assert(ssh->cscipher->blksize <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'E',keyspace); + assert(ssh->csmac->len <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace); + memset(keyspace, 0, sizeof(keyspace)); + } + + logeventf(ssh, "Initialised %.200s client->server encryption", + ssh->cscipher->text_name); + logeventf(ssh, "Initialised %.200s client->server MAC algorithm", + ssh->csmac->text_name); + if (ssh->cscomp->text_name) + logeventf(ssh, "Initialised %s compression", + ssh->cscomp->text_name); + + /* + * Now our end of the key exchange is complete, we can send all + * our queued higher-layer packets. + */ + ssh->queueing = FALSE; + ssh2_pkt_queuesend(ssh); + + /* + * Expect SSH2_MSG_NEWKEYS from server. + */ + crWaitUntil(pktin); + if (pktin->type != SSH2_MSG_NEWKEYS) { + bombout(("expected new-keys packet from server")); + crStop(0); + } + ssh->incoming_data_size = 0; /* start counting from here */ + + /* + * We've seen server NEWKEYS, so create and initialise + * server-to-client session keys. + */ + if (ssh->sc_cipher_ctx) + ssh->sccipher->free_context(ssh->sc_cipher_ctx); + ssh->sccipher = s->sccipher_tobe; + ssh->sc_cipher_ctx = ssh->sccipher->make_context(); + + if (ssh->sc_mac_ctx) + ssh->scmac->free_context(ssh->sc_mac_ctx); + ssh->scmac = s->scmac_tobe; + ssh->sc_mac_ctx = ssh->scmac->make_context(); + + if (ssh->sc_comp_ctx) + ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx); + ssh->sccomp = s->sccomp_tobe; + ssh->sc_comp_ctx = ssh->sccomp->decompress_init(); + + /* + * Set IVs on server-to-client keys. Here we use the exchange + * hash from the _first_ key exchange. + */ + { + unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS]; + assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'D',keyspace); + assert((ssh->sccipher->keylen+7) / 8 <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'B',keyspace); + assert(ssh->sccipher->blksize <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace); + ssh2_mkkey(ssh,s->K,s->exchange_hash,'F',keyspace); + assert(ssh->scmac->len <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); + ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace); + memset(keyspace, 0, sizeof(keyspace)); + } + logeventf(ssh, "Initialised %.200s server->client encryption", + ssh->sccipher->text_name); + logeventf(ssh, "Initialised %.200s server->client MAC algorithm", + ssh->scmac->text_name); + if (ssh->sccomp->text_name) + logeventf(ssh, "Initialised %s decompression", + ssh->sccomp->text_name); + + /* + * Free shared secret. + */ + freebn(s->K); + + /* + * Key exchange is over. Loop straight back round if we have a + * deferred rekey reason. + */ + if (ssh->deferred_rekey_reason) { + logevent(ssh->deferred_rekey_reason); + pktin = NULL; + ssh->deferred_rekey_reason = NULL; + goto begin_key_exchange; + } + + /* + * Otherwise, schedule a timer for our next rekey. + */ + ssh->kex_in_progress = FALSE; + ssh->last_rekey = GETTICKCOUNT(); + if (ssh->cfg.ssh_rekey_time != 0) + ssh->next_rekey = schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC, + ssh2_timer, ssh); + + /* + * If this is the first key exchange phase, we must pass the + * SSH2_MSG_NEWKEYS packet to the next layer, not because it + * wants to see it but because it will need time to initialise + * itself before it sees an actual packet. In subsequent key + * exchange phases, we don't pass SSH2_MSG_NEWKEYS on, because + * it would only confuse the layer above. + */ + if (s->activated_authconn) { + crReturn(0); + } + s->activated_authconn = TRUE; + + /* + * Now we're encrypting. Begin returning 1 to the protocol main + * function so that other things can run on top of the + * transport. If we ever see a KEXINIT, we must go back to the + * start. + * + * We _also_ go back to the start if we see pktin==NULL and + * inlen negative, because this is a special signal meaning + * `initiate client-driven rekey', and `in' contains a message + * giving the reason for the rekey. + * + * inlen==-1 means always initiate a rekey; + * inlen==-2 means that userauth has completed successfully and + * we should consider rekeying (for delayed compression). + */ + while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) || + (!pktin && inlen < 0))) { + wait_for_rekey: + crReturn(1); + } + if (pktin) { + logevent("Server initiated key re-exchange"); + } else { + if (inlen == -2) { + /* + * authconn has seen a USERAUTH_SUCCEEDED. Time to enable + * delayed compression, if it's available. + * + * draft-miller-secsh-compression-delayed-00 says that you + * negotiate delayed compression in the first key exchange, and + * both sides start compressing when the server has sent + * USERAUTH_SUCCESS. This has a race condition -- the server + * can't know when the client has seen it, and thus which incoming + * packets it should treat as compressed. + * + * Instead, we do the initial key exchange without offering the + * delayed methods, but note if the server offers them; when we + * get here, if a delayed method was available that was higher + * on our list than what we got, we initiate a rekey in which we + * _do_ list the delayed methods (and hopefully get it as a + * result). Subsequent rekeys will do the same. + */ + assert(!s->userauth_succeeded); /* should only happen once */ + s->userauth_succeeded = TRUE; + if (!s->pending_compression) + /* Can't see any point rekeying. */ + goto wait_for_rekey; /* this is utterly horrid */ + /* else fall through to rekey... */ + s->pending_compression = FALSE; + } + /* + * Now we've decided to rekey. + * + * Special case: if the server bug is set that doesn't + * allow rekeying, we give a different log message and + * continue waiting. (If such a server _initiates_ a rekey, + * we process it anyway!) + */ + if ((ssh->remote_bugs & BUG_SSH2_REKEY)) { + logeventf(ssh, "Server bug prevents key re-exchange (%s)", + (char *)in); + /* Reset the counters, so that at least this message doesn't + * hit the event log _too_ often. */ + ssh->outgoing_data_size = 0; + ssh->incoming_data_size = 0; + if (ssh->cfg.ssh_rekey_time != 0) { + ssh->next_rekey = + schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC, + ssh2_timer, ssh); + } + goto wait_for_rekey; /* this is still utterly horrid */ + } else { + logeventf(ssh, "Initiating key re-exchange (%s)", (char *)in); + } + } + goto begin_key_exchange; + + crFinish(1); +} + +/* + * Add data to an SSH-2 channel output buffer. + */ +static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, + int len) +{ + bufchain_add(&c->v.v2.outbuffer, buf, len); +} + +/* + * Attempt to send data on an SSH-2 channel. + */ +static int ssh2_try_send(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + struct Packet *pktout; + + while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) { + int len; + void *data; + bufchain_prefix(&c->v.v2.outbuffer, &data, &len); + if ((unsigned)len > c->v.v2.remwindow) + len = c->v.v2.remwindow; + if ((unsigned)len > c->v.v2.remmaxpkt) + len = c->v.v2.remmaxpkt; + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_addstring_start(pktout); + dont_log_data(ssh, pktout, PKTLOG_OMIT); + ssh2_pkt_addstring_data(pktout, data, len); + end_log_omission(ssh, pktout); + ssh2_pkt_send(ssh, pktout); + bufchain_consume(&c->v.v2.outbuffer, len); + c->v.v2.remwindow -= len; + } + + /* + * After having sent as much data as we can, return the amount + * still buffered. + */ + return bufchain_size(&c->v.v2.outbuffer); +} + +static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c) +{ + int bufsize; + if (c->closes) + return; /* don't send on closing channels */ + bufsize = ssh2_try_send(c); + if (bufsize == 0) { + switch (c->type) { + case CHAN_MAINSESSION: + /* stdin need not receive an unthrottle + * notification since it will be polled */ + break; + case CHAN_X11: + x11_unthrottle(c->u.x11.s); + break; + case CHAN_AGENT: + /* agent sockets are request/response and need no + * buffer management */ + break; + case CHAN_SOCKDATA: + pfd_unthrottle(c->u.pfd.s); + break; + } + } + + /* + * If we've emptied the channel's output buffer and there's a + * pending close event, start the channel-closing procedure. + */ + if (c->pending_close && bufchain_size(&c->v.v2.outbuffer) == 0) { + struct Packet *pktout; + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + c->closes = 1; + c->pending_close = FALSE; + } +} + +/* + * Set up most of a new ssh_channel for SSH-2. + */ +static void ssh2_channel_init(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + c->localid = alloc_channel_id(ssh); + c->closes = 0; + c->pending_close = FALSE; + c->throttling_conn = FALSE; + c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = + ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; + c->v.v2.winadj_head = c->v.v2.winadj_tail = NULL; + c->v.v2.throttle_state = UNTHROTTLED; + bufchain_init(&c->v.v2.outbuffer); +} + +/* + * Potentially enlarge the window on an SSH-2 channel. + */ +static void ssh2_set_window(struct ssh_channel *c, int newwin) +{ + Ssh ssh = c->ssh; + + /* + * Never send WINDOW_ADJUST for a channel that the remote side + * already thinks it's closed; there's no point, since it won't + * be sending any more data anyway. + */ + if (c->closes != 0) + return; + + /* + * If the remote end has a habit of ignoring maxpkt, limit the + * window so that it has no choice (assuming it doesn't ignore the + * window as well). + */ + if ((ssh->remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT) + newwin = OUR_V2_MAXPKT; + + + /* + * Only send a WINDOW_ADJUST if there's significantly more window + * available than the other end thinks there is. This saves us + * sending a WINDOW_ADJUST for every character in a shell session. + * + * "Significant" is arbitrarily defined as half the window size. + */ + if (newwin / 2 >= c->v.v2.locwindow) { + struct Packet *pktout; + struct winadj *wa; + + /* + * 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. + * + * We also only send this if the main channel has finished its + * initial CHANNEL_REQUESTs and installed the default + * CHANNEL_FAILURE handler, so as not to risk giving it + * unexpected CHANNEL_FAILUREs. + */ + if (newwin == c->v.v2.locmaxwin && + ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE]) { + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_addstring(pktout, "winadj@putty.projects.tartarus.org"); + ssh2_pkt_addbool(pktout, TRUE); + ssh2_pkt_send(ssh, pktout); + + /* + * CHANNEL_FAILURE doesn't come with any indication of + * what message caused it, so we have to keep track of the + * outstanding CHANNEL_REQUESTs ourselves. + */ + wa = snew(struct winadj); + wa->size = newwin - c->v.v2.locwindow; + wa->next = NULL; + if (!c->v.v2.winadj_head) + c->v.v2.winadj_head = wa; + else + c->v.v2.winadj_tail->next = wa; + c->v.v2.winadj_tail = wa; + if (c->v.v2.throttle_state != UNTHROTTLED) + c->v.v2.throttle_state = UNTHROTTLING; + } else { + /* Pretend the WINDOW_ADJUST was acked immediately. */ + c->v.v2.remlocwin = newwin; + c->v.v2.throttle_state = THROTTLED; + } + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_adduint32(pktout, newwin - c->v.v2.locwindow); + ssh2_pkt_send(ssh, pktout); + c->v.v2.locwindow = newwin; + } +} + +/* + * Find the channel associated with a message. If there's no channel, + * or it's not properly open, make a noise about it and return NULL. + */ +static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin) +{ + unsigned localid = ssh_pkt_getuint32(pktin); + struct ssh_channel *c; + + c = find234(ssh->channels, &localid, ssh_channelfind); + if (!c || + (c->halfopen && pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION && + pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) { + char *buf = dupprintf("Received %s for %s channel %u", + ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, + pktin->type), + c ? "half-open" : "nonexistent", localid); + ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); + sfree(buf); + return NULL; + } + return c; +} + +static int ssh2_handle_winadj_response(struct ssh_channel *c) +{ + struct winadj *wa = c->v.v2.winadj_head; + if (!wa) + return FALSE; + c->v.v2.winadj_head = wa->next; + c->v.v2.remlocwin += wa->size; + sfree(wa); + /* + * winadj messages are only sent when the window is fully open, so + * if we get an ack of one, we know any pending unthrottle is + * complete. + */ + if (c->v.v2.throttle_state == UNTHROTTLING) + c->v.v2.throttle_state = UNTHROTTLED; + return TRUE; +} + +static void ssh2_msg_channel_success(Ssh ssh, struct Packet *pktin) +{ + /* + * This should never get called. All channel requests are either + * sent with want_reply false, are sent before this handler gets + * installed, or are "winadj@putty" requests, which servers should + * never respond to with success. + * + * 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 handle it just the same as the + * expected FAILURE. + */ + struct ssh_channel *c; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (!ssh2_handle_winadj_response(c)) + ssh_disconnect(ssh, NULL, + "Received unsolicited SSH_MSG_CHANNEL_SUCCESS", + SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); +} + +static void ssh2_msg_channel_failure(Ssh ssh, struct Packet *pktin) +{ + /* + * The only time this should get called is for "winadj@putty" + * messages sent above. All other channel requests are either + * sent with want_reply false or are sent before this handler gets + * installed. + */ + struct ssh_channel *c; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (!ssh2_handle_winadj_response(c)) + ssh_disconnect(ssh, NULL, + "Received unsolicited SSH_MSG_CHANNEL_FAILURE", + SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); +} + +static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin) +{ + struct ssh_channel *c; + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (!c->closes) { + c->v.v2.remwindow += ssh_pkt_getuint32(pktin); + ssh2_try_send_and_unthrottle(ssh, c); + } +} + +static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin) +{ + char *data; + int length; + struct ssh_channel *c; + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA && + ssh_pkt_getuint32(pktin) != SSH2_EXTENDED_DATA_STDERR) + return; /* extended but not stderr */ + ssh_pkt_getstring(pktin, &data, &length); + if (data) { + int bufsize = 0; + c->v.v2.locwindow -= length; + c->v.v2.remlocwin -= length; + switch (c->type) { + case CHAN_MAINSESSION: + bufsize = + from_backend(ssh->frontend, pktin->type == + SSH2_MSG_CHANNEL_EXTENDED_DATA, + data, length); + break; + case CHAN_X11: + bufsize = x11_send(c->u.x11.s, data, length); + break; + case CHAN_SOCKDATA: + bufsize = pfd_send(c->u.pfd.s, data, length); + break; + case CHAN_AGENT: + while (length > 0) { + if (c->u.a.lensofar < 4) { + unsigned int l = min(4 - c->u.a.lensofar, + (unsigned)length); + memcpy(c->u.a.msglen + c->u.a.lensofar, + data, l); + data += l; + length -= l; + c->u.a.lensofar += l; + } + if (c->u.a.lensofar == 4) { + c->u.a.totallen = + 4 + GET_32BIT(c->u.a.msglen); + c->u.a.message = snewn(c->u.a.totallen, + unsigned char); + memcpy(c->u.a.message, c->u.a.msglen, 4); + } + if (c->u.a.lensofar >= 4 && length > 0) { + unsigned int l = + min(c->u.a.totallen - c->u.a.lensofar, + (unsigned)length); + memcpy(c->u.a.message + c->u.a.lensofar, + data, l); + data += l; + length -= l; + c->u.a.lensofar += l; + } + if (c->u.a.lensofar == c->u.a.totallen) { + void *reply; + int replylen; + if (agent_query(c->u.a.message, + c->u.a.totallen, + &reply, &replylen, + ssh_agentf_callback, c)) + ssh_agentf_callback(c, reply, replylen); + sfree(c->u.a.message); + c->u.a.lensofar = 0; + } + } + bufsize = 0; + break; + } + /* + * If it looks like the remote end hit the end of its window, + * and we didn't want it to do that, think about using a + * larger window. + */ + if (c->v.v2.remlocwin <= 0 && c->v.v2.throttle_state == UNTHROTTLED && + c->v.v2.locmaxwin < 0x40000000) + c->v.v2.locmaxwin += OUR_V2_WINSIZE; + /* + * If we are not buffering too much data, + * enlarge the window again at the remote side. + * If we are buffering too much, we may still + * need to adjust the window if the server's + * sent excess data. + */ + ssh2_set_window(c, bufsize < c->v.v2.locmaxwin ? + c->v.v2.locmaxwin - bufsize : 0); + /* + * If we're either buffering way too much data, or if we're + * buffering anything at all and we're in "simple" mode, + * throttle the whole channel. + */ + if ((bufsize > c->v.v2.locmaxwin || + (ssh->cfg.ssh_simple && bufsize > 0)) && + !c->throttling_conn) { + c->throttling_conn = 1; + ssh_throttle_conn(ssh, +1); + } + } +} + +static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin) +{ + struct ssh_channel *c; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + + if (c->type == CHAN_X11) { + /* + * Remote EOF on an X11 channel means we should + * wrap up and close the channel ourselves. + */ + x11_close(c->u.x11.s); + c->u.x11.s = NULL; + sshfwd_close(c); + } else if (c->type == CHAN_AGENT) { + sshfwd_close(c); + } else if (c->type == CHAN_SOCKDATA) { + pfd_close(c->u.pfd.s); + c->u.pfd.s = NULL; + sshfwd_close(c); + } +} + +static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin) +{ + struct ssh_channel *c; + struct Packet *pktout; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + /* Do pre-close processing on the channel. */ + switch (c->type) { + case CHAN_MAINSESSION: + ssh->mainchan = NULL; + update_specials_menu(ssh->frontend); + break; + case CHAN_X11: + if (c->u.x11.s != NULL) + x11_close(c->u.x11.s); + sshfwd_close(c); + break; + case CHAN_AGENT: + sshfwd_close(c); + break; + case CHAN_SOCKDATA: + if (c->u.pfd.s != NULL) + pfd_close(c->u.pfd.s); + sshfwd_close(c); + break; + } + if (c->closes == 0) { + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + } + del234(ssh->channels, c); + bufchain_clear(&c->v.v2.outbuffer); + sfree(c); + + /* + * See if that was the last channel left open. + * (This is only our termination condition if we're + * not running in -N mode.) + */ + if (!ssh->cfg.ssh_no_shell && count234(ssh->channels) == 0) { + /* + * We used to send SSH_MSG_DISCONNECT here, + * because I'd believed that _every_ conforming + * SSH-2 connection had to end with a disconnect + * being sent by at least one side; apparently + * I was wrong and it's perfectly OK to + * unceremoniously slam the connection shut + * when you're done, and indeed OpenSSH feels + * this is more polite than sending a + * DISCONNECT. So now we don't. + */ + ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE); + } +} + +static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) +{ + struct ssh_channel *c; + struct Packet *pktout; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (c->type != CHAN_SOCKDATA_DORMANT) + return; /* dunno why they're confirming this */ + c->remoteid = ssh_pkt_getuint32(pktin); + c->halfopen = FALSE; + c->type = CHAN_SOCKDATA; + c->v.v2.remwindow = ssh_pkt_getuint32(pktin); + c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); + if (c->u.pfd.s) + pfd_confirm(c->u.pfd.s); + if (c->closes) { + /* + * We have a pending close on this channel, + * which we decided on before the server acked + * the channel open. So now we know the + * remoteid, we can close it again. + */ + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + } +} + +static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) +{ + static const char *const reasons[] = { + "", + "Administratively prohibited", + "Connect failed", + "Unknown channel type", + "Resource shortage", + }; + unsigned reason_code; + char *reason_string; + int reason_length; + struct ssh_channel *c; + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + if (c->type != CHAN_SOCKDATA_DORMANT) + return; /* dunno why they're failing this */ + + reason_code = ssh_pkt_getuint32(pktin); + if (reason_code >= lenof(reasons)) + reason_code = 0; /* ensure reasons[reason_code] in range */ + ssh_pkt_getstring(pktin, &reason_string, &reason_length); + logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]", + reasons[reason_code], reason_length, reason_string); + + pfd_close(c->u.pfd.s); + + del234(ssh->channels, c); + sfree(c); +} + +static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin) +{ + char *type; + int typelen, want_reply; + int reply = SSH2_MSG_CHANNEL_FAILURE; /* default */ + struct ssh_channel *c; + struct Packet *pktout; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + ssh_pkt_getstring(pktin, &type, &typelen); + want_reply = ssh2_pkt_getbool(pktin); + + /* + * Having got the channel number, we now look at + * the request type string to see if it's something + * we recognise. + */ + if (c == ssh->mainchan) { + /* + * We recognise "exit-status" and "exit-signal" on + * the primary channel. + */ + if (typelen == 11 && + !memcmp(type, "exit-status", 11)) { + + ssh->exitcode = ssh_pkt_getuint32(pktin); + logeventf(ssh, "Server sent command exit status %d", + ssh->exitcode); + reply = SSH2_MSG_CHANNEL_SUCCESS; + + } else if (typelen == 11 && + !memcmp(type, "exit-signal", 11)) { + + int is_plausible = TRUE, is_int = FALSE; + char *fmt_sig = "", *fmt_msg = ""; + char *msg; + int msglen = 0, core = FALSE; + /* ICK: older versions of OpenSSH (e.g. 3.4p1) + * provide an `int' for the signal, despite its + * having been a `string' in the drafts of RFC 4254 since at + * least 2001. (Fixed in session.c 1.147.) Try to + * infer which we can safely parse it as. */ + { + unsigned char *p = pktin->body + + pktin->savedpos; + long len = pktin->length - pktin->savedpos; + unsigned long num = GET_32BIT(p); /* what is it? */ + /* If it's 0, it hardly matters; assume string */ + if (num == 0) { + is_int = FALSE; + } else { + int maybe_int = FALSE, maybe_str = FALSE; +#define CHECK_HYPOTHESIS(offset, result) \ + do { \ + long q = offset; \ + if (q >= 0 && q+4 <= len) { \ + q = q + 4 + GET_32BIT(p+q); \ + if (q >= 0 && q+4 <= len && \ + ((q = q + 4 + GET_32BIT(p+q))!= 0) && q == len) \ + result = TRUE; \ + } \ + } while(0) + CHECK_HYPOTHESIS(4+1, maybe_int); + CHECK_HYPOTHESIS(4+num+1, maybe_str); +#undef CHECK_HYPOTHESIS + if (maybe_int && !maybe_str) + is_int = TRUE; + else if (!maybe_int && maybe_str) + is_int = FALSE; + else + /* Crikey. Either or neither. Panic. */ + is_plausible = FALSE; + } + } + ssh->exitcode = 128; /* means `unknown signal' */ + if (is_plausible) { + if (is_int) { + /* Old non-standard OpenSSH. */ + int signum = ssh_pkt_getuint32(pktin); + fmt_sig = dupprintf(" %d", signum); + ssh->exitcode = 128 + signum; + } else { + /* As per RFC 4254. */ + char *sig; + int siglen; + ssh_pkt_getstring(pktin, &sig, &siglen); + /* Signal name isn't supposed to be blank, but + * let's cope gracefully if it is. */ + if (siglen) { + fmt_sig = dupprintf(" \"%.*s\"", + siglen, sig); + } + + /* + * Really hideous method of translating the + * signal description back into a locally + * meaningful number. + */ + + if (0) + ; +#define TRANSLATE_SIGNAL(s) \ + else if (siglen == lenof(#s)-1 && !memcmp(sig, #s, siglen)) \ + ssh->exitcode = 128 + SIG ## s +#ifdef SIGABRT + TRANSLATE_SIGNAL(ABRT); +#endif +#ifdef SIGALRM + TRANSLATE_SIGNAL(ALRM); +#endif +#ifdef SIGFPE + TRANSLATE_SIGNAL(FPE); +#endif +#ifdef SIGHUP + TRANSLATE_SIGNAL(HUP); +#endif +#ifdef SIGILL + TRANSLATE_SIGNAL(ILL); +#endif +#ifdef SIGINT + TRANSLATE_SIGNAL(INT); +#endif +#ifdef SIGKILL + TRANSLATE_SIGNAL(KILL); +#endif +#ifdef SIGPIPE + TRANSLATE_SIGNAL(PIPE); +#endif +#ifdef SIGQUIT + TRANSLATE_SIGNAL(QUIT); +#endif +#ifdef SIGSEGV + TRANSLATE_SIGNAL(SEGV); +#endif +#ifdef SIGTERM + TRANSLATE_SIGNAL(TERM); +#endif +#ifdef SIGUSR1 + TRANSLATE_SIGNAL(USR1); +#endif +#ifdef SIGUSR2 + TRANSLATE_SIGNAL(USR2); +#endif +#undef TRANSLATE_SIGNAL + else + ssh->exitcode = 128; + } + core = ssh2_pkt_getbool(pktin); + ssh_pkt_getstring(pktin, &msg, &msglen); + if (msglen) { + fmt_msg = dupprintf(" (\"%.*s\")", msglen, msg); + } + /* ignore lang tag */ + } /* else don't attempt to parse */ + logeventf(ssh, "Server exited on signal%s%s%s", + fmt_sig, core ? " (core dumped)" : "", + fmt_msg); + if (*fmt_sig) sfree(fmt_sig); + if (*fmt_msg) sfree(fmt_msg); + reply = SSH2_MSG_CHANNEL_SUCCESS; + + } + } else { + /* + * This is a channel request we don't know + * about, so we now either ignore the request + * or respond with CHANNEL_FAILURE, depending + * on want_reply. + */ + reply = SSH2_MSG_CHANNEL_FAILURE; + } + if (want_reply) { + pktout = ssh2_pkt_init(reply); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + } +} + +static void ssh2_msg_global_request(Ssh ssh, struct Packet *pktin) +{ + char *type; + int typelen, want_reply; + struct Packet *pktout; + + ssh_pkt_getstring(pktin, &type, &typelen); + want_reply = ssh2_pkt_getbool(pktin); + + /* + * We currently don't support any global requests + * at all, so we either ignore the request or + * respond with REQUEST_FAILURE, depending on + * want_reply. + */ + if (want_reply) { + pktout = ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE); + ssh2_pkt_send(ssh, pktout); + } +} + +static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) +{ + char *type; + int typelen; + char *peeraddr; + int peeraddrlen; + int peerport; + char *error = NULL; + struct ssh_channel *c; + unsigned remid, winsize, pktsize; + struct Packet *pktout; + + ssh_pkt_getstring(pktin, &type, &typelen); + c = snew(struct ssh_channel); + c->ssh = ssh; + + remid = ssh_pkt_getuint32(pktin); + winsize = ssh_pkt_getuint32(pktin); + pktsize = ssh_pkt_getuint32(pktin); + + if (typelen == 3 && !memcmp(type, "x11", 3)) { + char *addrstr; + const char *x11err; + + ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen); + addrstr = snewn(peeraddrlen+1, char); + memcpy(addrstr, peeraddr, peeraddrlen); + addrstr[peeraddrlen] = '\0'; + peerport = ssh_pkt_getuint32(pktin); + + logeventf(ssh, "Received X11 connect request from %s:%d", + addrstr, peerport); + + if (!ssh->X11_fwd_enabled) + error = "X11 forwarding is not enabled"; + else if ((x11err = x11_init(&c->u.x11.s, ssh->x11disp, c, + addrstr, peerport, &ssh->cfg)) != NULL) { + logeventf(ssh, "Local X11 connection failed: %s", x11err); + error = "Unable to open an X11 connection"; + } else { + logevent("Opening X11 forward connection succeeded"); + c->type = CHAN_X11; + } + + sfree(addrstr); + } else if (typelen == 15 && + !memcmp(type, "forwarded-tcpip", 15)) { + struct ssh_rportfwd pf, *realpf; + char *dummy; + int dummylen; + ssh_pkt_getstring(pktin, &dummy, &dummylen);/* skip address */ + pf.sport = ssh_pkt_getuint32(pktin); + ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen); + peerport = ssh_pkt_getuint32(pktin); + realpf = find234(ssh->rportfwds, &pf, NULL); + logeventf(ssh, "Received remote port %d open request " + "from %s:%d", pf.sport, peeraddr, peerport); + if (realpf == NULL) { + error = "Remote port is not recognised"; + } else { + const char *e = pfd_newconnect(&c->u.pfd.s, + realpf->dhost, + realpf->dport, c, + &ssh->cfg, + realpf->pfrec->addressfamily); + logeventf(ssh, "Attempting to forward remote port to " + "%s:%d", realpf->dhost, realpf->dport); + if (e != NULL) { + logeventf(ssh, "Port open failed: %s", e); + error = "Port open failed"; + } else { + logevent("Forwarded port opened successfully"); + c->type = CHAN_SOCKDATA; + } + } + } else if (typelen == 22 && + !memcmp(type, "auth-agent@openssh.com", 22)) { + if (!ssh->agentfwd_enabled) + error = "Agent forwarding is not enabled"; + else { + c->type = CHAN_AGENT; /* identify channel type */ + c->u.a.lensofar = 0; + } + } else { + error = "Unsupported channel type requested"; + } + + c->remoteid = remid; + c->halfopen = FALSE; + if (error) { + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_adduint32(pktout, SSH2_OPEN_CONNECT_FAILED); + ssh2_pkt_addstring(pktout, error); + ssh2_pkt_addstring(pktout, "en"); /* language tag */ + ssh2_pkt_send(ssh, pktout); + logeventf(ssh, "Rejected channel open: %s", error); + sfree(c); + } else { + ssh2_channel_init(c); + c->v.v2.remwindow = winsize; + c->v.v2.remmaxpkt = pktsize; + add234(ssh->channels, c); + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_adduint32(pktout, c->localid); + ssh2_pkt_adduint32(pktout, c->v.v2.locwindow); + ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ + ssh2_pkt_send(ssh, pktout); + } +} + +/* + * Buffer banner messages for later display at some convenient point, + * if we're going to display them. + */ +static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin) +{ + /* Arbitrary limit to prevent unbounded inflation of buffer */ + if (ssh->cfg.ssh_show_banner && + bufchain_size(&ssh->banner) <= 131072) { + char *banner = NULL; + int size = 0; + ssh_pkt_getstring(pktin, &banner, &size); + if (banner) + bufchain_add(&ssh->banner, banner, size); + } +} + +/* Helper function to deal with sending tty modes for "pty-req" */ +static void ssh2_send_ttymode(void *data, char *mode, char *val) +{ + struct Packet *pktout = (struct Packet *)data; + int i = 0; + unsigned int arg = 0; + while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++; + if (i == lenof(ssh_ttymodes)) return; + switch (ssh_ttymodes[i].type) { + case TTY_OP_CHAR: + arg = ssh_tty_parse_specchar(val); + break; + case TTY_OP_BOOL: + arg = ssh_tty_parse_boolean(val); + break; + } + ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode); + ssh2_pkt_adduint32(pktout, arg); +} + +/* + * Handle the SSH-2 userauth and connection layers. + */ +static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin) +{ + struct do_ssh2_authconn_state { + enum { + AUTH_TYPE_NONE, + AUTH_TYPE_PUBLICKEY, + AUTH_TYPE_PUBLICKEY_OFFER_LOUD, + AUTH_TYPE_PUBLICKEY_OFFER_QUIET, + AUTH_TYPE_PASSWORD, + AUTH_TYPE_GSSAPI, + AUTH_TYPE_KEYBOARD_INTERACTIVE, + AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET + } type; + int done_service_req; + int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter; + int tried_pubkey_config, done_agent; +#ifndef NO_GSSAPI + int can_gssapi; + int tried_gssapi; +#endif + int kbd_inter_refused; + int we_are_in, userauth_success; + prompts_t *cur_prompt; + int num_prompts; + char username[100]; + char *password; + int got_username; + void *publickey_blob; + int publickey_bloblen; + int publickey_encrypted; + char *publickey_algorithm; + char *publickey_comment; + unsigned char agent_request[5], *agent_response, *agentp; + int agent_responselen; + unsigned char *pkblob_in_agent; + int keyi, nkeys; + char *pkblob, *alg, *commentp; + int pklen, alglen, commentlen; + int siglen, retlen, len; + char *q, *agentreq, *ret; + int try_send; + int num_env, env_left, env_ok; + struct Packet *pktout; +#ifndef NO_GSSAPI + struct ssh_gss_library *gsslib; + Ssh_gss_ctx gss_ctx; + Ssh_gss_buf gss_buf; + Ssh_gss_buf gss_rcvtok, gss_sndtok; + Ssh_gss_name gss_srv_name; + Ssh_gss_stat gss_stat; +#endif + }; + crState(do_ssh2_authconn_state); + + crBegin(ssh->do_ssh2_authconn_crstate); + + s->done_service_req = FALSE; + s->we_are_in = s->userauth_success = FALSE; +#ifndef NO_GSSAPI + s->tried_gssapi = FALSE; +#endif + + if (!ssh->cfg.ssh_no_userauth) { + /* + * Request userauth protocol, and await a response to it. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); + ssh2_pkt_addstring(s->pktout, "ssh-userauth"); + ssh2_pkt_send(ssh, s->pktout); + crWaitUntilV(pktin); + if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) + s->done_service_req = TRUE; + } + if (!s->done_service_req) { + /* + * Request connection protocol directly, without authentication. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + ssh2_pkt_send(ssh, s->pktout); + crWaitUntilV(pktin); + if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) { + s->we_are_in = TRUE; /* no auth required */ + } else { + bombout(("Server refused service request")); + crStopV; + } + } + + /* Arrange to be able to deal with any BANNERs that come in. + * (We do this now as packets may come in during the next bit.) */ + bufchain_init(&ssh->banner); + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = + ssh2_msg_userauth_banner; + + /* + * Misc one-time setup for authentication. + */ + s->publickey_blob = NULL; + if (!s->we_are_in) { + + /* + * Load the public half of any configured public key file + * for later use. + */ + if (!filename_is_null(ssh->cfg.keyfile)) { + int keytype; + logeventf(ssh, "Reading private key file \"%.150s\"", + filename_to_str(&ssh->cfg.keyfile)); + keytype = key_type(&ssh->cfg.keyfile); + if (keytype == SSH_KEYTYPE_SSH2) { + const char *error; + s->publickey_blob = + ssh2_userkey_loadpub(&ssh->cfg.keyfile, + &s->publickey_algorithm, + &s->publickey_bloblen, + &s->publickey_comment, &error); + if (s->publickey_blob) { + s->publickey_encrypted = + ssh2_userkey_encrypted(&ssh->cfg.keyfile, NULL); + } else { + char *msgbuf; + logeventf(ssh, "Unable to load private key (%s)", + error); + msgbuf = dupprintf("Unable to load private key file " + "\"%.150s\" (%s)\r\n", + filename_to_str(&ssh->cfg.keyfile), + error); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + } + } else { + char *msgbuf; + logeventf(ssh, "Unable to use this key file (%s)", + key_type_to_str(keytype)); + msgbuf = dupprintf("Unable to use key file \"%.150s\"" + " (%s)\r\n", + filename_to_str(&ssh->cfg.keyfile), + key_type_to_str(keytype)); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + s->publickey_blob = NULL; + } + } + + /* + * Find out about any keys Pageant has (but if there's a + * public key configured, filter out all others). + */ + s->nkeys = 0; + s->agent_response = NULL; + s->pkblob_in_agent = NULL; + if (ssh->cfg.tryagent && agent_exists()) { + + void *r; + + logevent("Pageant is running. Requesting keys."); + + /* Request the keys held by the agent. */ + PUT_32BIT(s->agent_request, 1); + s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES; + if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen, + ssh_agent_callback, ssh)) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for agent response")); + crStopV; + } + } while (pktin || inlen > 0); + r = ssh->agent_response; + s->agent_responselen = ssh->agent_response_len; + } + s->agent_response = (unsigned char *) r; + if (s->agent_response && s->agent_responselen >= 5 && + s->agent_response[4] == SSH2_AGENT_IDENTITIES_ANSWER) { + int keyi; + unsigned char *p; + p = s->agent_response + 5; + s->nkeys = GET_32BIT(p); + p += 4; + logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys); + if (s->publickey_blob) { + /* See if configured key is in agent. */ + for (keyi = 0; keyi < s->nkeys; keyi++) { + s->pklen = GET_32BIT(p); + if (s->pklen == s->publickey_bloblen && + !memcmp(p+4, s->publickey_blob, + s->publickey_bloblen)) { + logeventf(ssh, "Pageant key #%d matches " + "configured key file", keyi); + s->keyi = keyi; + s->pkblob_in_agent = p; + break; + } + p += 4 + s->pklen; + p += GET_32BIT(p) + 4; /* comment */ + } + if (!s->pkblob_in_agent) { + logevent("Configured key file not in Pageant"); + s->nkeys = 0; + } + } + } else { + logevent("Failed to get reply from Pageant"); + } + } + + } + + /* + * 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->username[0] = '\0'; + s->got_username = FALSE; + while (!s->we_are_in) { + /* + * Get a username. + */ + if (s->got_username && !ssh->cfg.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 (!get_remote_username(&ssh->cfg, s->username, + sizeof(s->username))) { + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH login name"); + add_prompt(s->cur_prompt, dupstr("login as: "), TRUE, + lenof(s->username)); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * get_userpass_input() failed to get a username. + * Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); + crStopV; + } + memcpy(s->username, s->cur_prompt->prompts[0]->result, + lenof(s->username)); + free_prompts(s->cur_prompt); + } else { + char *stuff; + if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) { + stuff = dupprintf("Using username \"%s\".\r\n", s->username); + c_write_str(ssh, stuff); + sfree(stuff); + } + } + s->got_username = TRUE; + + /* + * Send an authentication request using method "none": (a) + * just in case it succeeds, and (b) so that we know what + * authentication methods we can usefully try next. + */ + ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; + + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection");/* service requested */ + ssh2_pkt_addstring(s->pktout, "none"); /* method */ + ssh2_pkt_send(ssh, s->pktout); + s->type = AUTH_TYPE_NONE; + s->gotit = FALSE; + s->we_are_in = FALSE; + + s->tried_pubkey_config = FALSE; + s->kbd_inter_refused = FALSE; + + /* Reset agent request state. */ + s->done_agent = FALSE; + if (s->agent_response) { + if (s->pkblob_in_agent) { + s->agentp = s->pkblob_in_agent; + } else { + s->agentp = s->agent_response + 5 + 4; + s->keyi = 0; + } + } + + while (1) { + char *methods = NULL; + int methlen = 0; + + /* + * Wait for the result of the last authentication request. + */ + if (!s->gotit) + crWaitUntilV(pktin); + /* + * Now is a convenient point to spew any banner material + * that we've accumulated. (This should ensure that when + * we exit the auth loop, we haven't any left to deal + * with.) + */ + { + int size = bufchain_size(&ssh->banner); + /* + * Don't show the banner if we're operating in + * non-verbose non-interactive mode. (It's probably + * a script, which means nobody will read the + * banner _anyway_, and moreover the printing of + * the banner will screw up processing on the + * output of (say) plink.) + */ + if (size && (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) { + char *banner = snewn(size, char); + bufchain_fetch(&ssh->banner, banner, size); + c_write_untrusted(ssh, banner, size); + sfree(banner); + } + bufchain_clear(&ssh->banner); + } + if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { + logevent("Access granted"); + s->we_are_in = s->userauth_success = TRUE; + break; + } + + if (pktin->type != SSH2_MSG_USERAUTH_FAILURE && s->type != AUTH_TYPE_GSSAPI) { + bombout(("Strange packet received during authentication: " + "type %d", pktin->type)); + crStopV; + } + + s->gotit = FALSE; + + /* + * OK, we're now sitting on a USERAUTH_FAILURE message, so + * we can look at the string in it and know what we can + * helpfully try next. + */ + if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { + ssh_pkt_getstring(pktin, &methods, &methlen); + if (!ssh2_pkt_getbool(pktin)) { + /* + * We have received an unequivocal Access + * Denied. This can translate to a variety of + * messages: + * + * - if we'd just tried "none" authentication, + * it's not worth printing anything at all + * + * - if we'd just tried a public key _offer_, + * the message should be "Server refused our + * key" (or no message at all if the key + * came from Pageant) + * + * - if we'd just tried anything else, the + * message really should be "Access denied". + * + * Additionally, if we'd just tried password + * authentication, we should break out of this + * whole loop so as to go back to the username + * prompt (iff we're configured to allow + * username change attempts). + */ + if (s->type == AUTH_TYPE_NONE) { + /* do nothing */ + } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD || + s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) { + if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD) + c_write_str(ssh, "Server refused our key\r\n"); + logevent("Server refused public key"); + } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) { + /* server declined keyboard-interactive; ignore */ + } else { + c_write_str(ssh, "Access denied\r\n"); + logevent("Access denied"); + if (s->type == AUTH_TYPE_PASSWORD && + ssh->cfg.change_username) { + /* XXX perhaps we should allow + * keyboard-interactive to do this too? */ + s->we_are_in = FALSE; + break; + } + } + } else { + c_write_str(ssh, "Further authentication required\r\n"); + logevent("Further authentication required"); + } + + s->can_pubkey = + in_commasep_string("publickey", methods, methlen); + s->can_passwd = + in_commasep_string("password", methods, methlen); + s->can_keyb_inter = ssh->cfg.try_ki_auth && + in_commasep_string("keyboard-interactive", methods, methlen); +#ifndef NO_GSSAPI + if (!ssh->gsslibs) + ssh->gsslibs = ssh_gss_setup(&ssh->cfg); + s->can_gssapi = ssh->cfg.try_gssapi_auth && + in_commasep_string("gssapi-with-mic", methods, methlen) && + ssh->gsslibs->nlibraries > 0; +#endif + } + + ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; + + if (s->can_pubkey && !s->done_agent && s->nkeys) { + + /* + * Attempt public-key authentication using a key from Pageant. + */ + + ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY; + + logeventf(ssh, "Trying Pageant key #%d", s->keyi); + + /* Unpack key from agent response */ + s->pklen = GET_32BIT(s->agentp); + s->agentp += 4; + s->pkblob = (char *)s->agentp; + s->agentp += s->pklen; + s->alglen = GET_32BIT(s->pkblob); + s->alg = s->pkblob + 4; + s->commentlen = GET_32BIT(s->agentp); + s->agentp += 4; + s->commentp = (char *)s->agentp; + s->agentp += s->commentlen; + /* s->agentp now points at next key, if any */ + + /* See if server will accept it */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); + /* method */ + ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */ + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen); + ssh2_pkt_send(ssh, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { + + /* Offer of key refused. */ + s->gotit = TRUE; + + } else { + + void *vret; + + if (flags & FLAG_VERBOSE) { + c_write_str(ssh, "Authenticating with " + "public key \""); + c_write(ssh, s->commentp, s->commentlen); + c_write_str(ssh, "\" from agent\r\n"); + } + + /* + * Server is willing to accept the key. + * Construct a SIGN_REQUEST. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); + /* method */ + ssh2_pkt_addbool(s->pktout, TRUE); /* signature included */ + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen); + + /* Ask agent for signature. */ + s->siglen = s->pktout->length - 5 + 4 + + ssh->v2_session_id_len; + if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) + s->siglen -= 4; + s->len = 1; /* message type */ + s->len += 4 + s->pklen; /* key blob */ + s->len += 4 + s->siglen; /* data to sign */ + s->len += 4; /* flags */ + s->agentreq = snewn(4 + s->len, char); + PUT_32BIT(s->agentreq, s->len); + s->q = s->agentreq + 4; + *s->q++ = SSH2_AGENTC_SIGN_REQUEST; + PUT_32BIT(s->q, s->pklen); + s->q += 4; + memcpy(s->q, s->pkblob, s->pklen); + s->q += s->pklen; + PUT_32BIT(s->q, s->siglen); + s->q += 4; + /* Now the data to be signed... */ + if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) { + PUT_32BIT(s->q, ssh->v2_session_id_len); + s->q += 4; + } + memcpy(s->q, ssh->v2_session_id, + ssh->v2_session_id_len); + s->q += ssh->v2_session_id_len; + memcpy(s->q, s->pktout->data + 5, + s->pktout->length - 5); + s->q += s->pktout->length - 5; + /* And finally the (zero) flags word. */ + PUT_32BIT(s->q, 0); + if (!agent_query(s->agentreq, s->len + 4, + &vret, &s->retlen, + ssh_agent_callback, ssh)) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server" + " while waiting for agent" + " response")); + crStopV; + } + } while (pktin || inlen > 0); + vret = ssh->agent_response; + s->retlen = ssh->agent_response_len; + } + s->ret = vret; + sfree(s->agentreq); + if (s->ret) { + if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) { + logevent("Sending Pageant's response"); + ssh2_add_sigblob(ssh, s->pktout, + s->pkblob, s->pklen, + s->ret + 9, + GET_32BIT(s->ret + 5)); + ssh2_pkt_send(ssh, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY; + } else { + /* FIXME: less drastic response */ + bombout(("Pageant failed to answer challenge")); + crStopV; + } + } + } + + /* Do we have any keys left to try? */ + if (s->pkblob_in_agent) { + s->done_agent = TRUE; + s->tried_pubkey_config = TRUE; + } else { + s->keyi++; + if (s->keyi >= s->nkeys) + s->done_agent = TRUE; + } + + } else if (s->can_pubkey && s->publickey_blob && + !s->tried_pubkey_config) { + + struct ssh2_userkey *key; /* not live over crReturn */ + char *passphrase; /* not live over crReturn */ + + ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY; + + s->tried_pubkey_config = TRUE; + + /* + * Try the public key supplied in the configuration. + * + * First, offer the public blob to see if the server is + * willing to accept it. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ + ssh2_pkt_addbool(s->pktout, FALSE); + /* no signature included */ + ssh2_pkt_addstring(s->pktout, s->publickey_algorithm); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, + (char *)s->publickey_blob, + s->publickey_bloblen); + ssh2_pkt_send(ssh, s->pktout); + logevent("Offered public key"); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { + /* Key refused. Give up. */ + s->gotit = TRUE; /* reconsider message next loop */ + s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; + continue; /* process this new message */ + } + logevent("Offer of public key accepted"); + + /* + * Actually attempt a serious authentication using + * the key. + */ + if (flags & FLAG_VERBOSE) { + c_write_str(ssh, "Authenticating with public key \""); + c_write_str(ssh, s->publickey_comment); + c_write_str(ssh, "\"\r\n"); + } + key = NULL; + while (!key) { + const char *error; /* not live over crReturn */ + if (s->publickey_encrypted) { + /* + * Get a passphrase from the user. + */ + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = FALSE; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%.100s\": ", + s->publickey_comment), + FALSE, SSH_MAX_PASSWORD_LEN); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, + in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* Failed to get a passphrase. Terminate. */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, + "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + passphrase = + dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + } else { + passphrase = NULL; /* no passphrase needed */ + } + + /* + * Try decrypting the key. + */ + key = ssh2_load_userkey(&ssh->cfg.keyfile, passphrase, + &error); + if (passphrase) { + /* burn the evidence */ + memset(passphrase, 0, strlen(passphrase)); + sfree(passphrase); + } + if (key == SSH2_WRONG_PASSPHRASE || key == NULL) { + if (passphrase && + (key == SSH2_WRONG_PASSPHRASE)) { + c_write_str(ssh, "Wrong passphrase\r\n"); + key = NULL; + /* and loop again */ + } else { + c_write_str(ssh, "Unable to load private key ("); + c_write_str(ssh, error); + c_write_str(ssh, ")\r\n"); + key = NULL; + break; /* try something else */ + } + } + } + + if (key) { + unsigned char *pkblob, *sigblob, *sigdata; + int pkblob_len, sigblob_len, sigdata_len; + int p; + + /* + * We have loaded the private key and the server + * has announced that it's willing to accept it. + * Hallelujah. Generate a signature and send it. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); + /* method */ + ssh2_pkt_addbool(s->pktout, TRUE); + /* signature follows */ + ssh2_pkt_addstring(s->pktout, key->alg->name); + pkblob = key->alg->public_blob(key->data, + &pkblob_len); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, (char *)pkblob, + pkblob_len); + + /* + * The data to be signed is: + * + * string session-id + * + * followed by everything so far placed in the + * outgoing packet. + */ + sigdata_len = s->pktout->length - 5 + 4 + + ssh->v2_session_id_len; + if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) + sigdata_len -= 4; + sigdata = snewn(sigdata_len, unsigned char); + p = 0; + if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) { + PUT_32BIT(sigdata+p, ssh->v2_session_id_len); + p += 4; + } + memcpy(sigdata+p, ssh->v2_session_id, + ssh->v2_session_id_len); + p += ssh->v2_session_id_len; + memcpy(sigdata+p, s->pktout->data + 5, + s->pktout->length - 5); + p += s->pktout->length - 5; + assert(p == sigdata_len); + sigblob = key->alg->sign(key->data, (char *)sigdata, + sigdata_len, &sigblob_len); + ssh2_add_sigblob(ssh, s->pktout, pkblob, pkblob_len, + sigblob, sigblob_len); + sfree(pkblob); + sfree(sigblob); + sfree(sigdata); + + ssh2_pkt_send(ssh, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY; + key->alg->freekey(key->data); + } + +#ifndef NO_GSSAPI + } else if (s->can_gssapi && !s->tried_gssapi) { + + /* GSSAPI Authentication */ + + int micoffset, len; + char *data; + Ssh_gss_buf mic; + s->type = AUTH_TYPE_GSSAPI; + s->tried_gssapi = TRUE; + s->gotit = TRUE; + ssh->pkt_actx = SSH2_PKTCTX_GSSAPI; + + /* + * Pick the highest GSS library on the preference + * list. + */ + { + int i, j; + s->gsslib = NULL; + for (i = 0; i < ngsslibs; i++) { + int want_id = ssh->cfg.ssh_gsslist[i]; + for (j = 0; j < ssh->gsslibs->nlibraries; j++) + if (ssh->gsslibs->libraries[j].id == want_id) { + s->gsslib = &ssh->gsslibs->libraries[j]; + goto got_gsslib; /* double break */ + } + } + got_gsslib: + /* + * We always expect to have found something in + * the above loop: we only came here if there + * was at least one viable GSS library, and the + * preference list should always mention + * everything and only change the order. + */ + assert(s->gsslib); + } + + if (s->gsslib->gsslogmsg) + logevent(s->gsslib->gsslogmsg); + + /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + ssh2_pkt_addstring(s->pktout, "gssapi-with-mic"); + + /* add mechanism info */ + s->gsslib->indicate_mech(s->gsslib, &s->gss_buf); + + /* number of GSSAPI mechanisms */ + ssh2_pkt_adduint32(s->pktout,1); + + /* length of OID + 2 */ + ssh2_pkt_adduint32(s->pktout, s->gss_buf.length + 2); + ssh2_pkt_addbyte(s->pktout, SSH2_GSS_OIDTYPE); + + /* length of OID */ + ssh2_pkt_addbyte(s->pktout, (unsigned char) s->gss_buf.length); + + ssh_pkt_adddata(s->pktout, s->gss_buf.value, + s->gss_buf.length); + ssh2_pkt_send(ssh, s->pktout); + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) { + logevent("GSSAPI authentication request refused"); + continue; + } + + /* check returned packet ... */ + + ssh_pkt_getstring(pktin, &data, &len); + s->gss_rcvtok.value = data; + s->gss_rcvtok.length = len; + if (s->gss_rcvtok.length != s->gss_buf.length + 2 || + ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE || + ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length || + memcmp((char *)s->gss_rcvtok.value + 2, + s->gss_buf.value,s->gss_buf.length) ) { + logevent("GSSAPI authentication - wrong response from server"); + continue; + } + + /* now start running */ + s->gss_stat = s->gsslib->import_name(s->gsslib, + ssh->fullhostname, + &s->gss_srv_name); + if (s->gss_stat != SSH_GSS_OK) { + if (s->gss_stat == SSH_GSS_BAD_HOST_NAME) + logevent("GSSAPI import name failed - Bad service name"); + else + logevent("GSSAPI import name failed"); + continue; + } + + /* fetch TGT into GSS engine */ + s->gss_stat = s->gsslib->acquire_cred(s->gsslib, &s->gss_ctx); + + if (s->gss_stat != SSH_GSS_OK) { + logevent("GSSAPI authentication failed to get credentials"); + s->gsslib->release_name(s->gsslib, &s->gss_srv_name); + continue; + } + + /* initial tokens are empty */ + SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); + SSH_GSS_CLEAR_BUF(&s->gss_sndtok); + + /* now enter the loop */ + do { + s->gss_stat = s->gsslib->init_sec_context + (s->gsslib, + &s->gss_ctx, + s->gss_srv_name, + ssh->cfg.gssapifwd, + &s->gss_rcvtok, + &s->gss_sndtok); + + if (s->gss_stat!=SSH_GSS_S_COMPLETE && + s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { + logevent("GSSAPI authentication initialisation failed"); + + if (s->gsslib->display_status(s->gsslib, s->gss_ctx, + &s->gss_buf) == SSH_GSS_OK) { + logevent(s->gss_buf.value); + sfree(s->gss_buf.value); + } + + break; + } + logevent("GSSAPI authentication initialised"); + + /* Client and server now exchange tokens until GSSAPI + * no longer says CONTINUE_NEEDED */ + + if (s->gss_sndtok.length != 0) { + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); + ssh_pkt_addstring_start(s->pktout); + ssh_pkt_addstring_data(s->pktout,s->gss_sndtok.value,s->gss_sndtok.length); + ssh2_pkt_send(ssh, s->pktout); + s->gsslib->free_tok(s->gsslib, &s->gss_sndtok); + } + + if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) { + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) { + logevent("GSSAPI authentication - bad server response"); + s->gss_stat = SSH_GSS_FAILURE; + break; + } + ssh_pkt_getstring(pktin, &data, &len); + s->gss_rcvtok.value = data; + s->gss_rcvtok.length = len; + } + } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED); + + if (s->gss_stat != SSH_GSS_OK) { + s->gsslib->release_name(s->gsslib, &s->gss_srv_name); + s->gsslib->release_cred(s->gsslib, &s->gss_ctx); + continue; + } + logevent("GSSAPI authentication loop finished OK"); + + /* Now send the MIC */ + + s->pktout = ssh2_pkt_init(0); + micoffset = s->pktout->length; + ssh_pkt_addstring_start(s->pktout); + ssh_pkt_addstring_data(s->pktout, (char *)ssh->v2_session_id, ssh->v2_session_id_len); + ssh_pkt_addbyte(s->pktout, SSH2_MSG_USERAUTH_REQUEST); + ssh_pkt_addstring(s->pktout, s->username); + ssh_pkt_addstring(s->pktout, "ssh-connection"); + ssh_pkt_addstring(s->pktout, "gssapi-with-mic"); + + s->gss_buf.value = (char *)s->pktout->data + micoffset; + s->gss_buf.length = s->pktout->length - micoffset; + + s->gsslib->get_mic(s->gsslib, s->gss_ctx, &s->gss_buf, &mic); + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC); + ssh_pkt_addstring_start(s->pktout); + ssh_pkt_addstring_data(s->pktout, mic.value, mic.length); + ssh2_pkt_send(ssh, s->pktout); + s->gsslib->free_mic(s->gsslib, &mic); + + s->gotit = FALSE; + + s->gsslib->release_name(s->gsslib, &s->gss_srv_name); + s->gsslib->release_cred(s->gsslib, &s->gss_ctx); + continue; +#endif + } else if (s->can_keyb_inter && !s->kbd_inter_refused) { + + /* + * Keyboard-interactive authentication. + */ + + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; + + ssh->pkt_actx = SSH2_PKTCTX_KBDINTER; + + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "keyboard-interactive"); + /* method */ + ssh2_pkt_addstring(s->pktout, ""); /* lang */ + ssh2_pkt_addstring(s->pktout, ""); /* submethods */ + ssh2_pkt_send(ssh, s->pktout); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) { + /* Server is not willing to do keyboard-interactive + * at all (or, bizarrely but legally, accepts the + * user without actually issuing any prompts). + * Give up on it entirely. */ + s->gotit = TRUE; + if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) + logevent("Keyboard-interactive authentication refused"); + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; + s->kbd_inter_refused = TRUE; /* don't try it again */ + continue; + } + + /* + * Loop while the server continues to send INFO_REQUESTs. + */ + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { + + char *name, *inst, *lang; + int name_len, inst_len, lang_len; + int i; + + /* + * We've got a fresh USERAUTH_INFO_REQUEST. + * Get the preamble and start building a prompt. + */ + ssh_pkt_getstring(pktin, &name, &name_len); + ssh_pkt_getstring(pktin, &inst, &inst_len); + ssh_pkt_getstring(pktin, &lang, &lang_len); + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + + /* + * Get any prompt(s) from the packet. + */ + s->num_prompts = ssh_pkt_getuint32(pktin); + for (i = 0; i < s->num_prompts; i++) { + char *prompt; + int prompt_len; + int echo; + static char noprompt[] = + ": "; + + ssh_pkt_getstring(pktin, &prompt, &prompt_len); + echo = ssh2_pkt_getbool(pktin); + if (!prompt_len) { + prompt = noprompt; + prompt_len = lenof(noprompt)-1; + } + add_prompt(s->cur_prompt, + dupprintf("%.*s", prompt_len, prompt), + echo, SSH_MAX_PASSWORD_LEN); + } + + if (name_len) { + /* FIXME: better prefix to distinguish from + * local prompts? */ + s->cur_prompt->name = + dupprintf("SSH server: %.*s", name_len, name); + s->cur_prompt->name_reqd = TRUE; + } else { + s->cur_prompt->name = + dupstr("SSH server authentication"); + s->cur_prompt->name_reqd = FALSE; + } + /* We add a prefix to try to make it clear that a prompt + * has come from the server. + * FIXME: ugly to print "Using..." in prompt _every_ + * time round. Can this be done more subtly? */ + /* Special case: for reasons best known to themselves, + * some servers send k-i requests with no prompts and + * nothing to display. Keep quiet in this case. */ + if (s->num_prompts || name_len || inst_len) { + s->cur_prompt->instruction = + dupprintf("Using keyboard-interactive authentication.%s%.*s", + inst_len ? "\n" : "", inst_len, inst); + s->cur_prompt->instr_reqd = TRUE; + } else { + s->cur_prompt->instr_reqd = FALSE; + } + + /* + * Display any instructions, and get the user's + * response(s). + */ + { + int ret; /* not live over crReturn */ + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + } + + /* + * Send the response(s) to the server. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); + ssh2_pkt_adduint32(s->pktout, s->num_prompts); + for (i=0; i < s->num_prompts; i++) { + dont_log_password(ssh, s->pktout, PKTLOG_BLANK); + ssh2_pkt_addstring(s->pktout, + s->cur_prompt->prompts[i]->result); + end_log_omission(ssh, s->pktout); + } + ssh2_pkt_send_with_padding(ssh, s->pktout, 256); + + /* + * Get the next packet in case it's another + * INFO_REQUEST. + */ + crWaitUntilV(pktin); + + } + + /* + * We should have SUCCESS or FAILURE now. + */ + s->gotit = TRUE; + + } else if (s->can_passwd) { + + /* + * Plain old password authentication. + */ + int ret; /* not live over crReturn */ + int changereq_first_time; /* not live over crReturn */ + + ssh->pkt_actx = SSH2_PKTCTX_PASSWORD; + + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%.90s@%.90s's password: ", + s->username, + ssh->savedhost), + FALSE, SSH_MAX_PASSWORD_LEN); + + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + /* + * Squirrel away the password. (We may need it later if + * asked to change it.) + */ + s->password = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + + /* + * Send the password packet. + * + * We pad out the password packet to 256 bytes to make + * it harder for an attacker to find the length of the + * user's password. + * + * Anyone using a password longer than 256 bytes + * probably doesn't have much to worry about from + * people who find out how long their password is! + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "password"); + ssh2_pkt_addbool(s->pktout, FALSE); + dont_log_password(ssh, s->pktout, PKTLOG_BLANK); + ssh2_pkt_addstring(s->pktout, s->password); + end_log_omission(ssh, s->pktout); + ssh2_pkt_send_with_padding(ssh, s->pktout, 256); + logevent("Sent password"); + s->type = AUTH_TYPE_PASSWORD; + + /* + * Wait for next packet, in case it's a password change + * request. + */ + crWaitUntilV(pktin); + changereq_first_time = TRUE; + + while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { + + /* + * We're being asked for a new password + * (perhaps not for the first time). + * Loop until the server accepts it. + */ + + int got_new = FALSE; /* not live over crReturn */ + char *prompt; /* not live over crReturn */ + int prompt_len; /* not live over crReturn */ + + { + char *msg; + if (changereq_first_time) + msg = "Server requested password change"; + else + msg = "Server rejected new password"; + logevent(msg); + c_write_str(ssh, msg); + c_write_str(ssh, "\r\n"); + } + + ssh_pkt_getstring(pktin, &prompt, &prompt_len); + + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("New SSH password"); + s->cur_prompt->instruction = + dupprintf("%.*s", prompt_len, 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, SSH_MAX_PASSWORD_LEN); + add_prompt(s->cur_prompt, dupstr("Enter new password: "), + FALSE, SSH_MAX_PASSWORD_LEN); + add_prompt(s->cur_prompt, dupstr("Confirm new password: "), + FALSE, SSH_MAX_PASSWORD_LEN); + + /* + * Loop until the user manages to enter the same + * password twice. + */ + while (!got_new) { + + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + /* burn the evidence */ + free_prompts(s->cur_prompt); + memset(s->password, 0, strlen(s->password)); + sfree(s->password); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + + /* + * If the user specified a new original password + * (IYSWIM), overwrite any previously specified + * one. + * (A side effect is that the user doesn't have to + * re-enter it if they louse up the new password.) + */ + if (s->cur_prompt->prompts[0]->result[0]) { + memset(s->password, 0, strlen(s->password)); + /* burn the evidence */ + sfree(s->password); + s->password = + dupstr(s->cur_prompt->prompts[0]->result); + } + + /* + * Check the two new passwords match. + */ + got_new = (strcmp(s->cur_prompt->prompts[1]->result, + s->cur_prompt->prompts[2]->result) + == 0); + if (!got_new) + /* They don't. Silly user. */ + c_write_str(ssh, "Passwords do not match\r\n"); + + } + + /* + * Send the new password (along with the old one). + * (see above for padding rationale) + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "password"); + ssh2_pkt_addbool(s->pktout, TRUE); + dont_log_password(ssh, s->pktout, PKTLOG_BLANK); + ssh2_pkt_addstring(s->pktout, s->password); + ssh2_pkt_addstring(s->pktout, + s->cur_prompt->prompts[1]->result); + free_prompts(s->cur_prompt); + end_log_omission(ssh, s->pktout); + ssh2_pkt_send_with_padding(ssh, s->pktout, 256); + logevent("Sent new password"); + + /* + * Now see what the server has to say about it. + * (If it's CHANGEREQ again, it's not happy with the + * new password.) + */ + crWaitUntilV(pktin); + changereq_first_time = FALSE; + + } + + /* + * We need to reexamine the current pktin at the top + * of the loop. Either: + * - we weren't asked to change password at all, in + * which case it's a SUCCESS or FAILURE with the + * usual meaning + * - we sent a new password, and the server was + * either OK with it (SUCCESS or FAILURE w/partial + * success) or unhappy with the _old_ password + * (FAILURE w/o partial success) + * In any of these cases, we go back to the top of + * the loop and start again. + */ + s->gotit = TRUE; + + /* + * We don't need the old password any more, in any + * case. Burn the evidence. + */ + memset(s->password, 0, strlen(s->password)); + sfree(s->password); + + } else { + char *str = dupprintf("No supported authentication methods available" + " (server sent: %.*s)", + methlen, methods); + + ssh_disconnect(ssh, str, + "No supported authentication methods available", + SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + FALSE); + sfree(str); + + crStopV; + + } + + } + } + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL; + + /* Clear up various bits and pieces from authentication. */ + if (s->publickey_blob) { + sfree(s->publickey_blob); + sfree(s->publickey_comment); + } + if (s->agent_response) + sfree(s->agent_response); + + if (s->userauth_success) { + /* + * We've just received USERAUTH_SUCCESS, and we haven't sent any + * packets since. Signal the transport layer to consider enacting + * delayed compression. + * + * (Relying on we_are_in is not sufficient, as + * draft-miller-secsh-compression-delayed is quite clear that it + * triggers on USERAUTH_SUCCESS specifically, and we_are_in can + * become set for other reasons.) + */ + do_ssh2_transport(ssh, "enabling delayed compression", -2, NULL); + } + + /* + * Now the connection protocol has started, one way or another. + */ + + ssh->channels = newtree234(ssh_channelcmp); + + /* + * Set up handlers for some connection protocol messages, so we + * don't have to handle them repeatedly in this coroutine. + */ + ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = + ssh2_msg_channel_window_adjust; + ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = + ssh2_msg_global_request; + + /* + * Create the main session channel. + */ + if (ssh->cfg.ssh_no_shell) { + ssh->mainchan = NULL; + } else if (*ssh->cfg.ssh_nc_host) { + /* + * Just start a direct-tcpip channel and use it as the main + * channel. + */ + ssh->mainchan = snew(struct ssh_channel); + ssh->mainchan->ssh = ssh; + ssh2_channel_init(ssh->mainchan); + logeventf(ssh, + "Opening direct-tcpip channel to %s:%d in place of session", + ssh->cfg.ssh_nc_host, ssh->cfg.ssh_nc_port); + s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); + ssh2_pkt_addstring(s->pktout, "direct-tcpip"); + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid); + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */ + ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */ + ssh2_pkt_addstring(s->pktout, ssh->cfg.ssh_nc_host); + ssh2_pkt_adduint32(s->pktout, ssh->cfg.ssh_nc_port); + /* + * There's nothing meaningful to put in the originator + * fields, but some servers insist on syntactically correct + * information. + */ + ssh2_pkt_addstring(s->pktout, "0.0.0.0"); + ssh2_pkt_adduint32(s->pktout, 0); + ssh2_pkt_send(ssh, s->pktout); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { + bombout(("Server refused to open a direct-tcpip channel")); + crStopV; + /* FIXME: error data comes back in FAILURE packet */ + } + if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) { + bombout(("Server's channel confirmation cited wrong channel")); + crStopV; + } + ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin); + ssh->mainchan->halfopen = FALSE; + ssh->mainchan->type = CHAN_MAINSESSION; + ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin); + ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); + add234(ssh->channels, ssh->mainchan); + update_specials_menu(ssh->frontend); + logevent("Opened direct-tcpip channel"); + ssh->ncmode = TRUE; + } else { + ssh->mainchan = snew(struct ssh_channel); + ssh->mainchan->ssh = ssh; + ssh2_channel_init(ssh->mainchan); + s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); + ssh2_pkt_addstring(s->pktout, "session"); + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid); + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */ + ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */ + ssh2_pkt_send(ssh, s->pktout); + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { + bombout(("Server refused to open a session")); + crStopV; + /* FIXME: error data comes back in FAILURE packet */ + } + if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) { + bombout(("Server's channel confirmation cited wrong channel")); + crStopV; + } + ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin); + ssh->mainchan->halfopen = FALSE; + ssh->mainchan->type = CHAN_MAINSESSION; + ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin); + ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); + add234(ssh->channels, ssh->mainchan); + update_specials_menu(ssh->frontend); + logevent("Opened channel for session"); + ssh->ncmode = FALSE; + } + + /* + * Now we have a channel, make dispatch table entries for + * general channel-based messages. + */ + ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = + ssh2_msg_channel_data; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_channel_eof; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_channel_close; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = + ssh2_msg_channel_open_confirmation; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = + ssh2_msg_channel_open_failure; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = + ssh2_msg_channel_request; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = + ssh2_msg_channel_open; + + if (ssh->mainchan && ssh->cfg.ssh_simple) { + /* + * This message indicates to the server that we promise + * not to try to run any other channel in parallel with + * this one, so it's safe for it to advertise a very large + * window and leave the flow control to TCP. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); + ssh2_pkt_addstring(s->pktout, "simple@putty.projects.tartarus.org"); + ssh2_pkt_addbool(s->pktout, 0); /* no reply */ + ssh2_pkt_send(ssh, s->pktout); + } + + /* + * Potentially enable X11 forwarding. + */ + if (ssh->mainchan && !ssh->ncmode && ssh->cfg.x11_forward && + (ssh->x11disp = x11_setup_display(ssh->cfg.x11_display, + ssh->cfg.x11_auth, &ssh->cfg))) { + logevent("Requesting X11 forwarding"); + s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); + ssh2_pkt_addstring(s->pktout, "x11-req"); + ssh2_pkt_addbool(s->pktout, 1); /* want reply */ + ssh2_pkt_addbool(s->pktout, 0); /* many connections */ + ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthprotoname); + /* + * Note that while we blank the X authentication data here, we don't + * take any special action to blank the start of an X11 channel, + * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection + * without having session blanking enabled is likely to leak your + * cookie into the log. + */ + dont_log_password(ssh, s->pktout, PKTLOG_BLANK); + ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthdatastring); + end_log_omission(ssh, s->pktout); + ssh2_pkt_adduint32(s->pktout, ssh->x11disp->screennum); + ssh2_pkt_send(ssh, s->pktout); + + crWaitUntilV(pktin); + + if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { + if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { + bombout(("Unexpected response to X11 forwarding request:" + " packet type %d", pktin->type)); + crStopV; + } + logevent("X11 forwarding refused"); + } else { + logevent("X11 forwarding enabled"); + ssh->X11_fwd_enabled = TRUE; + } + } + + /* + * Enable port forwardings. + */ + ssh_setup_portfwd(ssh, &ssh->cfg); + + /* + * Potentially enable agent forwarding. + */ + if (ssh->mainchan && !ssh->ncmode && ssh->cfg.agentfwd && agent_exists()) { + logevent("Requesting OpenSSH-style agent forwarding"); + s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); + ssh2_pkt_addstring(s->pktout, "auth-agent-req@openssh.com"); + ssh2_pkt_addbool(s->pktout, 1); /* want reply */ + ssh2_pkt_send(ssh, s->pktout); + + crWaitUntilV(pktin); + + if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { + if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { + bombout(("Unexpected response to agent forwarding request:" + " packet type %d", pktin->type)); + crStopV; + } + logevent("Agent forwarding refused"); + } else { + logevent("Agent forwarding enabled"); + ssh->agentfwd_enabled = TRUE; + } + } + + /* + * Now allocate a pty for the session. + */ + if (ssh->mainchan && !ssh->ncmode && !ssh->cfg.nopty) { + /* Unpick the terminal-speed string. */ + /* XXX perhaps we should allow no speeds to be sent. */ + ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ + sscanf(ssh->cfg.termspeed, "%d,%d", &ssh->ospeed, &ssh->ispeed); + /* Build the pty request. */ + s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */ + ssh2_pkt_addstring(s->pktout, "pty-req"); + ssh2_pkt_addbool(s->pktout, 1); /* want reply */ + ssh2_pkt_addstring(s->pktout, ssh->cfg.termtype); + ssh2_pkt_adduint32(s->pktout, ssh->term_width); + ssh2_pkt_adduint32(s->pktout, ssh->term_height); + ssh2_pkt_adduint32(s->pktout, 0); /* pixel width */ + ssh2_pkt_adduint32(s->pktout, 0); /* pixel height */ + ssh2_pkt_addstring_start(s->pktout); + parse_ttymodes(ssh, ssh->cfg.ttymodes, + ssh2_send_ttymode, (void *)s->pktout); + ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_ISPEED); + ssh2_pkt_adduint32(s->pktout, ssh->ispeed); + ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_OSPEED); + ssh2_pkt_adduint32(s->pktout, ssh->ospeed); + ssh2_pkt_addstring_data(s->pktout, "\0", 1); /* TTY_OP_END */ + ssh2_pkt_send(ssh, s->pktout); + ssh->state = SSH_STATE_INTERMED; + + crWaitUntilV(pktin); + + if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { + if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { + bombout(("Unexpected response to pty request:" + " packet type %d", pktin->type)); + crStopV; + } + c_write_str(ssh, "Server refused to allocate pty\r\n"); + ssh->editing = ssh->echoing = 1; + } else { + logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", + ssh->ospeed, ssh->ispeed); + } + } else { + ssh->editing = ssh->echoing = 1; + } + + /* + * Send environment variables. + * + * Simplest thing here is to send all the requests at once, and + * then wait for a whole bunch of successes or failures. + */ + if (ssh->mainchan && !ssh->ncmode && *ssh->cfg.environmt) { + char *e = ssh->cfg.environmt; + char *var, *varend, *val; + + s->num_env = 0; + + while (*e) { + var = e; + while (*e && *e != '\t') e++; + varend = e; + if (*e == '\t') e++; + val = e; + while (*e) e++; + e++; + + s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); + ssh2_pkt_addstring(s->pktout, "env"); + ssh2_pkt_addbool(s->pktout, 1); /* want reply */ + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, var, varend-var); + ssh2_pkt_addstring(s->pktout, val); + ssh2_pkt_send(ssh, s->pktout); + + s->num_env++; + } + + logeventf(ssh, "Sent %d environment variables", s->num_env); + + s->env_ok = 0; + s->env_left = s->num_env; + + while (s->env_left > 0) { + crWaitUntilV(pktin); + + if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { + if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { + bombout(("Unexpected response to environment request:" + " packet type %d", pktin->type)); + crStopV; + } + } else { + s->env_ok++; + } + + s->env_left--; + } + + if (s->env_ok == s->num_env) { + logevent("All environment variables successfully set"); + } else if (s->env_ok == 0) { + logevent("All environment variables refused"); + c_write_str(ssh, "Server refused to set environment variables\r\n"); + } else { + logeventf(ssh, "%d environment variables refused", + s->num_env - s->env_ok); + c_write_str(ssh, "Server refused to set all environment variables\r\n"); + } + } + + /* + * Start a shell or a remote command. We may have to attempt + * this twice if the config data has provided a second choice + * of command. + */ + if (ssh->mainchan && !ssh->ncmode) while (1) { + int subsys; + char *cmd; + + if (ssh->fallback_cmd) { + subsys = ssh->cfg.ssh_subsys2; + cmd = ssh->cfg.remote_cmd_ptr2; + } else { + subsys = ssh->cfg.ssh_subsys; + cmd = ssh->cfg.remote_cmd_ptr; + if (!cmd) cmd = ssh->cfg.remote_cmd; + } + + s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */ + if (subsys) { + ssh2_pkt_addstring(s->pktout, "subsystem"); + ssh2_pkt_addbool(s->pktout, 1); /* want reply */ + ssh2_pkt_addstring(s->pktout, cmd); + } else if (*cmd) { + ssh2_pkt_addstring(s->pktout, "exec"); + ssh2_pkt_addbool(s->pktout, 1); /* want reply */ + ssh2_pkt_addstring(s->pktout, cmd); + } else { + ssh2_pkt_addstring(s->pktout, "shell"); + ssh2_pkt_addbool(s->pktout, 1); /* want reply */ + } + ssh2_pkt_send(ssh, s->pktout); + + crWaitUntilV(pktin); + + if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { + if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { + bombout(("Unexpected response to shell/command request:" + " packet type %d", pktin->type)); + crStopV; + } + /* + * We failed to start the command. If this is the + * fallback command, we really are finished; if it's + * not, and if the fallback command exists, try falling + * back to it before complaining. + */ + if (!ssh->fallback_cmd && ssh->cfg.remote_cmd_ptr2 != NULL) { + logevent("Primary command failed; attempting fallback"); + ssh->fallback_cmd = TRUE; + continue; + } + bombout(("Server refused to start a shell/command")); + crStopV; + } else { + logevent("Started a shell/command"); + } + break; + } + + ssh->state = SSH_STATE_SESSION; + if (ssh->size_needed) + ssh_size(ssh, ssh->term_width, ssh->term_height); + if (ssh->eof_needed) + ssh_special(ssh, TS_EOF); + + /* + * All the initial channel requests are done, so install the default + * failure handler. + */ + ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_success; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_failure; + + /* + * Transfer data! + */ + if (ssh->ldisc) + ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */ + if (ssh->mainchan) + ssh->send_ok = 1; + while (1) { + crReturnV; + s->try_send = FALSE; + if (pktin) { + + /* + * _All_ the connection-layer packets we expect to + * receive are now handled by the dispatch table. + * Anything that reaches here must be bogus. + */ + + bombout(("Strange packet received: type %d", pktin->type)); + crStopV; + } else if (ssh->mainchan) { + /* + * We have spare data. Add it to the channel buffer. + */ + ssh2_add_channel_data(ssh->mainchan, (char *)in, inlen); + s->try_send = TRUE; + } + if (s->try_send) { + int i; + struct ssh_channel *c; + /* + * Try to send data on all channels if we can. + */ + for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) + ssh2_try_send_and_unthrottle(ssh, c); + } + } + + crFinishV; +} + +/* + * Handlers for SSH-2 messages that might arrive at any moment. + */ +static void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin) +{ + /* log reason code in disconnect message */ + char *buf, *msg; + int reason, msglen; + + reason = ssh_pkt_getuint32(pktin); + ssh_pkt_getstring(pktin, &msg, &msglen); + + if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) { + buf = dupprintf("Received disconnect message (%s)", + ssh2_disconnect_reasons[reason]); + } else { + buf = dupprintf("Received disconnect message (unknown" + " type %d)", reason); + } + logevent(buf); + sfree(buf); + buf = dupprintf("Disconnection message text: %.*s", + msglen, msg); + logevent(buf); + bombout(("Server sent disconnect message\ntype %d (%s):\n\"%.*s\"", + reason, + (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? + ssh2_disconnect_reasons[reason] : "unknown", + msglen, msg)); + sfree(buf); +} + +static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin) +{ + /* log the debug message */ + char *msg; + int msglen; + + /* XXX maybe we should actually take notice of the return value */ + ssh2_pkt_getbool(pktin); + ssh_pkt_getstring(pktin, &msg, &msglen); + + logeventf(ssh, "Remote debug message: %.*s", msglen, msg); +} + +static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin) +{ + struct Packet *pktout; + pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED); + ssh2_pkt_adduint32(pktout, pktin->sequence); + /* + * UNIMPLEMENTED messages MUST appear in the same order as the + * messages they respond to. Hence, never queue them. + */ + ssh2_pkt_send_noqueue(ssh, pktout); +} + +/* + * Handle the top-level SSH-2 protocol. + */ +static void ssh2_protocol_setup(Ssh ssh) +{ + int i; + + /* + * Most messages cause SSH2_MSG_UNIMPLEMENTED. + */ + for (i = 0; i < 256; i++) + ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented; + + /* + * Any message we actually understand, we set to NULL so that + * the coroutines will get it. + */ + ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = NULL; + ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = NULL; + ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = NULL; + ssh->packet_dispatch[SSH2_MSG_KEXINIT] = NULL; + ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = NULL; + ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = NULL; + ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = NULL; + /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = NULL; duplicate case value */ + /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = NULL; duplicate case value */ + ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = NULL; + ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = NULL; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = NULL; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = NULL; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = NULL; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = NULL; + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = NULL; duplicate case value */ + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = NULL; duplicate case value */ + ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = NULL; + ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = NULL; + ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = NULL; + ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = NULL; + + /* + * These special message types we install handlers for. + */ + ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect; + ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; /* shared with SSH-1 */ + ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug; +} + +static void ssh2_timer(void *ctx, long now) +{ + Ssh ssh = (Ssh)ctx; + + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (!ssh->kex_in_progress && ssh->cfg.ssh_rekey_time != 0 && + now - ssh->next_rekey >= 0) { + do_ssh2_transport(ssh, "timeout", -1, NULL); + } +} + +static void ssh2_protocol(Ssh ssh, void *vin, int inlen, + struct Packet *pktin) +{ + unsigned char *in = (unsigned char *)vin; + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (pktin) { + ssh->incoming_data_size += pktin->encrypted_len; + if (!ssh->kex_in_progress && + ssh->max_data_size != 0 && + ssh->incoming_data_size > ssh->max_data_size) + do_ssh2_transport(ssh, "too much data received", -1, NULL); + } + + if (pktin && ssh->packet_dispatch[pktin->type]) { + ssh->packet_dispatch[pktin->type](ssh, pktin); + return; + } + + if (!ssh->protocol_initial_phase_done || + (pktin && pktin->type >= 20 && pktin->type < 50)) { + if (do_ssh2_transport(ssh, in, inlen, pktin) && + !ssh->protocol_initial_phase_done) { + ssh->protocol_initial_phase_done = TRUE; + /* + * Allow authconn to initialise itself. + */ + do_ssh2_authconn(ssh, NULL, 0, NULL); + } + } else { + do_ssh2_authconn(ssh, in, inlen, pktin); + } +} + +/* + * Called to set up the connection. + * + * Returns an error message, or NULL on success. + */ +static const char *ssh_init(void *frontend_handle, void **backend_handle, + Config *cfg, + char *host, int port, char **realhost, int nodelay, + int keepalive) +{ + const char *p; + Ssh ssh; + + ssh = snew(struct ssh_tag); + ssh->cfg = *cfg; /* STRUCTURE COPY */ + ssh->version = 0; /* when not ready yet */ + ssh->s = NULL; + ssh->cipher = NULL; + ssh->v1_cipher_ctx = NULL; + ssh->crcda_ctx = NULL; + ssh->cscipher = NULL; + ssh->cs_cipher_ctx = NULL; + ssh->sccipher = NULL; + ssh->sc_cipher_ctx = NULL; + ssh->csmac = NULL; + ssh->cs_mac_ctx = NULL; + ssh->scmac = NULL; + ssh->sc_mac_ctx = NULL; + ssh->cscomp = NULL; + ssh->cs_comp_ctx = NULL; + ssh->sccomp = NULL; + ssh->sc_comp_ctx = NULL; + ssh->kex = NULL; + ssh->kex_ctx = NULL; + ssh->hostkey = NULL; + ssh->exitcode = -1; + ssh->close_expected = FALSE; + ssh->clean_exit = FALSE; + ssh->state = SSH_STATE_PREPACKET; + ssh->size_needed = FALSE; + ssh->eof_needed = FALSE; + ssh->ldisc = NULL; + ssh->logctx = NULL; + ssh->deferred_send_data = NULL; + ssh->deferred_len = 0; + ssh->deferred_size = 0; + ssh->fallback_cmd = 0; + ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; + ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; + ssh->x11disp = NULL; + ssh->v1_compressing = FALSE; + ssh->v2_outgoing_sequence = 0; + ssh->ssh1_rdpkt_crstate = 0; + ssh->ssh2_rdpkt_crstate = 0; + ssh->do_ssh_init_crstate = 0; + ssh->ssh_gotdata_crstate = 0; + ssh->do_ssh1_connection_crstate = 0; + ssh->do_ssh1_login_crstate = 0; + ssh->do_ssh2_transport_crstate = 0; + ssh->do_ssh2_authconn_crstate = 0; + ssh->do_ssh_init_state = NULL; + ssh->do_ssh1_login_state = NULL; + ssh->do_ssh2_transport_state = NULL; + ssh->do_ssh2_authconn_state = NULL; + ssh->v_c = NULL; + ssh->v_s = NULL; + ssh->mainchan = NULL; + ssh->throttled_all = 0; + ssh->v1_stdout_throttling = 0; + ssh->queue = NULL; + ssh->queuelen = ssh->queuesize = 0; + ssh->queueing = FALSE; + ssh->qhead = ssh->qtail = NULL; + ssh->deferred_rekey_reason = NULL; + bufchain_init(&ssh->queued_incoming_data); + ssh->frozen = FALSE; + + *backend_handle = ssh; + +#ifdef MSCRYPTOAPI + if (crypto_startup() == 0) + return "Microsoft high encryption pack not installed!"; +#endif + + ssh->frontend = frontend_handle; + ssh->term_width = ssh->cfg.width; + ssh->term_height = ssh->cfg.height; + + ssh->channels = NULL; + ssh->rportfwds = NULL; + ssh->portfwds = NULL; + + ssh->send_ok = 0; + ssh->editing = 0; + ssh->echoing = 0; + ssh->conn_throttle_count = 0; + ssh->overall_bufsize = 0; + ssh->fallback_cmd = 0; + + ssh->protocol = NULL; + + ssh->protocol_initial_phase_done = FALSE; + + ssh->pinger = NULL; + + ssh->incoming_data_size = ssh->outgoing_data_size = + ssh->deferred_data_size = 0L; + ssh->max_data_size = parse_blocksize(ssh->cfg.ssh_rekey_data); + ssh->kex_in_progress = FALSE; + +#ifndef NO_GSSAPI + ssh->gsslibs = NULL; +#endif + + p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive); + if (p != NULL) + return p; + + random_ref(); + + return NULL; +} + +static void ssh_free(void *handle) +{ + Ssh ssh = (Ssh) handle; + struct ssh_channel *c; + struct ssh_rportfwd *pf; + + if (ssh->v1_cipher_ctx) + ssh->cipher->free_context(ssh->v1_cipher_ctx); + if (ssh->cs_cipher_ctx) + ssh->cscipher->free_context(ssh->cs_cipher_ctx); + if (ssh->sc_cipher_ctx) + ssh->sccipher->free_context(ssh->sc_cipher_ctx); + if (ssh->cs_mac_ctx) + ssh->csmac->free_context(ssh->cs_mac_ctx); + if (ssh->sc_mac_ctx) + ssh->scmac->free_context(ssh->sc_mac_ctx); + if (ssh->cs_comp_ctx) { + if (ssh->cscomp) + ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx); + else + zlib_compress_cleanup(ssh->cs_comp_ctx); + } + if (ssh->sc_comp_ctx) { + if (ssh->sccomp) + ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx); + else + zlib_decompress_cleanup(ssh->sc_comp_ctx); + } + if (ssh->kex_ctx) + dh_cleanup(ssh->kex_ctx); + sfree(ssh->savedhost); + + while (ssh->queuelen-- > 0) + ssh_free_packet(ssh->queue[ssh->queuelen]); + sfree(ssh->queue); + + while (ssh->qhead) { + struct queued_handler *qh = ssh->qhead; + ssh->qhead = qh->next; + sfree(ssh->qhead); + } + ssh->qhead = ssh->qtail = NULL; + + if (ssh->channels) { + while ((c = delpos234(ssh->channels, 0)) != NULL) { + switch (c->type) { + case CHAN_X11: + if (c->u.x11.s != NULL) + x11_close(c->u.x11.s); + break; + case CHAN_SOCKDATA: + case CHAN_SOCKDATA_DORMANT: + if (c->u.pfd.s != NULL) + pfd_close(c->u.pfd.s); + break; + } + sfree(c); + } + freetree234(ssh->channels); + ssh->channels = NULL; + } + + if (ssh->rportfwds) { + while ((pf = delpos234(ssh->rportfwds, 0)) != NULL) + free_rportfwd(pf); + freetree234(ssh->rportfwds); + ssh->rportfwds = NULL; + } + sfree(ssh->deferred_send_data); + if (ssh->x11disp) + x11_free_display(ssh->x11disp); + sfree(ssh->do_ssh_init_state); + sfree(ssh->do_ssh1_login_state); + sfree(ssh->do_ssh2_transport_state); + sfree(ssh->do_ssh2_authconn_state); + sfree(ssh->v_c); + sfree(ssh->v_s); + sfree(ssh->fullhostname); + if (ssh->crcda_ctx) { + crcda_free_context(ssh->crcda_ctx); + ssh->crcda_ctx = NULL; + } + if (ssh->s) + ssh_do_close(ssh, TRUE); + expire_timer_context(ssh); + if (ssh->pinger) + pinger_free(ssh->pinger); + bufchain_clear(&ssh->queued_incoming_data); +#ifndef NO_GSSAPI + if (ssh->gsslibs) + ssh_gss_cleanup(ssh->gsslibs); +#endif + sfree(ssh); + + random_unref(); +} + +/* + * Reconfigure the SSH backend. + */ +static void ssh_reconfig(void *handle, Config *cfg) +{ + Ssh ssh = (Ssh) handle; + char *rekeying = NULL, rekey_mandatory = FALSE; + unsigned long old_max_data_size; + + pinger_reconfig(ssh->pinger, &ssh->cfg, cfg); + if (ssh->portfwds) + ssh_setup_portfwd(ssh, cfg); + + if (ssh->cfg.ssh_rekey_time != cfg->ssh_rekey_time && + cfg->ssh_rekey_time != 0) { + long new_next = ssh->last_rekey + cfg->ssh_rekey_time*60*TICKSPERSEC; + long now = GETTICKCOUNT(); + + if (new_next - now < 0) { + rekeying = "timeout shortened"; + } else { + ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh); + } + } + + old_max_data_size = ssh->max_data_size; + ssh->max_data_size = parse_blocksize(cfg->ssh_rekey_data); + if (old_max_data_size != ssh->max_data_size && + ssh->max_data_size != 0) { + if (ssh->outgoing_data_size > ssh->max_data_size || + ssh->incoming_data_size > ssh->max_data_size) + rekeying = "data limit lowered"; + } + + if (ssh->cfg.compression != cfg->compression) { + rekeying = "compression setting changed"; + rekey_mandatory = TRUE; + } + + if (ssh->cfg.ssh2_des_cbc != cfg->ssh2_des_cbc || + memcmp(ssh->cfg.ssh_cipherlist, cfg->ssh_cipherlist, + sizeof(ssh->cfg.ssh_cipherlist))) { + rekeying = "cipher settings changed"; + rekey_mandatory = TRUE; + } + + ssh->cfg = *cfg; /* STRUCTURE COPY */ + + if (rekeying) { + if (!ssh->kex_in_progress) { + do_ssh2_transport(ssh, rekeying, -1, NULL); + } else if (rekey_mandatory) { + ssh->deferred_rekey_reason = rekeying; + } + } +} + +/* + * Called to send data down the SSH connection. + */ +static int ssh_send(void *handle, char *buf, int len) +{ + Ssh ssh = (Ssh) handle; + + if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL) + return 0; + + ssh->protocol(ssh, (unsigned char *)buf, len, 0); + + return ssh_sendbuffer(ssh); +} + +/* + * Called to query the current amount of buffered stdin data. + */ +static int ssh_sendbuffer(void *handle) +{ + Ssh ssh = (Ssh) handle; + int override_value; + + if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL) + return 0; + + /* + * If the SSH socket itself has backed up, add the total backup + * size on that to any individual buffer on the stdin channel. + */ + override_value = 0; + if (ssh->throttled_all) + override_value = ssh->overall_bufsize; + + if (ssh->version == 1) { + return override_value; + } else if (ssh->version == 2) { + if (!ssh->mainchan || ssh->mainchan->closes > 0) + return override_value; + else + return (override_value + + bufchain_size(&ssh->mainchan->v.v2.outbuffer)); + } + + return 0; +} + +/* + * Called to set the size of the window from SSH's POV. + */ +static void ssh_size(void *handle, int width, int height) +{ + Ssh ssh = (Ssh) handle; + struct Packet *pktout; + + ssh->term_width = width; + ssh->term_height = height; + + switch (ssh->state) { + case SSH_STATE_BEFORE_SIZE: + case SSH_STATE_PREPACKET: + case SSH_STATE_CLOSED: + break; /* do nothing */ + case SSH_STATE_INTERMED: + ssh->size_needed = TRUE; /* buffer for later */ + break; + case SSH_STATE_SESSION: + if (!ssh->cfg.nopty) { + if (ssh->version == 1) { + send_packet(ssh, SSH1_CMSG_WINDOW_SIZE, + PKT_INT, ssh->term_height, + PKT_INT, ssh->term_width, + PKT_INT, 0, PKT_INT, 0, PKT_END); + } else if (ssh->mainchan) { + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid); + ssh2_pkt_addstring(pktout, "window-change"); + ssh2_pkt_addbool(pktout, 0); + ssh2_pkt_adduint32(pktout, ssh->term_width); + ssh2_pkt_adduint32(pktout, ssh->term_height); + ssh2_pkt_adduint32(pktout, 0); + ssh2_pkt_adduint32(pktout, 0); + ssh2_pkt_send(ssh, pktout); + } + } + break; + } +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *ssh_get_specials(void *handle) +{ + static const struct telnet_special ssh1_ignore_special[] = { + {"IGNORE message", TS_NOP} + }; + static const struct telnet_special ssh2_ignore_special[] = { + {"IGNORE message", TS_NOP}, + }; + static const struct telnet_special ssh2_rekey_special[] = { + {"Repeat key exchange", TS_REKEY}, + }; + static const struct telnet_special ssh2_session_specials[] = { + {NULL, TS_SEP}, + {"Break", TS_BRK}, + /* These are the signal names defined by RFC 4254. + * They include all the ISO C signals, but are a subset of the POSIX + * required signals. */ + {"SIGINT (Interrupt)", TS_SIGINT}, + {"SIGTERM (Terminate)", TS_SIGTERM}, + {"SIGKILL (Kill)", TS_SIGKILL}, + {"SIGQUIT (Quit)", TS_SIGQUIT}, + {"SIGHUP (Hangup)", TS_SIGHUP}, + {"More signals", TS_SUBMENU}, + {"SIGABRT", TS_SIGABRT}, {"SIGALRM", TS_SIGALRM}, + {"SIGFPE", TS_SIGFPE}, {"SIGILL", TS_SIGILL}, + {"SIGPIPE", TS_SIGPIPE}, {"SIGSEGV", TS_SIGSEGV}, + {"SIGUSR1", TS_SIGUSR1}, {"SIGUSR2", TS_SIGUSR2}, + {NULL, TS_EXITMENU} + }; + static const struct telnet_special specials_end[] = { + {NULL, TS_EXITMENU} + }; + /* XXX review this length for any changes: */ + static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) + + lenof(ssh2_rekey_special) + + lenof(ssh2_session_specials) + + lenof(specials_end)]; + Ssh ssh = (Ssh) handle; + int i = 0; +#define ADD_SPECIALS(name) \ + do { \ + assert((i + lenof(name)) <= lenof(ssh_specials)); \ + memcpy(&ssh_specials[i], name, sizeof name); \ + i += lenof(name); \ + } while(0) + + if (ssh->version == 1) { + /* Don't bother offering IGNORE if we've decided the remote + * won't cope with it, since we wouldn't bother sending it if + * asked anyway. */ + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) + ADD_SPECIALS(ssh1_ignore_special); + } else if (ssh->version == 2) { + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) + ADD_SPECIALS(ssh2_ignore_special); + if (!(ssh->remote_bugs & BUG_SSH2_REKEY)) + ADD_SPECIALS(ssh2_rekey_special); + if (ssh->mainchan) + ADD_SPECIALS(ssh2_session_specials); + } /* else we're not ready yet */ + + if (i) { + ADD_SPECIALS(specials_end); + return ssh_specials; + } else { + return NULL; + } +#undef ADD_SPECIALS +} + +/* + * Send special codes. TS_EOF is useful for `plink', so you + * can send an EOF and collect resulting output (e.g. `plink + * hostname sort'). + */ +static void ssh_special(void *handle, Telnet_Special code) +{ + Ssh ssh = (Ssh) handle; + struct Packet *pktout; + + if (code == TS_EOF) { + if (ssh->state != SSH_STATE_SESSION) { + /* + * Buffer the EOF in case we are pre-SESSION, so we can + * send it as soon as we reach SESSION. + */ + if (code == TS_EOF) + ssh->eof_needed = TRUE; + return; + } + if (ssh->version == 1) { + send_packet(ssh, SSH1_CMSG_EOF, PKT_END); + } else if (ssh->mainchan) { + struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF); + ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid); + ssh2_pkt_send(ssh, pktout); + ssh->send_ok = 0; /* now stop trying to read from stdin */ + } + logevent("Sent EOF message"); + } else if (code == TS_PING || code == TS_NOP) { + if (ssh->state == SSH_STATE_CLOSED + || ssh->state == SSH_STATE_PREPACKET) return; + if (ssh->version == 1) { + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) + send_packet(ssh, SSH1_MSG_IGNORE, PKT_STR, "", PKT_END); + } else { + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { + pktout = ssh2_pkt_init(SSH2_MSG_IGNORE); + ssh2_pkt_addstring_start(pktout); + ssh2_pkt_send_noqueue(ssh, pktout); + } + } + } else if (code == TS_REKEY) { + if (!ssh->kex_in_progress && ssh->version == 2) { + do_ssh2_transport(ssh, "at user request", -1, NULL); + } + } else if (code == TS_BRK) { + if (ssh->state == SSH_STATE_CLOSED + || ssh->state == SSH_STATE_PREPACKET) return; + if (ssh->version == 1) { + logevent("Unable to send BREAK signal in SSH-1"); + } else if (ssh->mainchan) { + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid); + ssh2_pkt_addstring(pktout, "break"); + ssh2_pkt_addbool(pktout, 0); + ssh2_pkt_adduint32(pktout, 0); /* default break length */ + ssh2_pkt_send(ssh, pktout); + } + } else { + /* Is is a POSIX signal? */ + char *signame = NULL; + if (code == TS_SIGABRT) signame = "ABRT"; + if (code == TS_SIGALRM) signame = "ALRM"; + if (code == TS_SIGFPE) signame = "FPE"; + if (code == TS_SIGHUP) signame = "HUP"; + if (code == TS_SIGILL) signame = "ILL"; + if (code == TS_SIGINT) signame = "INT"; + if (code == TS_SIGKILL) signame = "KILL"; + if (code == TS_SIGPIPE) signame = "PIPE"; + if (code == TS_SIGQUIT) signame = "QUIT"; + if (code == TS_SIGSEGV) signame = "SEGV"; + if (code == TS_SIGTERM) signame = "TERM"; + if (code == TS_SIGUSR1) signame = "USR1"; + if (code == TS_SIGUSR2) signame = "USR2"; + /* The SSH-2 protocol does in principle support arbitrary named + * signals, including signame@domain, but we don't support those. */ + if (signame) { + /* It's a signal. */ + if (ssh->version == 2 && ssh->mainchan) { + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid); + ssh2_pkt_addstring(pktout, "signal"); + ssh2_pkt_addbool(pktout, 0); + ssh2_pkt_addstring(pktout, signame); + ssh2_pkt_send(ssh, pktout); + logeventf(ssh, "Sent signal SIG%s", signame); + } + } else { + /* Never heard of it. Do nothing */ + } + } +} + +void *new_sock_channel(void *handle, Socket s) +{ + Ssh ssh = (Ssh) handle; + struct ssh_channel *c; + c = snew(struct ssh_channel); + + c->ssh = ssh; + ssh2_channel_init(c); + c->halfopen = TRUE; + c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */ + c->u.pfd.s = s; + add234(ssh->channels, c); + return c; +} + +/* + * This is called when stdout/stderr (the entity to which + * from_backend sends data) manages to clear some backlog. + */ +static void ssh_unthrottle(void *handle, int bufsize) +{ + Ssh ssh = (Ssh) handle; + int buflimit; + + if (ssh->version == 1) { + if (ssh->v1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) { + ssh->v1_stdout_throttling = 0; + ssh_throttle_conn(ssh, -1); + } + } else { + if (ssh->mainchan) { + ssh2_set_window(ssh->mainchan, + bufsize < ssh->mainchan->v.v2.locmaxwin ? + ssh->mainchan->v.v2.locmaxwin - bufsize : 0); + if (ssh->cfg.ssh_simple) + buflimit = 0; + else + buflimit = ssh->mainchan->v.v2.locmaxwin; + if (ssh->mainchan->throttling_conn && bufsize <= buflimit) { + ssh->mainchan->throttling_conn = 0; + ssh_throttle_conn(ssh, -1); + } + } + } +} + +void ssh_send_port_open(void *channel, char *hostname, int port, char *org) +{ + struct ssh_channel *c = (struct ssh_channel *)channel; + Ssh ssh = c->ssh; + struct Packet *pktout; + + logeventf(ssh, "Opening forwarded connection to %s:%d", hostname, port); + + if (ssh->version == 1) { + send_packet(ssh, SSH1_MSG_PORT_OPEN, + PKT_INT, c->localid, + PKT_STR, hostname, + PKT_INT, port, + /* PKT_STR, , */ + PKT_END); + } else { + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); + ssh2_pkt_addstring(pktout, "direct-tcpip"); + ssh2_pkt_adduint32(pktout, c->localid); + ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */ + ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ + ssh2_pkt_addstring(pktout, hostname); + ssh2_pkt_adduint32(pktout, port); + /* + * We make up values for the originator data; partly it's + * too much hassle to keep track, and partly I'm not + * convinced the server should be told details like that + * about my local network configuration. + * The "originator IP address" is syntactically a numeric + * IP address, and some servers (e.g., Tectia) get upset + * if it doesn't match this syntax. + */ + ssh2_pkt_addstring(pktout, "0.0.0.0"); + ssh2_pkt_adduint32(pktout, 0); + ssh2_pkt_send(ssh, pktout); + } +} + +static int ssh_connected(void *handle) +{ + Ssh ssh = (Ssh) handle; + return ssh->s != NULL; +} + +static int ssh_sendok(void *handle) +{ + Ssh ssh = (Ssh) handle; + return ssh->send_ok; +} + +static int ssh_ldisc(void *handle, int option) +{ + Ssh ssh = (Ssh) handle; + if (option == LD_ECHO) + return ssh->echoing; + if (option == LD_EDIT) + return ssh->editing; + return FALSE; +} + +static void ssh_provide_ldisc(void *handle, void *ldisc) +{ + Ssh ssh = (Ssh) handle; + ssh->ldisc = ldisc; +} + +static void ssh_provide_logctx(void *handle, void *logctx) +{ + Ssh ssh = (Ssh) handle; + ssh->logctx = logctx; +} + +static int ssh_return_exitcode(void *handle) +{ + Ssh ssh = (Ssh) handle; + if (ssh->s != NULL) + return -1; + else + return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX); +} + +/* + * cfg_info for SSH is the currently running version of the + * protocol. (1 for 1; 2 for 2; 0 for not-decided-yet.) + */ +static int ssh_cfg_info(void *handle) +{ + Ssh ssh = (Ssh) handle; + return ssh->version; +} + +/* + * Gross hack: pscp will try to start SFTP but fall back to scp1 if + * that fails. This variable is the means by which scp.c can reach + * into the SSH code and find out which one it got. + */ +extern int ssh_fallback_cmd(void *handle) +{ + Ssh ssh = (Ssh) handle; + return ssh->fallback_cmd; +} + +Backend ssh_backend = { + ssh_init, + ssh_free, + ssh_reconfig, + ssh_send, + ssh_sendbuffer, + ssh_size, + ssh_special, + ssh_get_specials, + ssh_connected, + ssh_return_exitcode, + ssh_sendok, + ssh_ldisc, + ssh_provide_ldisc, + ssh_provide_logctx, + ssh_unthrottle, + ssh_cfg_info, + "ssh", + PROT_SSH, + 22 +}; diff --git a/putty/SSH.H b/putty/SSH.H new file mode 100644 index 0000000..605b608 --- /dev/null +++ b/putty/SSH.H @@ -0,0 +1,600 @@ +#include +#include + +#include "puttymem.h" +#include "tree234.h" +#include "network.h" +#include "int64.h" +#include "misc.h" + +struct ssh_channel; + +extern void sshfwd_close(struct ssh_channel *c); +extern int sshfwd_write(struct ssh_channel *c, char *, int); +extern void sshfwd_unthrottle(struct ssh_channel *c, int bufsize); + +/* + * Useful thing. + */ +#ifndef lenof +#define lenof(x) ( (sizeof((x))) / (sizeof(*(x)))) +#endif + +#define SSH_CIPHER_IDEA 1 +#define SSH_CIPHER_DES 2 +#define SSH_CIPHER_3DES 3 +#define SSH_CIPHER_BLOWFISH 6 + +#ifdef MSCRYPTOAPI +#define APIEXTRA 8 +#else +#define APIEXTRA 0 +#endif + +#ifndef BIGNUM_INTERNAL +typedef void *Bignum; +#endif + +struct RSAKey { + int bits; + int bytes; +#ifdef MSCRYPTOAPI + unsigned long exponent; + unsigned char *modulus; +#else + Bignum modulus; + Bignum exponent; + Bignum private_exponent; + Bignum p; + Bignum q; + Bignum iqmp; +#endif + char *comment; +}; + +struct dss_key { + Bignum p, q, g, y, x; +}; + +int makekey(unsigned char *data, int len, struct RSAKey *result, + unsigned char **keystr, int order); +int makeprivate(unsigned char *data, int len, struct RSAKey *result); +int rsaencrypt(unsigned char *data, int length, struct RSAKey *key); +Bignum rsadecrypt(Bignum input, struct RSAKey *key); +void rsasign(unsigned char *data, int length, struct RSAKey *key); +void rsasanitise(struct RSAKey *key); +int rsastr_len(struct RSAKey *key); +void rsastr_fmt(char *str, struct RSAKey *key); +void rsa_fingerprint(char *str, int len, struct RSAKey *key); +int rsa_verify(struct RSAKey *key); +unsigned char *rsa_public_blob(struct RSAKey *key, int *len); +int rsa_public_blob_len(void *data, int maxlen); +void freersakey(struct RSAKey *key); + +#ifndef PUTTY_UINT32_DEFINED +/* This makes assumptions about the int type. */ +typedef unsigned int uint32; +#define PUTTY_UINT32_DEFINED +#endif +typedef uint32 word32; + +unsigned long crc32_compute(const void *s, size_t len); +unsigned long crc32_update(unsigned long crc_input, const void *s, size_t len); + +/* SSH CRC compensation attack detector */ +void *crcda_make_context(void); +void crcda_free_context(void *handle); +int detect_attack(void *handle, unsigned char *buf, uint32 len, + unsigned char *IV); + +/* + * SSH2 RSA key exchange functions + */ +struct ssh_hash; +void *ssh_rsakex_newkey(char *data, int len); +void ssh_rsakex_freekey(void *key); +int ssh_rsakex_klen(void *key); +void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen, + unsigned char *out, int outlen, + void *key); + +typedef struct { + uint32 h[4]; +} MD5_Core_State; + +struct MD5Context { +#ifdef MSCRYPTOAPI + unsigned long hHash; +#else + MD5_Core_State core; + unsigned char block[64]; + int blkused; + uint32 lenhi, lenlo; +#endif +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, unsigned char const *buf, + unsigned len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); +void MD5Simple(void const *p, unsigned len, unsigned char output[16]); + +void *hmacmd5_make_context(void); +void hmacmd5_free_context(void *handle); +void hmacmd5_key(void *handle, void const *key, int len); +void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len, + unsigned char *hmac); + +typedef struct { + uint32 h[5]; + unsigned char block[64]; + int blkused; + uint32 lenhi, lenlo; +} SHA_State; +void SHA_Init(SHA_State * s); +void SHA_Bytes(SHA_State * s, void *p, int len); +void SHA_Final(SHA_State * s, unsigned char *output); +void SHA_Simple(void *p, int len, unsigned char *output); + +void hmac_sha1_simple(void *key, int keylen, void *data, int datalen, + unsigned char *output); +typedef struct { + uint32 h[8]; + unsigned char block[64]; + int blkused; + uint32 lenhi, lenlo; +} SHA256_State; +void SHA256_Init(SHA256_State * s); +void SHA256_Bytes(SHA256_State * s, const void *p, int len); +void SHA256_Final(SHA256_State * s, unsigned char *output); +void SHA256_Simple(const void *p, int len, unsigned char *output); + +typedef struct { + uint64 h[8]; + unsigned char block[128]; + int blkused; + uint32 len[4]; +} SHA512_State; +void SHA512_Init(SHA512_State * s); +void SHA512_Bytes(SHA512_State * s, const void *p, int len); +void SHA512_Final(SHA512_State * s, unsigned char *output); +void SHA512_Simple(const void *p, int len, unsigned char *output); + +struct ssh_cipher { + void *(*make_context)(void); + void (*free_context)(void *); + void (*sesskey) (void *, unsigned char *key); /* for SSH-1 */ + void (*encrypt) (void *, unsigned char *blk, int len); + void (*decrypt) (void *, unsigned char *blk, int len); + int blksize; + char *text_name; +}; + +struct ssh2_cipher { + void *(*make_context)(void); + void (*free_context)(void *); + void (*setiv) (void *, unsigned char *key); /* for SSH-2 */ + void (*setkey) (void *, unsigned char *key);/* for SSH-2 */ + void (*encrypt) (void *, unsigned char *blk, int len); + void (*decrypt) (void *, unsigned char *blk, int len); + char *name; + int blksize; + int keylen; + unsigned int flags; +#define SSH_CIPHER_IS_CBC 1 + char *text_name; +}; + +struct ssh2_ciphers { + int nciphers; + const struct ssh2_cipher *const *list; +}; + +struct ssh_mac { + void *(*make_context)(void); + void (*free_context)(void *); + void (*setkey) (void *, unsigned char *key); + /* whole-packet operations */ + void (*generate) (void *, unsigned char *blk, int len, unsigned long seq); + int (*verify) (void *, unsigned char *blk, int len, unsigned long seq); + /* partial-packet operations */ + void (*start) (void *); + void (*bytes) (void *, unsigned char const *, int); + void (*genresult) (void *, unsigned char *); + int (*verresult) (void *, unsigned char const *); + char *name; + int len; + char *text_name; +}; + +struct ssh_hash { + void *(*init)(void); /* also allocates context */ + void (*bytes)(void *, void *, int); + void (*final)(void *, unsigned char *); /* also frees context */ + int hlen; /* output length in bytes */ + char *text_name; +}; + +struct ssh_kex { + char *name, *groupname; + enum { KEXTYPE_DH, KEXTYPE_RSA } main_type; + /* For DH */ + const unsigned char *pdata, *gdata; /* NULL means group exchange */ + int plen, glen; + const struct ssh_hash *hash; +}; + +struct ssh_kexes { + int nkexes; + const struct ssh_kex *const *list; +}; + +struct ssh_signkey { + void *(*newkey) (char *data, int len); + void (*freekey) (void *key); + char *(*fmtkey) (void *key); + unsigned char *(*public_blob) (void *key, int *len); + unsigned char *(*private_blob) (void *key, int *len); + void *(*createkey) (unsigned char *pub_blob, int pub_len, + unsigned char *priv_blob, int priv_len); + void *(*openssh_createkey) (unsigned char **blob, int *len); + int (*openssh_fmtkey) (void *key, unsigned char *blob, int len); + int (*pubkey_bits) (void *blob, int len); + char *(*fingerprint) (void *key); + int (*verifysig) (void *key, char *sig, int siglen, + char *data, int datalen); + unsigned char *(*sign) (void *key, char *data, int datalen, + int *siglen); + char *name; + char *keytype; /* for host key cache */ +}; + +struct ssh_compress { + char *name; + /* For zlib@openssh.com: if non-NULL, this name will be considered once + * userauth has completed successfully. */ + char *delayed_name; + void *(*compress_init) (void); + void (*compress_cleanup) (void *); + int (*compress) (void *, unsigned char *block, int len, + unsigned char **outblock, int *outlen); + void *(*decompress_init) (void); + void (*decompress_cleanup) (void *); + int (*decompress) (void *, unsigned char *block, int len, + unsigned char **outblock, int *outlen); + int (*disable_compression) (void *); + char *text_name; +}; + +struct ssh2_userkey { + const struct ssh_signkey *alg; /* the key algorithm */ + void *data; /* the key data */ + char *comment; /* the key comment */ +}; + +/* The maximum length of any hash algorithm used in kex. (bytes) */ +#define SSH2_KEX_MAX_HASH_LEN (32) /* SHA-256 */ + +extern const struct ssh_cipher ssh_3des; +extern const struct ssh_cipher ssh_des; +extern const struct ssh_cipher ssh_blowfish_ssh1; +extern const struct ssh2_ciphers ssh2_3des; +extern const struct ssh2_ciphers ssh2_des; +extern const struct ssh2_ciphers ssh2_aes; +extern const struct ssh2_ciphers ssh2_blowfish; +extern const struct ssh2_ciphers ssh2_arcfour; +extern const struct ssh_hash ssh_sha1; +extern const struct ssh_hash ssh_sha256; +extern const struct ssh_kexes ssh_diffiehellman_group1; +extern const struct ssh_kexes ssh_diffiehellman_group14; +extern const struct ssh_kexes ssh_diffiehellman_gex; +extern const struct ssh_kexes ssh_rsa_kex; +extern const struct ssh_signkey ssh_dss; +extern const struct ssh_signkey ssh_rsa; +extern const struct ssh_mac ssh_hmac_md5; +extern const struct ssh_mac ssh_hmac_sha1; +extern const struct ssh_mac ssh_hmac_sha1_buggy; +extern const struct ssh_mac ssh_hmac_sha1_96; +extern const struct ssh_mac ssh_hmac_sha1_96_buggy; + +void *aes_make_context(void); +void aes_free_context(void *handle); +void aes128_key(void *handle, unsigned char *key); +void aes192_key(void *handle, unsigned char *key); +void aes256_key(void *handle, unsigned char *key); +void aes_iv(void *handle, unsigned char *iv); +void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len); +void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len); + +/* + * PuTTY version number formatted as an SSH version string. + */ +extern char sshver[]; + +/* + * Gross hack: pscp will try to start SFTP but fall back to scp1 if + * that fails. This variable is the means by which scp.c can reach + * into the SSH code and find out which one it got. + */ +extern int ssh_fallback_cmd(void *handle); + +#ifndef MSCRYPTOAPI +void SHATransform(word32 * digest, word32 * data); +#endif + +int random_byte(void); +void random_add_noise(void *noise, int length); +void random_add_heavynoise(void *noise, int length); + +void logevent(void *, const char *); + +/* Allocate and register a new channel for port forwarding */ +void *new_sock_channel(void *handle, Socket s); +void ssh_send_port_open(void *channel, char *hostname, int port, char *org); + +/* Exports from portfwd.c */ +extern const char *pfd_newconnect(Socket * s, char *hostname, int port, + void *c, const Config *cfg, + int addressfamily); +/* desthost == NULL indicates dynamic (SOCKS) port forwarding */ +extern const char *pfd_addforward(char *desthost, int destport, char *srcaddr, + int port, void *backhandle, + const Config *cfg, void **sockdata, + int address_family); +extern void pfd_close(Socket s); +extern void pfd_terminate(void *sockdata); +extern int pfd_send(Socket s, char *data, int len); +extern void pfd_confirm(Socket s); +extern void pfd_unthrottle(Socket s); +extern void pfd_override_throttle(Socket s, int enable); + +/* Exports from x11fwd.c */ +enum { + X11_TRANS_IPV4 = 0, X11_TRANS_IPV6 = 6, X11_TRANS_UNIX = 256 +}; +struct X11Display { + /* Broken-down components of the display name itself */ + int unixdomain; + char *hostname; + int displaynum; + int screennum; + /* OSX sometimes replaces all the above with a full Unix-socket pathname */ + char *unixsocketpath; + + /* PuTTY networking SockAddr to connect to the display, and associated + * gubbins */ + SockAddr addr; + int port; + char *realhost; + + /* Auth details we invented for the virtual display on the SSH server. */ + int remoteauthproto; + unsigned char *remoteauthdata; + int remoteauthdatalen; + char *remoteauthprotoname; + char *remoteauthdatastring; + + /* Our local auth details for talking to the real X display. */ + int localauthproto; + unsigned char *localauthdata; + int localauthdatalen; + + /* + * Used inside x11fwd.c to remember recently seen + * XDM-AUTHORIZATION-1 strings, to avoid replay attacks. + */ + tree234 *xdmseen; +}; +/* + * x11_setup_display() parses the display variable and fills in an + * X11Display structure. Some remote auth details are invented; + * the supplied authtype parameter configures the preferred + * authorisation protocol to use at the remote end. The local auth + * details are looked up by calling platform_get_x11_auth. + */ +extern struct X11Display *x11_setup_display(char *display, int authtype, + const Config *); +void x11_free_display(struct X11Display *disp); +extern const char *x11_init(Socket *, struct X11Display *, void *, + const char *, int, const Config *); +extern void x11_close(Socket); +extern int x11_send(Socket, char *, int); +extern void x11_unthrottle(Socket s); +extern void x11_override_throttle(Socket s, int enable); +char *x11_display(const char *display); +/* Platform-dependent X11 functions */ +extern void platform_get_x11_auth(struct X11Display *display, + const Config *); + /* examine a mostly-filled-in X11Display and fill in localauth* */ +extern const int platform_uses_x11_unix_by_default; + /* choose default X transport in the absence of a specified one */ +SockAddr platform_get_x11_unix_address(const char *path, int displaynum); + /* make up a SockAddr naming the address for displaynum */ +char *platform_get_x_display(void); + /* allocated local X display string, if any */ +/* Callbacks in x11.c usable _by_ platform X11 functions */ +/* + * This function does the job of platform_get_x11_auth, provided + * it is told where to find a normally formatted .Xauthority file: + * it opens that file, parses it to find an auth record which + * matches the display details in "display", and fills in the + * localauth fields. + * + * It is expected that most implementations of + * platform_get_x11_auth() will work by finding their system's + * .Xauthority file, adjusting the display details if necessary + * for local oddities like Unix-domain socket transport, and + * calling this function to do the rest of the work. + */ +void x11_get_auth_from_authfile(struct X11Display *display, + const char *authfilename); + +Bignum copybn(Bignum b); +Bignum bn_power_2(int n); +void bn_restore_invariant(Bignum b); +Bignum bignum_from_long(unsigned long n); +void freebn(Bignum b); +Bignum modpow(Bignum base, Bignum exp, Bignum mod); +Bignum modmul(Bignum a, Bignum b, Bignum mod); +void decbn(Bignum n); +extern Bignum Zero, One; +Bignum bignum_from_bytes(const unsigned char *data, int nbytes); +int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result); +int bignum_bitcount(Bignum bn); +int ssh1_bignum_length(Bignum bn); +int ssh2_bignum_length(Bignum bn); +int bignum_byte(Bignum bn, int i); +int bignum_bit(Bignum bn, int i); +void bignum_set_bit(Bignum bn, int i, int value); +int ssh1_write_bignum(void *data, Bignum bn); +Bignum biggcd(Bignum a, Bignum b); +unsigned short bignum_mod_short(Bignum number, unsigned short modulus); +Bignum bignum_add_long(Bignum number, unsigned long addend); +Bignum bigadd(Bignum a, Bignum b); +Bignum bigsub(Bignum a, Bignum b); +Bignum bigmul(Bignum a, Bignum b); +Bignum bigmuladd(Bignum a, Bignum b, Bignum addend); +Bignum bigdiv(Bignum a, Bignum b); +Bignum bigmod(Bignum a, Bignum b); +Bignum modinv(Bignum number, Bignum modulus); +Bignum bignum_bitmask(Bignum number); +Bignum bignum_rshift(Bignum number, int shift); +int bignum_cmp(Bignum a, Bignum b); +char *bignum_decimal(Bignum x); + +#ifdef DEBUG +void diagbn(char *prefix, Bignum md); +#endif + +void *dh_setup_group(const struct ssh_kex *kex); +void *dh_setup_gex(Bignum pval, Bignum gval); +void dh_cleanup(void *); +Bignum dh_create_e(void *, int nbits); +Bignum dh_find_K(void *, Bignum f); + +int loadrsakey(const Filename *filename, struct RSAKey *key, + char *passphrase, const char **errorstr); +int rsakey_encrypted(const Filename *filename, char **comment); +int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen, + char **commentptr, const char **errorstr); + +int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase); + +extern int base64_decode_atom(char *atom, unsigned char *out); +extern int base64_lines(int datalen); +extern void base64_encode_atom(unsigned char *data, int n, char *out); +extern void base64_encode(FILE *fp, unsigned char *data, int datalen, int cpl); + +/* ssh2_load_userkey can return this as an error */ +extern struct ssh2_userkey ssh2_wrong_passphrase; +#define SSH2_WRONG_PASSPHRASE (&ssh2_wrong_passphrase) + +int ssh2_userkey_encrypted(const Filename *filename, char **comment); +struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, + char *passphrase, const char **errorstr); +unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm, + int *pub_blob_len, char **commentptr, + const char **errorstr); +int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, + char *passphrase); +const struct ssh_signkey *find_pubkey_alg(const char *name); + +enum { + SSH_KEYTYPE_UNOPENABLE, + SSH_KEYTYPE_UNKNOWN, + SSH_KEYTYPE_SSH1, SSH_KEYTYPE_SSH2, + SSH_KEYTYPE_OPENSSH, SSH_KEYTYPE_SSHCOM +}; +int key_type(const Filename *filename); +char *key_type_to_str(int type); + +int import_possible(int type); +int import_target_type(int type); +int import_encrypted(const Filename *filename, int type, char **comment); +int import_ssh1(const Filename *filename, int type, + struct RSAKey *key, char *passphrase, const char **errmsg_p); +struct ssh2_userkey *import_ssh2(const Filename *filename, int type, + char *passphrase, const char **errmsg_p); +int export_ssh1(const Filename *filename, int type, + struct RSAKey *key, char *passphrase); +int export_ssh2(const Filename *filename, int type, + struct ssh2_userkey *key, char *passphrase); + +void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len); +void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len); +void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, + unsigned char *blk, int len); +void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, + unsigned char *blk, int len); +void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk, + int len); +void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, + int len); + +void des_encrypt_xdmauth(unsigned char *key, unsigned char *blk, int len); +void des_decrypt_xdmauth(unsigned char *key, unsigned char *blk, int len); + +/* + * For progress updates in the key generation utility. + */ +#define PROGFN_INITIALISE 1 +#define PROGFN_LIN_PHASE 2 +#define PROGFN_EXP_PHASE 3 +#define PROGFN_PHASE_EXTENT 4 +#define PROGFN_READY 5 +#define PROGFN_PROGRESS 6 +typedef void (*progfn_t) (void *param, int action, int phase, int progress); + +int rsa_generate(struct RSAKey *key, int bits, progfn_t pfn, + void *pfnparam); +int dsa_generate(struct dss_key *key, int bits, progfn_t pfn, + void *pfnparam); +Bignum primegen(int bits, int modulus, int residue, Bignum factor, + int phase, progfn_t pfn, void *pfnparam); + + +/* + * zlib compression. + */ +void *zlib_compress_init(void); +void zlib_compress_cleanup(void *); +void *zlib_decompress_init(void); +void zlib_decompress_cleanup(void *); +int zlib_compress_block(void *, unsigned char *block, int len, + unsigned char **outblock, int *outlen); +int zlib_decompress_block(void *, unsigned char *block, int len, + unsigned char **outblock, int *outlen); + +/* + * SSH-1 agent messages. + */ +#define SSH1_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH1_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH1_AGENTC_RSA_CHALLENGE 3 +#define SSH1_AGENT_RSA_RESPONSE 4 +#define SSH1_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH1_AGENTC_REMOVE_RSA_IDENTITY 8 +#define SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 /* openssh private? */ + +/* + * Messages common to SSH-1 and OpenSSH's SSH-2. + */ +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 + +/* + * OpenSSH's SSH-2 agent messages. + */ +#define SSH2_AGENTC_REQUEST_IDENTITIES 11 +#define SSH2_AGENT_IDENTITIES_ANSWER 12 +#define SSH2_AGENTC_SIGN_REQUEST 13 +#define SSH2_AGENT_SIGN_RESPONSE 14 +#define SSH2_AGENTC_ADD_IDENTITY 17 +#define SSH2_AGENTC_REMOVE_IDENTITY 18 +#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 + +/* + * Need this to warn about support for the original SSH-2 keyfile + * format. + */ +void old_keyfile_warning(void); diff --git a/putty/SSHAES.C b/putty/SSHAES.C new file mode 100644 index 0000000..7684cd9 --- /dev/null +++ b/putty/SSHAES.C @@ -0,0 +1,1234 @@ +/* + * aes.c - implementation of AES / Rijndael + * + * AES is a flexible algorithm as regards endianness: it has no + * inherent preference as to which way round you should form words + * from the input byte stream. It talks endlessly of four-byte + * _vectors_, but never of 32-bit _words_ - there's no 32-bit + * addition at all, which would force an endianness by means of + * which way the carries went. So it would be possible to write a + * working AES that read words big-endian, and another working one + * that read them little-endian, just by computing a different set + * of tables - with no speed drop. + * + * It's therefore tempting to do just that, and remove the overhead + * of GET_32BIT_MSB_FIRST() et al, allowing every system to use its + * own endianness-native code; but I decided not to, partly for + * ease of testing, and mostly because I like the flexibility that + * allows you to encrypt a non-word-aligned block of memory (which + * many systems would stop being able to do if I went the + * endianness-dependent route). + * + * This implementation reads and stores words big-endian, but + * that's a minor implementation detail. By flipping the endianness + * of everything in the E0..E3, D0..D3 tables, and substituting + * GET_32BIT_LSB_FIRST for GET_32BIT_MSB_FIRST, I could create an + * implementation that worked internally little-endian and gave the + * same answers at the same speed. + */ + +#include +#include + +#include "ssh.h" + +#define MAX_NR 14 /* max no of rounds */ +#define MAX_NK 8 /* max no of words in input key */ +#define MAX_NB 8 /* max no of words in cipher blk */ + +#define mulby2(x) ( ((x&0x7F) << 1) ^ (x & 0x80 ? 0x1B : 0) ) + +typedef struct AESContext AESContext; + +struct AESContext { + word32 keysched[(MAX_NR + 1) * MAX_NB]; + word32 invkeysched[(MAX_NR + 1) * MAX_NB]; + void (*encrypt) (AESContext * ctx, word32 * block); + void (*decrypt) (AESContext * ctx, word32 * block); + word32 iv[MAX_NB]; + int Nb, Nr; +}; + +static const unsigned char Sbox[256] = { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, + 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, + 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, + 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, + 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, + 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, + 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, + 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, + 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, + 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, + 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, + 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 +}; + +static const unsigned char Sboxinv[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, + 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, + 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, + 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, + 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, + 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, + 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, + 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, + 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, + 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, + 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, + 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, + 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, + 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, + 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, + 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, + 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d +}; + +static const word32 E0[256] = { + 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, + 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, + 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, + 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, + 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, + 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, + 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, + 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, + 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, + 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, + 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, + 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, + 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, + 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, + 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, + 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, + 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, + 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, + 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, + 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, + 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, + 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, + 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, + 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, + 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, + 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, + 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, + 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, + 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, + 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, + 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, + 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, + 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, + 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, + 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, + 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, + 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, + 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, + 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, + 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, + 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, + 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, + 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, + 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, + 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, + 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, + 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, + 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, + 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, + 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, + 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, + 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, + 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, + 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, + 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, + 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, + 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, + 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, + 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, + 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, + 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, + 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, + 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, + 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a, +}; +static const word32 E1[256] = { + 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, + 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, + 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, + 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, + 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, + 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, + 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, + 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, + 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, + 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, + 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, + 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, + 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, + 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, + 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, + 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, + 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, + 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, + 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, + 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, + 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, + 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, + 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, + 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, + 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, + 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, + 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, + 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, + 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, + 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, + 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, + 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, + 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, + 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, + 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, + 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, + 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, + 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, + 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, + 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, + 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, + 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, + 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, + 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, + 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, + 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, + 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, + 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, + 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, + 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, + 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, + 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, + 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, + 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, + 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, + 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, + 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, + 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, + 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, + 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, + 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, + 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, + 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, + 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616, +}; +static const word32 E2[256] = { + 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, + 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, + 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, + 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, + 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, + 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, + 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, + 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, + 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, + 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, + 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, + 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, + 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, + 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, + 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, + 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, + 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, + 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, + 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, + 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, + 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, + 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, + 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, + 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, + 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, + 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, + 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, + 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, + 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, + 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, + 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, + 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, + 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, + 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, + 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, + 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, + 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, + 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, + 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, + 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, + 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, + 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, + 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, + 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, + 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, + 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, + 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, + 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, + 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, + 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, + 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, + 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, + 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, + 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, + 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, + 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, + 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, + 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, + 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, + 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, + 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, + 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, + 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, + 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16, +}; +static const word32 E3[256] = { + 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, + 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, + 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, + 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, + 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, + 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, + 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, + 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, + 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, + 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, + 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, + 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, + 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, + 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, + 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, + 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, + 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, + 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, + 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, + 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, + 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, + 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, + 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, + 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, + 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, + 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, + 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, + 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, + 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, + 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, + 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, + 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, + 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, + 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, + 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, + 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, + 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, + 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, + 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, + 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, + 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, + 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, + 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, + 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, + 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, + 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, + 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, + 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, + 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, + 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, + 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, + 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, + 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, + 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, + 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, + 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, + 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, + 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, + 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, + 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, + 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, + 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, + 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, + 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c, +}; +static const word32 D0[256] = { + 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, + 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, + 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, + 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, + 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, + 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, + 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, + 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, + 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, + 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, + 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, + 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, + 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, + 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, + 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, + 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, + 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, + 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, + 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, + 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, + 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, + 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, + 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, + 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, + 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, + 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, + 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, + 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, + 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, + 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, + 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, + 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, + 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, + 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, + 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, + 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, + 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, + 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, + 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, + 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, + 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, + 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, + 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, + 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, + 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, + 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, + 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, + 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, + 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, + 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, + 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, + 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, + 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, + 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, + 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, + 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, + 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, + 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, + 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, + 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, + 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, + 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, + 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, + 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742, +}; +static const word32 D1[256] = { + 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, + 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, + 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, + 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, + 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, + 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, + 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, + 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, + 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, + 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, + 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, + 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, + 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, + 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, + 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, + 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, + 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, + 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, + 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, + 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, + 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, + 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, + 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, + 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, + 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, + 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, + 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, + 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, + 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, + 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, + 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, + 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, + 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, + 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, + 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, + 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, + 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, + 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, + 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, + 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, + 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, + 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, + 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, + 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, + 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, + 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, + 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, + 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, + 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, + 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, + 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, + 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, + 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, + 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, + 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, + 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, + 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, + 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, + 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, + 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, + 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, + 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, + 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, + 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857, +}; +static const word32 D2[256] = { + 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, + 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, + 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, + 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, + 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, + 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, + 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, + 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, + 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, + 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, + 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, + 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, + 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, + 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, + 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, + 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, + 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, + 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, + 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, + 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, + 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, + 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, + 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, + 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, + 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, + 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, + 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, + 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, + 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, + 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, + 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, + 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, + 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, + 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, + 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, + 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, + 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, + 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, + 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, + 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, + 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, + 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, + 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, + 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, + 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, + 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, + 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, + 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, + 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, + 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, + 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, + 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, + 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, + 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, + 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, + 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, + 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, + 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, + 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, + 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, + 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, + 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, + 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, + 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8, +}; +static const word32 D3[256] = { + 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, + 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, + 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, + 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, + 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, + 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, + 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, + 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, + 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, + 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, + 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, + 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, + 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, + 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, + 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, + 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, + 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, + 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, + 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, + 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, + 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, + 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, + 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, + 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, + 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, + 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, + 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, + 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, + 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, + 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, + 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, + 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, + 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, + 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, + 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, + 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, + 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, + 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, + 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, + 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, + 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, + 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, + 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, + 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, + 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, + 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, + 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, + 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, + 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, + 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, + 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, + 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, + 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, + 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, + 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, + 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, + 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, + 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, + 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, + 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, + 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, + 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, + 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, + 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0, +}; + +/* + * Common macros in both the encryption and decryption routines. + */ +#define ADD_ROUND_KEY_4 (block[0]^=*keysched++, block[1]^=*keysched++, \ + block[2]^=*keysched++, block[3]^=*keysched++) +#define ADD_ROUND_KEY_6 (block[0]^=*keysched++, block[1]^=*keysched++, \ + block[2]^=*keysched++, block[3]^=*keysched++, \ + block[4]^=*keysched++, block[5]^=*keysched++) +#define ADD_ROUND_KEY_8 (block[0]^=*keysched++, block[1]^=*keysched++, \ + block[2]^=*keysched++, block[3]^=*keysched++, \ + block[4]^=*keysched++, block[5]^=*keysched++, \ + block[6]^=*keysched++, block[7]^=*keysched++) +#define MOVEWORD(i) ( block[i] = newstate[i] ) + +/* + * Macros for the encryption routine. There are three encryption + * cores, for Nb=4,6,8. + */ +#define MAKEWORD(i) ( newstate[i] = (E0[(block[i] >> 24) & 0xFF] ^ \ + E1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \ + E2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \ + E3[block[(i+C3)%Nb] & 0xFF]) ) +#define LASTWORD(i) ( newstate[i] = (Sbox[(block[i] >> 24) & 0xFF] << 24) | \ + (Sbox[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \ + (Sbox[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \ + (Sbox[(block[(i+C3)%Nb] ) & 0xFF] ) ) + +/* + * Core encrypt routines, expecting word32 inputs read big-endian + * from the byte-oriented input stream. + */ +static void aes_encrypt_nb_4(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 1, C2 = 2, C3 = 3, Nb = 4; + word32 *keysched = ctx->keysched; + word32 newstate[4]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_4; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + } + ADD_ROUND_KEY_4; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + ADD_ROUND_KEY_4; +} +static void aes_encrypt_nb_6(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 1, C2 = 2, C3 = 3, Nb = 6; + word32 *keysched = ctx->keysched; + word32 newstate[6]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_6; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MAKEWORD(4); + MAKEWORD(5); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + } + ADD_ROUND_KEY_6; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + LASTWORD(4); + LASTWORD(5); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + ADD_ROUND_KEY_6; +} +static void aes_encrypt_nb_8(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 1, C2 = 3, C3 = 4, Nb = 8; + word32 *keysched = ctx->keysched; + word32 newstate[8]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_8; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MAKEWORD(4); + MAKEWORD(5); + MAKEWORD(6); + MAKEWORD(7); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + MOVEWORD(6); + MOVEWORD(7); + } + ADD_ROUND_KEY_8; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + LASTWORD(4); + LASTWORD(5); + LASTWORD(6); + LASTWORD(7); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + MOVEWORD(6); + MOVEWORD(7); + ADD_ROUND_KEY_8; +} + +#undef MAKEWORD +#undef LASTWORD + +/* + * Macros for the decryption routine. There are three decryption + * cores, for Nb=4,6,8. + */ +#define MAKEWORD(i) ( newstate[i] = (D0[(block[i] >> 24) & 0xFF] ^ \ + D1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \ + D2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \ + D3[block[(i+C3)%Nb] & 0xFF]) ) +#define LASTWORD(i) (newstate[i] = (Sboxinv[(block[i] >> 24) & 0xFF] << 24) | \ + (Sboxinv[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \ + (Sboxinv[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \ + (Sboxinv[(block[(i+C3)%Nb] ) & 0xFF] ) ) + +/* + * Core decrypt routines, expecting word32 inputs read big-endian + * from the byte-oriented input stream. + */ +static void aes_decrypt_nb_4(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 4 - 1, C2 = 4 - 2, C3 = 4 - 3, Nb = 4; + word32 *keysched = ctx->invkeysched; + word32 newstate[4]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_4; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + } + ADD_ROUND_KEY_4; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + ADD_ROUND_KEY_4; +} +static void aes_decrypt_nb_6(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 6 - 1, C2 = 6 - 2, C3 = 6 - 3, Nb = 6; + word32 *keysched = ctx->invkeysched; + word32 newstate[6]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_6; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MAKEWORD(4); + MAKEWORD(5); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + } + ADD_ROUND_KEY_6; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + LASTWORD(4); + LASTWORD(5); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + ADD_ROUND_KEY_6; +} +static void aes_decrypt_nb_8(AESContext * ctx, word32 * block) +{ + int i; + static const int C1 = 8 - 1, C2 = 8 - 3, C3 = 8 - 4, Nb = 8; + word32 *keysched = ctx->invkeysched; + word32 newstate[8]; + for (i = 0; i < ctx->Nr - 1; i++) { + ADD_ROUND_KEY_8; + MAKEWORD(0); + MAKEWORD(1); + MAKEWORD(2); + MAKEWORD(3); + MAKEWORD(4); + MAKEWORD(5); + MAKEWORD(6); + MAKEWORD(7); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + MOVEWORD(6); + MOVEWORD(7); + } + ADD_ROUND_KEY_8; + LASTWORD(0); + LASTWORD(1); + LASTWORD(2); + LASTWORD(3); + LASTWORD(4); + LASTWORD(5); + LASTWORD(6); + LASTWORD(7); + MOVEWORD(0); + MOVEWORD(1); + MOVEWORD(2); + MOVEWORD(3); + MOVEWORD(4); + MOVEWORD(5); + MOVEWORD(6); + MOVEWORD(7); + ADD_ROUND_KEY_8; +} + +#undef MAKEWORD +#undef LASTWORD + + +/* + * Set up an AESContext. `keylen' and `blocklen' are measured in + * bytes; each can be either 16 (128-bit), 24 (192-bit), or 32 + * (256-bit). + */ +static void aes_setup(AESContext * ctx, int blocklen, + unsigned char *key, int keylen) +{ + int i, j, Nk, rconst; + + assert(blocklen == 16 || blocklen == 24 || blocklen == 32); + assert(keylen == 16 || keylen == 24 || keylen == 32); + + /* + * Basic parameters. Words per block, words in key, rounds. + */ + Nk = keylen / 4; + ctx->Nb = blocklen / 4; + ctx->Nr = 6 + (ctx->Nb > Nk ? ctx->Nb : Nk); + + /* + * Assign core-function pointers. + */ + if (ctx->Nb == 8) + ctx->encrypt = aes_encrypt_nb_8, ctx->decrypt = aes_decrypt_nb_8; + else if (ctx->Nb == 6) + ctx->encrypt = aes_encrypt_nb_6, ctx->decrypt = aes_decrypt_nb_6; + else if (ctx->Nb == 4) + ctx->encrypt = aes_encrypt_nb_4, ctx->decrypt = aes_decrypt_nb_4; + + /* + * Now do the key setup itself. + */ + rconst = 1; + for (i = 0; i < (ctx->Nr + 1) * ctx->Nb; i++) { + if (i < Nk) + ctx->keysched[i] = GET_32BIT_MSB_FIRST(key + 4 * i); + else { + word32 temp = ctx->keysched[i - 1]; + if (i % Nk == 0) { + int a, b, c, d; + a = (temp >> 16) & 0xFF; + b = (temp >> 8) & 0xFF; + c = (temp >> 0) & 0xFF; + d = (temp >> 24) & 0xFF; + temp = Sbox[a] ^ rconst; + temp = (temp << 8) | Sbox[b]; + temp = (temp << 8) | Sbox[c]; + temp = (temp << 8) | Sbox[d]; + rconst = mulby2(rconst); + } else if (i % Nk == 4 && Nk > 6) { + int a, b, c, d; + a = (temp >> 24) & 0xFF; + b = (temp >> 16) & 0xFF; + c = (temp >> 8) & 0xFF; + d = (temp >> 0) & 0xFF; + temp = Sbox[a]; + temp = (temp << 8) | Sbox[b]; + temp = (temp << 8) | Sbox[c]; + temp = (temp << 8) | Sbox[d]; + } + ctx->keysched[i] = ctx->keysched[i - Nk] ^ temp; + } + } + + /* + * Now prepare the modified keys for the inverse cipher. + */ + for (i = 0; i <= ctx->Nr; i++) { + for (j = 0; j < ctx->Nb; j++) { + word32 temp; + temp = ctx->keysched[(ctx->Nr - i) * ctx->Nb + j]; + if (i != 0 && i != ctx->Nr) { + /* + * Perform the InvMixColumn operation on i. The D + * tables give the result of InvMixColumn applied + * to Sboxinv on individual bytes, so we should + * compose Sbox with the D tables for this. + */ + int a, b, c, d; + a = (temp >> 24) & 0xFF; + b = (temp >> 16) & 0xFF; + c = (temp >> 8) & 0xFF; + d = (temp >> 0) & 0xFF; + temp = D0[Sbox[a]]; + temp ^= D1[Sbox[b]]; + temp ^= D2[Sbox[c]]; + temp ^= D3[Sbox[d]]; + } + ctx->invkeysched[i * ctx->Nb + j] = temp; + } + } +} + +static void aes_encrypt(AESContext * ctx, word32 * block) +{ + ctx->encrypt(ctx, block); +} + +static void aes_decrypt(AESContext * ctx, word32 * block) +{ + ctx->decrypt(ctx, block); +} + +static void aes_encrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +{ + word32 iv[4]; + int i; + + assert((len & 15) == 0); + + memcpy(iv, ctx->iv, sizeof(iv)); + + while (len > 0) { + for (i = 0; i < 4; i++) + iv[i] ^= GET_32BIT_MSB_FIRST(blk + 4 * i); + aes_encrypt(ctx, iv); + for (i = 0; i < 4; i++) + PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i]); + blk += 16; + len -= 16; + } + + memcpy(ctx->iv, iv, sizeof(iv)); +} + +static void aes_decrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +{ + word32 iv[4], x[4], ct[4]; + int i; + + assert((len & 15) == 0); + + memcpy(iv, ctx->iv, sizeof(iv)); + + while (len > 0) { + for (i = 0; i < 4; i++) + x[i] = ct[i] = GET_32BIT_MSB_FIRST(blk + 4 * i); + aes_decrypt(ctx, x); + for (i = 0; i < 4; i++) { + PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i] ^ x[i]); + iv[i] = ct[i]; + } + blk += 16; + len -= 16; + } + + memcpy(ctx->iv, iv, sizeof(iv)); +} + +static void aes_sdctr(unsigned char *blk, int len, AESContext *ctx) +{ + word32 iv[4], b[4], tmp; + int i; + + assert((len & 15) == 0); + + memcpy(iv, ctx->iv, sizeof(iv)); + + while (len > 0) { + memcpy(b, iv, sizeof(b)); + aes_encrypt(ctx, b); + for (i = 0; i < 4; i++) { + tmp = GET_32BIT_MSB_FIRST(blk + 4 * i); + PUT_32BIT_MSB_FIRST(blk + 4 * i, tmp ^ b[i]); + } + for (i = 3; i >= 0; i--) + if ((iv[i] = (iv[i] + 1) & 0xffffffff) != 0) + break; + blk += 16; + len -= 16; + } + + memcpy(ctx->iv, iv, sizeof(iv)); +} + +void *aes_make_context(void) +{ + return snew(AESContext); +} + +void aes_free_context(void *handle) +{ + sfree(handle); +} + +void aes128_key(void *handle, unsigned char *key) +{ + AESContext *ctx = (AESContext *)handle; + aes_setup(ctx, 16, key, 16); +} + +void aes192_key(void *handle, unsigned char *key) +{ + AESContext *ctx = (AESContext *)handle; + aes_setup(ctx, 16, key, 24); +} + +void aes256_key(void *handle, unsigned char *key) +{ + AESContext *ctx = (AESContext *)handle; + aes_setup(ctx, 16, key, 32); +} + +void aes_iv(void *handle, unsigned char *iv) +{ + AESContext *ctx = (AESContext *)handle; + int i; + for (i = 0; i < 4; i++) + ctx->iv[i] = GET_32BIT_MSB_FIRST(iv + 4 * i); +} + +void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len) +{ + AESContext *ctx = (AESContext *)handle; + aes_encrypt_cbc(blk, len, ctx); +} + +void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len) +{ + AESContext *ctx = (AESContext *)handle; + aes_decrypt_cbc(blk, len, ctx); +} + +static void aes_ssh2_sdctr(void *handle, unsigned char *blk, int len) +{ + AESContext *ctx = (AESContext *)handle; + aes_sdctr(blk, len, ctx); +} + +void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +{ + AESContext ctx; + aes_setup(&ctx, 16, key, 32); + memset(ctx.iv, 0, sizeof(ctx.iv)); + aes_encrypt_cbc(blk, len, &ctx); + memset(&ctx, 0, sizeof(ctx)); +} + +void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +{ + AESContext ctx; + aes_setup(&ctx, 16, key, 32); + memset(ctx.iv, 0, sizeof(ctx.iv)); + aes_decrypt_cbc(blk, len, &ctx); + memset(&ctx, 0, sizeof(ctx)); +} + +static const struct ssh2_cipher ssh_aes128_ctr = { + aes_make_context, aes_free_context, aes_iv, aes128_key, + aes_ssh2_sdctr, aes_ssh2_sdctr, + "aes128-ctr", + 16, 128, 0, "AES-128 SDCTR" +}; + +static const struct ssh2_cipher ssh_aes192_ctr = { + aes_make_context, aes_free_context, aes_iv, aes192_key, + aes_ssh2_sdctr, aes_ssh2_sdctr, + "aes192-ctr", + 16, 192, 0, "AES-192 SDCTR" +}; + +static const struct ssh2_cipher ssh_aes256_ctr = { + aes_make_context, aes_free_context, aes_iv, aes256_key, + aes_ssh2_sdctr, aes_ssh2_sdctr, + "aes256-ctr", + 16, 256, 0, "AES-256 SDCTR" +}; + +static const struct ssh2_cipher ssh_aes128 = { + aes_make_context, aes_free_context, aes_iv, aes128_key, + aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, + "aes128-cbc", + 16, 128, SSH_CIPHER_IS_CBC, "AES-128 CBC" +}; + +static const struct ssh2_cipher ssh_aes192 = { + aes_make_context, aes_free_context, aes_iv, aes192_key, + aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, + "aes192-cbc", + 16, 192, SSH_CIPHER_IS_CBC, "AES-192 CBC" +}; + +static const struct ssh2_cipher ssh_aes256 = { + aes_make_context, aes_free_context, aes_iv, aes256_key, + aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, + "aes256-cbc", + 16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC" +}; + +static const struct ssh2_cipher ssh_rijndael_lysator = { + aes_make_context, aes_free_context, aes_iv, aes256_key, + aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, + "rijndael-cbc@lysator.liu.se", + 16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC" +}; + +static const struct ssh2_cipher *const aes_list[] = { + &ssh_aes256_ctr, + &ssh_aes256, + &ssh_rijndael_lysator, + &ssh_aes192_ctr, + &ssh_aes192, + &ssh_aes128_ctr, + &ssh_aes128, +}; + +const struct ssh2_ciphers ssh2_aes = { + sizeof(aes_list) / sizeof(*aes_list), + aes_list +}; diff --git a/putty/SSHARCF.C b/putty/SSHARCF.C new file mode 100644 index 0000000..e0b247e --- /dev/null +++ b/putty/SSHARCF.C @@ -0,0 +1,123 @@ +/* + * Arcfour (RC4) implementation for PuTTY. + * + * Coded from Schneier. + */ + +#include +#include "ssh.h" + +typedef struct { + unsigned char i, j, s[256]; +} ArcfourContext; + +static void arcfour_block(void *handle, unsigned char *blk, int len) +{ + ArcfourContext *ctx = (ArcfourContext *)handle; + unsigned k; + unsigned char tmp, i, j, *s; + + s = ctx->s; + i = ctx->i; j = ctx->j; + for (k = 0; (int)k < len; k++) { + i = (i + 1) & 0xff; + j = (j + s[i]) & 0xff; + tmp = s[i]; s[i] = s[j]; s[j] = tmp; + blk[k] ^= s[(s[i]+s[j]) & 0xff]; + } + ctx->i = i; ctx->j = j; +} + +static void arcfour_setkey(ArcfourContext *ctx, unsigned char const *key, + unsigned keybytes) +{ + unsigned char tmp, k[256], *s; + unsigned i, j; + + s = ctx->s; + assert(keybytes <= 256); + ctx->i = ctx->j = 0; + for (i = 0; i < 256; i++) { + s[i] = i; + k[i] = key[i % keybytes]; + } + j = 0; + for (i = 0; i < 256; i++) { + j = (j + s[i] + k[i]) & 0xff; + tmp = s[i]; s[i] = s[j]; s[j] = tmp; + } +} + +/* -- Interface with PuTTY -- */ + +/* + * We don't implement Arcfour in SSH-1 because it's utterly insecure in + * several ways. See CERT Vulnerability Notes VU#25309, VU#665372, + * and VU#565052. + * + * We don't implement the "arcfour" algorithm in SSH-2 because it doesn't + * stir the cipher state before emitting keystream, and hence is likely + * to leak data about the key. + */ + +static void *arcfour_make_context(void) +{ + return snew(ArcfourContext); +} + +static void arcfour_free_context(void *handle) +{ + sfree(handle); +} + +static void arcfour_stir(ArcfourContext *ctx) +{ + unsigned char *junk = snewn(1536, unsigned char); + memset(junk, 0, 1536); + arcfour_block(ctx, junk, 1536); + memset(junk, 0, 1536); + sfree(junk); +} + +static void arcfour128_key(void *handle, unsigned char *key) +{ + ArcfourContext *ctx = (ArcfourContext *)handle; + arcfour_setkey(ctx, key, 16); + arcfour_stir(ctx); +} + +static void arcfour256_key(void *handle, unsigned char *key) +{ + ArcfourContext *ctx = (ArcfourContext *)handle; + arcfour_setkey(ctx, key, 32); + arcfour_stir(ctx); +} + +static void arcfour_iv(void *handle, unsigned char *key) +{ + +} + +const struct ssh2_cipher ssh_arcfour128_ssh2 = { + arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour128_key, + arcfour_block, arcfour_block, + "arcfour128", + 1, 128, 0, "Arcfour-128" +}; + +const struct ssh2_cipher ssh_arcfour256_ssh2 = { + arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour256_key, + arcfour_block, arcfour_block, + "arcfour256", + 1, 256, 0, "Arcfour-256" +}; + +static const struct ssh2_cipher *const arcfour_list[] = { + &ssh_arcfour256_ssh2, + &ssh_arcfour128_ssh2, +}; + +const struct ssh2_ciphers ssh2_arcfour = { + sizeof(arcfour_list) / sizeof(*arcfour_list), + arcfour_list +}; diff --git a/putty/SSHBLOWF.C b/putty/SSHBLOWF.C new file mode 100644 index 0000000..f770170 --- /dev/null +++ b/putty/SSHBLOWF.C @@ -0,0 +1,588 @@ +/* + * Blowfish implementation for PuTTY. + * + * Coded from scratch from the algorithm description. + */ + +#include +#include +#include "ssh.h" + +typedef struct { + word32 S0[256], S1[256], S2[256], S3[256], P[18]; + word32 iv0, iv1; /* for CBC mode */ +} BlowfishContext; + +/* + * The Blowfish init data: hex digits of the fractional part of pi. + * (ie pi as a hex fraction is 3.243F6A8885A308D3...) + */ +static const word32 parray[] = { + 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, + 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, + 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B, +}; + +static const word32 sbox0[] = { + 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, + 0xBA7C9045, 0xF12C7F99, 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16, + 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658, + 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, + 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, 0x8E79DCB0, 0x603A180E, + 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, + 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6, + 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A, + 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C, + 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, + 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, 0xEF845D5D, 0xE98575B1, + 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, + 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A, + 0x670C9C61, 0xABD388F0, 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3, + 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176, + 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, + 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, 0x4ED3AA62, 0x363F7706, + 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, + 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B, + 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463, + 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C, + 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, + 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A, + 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, + 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760, + 0x53317B48, 0x3E00DF82, 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, + 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8, + 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, + 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33, + 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, + 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0, + 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C, + 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777, + 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, + 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, 0x165FA266, 0x80957705, + 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, + 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E, + 0x226800BB, 0x57B8E0AF, 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA, + 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9, + 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, + 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, 0x08BA6FB5, 0x571BE91F, + 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, + 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A, +}; + +static const word32 sbox1[] = { + 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, + 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1, + 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, + 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, + 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, + 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, + 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, + 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD, + 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC, + 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, + 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, 0x4E548B38, 0x4F6DB908, + 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, + 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, + 0x501ADDE6, 0x9F84CD87, 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, + 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908, + 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, + 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, 0x043556F1, 0xD7A3C76B, + 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, + 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, + 0x2965DCB9, 0x99E71D0F, 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A, + 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D, + 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, + 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, + 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, + 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, + 0x0334FE1E, 0xAA0363CF, 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, + 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA, + 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, + 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, 0xF837889A, 0x97E32D77, + 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, + 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, + 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, + 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA, + 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, + 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, 0xCF62A1F2, 0x5B8D2646, + 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, + 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, + 0x1DADF43E, 0x233F7061, 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB, + 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E, + 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, + 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, + 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, + 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7, +}; + +static const word32 sbox2[] = { + 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, + 0xBCF46B2E, 0xD4A20068, 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF, + 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, + 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, + 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, 0x28507825, 0x530429F4, + 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, + 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, + 0xCE78A399, 0x406B2A42, 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, + 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332, + 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, + 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, 0x55A867BC, 0xA1159A58, + 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, + 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, + 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17, + 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60, + 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, + 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, + 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, + 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, + 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, + 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3, + 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, + 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, 0x37392EB3, 0xCC115979, + 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, + 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, + 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, + 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086, + 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, + 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, + 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, + 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, + 0x846A0E79, 0x915F95E2, 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C, + 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09, + 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, + 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, 0xDCB7DA83, 0x573906FE, + 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, + 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, + 0x006058AA, 0x30DC7D62, 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634, + 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188, + 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, + 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, + 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, + 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0, +}; + +static const word32 sbox3[] = { + 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, + 0xD3822740, 0x99BC9BBE, 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, + 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, + 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, + 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, + 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, + 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, + 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59, + 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797, + 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, + 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, 0xE029AC71, 0xE019A5E6, + 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, + 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, + 0x03A16125, 0x0564F0BD, 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A, + 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5, + 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, + 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, + 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, + 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, + 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, + 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB, + 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, + 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, + 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, + 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, + 0xBB3A792B, 0x344525BD, 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, + 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A, + 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, + 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, 0xBF97222C, 0x15E6FC2A, + 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, + 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, + 0x4C98A0BE, 0x3278E964, 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E, + 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E, + 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, + 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, 0xF523F357, 0xA6327623, + 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, + 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, + 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, + 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3, + 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, + 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, + 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, + 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6, +}; + +#define Fprime(a,b,c,d) ( ( (S0[a] + S1[b]) ^ S2[c] ) + S3[d] ) +#define F(x) Fprime( ((x>>24)&0xFF), ((x>>16)&0xFF), ((x>>8)&0xFF), (x&0xFF) ) +#define ROUND(n) ( xL ^= P[n], t = xL, xL = F(xL) ^ xR, xR = t ) + +static void blowfish_encrypt(word32 xL, word32 xR, word32 * output, + BlowfishContext * ctx) +{ + word32 *S0 = ctx->S0; + word32 *S1 = ctx->S1; + word32 *S2 = ctx->S2; + word32 *S3 = ctx->S3; + word32 *P = ctx->P; + word32 t; + + ROUND(0); + ROUND(1); + ROUND(2); + ROUND(3); + ROUND(4); + ROUND(5); + ROUND(6); + ROUND(7); + ROUND(8); + ROUND(9); + ROUND(10); + ROUND(11); + ROUND(12); + ROUND(13); + ROUND(14); + ROUND(15); + xL ^= P[16]; + xR ^= P[17]; + + output[0] = xR; + output[1] = xL; +} + +static void blowfish_decrypt(word32 xL, word32 xR, word32 * output, + BlowfishContext * ctx) +{ + word32 *S0 = ctx->S0; + word32 *S1 = ctx->S1; + word32 *S2 = ctx->S2; + word32 *S3 = ctx->S3; + word32 *P = ctx->P; + word32 t; + + ROUND(17); + ROUND(16); + ROUND(15); + ROUND(14); + ROUND(13); + ROUND(12); + ROUND(11); + ROUND(10); + ROUND(9); + ROUND(8); + ROUND(7); + ROUND(6); + ROUND(5); + ROUND(4); + ROUND(3); + ROUND(2); + xL ^= P[1]; + xR ^= P[0]; + + output[0] = xR; + output[1] = xL; +} + +static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + word32 xL, xR, out[2], iv0, iv1; + + assert((len & 7) == 0); + + iv0 = ctx->iv0; + iv1 = ctx->iv1; + + while (len > 0) { + xL = GET_32BIT_LSB_FIRST(blk); + xR = GET_32BIT_LSB_FIRST(blk + 4); + iv0 ^= xL; + iv1 ^= xR; + blowfish_encrypt(iv0, iv1, out, ctx); + iv0 = out[0]; + iv1 = out[1]; + PUT_32BIT_LSB_FIRST(blk, iv0); + PUT_32BIT_LSB_FIRST(blk + 4, iv1); + blk += 8; + len -= 8; + } + + ctx->iv0 = iv0; + ctx->iv1 = iv1; +} + +static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + word32 xL, xR, out[2], iv0, iv1; + + assert((len & 7) == 0); + + iv0 = ctx->iv0; + iv1 = ctx->iv1; + + while (len > 0) { + xL = GET_32BIT_LSB_FIRST(blk); + xR = GET_32BIT_LSB_FIRST(blk + 4); + blowfish_decrypt(xL, xR, out, ctx); + iv0 ^= out[0]; + iv1 ^= out[1]; + PUT_32BIT_LSB_FIRST(blk, iv0); + PUT_32BIT_LSB_FIRST(blk + 4, iv1); + iv0 = xL; + iv1 = xR; + blk += 8; + len -= 8; + } + + ctx->iv0 = iv0; + ctx->iv1 = iv1; +} + +static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + word32 xL, xR, out[2], iv0, iv1; + + assert((len & 7) == 0); + + iv0 = ctx->iv0; + iv1 = ctx->iv1; + + while (len > 0) { + xL = GET_32BIT_MSB_FIRST(blk); + xR = GET_32BIT_MSB_FIRST(blk + 4); + iv0 ^= xL; + iv1 ^= xR; + blowfish_encrypt(iv0, iv1, out, ctx); + iv0 = out[0]; + iv1 = out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + blk += 8; + len -= 8; + } + + ctx->iv0 = iv0; + ctx->iv1 = iv1; +} + +static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + word32 xL, xR, out[2], iv0, iv1; + + assert((len & 7) == 0); + + iv0 = ctx->iv0; + iv1 = ctx->iv1; + + while (len > 0) { + xL = GET_32BIT_MSB_FIRST(blk); + xR = GET_32BIT_MSB_FIRST(blk + 4); + blowfish_decrypt(xL, xR, out, ctx); + iv0 ^= out[0]; + iv1 ^= out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + iv0 = xL; + iv1 = xR; + blk += 8; + len -= 8; + } + + ctx->iv0 = iv0; + ctx->iv1 = iv1; +} + +static void blowfish_msb_sdctr(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + word32 b[2], iv0, iv1, tmp; + + assert((len & 7) == 0); + + iv0 = ctx->iv0; + iv1 = ctx->iv1; + + while (len > 0) { + blowfish_encrypt(iv0, iv1, b, ctx); + tmp = GET_32BIT_MSB_FIRST(blk); + PUT_32BIT_MSB_FIRST(blk, tmp ^ b[0]); + tmp = GET_32BIT_MSB_FIRST(blk + 4); + PUT_32BIT_MSB_FIRST(blk + 4, tmp ^ b[1]); + if ((iv1 = (iv1 + 1) & 0xffffffff) == 0) + iv0 = (iv0 + 1) & 0xffffffff; + blk += 8; + len -= 8; + } + + ctx->iv0 = iv0; + ctx->iv1 = iv1; +} + +static void blowfish_setkey(BlowfishContext * ctx, + const unsigned char *key, short keybytes) +{ + word32 *S0 = ctx->S0; + word32 *S1 = ctx->S1; + word32 *S2 = ctx->S2; + word32 *S3 = ctx->S3; + word32 *P = ctx->P; + word32 str[2]; + int i; + + for (i = 0; i < 18; i++) { + P[i] = parray[i]; + P[i] ^= + ((word32) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24; + P[i] ^= + ((word32) (unsigned char) (key[(i * 4 + 1) % keybytes])) << 16; + P[i] ^= + ((word32) (unsigned char) (key[(i * 4 + 2) % keybytes])) << 8; + P[i] ^= ((word32) (unsigned char) (key[(i * 4 + 3) % keybytes])); + } + + for (i = 0; i < 256; i++) { + S0[i] = sbox0[i]; + S1[i] = sbox1[i]; + S2[i] = sbox2[i]; + S3[i] = sbox3[i]; + } + + str[0] = str[1] = 0; + + for (i = 0; i < 18; i += 2) { + blowfish_encrypt(str[0], str[1], str, ctx); + P[i] = str[0]; + P[i + 1] = str[1]; + } + + for (i = 0; i < 256; i += 2) { + blowfish_encrypt(str[0], str[1], str, ctx); + S0[i] = str[0]; + S0[i + 1] = str[1]; + } + for (i = 0; i < 256; i += 2) { + blowfish_encrypt(str[0], str[1], str, ctx); + S1[i] = str[0]; + S1[i + 1] = str[1]; + } + for (i = 0; i < 256; i += 2) { + blowfish_encrypt(str[0], str[1], str, ctx); + S2[i] = str[0]; + S2[i + 1] = str[1]; + } + for (i = 0; i < 256; i += 2) { + blowfish_encrypt(str[0], str[1], str, ctx); + S3[i] = str[0]; + S3[i + 1] = str[1]; + } +} + +/* -- Interface with PuTTY -- */ + +#define SSH_SESSION_KEY_LENGTH 32 + +static void *blowfish_make_context(void) +{ + return snew(BlowfishContext); +} + +static void *blowfish_ssh1_make_context(void) +{ + /* In SSH-1, need one key for each direction */ + return snewn(2, BlowfishContext); +} + +static void blowfish_free_context(void *handle) +{ + sfree(handle); +} + +static void blowfish_key(void *handle, unsigned char *key) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_setkey(ctx, key, 16); +} + +static void blowfish256_key(void *handle, unsigned char *key) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_setkey(ctx, key, 32); +} + +static void blowfish_iv(void *handle, unsigned char *key) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + ctx->iv0 = GET_32BIT_MSB_FIRST(key); + ctx->iv1 = GET_32BIT_MSB_FIRST(key + 4); +} + +static void blowfish_sesskey(void *handle, unsigned char *key) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_setkey(ctx, key, SSH_SESSION_KEY_LENGTH); + ctx->iv0 = 0; + ctx->iv1 = 0; + ctx[1] = ctx[0]; /* structure copy */ +} + +static void blowfish_ssh1_encrypt_blk(void *handle, unsigned char *blk, + int len) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_lsb_encrypt_cbc(blk, len, ctx); +} + +static void blowfish_ssh1_decrypt_blk(void *handle, unsigned char *blk, + int len) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_lsb_decrypt_cbc(blk, len, ctx+1); +} + +static void blowfish_ssh2_encrypt_blk(void *handle, unsigned char *blk, + int len) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_msb_encrypt_cbc(blk, len, ctx); +} + +static void blowfish_ssh2_decrypt_blk(void *handle, unsigned char *blk, + int len) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_msb_decrypt_cbc(blk, len, ctx); +} + +static void blowfish_ssh2_sdctr(void *handle, unsigned char *blk, + int len) +{ + BlowfishContext *ctx = (BlowfishContext *)handle; + blowfish_msb_sdctr(blk, len, ctx); +} + +const struct ssh_cipher ssh_blowfish_ssh1 = { + blowfish_ssh1_make_context, blowfish_free_context, blowfish_sesskey, + blowfish_ssh1_encrypt_blk, blowfish_ssh1_decrypt_blk, + 8, "Blowfish-128 CBC" +}; + +static const struct ssh2_cipher ssh_blowfish_ssh2 = { + blowfish_make_context, blowfish_free_context, blowfish_iv, blowfish_key, + blowfish_ssh2_encrypt_blk, blowfish_ssh2_decrypt_blk, + "blowfish-cbc", + 8, 128, SSH_CIPHER_IS_CBC, "Blowfish-128 CBC" +}; + +static const struct ssh2_cipher ssh_blowfish_ssh2_ctr = { + blowfish_make_context, blowfish_free_context, blowfish_iv, blowfish256_key, + blowfish_ssh2_sdctr, blowfish_ssh2_sdctr, + "blowfish-ctr", + 8, 256, 0, "Blowfish-256 SDCTR" +}; + +static const struct ssh2_cipher *const blowfish_list[] = { + &ssh_blowfish_ssh2_ctr, + &ssh_blowfish_ssh2 +}; + +const struct ssh2_ciphers ssh2_blowfish = { + sizeof(blowfish_list) / sizeof(*blowfish_list), + blowfish_list +}; diff --git a/putty/SSHBN.C b/putty/SSHBN.C new file mode 100644 index 0000000..51cecdf --- /dev/null +++ b/putty/SSHBN.C @@ -0,0 +1,1918 @@ +/* + * Bignum routines for RSA and DH and stuff. + */ + +#include +#include +#include +#include + +#include "misc.h" + +/* + * Usage notes: + * * Do not call the DIVMOD_WORD macro with expressions such as array + * subscripts, as some implementations object to this (see below). + * * Note that none of the division methods below will cope if the + * quotient won't fit into BIGNUM_INT_BITS. Callers should be careful + * to avoid this case. + * If this condition occurs, in the case of the x86 DIV instruction, + * an overflow exception will occur, which (according to a correspondent) + * will manifest on Windows as something like + * 0xC0000095: Integer overflow + * The C variant won't give the right answer, either. + */ + +#if defined __GNUC__ && defined __i386__ +typedef unsigned long BignumInt; +typedef unsigned long long BignumDblInt; +#define BIGNUM_INT_MASK 0xFFFFFFFFUL +#define BIGNUM_TOP_BIT 0x80000000UL +#define BIGNUM_INT_BITS 32 +#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2) +#define DIVMOD_WORD(q, r, hi, lo, w) \ + __asm__("div %2" : \ + "=d" (r), "=a" (q) : \ + "r" (w), "d" (hi), "a" (lo)) +#elif defined _MSC_VER && defined _M_IX86 +typedef unsigned __int32 BignumInt; +typedef unsigned __int64 BignumDblInt; +#define BIGNUM_INT_MASK 0xFFFFFFFFUL +#define BIGNUM_TOP_BIT 0x80000000UL +#define BIGNUM_INT_BITS 32 +#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2) +/* Note: MASM interprets array subscripts in the macro arguments as + * assembler syntax, which gives the wrong answer. Don't supply them. + * */ +#define DIVMOD_WORD(q, r, hi, lo, w) do { \ + __asm mov edx, hi \ + __asm mov eax, lo \ + __asm div w \ + __asm mov r, edx \ + __asm mov q, eax \ +} while(0) +#elif defined _LP64 +/* 64-bit architectures can do 32x32->64 chunks at a time */ +typedef unsigned int BignumInt; +typedef unsigned long BignumDblInt; +#define BIGNUM_INT_MASK 0xFFFFFFFFU +#define BIGNUM_TOP_BIT 0x80000000U +#define BIGNUM_INT_BITS 32 +#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2) +#define DIVMOD_WORD(q, r, hi, lo, w) do { \ + BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \ + q = n / w; \ + r = n % w; \ +} while (0) +#elif defined _LLP64 +/* 64-bit architectures in which unsigned long is 32 bits, not 64 */ +typedef unsigned long BignumInt; +typedef unsigned long long BignumDblInt; +#define BIGNUM_INT_MASK 0xFFFFFFFFUL +#define BIGNUM_TOP_BIT 0x80000000UL +#define BIGNUM_INT_BITS 32 +#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2) +#define DIVMOD_WORD(q, r, hi, lo, w) do { \ + BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \ + q = n / w; \ + r = n % w; \ +} while (0) +#else +/* Fallback for all other cases */ +typedef unsigned short BignumInt; +typedef unsigned long BignumDblInt; +#define BIGNUM_INT_MASK 0xFFFFU +#define BIGNUM_TOP_BIT 0x8000U +#define BIGNUM_INT_BITS 16 +#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2) +#define DIVMOD_WORD(q, r, hi, lo, w) do { \ + BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \ + q = n / w; \ + r = n % w; \ +} while (0) +#endif + +#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8) + +#define BIGNUM_INTERNAL +typedef BignumInt *Bignum; + +#include "ssh.h" + +BignumInt bnZero[1] = { 0 }; +BignumInt bnOne[2] = { 1, 1 }; + +/* + * The Bignum format is an array of `BignumInt'. The first + * element of the array counts the remaining elements. The + * remaining elements express the actual number, base 2^BIGNUM_INT_BITS, _least_ + * significant digit first. (So it's trivial to extract the bit + * with value 2^n for any n.) + * + * All Bignums in this module are positive. Negative numbers must + * be dealt with outside it. + * + * INVARIANT: the most significant word of any Bignum must be + * nonzero. + */ + +Bignum Zero = bnZero, One = bnOne; + +static Bignum newbn(int length) +{ + Bignum b = snewn(length + 1, BignumInt); + if (!b) + abort(); /* FIXME */ + memset(b, 0, (length + 1) * sizeof(*b)); + b[0] = length; + return b; +} + +void bn_restore_invariant(Bignum b) +{ + while (b[0] > 1 && b[b[0]] == 0) + b[0]--; +} + +Bignum copybn(Bignum orig) +{ + Bignum b = snewn(orig[0] + 1, BignumInt); + if (!b) + abort(); /* FIXME */ + memcpy(b, orig, (orig[0] + 1) * sizeof(*b)); + return b; +} + +void freebn(Bignum b) +{ + /* + * Burn the evidence, just in case. + */ + memset(b, 0, sizeof(b[0]) * (b[0] + 1)); + sfree(b); +} + +Bignum bn_power_2(int n) +{ + Bignum ret = newbn(n / BIGNUM_INT_BITS + 1); + bignum_set_bit(ret, n, 1); + return ret; +} + +/* + * Internal addition. Sets c = a - b, where 'a', 'b' and 'c' are all + * big-endian arrays of 'len' BignumInts. Returns a BignumInt carried + * off the top. + */ +static BignumInt internal_add(const BignumInt *a, const BignumInt *b, + BignumInt *c, int len) +{ + int i; + BignumDblInt carry = 0; + + for (i = len-1; i >= 0; i--) { + carry += (BignumDblInt)a[i] + b[i]; + c[i] = (BignumInt)carry; + carry >>= BIGNUM_INT_BITS; + } + + return (BignumInt)carry; +} + +/* + * Internal subtraction. Sets c = a - b, where 'a', 'b' and 'c' are + * all big-endian arrays of 'len' BignumInts. Any borrow from the top + * is ignored. + */ +static void internal_sub(const BignumInt *a, const BignumInt *b, + BignumInt *c, int len) +{ + int i; + BignumDblInt carry = 1; + + for (i = len-1; i >= 0; i--) { + carry += (BignumDblInt)a[i] + (b[i] ^ BIGNUM_INT_MASK); + c[i] = (BignumInt)carry; + carry >>= BIGNUM_INT_BITS; + } +} + +/* + * Compute c = a * b. + * Input is in the first len words of a and b. + * Result is returned in the first 2*len words of c. + * + * 'scratch' must point to an array of BignumInt of size at least + * mul_compute_scratch(len). (This covers the needs of internal_mul + * and all its recursive calls to itself.) + */ +#define KARATSUBA_THRESHOLD 50 +static int mul_compute_scratch(int len) +{ + int ret = 0; + while (len > KARATSUBA_THRESHOLD) { + int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */ + int midlen = botlen + 1; + ret += 4*midlen; + len = midlen; + } + return ret; +} +static void internal_mul(const BignumInt *a, const BignumInt *b, + BignumInt *c, int len, BignumInt *scratch) +{ + if (len > KARATSUBA_THRESHOLD) { + int i; + + /* + * Karatsuba divide-and-conquer algorithm. Cut each input in + * half, so that it's expressed as two big 'digits' in a giant + * base D: + * + * a = a_1 D + a_0 + * b = b_1 D + b_0 + * + * Then the product is of course + * + * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0 + * + * and we compute the three coefficients by recursively + * calling ourself to do half-length multiplications. + * + * The clever bit that makes this worth doing is that we only + * need _one_ half-length multiplication for the central + * coefficient rather than the two that it obviouly looks + * like, because we can use a single multiplication to compute + * + * (a_1 + a_0) (b_1 + b_0) = a_1 b_1 + a_1 b_0 + a_0 b_1 + a_0 b_0 + * + * and then we subtract the other two coefficients (a_1 b_1 + * and a_0 b_0) which we were computing anyway. + * + * Hence we get to multiply two numbers of length N in about + * three times as much work as it takes to multiply numbers of + * length N/2, which is obviously better than the four times + * as much work it would take if we just did a long + * conventional multiply. + */ + + int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */ + int midlen = botlen + 1; + BignumDblInt carry; +#ifdef KARA_DEBUG + int i; +#endif + + /* + * The coefficients a_1 b_1 and a_0 b_0 just avoid overlapping + * in the output array, so we can compute them immediately in + * place. + */ + +#ifdef KARA_DEBUG + printf("a1,a0 = 0x"); + for (i = 0; i < len; i++) { + if (i == toplen) printf(", 0x"); + printf("%0*x", BIGNUM_INT_BITS/4, a[i]); + } + printf("\n"); + printf("b1,b0 = 0x"); + for (i = 0; i < len; i++) { + if (i == toplen) printf(", 0x"); + printf("%0*x", BIGNUM_INT_BITS/4, b[i]); + } + printf("\n"); +#endif + + /* a_1 b_1 */ + internal_mul(a, b, c, toplen, scratch); +#ifdef KARA_DEBUG + printf("a1b1 = 0x"); + for (i = 0; i < 2*toplen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, c[i]); + } + printf("\n"); +#endif + + /* a_0 b_0 */ + internal_mul(a + toplen, b + toplen, c + 2*toplen, botlen, scratch); +#ifdef KARA_DEBUG + printf("a0b0 = 0x"); + for (i = 0; i < 2*botlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, c[2*toplen+i]); + } + printf("\n"); +#endif + + /* Zero padding. midlen exceeds toplen by at most 2, so just + * zero the first two words of each input and the rest will be + * copied over. */ + scratch[0] = scratch[1] = scratch[midlen] = scratch[midlen+1] = 0; + + for (i = 0; i < toplen; i++) { + scratch[midlen - toplen + i] = a[i]; /* a_1 */ + scratch[2*midlen - toplen + i] = b[i]; /* b_1 */ + } + + /* compute a_1 + a_0 */ + scratch[0] = internal_add(scratch+1, a+toplen, scratch+1, botlen); +#ifdef KARA_DEBUG + printf("a1plusa0 = 0x"); + for (i = 0; i < midlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]); + } + printf("\n"); +#endif + /* compute b_1 + b_0 */ + scratch[midlen] = internal_add(scratch+midlen+1, b+toplen, + scratch+midlen+1, botlen); +#ifdef KARA_DEBUG + printf("b1plusb0 = 0x"); + for (i = 0; i < midlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, scratch[midlen+i]); + } + printf("\n"); +#endif + + /* + * Now we can do the third multiplication. + */ + internal_mul(scratch, scratch + midlen, scratch + 2*midlen, midlen, + scratch + 4*midlen); +#ifdef KARA_DEBUG + printf("a1plusa0timesb1plusb0 = 0x"); + for (i = 0; i < 2*midlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]); + } + printf("\n"); +#endif + + /* + * Now we can reuse the first half of 'scratch' to compute the + * sum of the outer two coefficients, to subtract from that + * product to obtain the middle one. + */ + scratch[0] = scratch[1] = scratch[2] = scratch[3] = 0; + for (i = 0; i < 2*toplen; i++) + scratch[2*midlen - 2*toplen + i] = c[i]; + scratch[1] = internal_add(scratch+2, c + 2*toplen, + scratch+2, 2*botlen); +#ifdef KARA_DEBUG + printf("a1b1plusa0b0 = 0x"); + for (i = 0; i < 2*midlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]); + } + printf("\n"); +#endif + + internal_sub(scratch + 2*midlen, scratch, + scratch + 2*midlen, 2*midlen); +#ifdef KARA_DEBUG + printf("a1b0plusa0b1 = 0x"); + for (i = 0; i < 2*midlen; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]); + } + printf("\n"); +#endif + + /* + * And now all we need to do is to add that middle coefficient + * back into the output. We may have to propagate a carry + * further up the output, but we can be sure it won't + * propagate right the way off the top. + */ + carry = internal_add(c + 2*len - botlen - 2*midlen, + scratch + 2*midlen, + c + 2*len - botlen - 2*midlen, 2*midlen); + i = 2*len - botlen - 2*midlen - 1; + while (carry) { + assert(i >= 0); + carry += c[i]; + c[i] = (BignumInt)carry; + carry >>= BIGNUM_INT_BITS; + i--; + } +#ifdef KARA_DEBUG + printf("ab = 0x"); + for (i = 0; i < 2*len; i++) { + printf("%0*x", BIGNUM_INT_BITS/4, c[i]); + } + printf("\n"); +#endif + + } else { + int i; + BignumInt carry; + BignumDblInt t; + const BignumInt *ap, *bp; + BignumInt *cp, *cps; + + /* + * Multiply in the ordinary O(N^2) way. + */ + + for (i = 0; i < 2 * len; i++) + c[i] = 0; + + for (cps = c + 2*len, ap = a + len; ap-- > a; cps--) { + carry = 0; + for (cp = cps, bp = b + len; cp--, bp-- > b ;) { + t = (MUL_WORD(*ap, *bp) + carry) + *cp; + *cp = (BignumInt) t; + carry = (BignumInt)(t >> BIGNUM_INT_BITS); + } + *cp = carry; + } + } +} + +/* + * Variant form of internal_mul used for the initial step of + * Montgomery reduction. Only bothers outputting 'len' words + * (everything above that is thrown away). + */ +static void internal_mul_low(const BignumInt *a, const BignumInt *b, + BignumInt *c, int len, BignumInt *scratch) +{ + if (len > KARATSUBA_THRESHOLD) { + int i; + + /* + * Karatsuba-aware version of internal_mul_low. As before, we + * express each input value as a shifted combination of two + * halves: + * + * a = a_1 D + a_0 + * b = b_1 D + b_0 + * + * Then the full product is, as before, + * + * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0 + * + * Provided we choose D on the large side (so that a_0 and b_0 + * are _at least_ as long as a_1 and b_1), we don't need the + * topmost term at all, and we only need half of the middle + * term. So there's no point in doing the proper Karatsuba + * optimisation which computes the middle term using the top + * one, because we'd take as long computing the top one as + * just computing the middle one directly. + * + * So instead, we do a much more obvious thing: we call the + * fully optimised internal_mul to compute a_0 b_0, and we + * recursively call ourself to compute the _bottom halves_ of + * a_1 b_0 and a_0 b_1, each of which we add into the result + * in the obvious way. + * + * In other words, there's no actual Karatsuba _optimisation_ + * in this function; the only benefit in doing it this way is + * that we call internal_mul proper for a large part of the + * work, and _that_ can optimise its operation. + */ + + int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */ + + /* + * Scratch space for the various bits and pieces we're going + * to be adding together: we need botlen*2 words for a_0 b_0 + * (though we may end up throwing away its topmost word), and + * toplen words for each of a_1 b_0 and a_0 b_1. That adds up + * to exactly 2*len. + */ + + /* a_0 b_0 */ + internal_mul(a + toplen, b + toplen, scratch + 2*toplen, botlen, + scratch + 2*len); + + /* a_1 b_0 */ + internal_mul_low(a, b + len - toplen, scratch + toplen, toplen, + scratch + 2*len); + + /* a_0 b_1 */ + internal_mul_low(a + len - toplen, b, scratch, toplen, + scratch + 2*len); + + /* Copy the bottom half of the big coefficient into place */ + for (i = 0; i < botlen; i++) + c[toplen + i] = scratch[2*toplen + botlen + i]; + + /* Add the two small coefficients, throwing away the returned carry */ + internal_add(scratch, scratch + toplen, scratch, toplen); + + /* And add that to the large coefficient, leaving the result in c. */ + internal_add(scratch, scratch + 2*toplen + botlen - toplen, + c, toplen); + + } else { + int i; + BignumInt carry; + BignumDblInt t; + const BignumInt *ap, *bp; + BignumInt *cp, *cps; + + /* + * Multiply in the ordinary O(N^2) way. + */ + + for (i = 0; i < len; i++) + c[i] = 0; + + for (cps = c + len, ap = a + len; ap-- > a; cps--) { + carry = 0; + for (cp = cps, bp = b + len; bp--, cp-- > c ;) { + t = (MUL_WORD(*ap, *bp) + carry) + *cp; + *cp = (BignumInt) t; + carry = (BignumInt)(t >> BIGNUM_INT_BITS); + } + } + } +} + +/* + * Montgomery reduction. Expects x to be a big-endian array of 2*len + * BignumInts whose value satisfies 0 <= x < rn (where r = 2^(len * + * BIGNUM_INT_BITS) is the Montgomery base). Returns in the same array + * a value x' which is congruent to xr^{-1} mod n, and satisfies 0 <= + * x' < n. + * + * 'n' and 'mninv' should be big-endian arrays of 'len' BignumInts + * each, containing respectively n and the multiplicative inverse of + * -n mod r. + * + * 'tmp' is an array of BignumInt used as scratch space, of length at + * least 3*len + mul_compute_scratch(len). + */ +static void monty_reduce(BignumInt *x, const BignumInt *n, + const BignumInt *mninv, BignumInt *tmp, int len) +{ + int i; + BignumInt carry; + + /* + * Multiply x by (-n)^{-1} mod r. This gives us a value m such + * that mn is congruent to -x mod r. Hence, mn+x is an exact + * multiple of r, and is also (obviously) congruent to x mod n. + */ + internal_mul_low(x + len, mninv, tmp, len, tmp + 3*len); + + /* + * Compute t = (mn+x)/r in ordinary, non-modular, integer + * arithmetic. By construction this is exact, and is congruent mod + * n to x * r^{-1}, i.e. the answer we want. + * + * The following multiply leaves that answer in the _most_ + * significant half of the 'x' array, so then we must shift it + * down. + */ + internal_mul(tmp, n, tmp+len, len, tmp + 3*len); + carry = internal_add(x, tmp+len, x, 2*len); + for (i = 0; i < len; i++) + x[len + i] = x[i], x[i] = 0; + + /* + * Reduce t mod n. This doesn't require a full-on division by n, + * but merely a test and single optional subtraction, since we can + * show that 0 <= t < 2n. + * + * Proof: + * + we computed m mod r, so 0 <= m < r. + * + so 0 <= mn < rn, obviously + * + hence we only need 0 <= x < rn to guarantee that 0 <= mn+x < 2rn + * + yielding 0 <= (mn+x)/r < 2n as required. + */ + if (!carry) { + for (i = 0; i < len; i++) + if (x[len + i] != n[i]) + break; + } + if (carry || i >= len || x[len + i] > n[i]) + internal_sub(x+len, n, x+len, len); +} + +static void internal_add_shifted(BignumInt *number, + unsigned n, int shift) +{ + int word = 1 + (shift / BIGNUM_INT_BITS); + int bshift = shift % BIGNUM_INT_BITS; + BignumDblInt addend; + + addend = (BignumDblInt)n << bshift; + + while (addend) { + addend += number[word]; + number[word] = (BignumInt) addend & BIGNUM_INT_MASK; + addend >>= BIGNUM_INT_BITS; + word++; + } +} + +/* + * Compute a = a % m. + * Input in first alen words of a and first mlen words of m. + * Output in first alen words of a + * (of which first alen-mlen words will be zero). + * The MSW of m MUST have its high bit set. + * Quotient is accumulated in the `quotient' array, which is a Bignum + * rather than the internal bigendian format. Quotient parts are shifted + * left by `qshift' before adding into quot. + */ +static void internal_mod(BignumInt *a, int alen, + BignumInt *m, int mlen, + BignumInt *quot, int qshift) +{ + BignumInt m0, m1; + unsigned int h; + int i, k; + + m0 = m[0]; + if (mlen > 1) + m1 = m[1]; + else + m1 = 0; + + for (i = 0; i <= alen - mlen; i++) { + BignumDblInt t; + unsigned int q, r, c, ai1; + + if (i == 0) { + h = 0; + } else { + h = a[i - 1]; + a[i - 1] = 0; + } + + if (i == alen - 1) + ai1 = 0; + else + ai1 = a[i + 1]; + + /* Find q = h:a[i] / m0 */ + if (h >= m0) { + /* + * Special case. + * + * To illustrate it, suppose a BignumInt is 8 bits, and + * we are dividing (say) A1:23:45:67 by A1:B2:C3. Then + * our initial division will be 0xA123 / 0xA1, which + * will give a quotient of 0x100 and a divide overflow. + * However, the invariants in this division algorithm + * are not violated, since the full number A1:23:... is + * _less_ than the quotient prefix A1:B2:... and so the + * following correction loop would have sorted it out. + * + * In this situation we set q to be the largest + * quotient we _can_ stomach (0xFF, of course). + */ + q = BIGNUM_INT_MASK; + } else { + /* Macro doesn't want an array subscript expression passed + * into it (see definition), so use a temporary. */ + BignumInt tmplo = a[i]; + DIVMOD_WORD(q, r, h, tmplo, m0); + + /* Refine our estimate of q by looking at + h:a[i]:a[i+1] / m0:m1 */ + t = MUL_WORD(m1, q); + if (t > ((BignumDblInt) r << BIGNUM_INT_BITS) + ai1) { + q--; + t -= m1; + r = (r + m0) & BIGNUM_INT_MASK; /* overflow? */ + if (r >= (BignumDblInt) m0 && + t > ((BignumDblInt) r << BIGNUM_INT_BITS) + ai1) q--; + } + } + + /* Subtract q * m from a[i...] */ + c = 0; + for (k = mlen - 1; k >= 0; k--) { + t = MUL_WORD(q, m[k]); + t += c; + c = (unsigned)(t >> BIGNUM_INT_BITS); + if ((BignumInt) t > a[i + k]) + c++; + a[i + k] -= (BignumInt) t; + } + + /* Add back m in case of borrow */ + if (c != h) { + t = 0; + for (k = mlen - 1; k >= 0; k--) { + t += m[k]; + t += a[i + k]; + a[i + k] = (BignumInt) t; + t = t >> BIGNUM_INT_BITS; + } + q--; + } + if (quot) + internal_add_shifted(quot, q, qshift + BIGNUM_INT_BITS * (alen - mlen - i)); + } +} + +/* + * Compute (base ^ exp) % mod, the pedestrian way. + */ +Bignum modpow_simple(Bignum base_in, Bignum exp, Bignum mod) +{ + BignumInt *a, *b, *n, *m, *scratch; + int mshift; + int mlen, scratchlen, i, j; + Bignum base, result; + + /* + * The most significant word of mod needs to be non-zero. It + * should already be, but let's make sure. + */ + assert(mod[mod[0]] != 0); + + /* + * Make sure the base is smaller than the modulus, by reducing + * it modulo the modulus if not. + */ + base = bigmod(base_in, mod); + + /* Allocate m of size mlen, copy mod to m */ + /* We use big endian internally */ + mlen = mod[0]; + m = snewn(mlen, BignumInt); + for (j = 0; j < mlen; j++) + m[j] = mod[mod[0] - j]; + + /* Shift m left to make msb bit set */ + for (mshift = 0; mshift < BIGNUM_INT_BITS-1; mshift++) + if ((m[0] << mshift) & BIGNUM_TOP_BIT) + break; + if (mshift) { + for (i = 0; i < mlen - 1; i++) + m[i] = (m[i] << mshift) | (m[i + 1] >> (BIGNUM_INT_BITS - mshift)); + m[mlen - 1] = m[mlen - 1] << mshift; + } + + /* Allocate n of size mlen, copy base to n */ + n = snewn(mlen, BignumInt); + i = mlen - base[0]; + for (j = 0; j < i; j++) + n[j] = 0; + for (j = 0; j < (int)base[0]; j++) + n[i + j] = base[base[0] - j]; + + /* Allocate a and b of size 2*mlen. Set a = 1 */ + a = snewn(2 * mlen, BignumInt); + b = snewn(2 * mlen, BignumInt); + for (i = 0; i < 2 * mlen; i++) + a[i] = 0; + a[2 * mlen - 1] = 1; + + /* Scratch space for multiplies */ + scratchlen = mul_compute_scratch(mlen); + scratch = snewn(scratchlen, BignumInt); + + /* Skip leading zero bits of exp. */ + i = 0; + j = BIGNUM_INT_BITS-1; + while (i < (int)exp[0] && (exp[exp[0] - i] & (1 << j)) == 0) { + j--; + if (j < 0) { + i++; + j = BIGNUM_INT_BITS-1; + } + } + + /* Main computation */ + while (i < (int)exp[0]) { + while (j >= 0) { + internal_mul(a + mlen, a + mlen, b, mlen, scratch); + internal_mod(b, mlen * 2, m, mlen, NULL, 0); + if ((exp[exp[0] - i] & (1 << j)) != 0) { + internal_mul(b + mlen, n, a, mlen, scratch); + internal_mod(a, mlen * 2, m, mlen, NULL, 0); + } else { + BignumInt *t; + t = a; + a = b; + b = t; + } + j--; + } + i++; + j = BIGNUM_INT_BITS-1; + } + + /* Fixup result in case the modulus was shifted */ + if (mshift) { + for (i = mlen - 1; i < 2 * mlen - 1; i++) + a[i] = (a[i] << mshift) | (a[i + 1] >> (BIGNUM_INT_BITS - mshift)); + a[2 * mlen - 1] = a[2 * mlen - 1] << mshift; + internal_mod(a, mlen * 2, m, mlen, NULL, 0); + for (i = 2 * mlen - 1; i >= mlen; i--) + a[i] = (a[i] >> mshift) | (a[i - 1] << (BIGNUM_INT_BITS - mshift)); + } + + /* Copy result to buffer */ + result = newbn(mod[0]); + for (i = 0; i < mlen; i++) + result[result[0] - i] = a[i + mlen]; + while (result[0] > 1 && result[result[0]] == 0) + result[0]--; + + /* Free temporary arrays */ + for (i = 0; i < 2 * mlen; i++) + a[i] = 0; + sfree(a); + for (i = 0; i < scratchlen; i++) + scratch[i] = 0; + sfree(scratch); + for (i = 0; i < 2 * mlen; i++) + b[i] = 0; + sfree(b); + for (i = 0; i < mlen; i++) + m[i] = 0; + sfree(m); + for (i = 0; i < mlen; i++) + n[i] = 0; + sfree(n); + + freebn(base); + + return result; +} + +/* + * Compute (base ^ exp) % mod. Uses the Montgomery multiplication + * technique where possible, falling back to modpow_simple otherwise. + */ +Bignum modpow(Bignum base_in, Bignum exp, Bignum mod) +{ + BignumInt *a, *b, *x, *n, *mninv, *scratch; + int len, scratchlen, i, j; + Bignum base, base2, r, rn, inv, result; + + /* + * The most significant word of mod needs to be non-zero. It + * should already be, but let's make sure. + */ + assert(mod[mod[0]] != 0); + + /* + * mod had better be odd, or we can't do Montgomery multiplication + * using a power of two at all. + */ + if (!(mod[1] & 1)) + return modpow_simple(base_in, exp, mod); + + /* + * Make sure the base is smaller than the modulus, by reducing + * it modulo the modulus if not. + */ + base = bigmod(base_in, mod); + + /* + * Compute the inverse of n mod r, for monty_reduce. (In fact we + * want the inverse of _minus_ n mod r, but we'll sort that out + * below.) + */ + len = mod[0]; + r = bn_power_2(BIGNUM_INT_BITS * len); + inv = modinv(mod, r); + + /* + * Multiply the base by r mod n, to get it into Montgomery + * representation. + */ + base2 = modmul(base, r, mod); + freebn(base); + base = base2; + + rn = bigmod(r, mod); /* r mod n, i.e. Montgomerified 1 */ + + freebn(r); /* won't need this any more */ + + /* + * Set up internal arrays of the right lengths, in big-endian + * format, containing the base, the modulus, and the modulus's + * inverse. + */ + n = snewn(len, BignumInt); + for (j = 0; j < len; j++) + n[len - 1 - j] = mod[j + 1]; + + mninv = snewn(len, BignumInt); + for (j = 0; j < len; j++) + mninv[len - 1 - j] = (j < (int)inv[0] ? inv[j + 1] : 0); + freebn(inv); /* we don't need this copy of it any more */ + /* Now negate mninv mod r, so it's the inverse of -n rather than +n. */ + x = snewn(len, BignumInt); + for (j = 0; j < len; j++) + x[j] = 0; + internal_sub(x, mninv, mninv, len); + + /* x = snewn(len, BignumInt); */ /* already done above */ + for (j = 0; j < len; j++) + x[len - 1 - j] = (j < (int)base[0] ? base[j + 1] : 0); + freebn(base); /* we don't need this copy of it any more */ + + a = snewn(2*len, BignumInt); + b = snewn(2*len, BignumInt); + for (j = 0; j < len; j++) + a[2*len - 1 - j] = (j < (int)rn[0] ? rn[j + 1] : 0); + freebn(rn); + + /* Scratch space for multiplies */ + scratchlen = 3*len + mul_compute_scratch(len); + scratch = snewn(scratchlen, BignumInt); + + /* Skip leading zero bits of exp. */ + i = 0; + j = BIGNUM_INT_BITS-1; + while (i < (int)exp[0] && (exp[exp[0] - i] & (1 << j)) == 0) { + j--; + if (j < 0) { + i++; + j = BIGNUM_INT_BITS-1; + } + } + + /* Main computation */ + while (i < (int)exp[0]) { + while (j >= 0) { + internal_mul(a + len, a + len, b, len, scratch); + monty_reduce(b, n, mninv, scratch, len); + if ((exp[exp[0] - i] & (1 << j)) != 0) { + internal_mul(b + len, x, a, len, scratch); + monty_reduce(a, n, mninv, scratch, len); + } else { + BignumInt *t; + t = a; + a = b; + b = t; + } + j--; + } + i++; + j = BIGNUM_INT_BITS-1; + } + + /* + * Final monty_reduce to get back from the adjusted Montgomery + * representation. + */ + monty_reduce(a, n, mninv, scratch, len); + + /* Copy result to buffer */ + result = newbn(mod[0]); + for (i = 0; i < len; i++) + result[result[0] - i] = a[i + len]; + while (result[0] > 1 && result[result[0]] == 0) + result[0]--; + + /* Free temporary arrays */ + for (i = 0; i < scratchlen; i++) + scratch[i] = 0; + sfree(scratch); + for (i = 0; i < 2 * len; i++) + a[i] = 0; + sfree(a); + for (i = 0; i < 2 * len; i++) + b[i] = 0; + sfree(b); + for (i = 0; i < len; i++) + mninv[i] = 0; + sfree(mninv); + for (i = 0; i < len; i++) + n[i] = 0; + sfree(n); + for (i = 0; i < len; i++) + x[i] = 0; + sfree(x); + + return result; +} + +/* + * Compute (p * q) % mod. + * The most significant word of mod MUST be non-zero. + * We assume that the result array is the same size as the mod array. + */ +Bignum modmul(Bignum p, Bignum q, Bignum mod) +{ + BignumInt *a, *n, *m, *o, *scratch; + int mshift, scratchlen; + int pqlen, mlen, rlen, i, j; + Bignum result; + + /* Allocate m of size mlen, copy mod to m */ + /* We use big endian internally */ + mlen = mod[0]; + m = snewn(mlen, BignumInt); + for (j = 0; j < mlen; j++) + m[j] = mod[mod[0] - j]; + + /* Shift m left to make msb bit set */ + for (mshift = 0; mshift < BIGNUM_INT_BITS-1; mshift++) + if ((m[0] << mshift) & BIGNUM_TOP_BIT) + break; + if (mshift) { + for (i = 0; i < mlen - 1; i++) + m[i] = (m[i] << mshift) | (m[i + 1] >> (BIGNUM_INT_BITS - mshift)); + m[mlen - 1] = m[mlen - 1] << mshift; + } + + pqlen = (p[0] > q[0] ? p[0] : q[0]); + + /* Allocate n of size pqlen, copy p to n */ + n = snewn(pqlen, BignumInt); + i = pqlen - p[0]; + for (j = 0; j < i; j++) + n[j] = 0; + for (j = 0; j < (int)p[0]; j++) + n[i + j] = p[p[0] - j]; + + /* Allocate o of size pqlen, copy q to o */ + o = snewn(pqlen, BignumInt); + i = pqlen - q[0]; + for (j = 0; j < i; j++) + o[j] = 0; + for (j = 0; j < (int)q[0]; j++) + o[i + j] = q[q[0] - j]; + + /* Allocate a of size 2*pqlen for result */ + a = snewn(2 * pqlen, BignumInt); + + /* Scratch space for multiplies */ + scratchlen = mul_compute_scratch(pqlen); + scratch = snewn(scratchlen, BignumInt); + + /* Main computation */ + internal_mul(n, o, a, pqlen, scratch); + internal_mod(a, pqlen * 2, m, mlen, NULL, 0); + + /* Fixup result in case the modulus was shifted */ + if (mshift) { + for (i = 2 * pqlen - mlen - 1; i < 2 * pqlen - 1; i++) + a[i] = (a[i] << mshift) | (a[i + 1] >> (BIGNUM_INT_BITS - mshift)); + a[2 * pqlen - 1] = a[2 * pqlen - 1] << mshift; + internal_mod(a, pqlen * 2, m, mlen, NULL, 0); + for (i = 2 * pqlen - 1; i >= 2 * pqlen - mlen; i--) + a[i] = (a[i] >> mshift) | (a[i - 1] << (BIGNUM_INT_BITS - mshift)); + } + + /* Copy result to buffer */ + rlen = (mlen < pqlen * 2 ? mlen : pqlen * 2); + result = newbn(rlen); + for (i = 0; i < rlen; i++) + result[result[0] - i] = a[i + 2 * pqlen - rlen]; + while (result[0] > 1 && result[result[0]] == 0) + result[0]--; + + /* Free temporary arrays */ + for (i = 0; i < scratchlen; i++) + scratch[i] = 0; + sfree(scratch); + for (i = 0; i < 2 * pqlen; i++) + a[i] = 0; + sfree(a); + for (i = 0; i < mlen; i++) + m[i] = 0; + sfree(m); + for (i = 0; i < pqlen; i++) + n[i] = 0; + sfree(n); + for (i = 0; i < pqlen; i++) + o[i] = 0; + sfree(o); + + return result; +} + +/* + * Compute p % mod. + * The most significant word of mod MUST be non-zero. + * We assume that the result array is the same size as the mod array. + * We optionally write out a quotient if `quotient' is non-NULL. + * We can avoid writing out the result if `result' is NULL. + */ +static void bigdivmod(Bignum p, Bignum mod, Bignum result, Bignum quotient) +{ + BignumInt *n, *m; + int mshift; + int plen, mlen, i, j; + + /* Allocate m of size mlen, copy mod to m */ + /* We use big endian internally */ + mlen = mod[0]; + m = snewn(mlen, BignumInt); + for (j = 0; j < mlen; j++) + m[j] = mod[mod[0] - j]; + + /* Shift m left to make msb bit set */ + for (mshift = 0; mshift < BIGNUM_INT_BITS-1; mshift++) + if ((m[0] << mshift) & BIGNUM_TOP_BIT) + break; + if (mshift) { + for (i = 0; i < mlen - 1; i++) + m[i] = (m[i] << mshift) | (m[i + 1] >> (BIGNUM_INT_BITS - mshift)); + m[mlen - 1] = m[mlen - 1] << mshift; + } + + plen = p[0]; + /* Ensure plen > mlen */ + if (plen <= mlen) + plen = mlen + 1; + + /* Allocate n of size plen, copy p to n */ + n = snewn(plen, BignumInt); + for (j = 0; j < plen; j++) + n[j] = 0; + for (j = 1; j <= (int)p[0]; j++) + n[plen - j] = p[j]; + + /* Main computation */ + internal_mod(n, plen, m, mlen, quotient, mshift); + + /* Fixup result in case the modulus was shifted */ + if (mshift) { + for (i = plen - mlen - 1; i < plen - 1; i++) + n[i] = (n[i] << mshift) | (n[i + 1] >> (BIGNUM_INT_BITS - mshift)); + n[plen - 1] = n[plen - 1] << mshift; + internal_mod(n, plen, m, mlen, quotient, 0); + for (i = plen - 1; i >= plen - mlen; i--) + n[i] = (n[i] >> mshift) | (n[i - 1] << (BIGNUM_INT_BITS - mshift)); + } + + /* Copy result to buffer */ + if (result) { + for (i = 1; i <= (int)result[0]; i++) { + int j = plen - i; + result[i] = j >= 0 ? n[j] : 0; + } + } + + /* Free temporary arrays */ + for (i = 0; i < mlen; i++) + m[i] = 0; + sfree(m); + for (i = 0; i < plen; i++) + n[i] = 0; + sfree(n); +} + +/* + * Decrement a number. + */ +void decbn(Bignum bn) +{ + int i = 1; + while (i < (int)bn[0] && bn[i] == 0) + bn[i++] = BIGNUM_INT_MASK; + bn[i]--; +} + +Bignum bignum_from_bytes(const unsigned char *data, int nbytes) +{ + Bignum result; + int w, i; + + w = (nbytes + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES; /* bytes->words */ + + result = newbn(w); + for (i = 1; i <= w; i++) + result[i] = 0; + for (i = nbytes; i--;) { + unsigned char byte = *data++; + result[1 + i / BIGNUM_INT_BYTES] |= byte << (8*i % BIGNUM_INT_BITS); + } + + while (result[0] > 1 && result[result[0]] == 0) + result[0]--; + return result; +} + +/* + * Read an SSH-1-format bignum from a data buffer. Return the number + * of bytes consumed, or -1 if there wasn't enough data. + */ +int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result) +{ + const unsigned char *p = data; + int i; + int w, b; + + if (len < 2) + return -1; + + w = 0; + for (i = 0; i < 2; i++) + w = (w << 8) + *p++; + b = (w + 7) / 8; /* bits -> bytes */ + + if (len < b+2) + return -1; + + if (!result) /* just return length */ + return b + 2; + + *result = bignum_from_bytes(p, b); + + return p + b - data; +} + +/* + * Return the bit count of a bignum, for SSH-1 encoding. + */ +int bignum_bitcount(Bignum bn) +{ + int bitcount = bn[0] * BIGNUM_INT_BITS - 1; + while (bitcount >= 0 + && (bn[bitcount / BIGNUM_INT_BITS + 1] >> (bitcount % BIGNUM_INT_BITS)) == 0) bitcount--; + return bitcount + 1; +} + +/* + * Return the byte length of a bignum when SSH-1 encoded. + */ +int ssh1_bignum_length(Bignum bn) +{ + return 2 + (bignum_bitcount(bn) + 7) / 8; +} + +/* + * Return the byte length of a bignum when SSH-2 encoded. + */ +int ssh2_bignum_length(Bignum bn) +{ + return 4 + (bignum_bitcount(bn) + 8) / 8; +} + +/* + * Return a byte from a bignum; 0 is least significant, etc. + */ +int bignum_byte(Bignum bn, int i) +{ + if (i >= (int)(BIGNUM_INT_BYTES * bn[0])) + return 0; /* beyond the end */ + else + return (bn[i / BIGNUM_INT_BYTES + 1] >> + ((i % BIGNUM_INT_BYTES)*8)) & 0xFF; +} + +/* + * Return a bit from a bignum; 0 is least significant, etc. + */ +int bignum_bit(Bignum bn, int i) +{ + if (i >= (int)(BIGNUM_INT_BITS * bn[0])) + return 0; /* beyond the end */ + else + return (bn[i / BIGNUM_INT_BITS + 1] >> (i % BIGNUM_INT_BITS)) & 1; +} + +/* + * Set a bit in a bignum; 0 is least significant, etc. + */ +void bignum_set_bit(Bignum bn, int bitnum, int value) +{ + if (bitnum >= (int)(BIGNUM_INT_BITS * bn[0])) + abort(); /* beyond the end */ + else { + int v = bitnum / BIGNUM_INT_BITS + 1; + int mask = 1 << (bitnum % BIGNUM_INT_BITS); + if (value) + bn[v] |= mask; + else + bn[v] &= ~mask; + } +} + +/* + * Write a SSH-1-format bignum into a buffer. It is assumed the + * buffer is big enough. Returns the number of bytes used. + */ +int ssh1_write_bignum(void *data, Bignum bn) +{ + unsigned char *p = data; + int len = ssh1_bignum_length(bn); + int i; + int bitc = bignum_bitcount(bn); + + *p++ = (bitc >> 8) & 0xFF; + *p++ = (bitc) & 0xFF; + for (i = len - 2; i--;) + *p++ = bignum_byte(bn, i); + return len; +} + +/* + * Compare two bignums. Returns like strcmp. + */ +int bignum_cmp(Bignum a, Bignum b) +{ + int amax = a[0], bmax = b[0]; + int i = (amax > bmax ? amax : bmax); + while (i) { + BignumInt aval = (i > amax ? 0 : a[i]); + BignumInt bval = (i > bmax ? 0 : b[i]); + if (aval < bval) + return -1; + if (aval > bval) + return +1; + i--; + } + return 0; +} + +/* + * Right-shift one bignum to form another. + */ +Bignum bignum_rshift(Bignum a, int shift) +{ + Bignum ret; + int i, shiftw, shiftb, shiftbb, bits; + BignumInt ai, ai1; + + bits = bignum_bitcount(a) - shift; + ret = newbn((bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS); + + if (ret) { + shiftw = shift / BIGNUM_INT_BITS; + shiftb = shift % BIGNUM_INT_BITS; + shiftbb = BIGNUM_INT_BITS - shiftb; + + ai1 = a[shiftw + 1]; + for (i = 1; i <= (int)ret[0]; i++) { + ai = ai1; + ai1 = (i + shiftw + 1 <= (int)a[0] ? a[i + shiftw + 1] : 0); + ret[i] = ((ai >> shiftb) | (ai1 << shiftbb)) & BIGNUM_INT_MASK; + } + } + + return ret; +} + +/* + * Non-modular multiplication and addition. + */ +Bignum bigmuladd(Bignum a, Bignum b, Bignum addend) +{ + int alen = a[0], blen = b[0]; + int mlen = (alen > blen ? alen : blen); + int rlen, i, maxspot; + int wslen; + BignumInt *workspace; + Bignum ret; + + /* mlen space for a, mlen space for b, 2*mlen for result, + * plus scratch space for multiplication */ + wslen = mlen * 4 + mul_compute_scratch(mlen); + workspace = snewn(wslen, BignumInt); + for (i = 0; i < mlen; i++) { + workspace[0 * mlen + i] = (mlen - i <= (int)a[0] ? a[mlen - i] : 0); + workspace[1 * mlen + i] = (mlen - i <= (int)b[0] ? b[mlen - i] : 0); + } + + internal_mul(workspace + 0 * mlen, workspace + 1 * mlen, + workspace + 2 * mlen, mlen, workspace + 4 * mlen); + + /* now just copy the result back */ + rlen = alen + blen + 1; + if (addend && rlen <= (int)addend[0]) + rlen = addend[0] + 1; + ret = newbn(rlen); + maxspot = 0; + for (i = 1; i <= (int)ret[0]; i++) { + ret[i] = (i <= 2 * mlen ? workspace[4 * mlen - i] : 0); + if (ret[i] != 0) + maxspot = i; + } + ret[0] = maxspot; + + /* now add in the addend, if any */ + if (addend) { + BignumDblInt carry = 0; + for (i = 1; i <= rlen; i++) { + carry += (i <= (int)ret[0] ? ret[i] : 0); + carry += (i <= (int)addend[0] ? addend[i] : 0); + ret[i] = (BignumInt) carry & BIGNUM_INT_MASK; + carry >>= BIGNUM_INT_BITS; + if (ret[i] != 0 && i > maxspot) + maxspot = i; + } + } + ret[0] = maxspot; + + for (i = 0; i < wslen; i++) + workspace[i] = 0; + sfree(workspace); + return ret; +} + +/* + * Non-modular multiplication. + */ +Bignum bigmul(Bignum a, Bignum b) +{ + return bigmuladd(a, b, NULL); +} + +/* + * Simple addition. + */ +Bignum bigadd(Bignum a, Bignum b) +{ + int alen = a[0], blen = b[0]; + int rlen = (alen > blen ? alen : blen) + 1; + int i, maxspot; + Bignum ret; + BignumDblInt carry; + + ret = newbn(rlen); + + carry = 0; + maxspot = 0; + for (i = 1; i <= rlen; i++) { + carry += (i <= (int)a[0] ? a[i] : 0); + carry += (i <= (int)b[0] ? b[i] : 0); + ret[i] = (BignumInt) carry & BIGNUM_INT_MASK; + carry >>= BIGNUM_INT_BITS; + if (ret[i] != 0 && i > maxspot) + maxspot = i; + } + ret[0] = maxspot; + + return ret; +} + +/* + * Subtraction. Returns a-b, or NULL if the result would come out + * negative (recall that this entire bignum module only handles + * positive numbers). + */ +Bignum bigsub(Bignum a, Bignum b) +{ + int alen = a[0], blen = b[0]; + int rlen = (alen > blen ? alen : blen); + int i, maxspot; + Bignum ret; + BignumDblInt carry; + + ret = newbn(rlen); + + carry = 1; + maxspot = 0; + for (i = 1; i <= rlen; i++) { + carry += (i <= (int)a[0] ? a[i] : 0); + carry += (i <= (int)b[0] ? b[i] ^ BIGNUM_INT_MASK : BIGNUM_INT_MASK); + ret[i] = (BignumInt) carry & BIGNUM_INT_MASK; + carry >>= BIGNUM_INT_BITS; + if (ret[i] != 0 && i > maxspot) + maxspot = i; + } + ret[0] = maxspot; + + if (!carry) { + freebn(ret); + return NULL; + } + + return ret; +} + +/* + * Create a bignum which is the bitmask covering another one. That + * is, the smallest integer which is >= N and is also one less than + * a power of two. + */ +Bignum bignum_bitmask(Bignum n) +{ + Bignum ret = copybn(n); + int i; + BignumInt j; + + i = ret[0]; + while (n[i] == 0 && i > 0) + i--; + if (i <= 0) + return ret; /* input was zero */ + j = 1; + while (j < n[i]) + j = 2 * j + 1; + ret[i] = j; + while (--i > 0) + ret[i] = BIGNUM_INT_MASK; + return ret; +} + +/* + * Convert a (max 32-bit) long into a bignum. + */ +Bignum bignum_from_long(unsigned long nn) +{ + Bignum ret; + BignumDblInt n = nn; + + ret = newbn(3); + ret[1] = (BignumInt)(n & BIGNUM_INT_MASK); + ret[2] = (BignumInt)((n >> BIGNUM_INT_BITS) & BIGNUM_INT_MASK); + ret[3] = 0; + ret[0] = (ret[2] ? 2 : 1); + return ret; +} + +/* + * Add a long to a bignum. + */ +Bignum bignum_add_long(Bignum number, unsigned long addendx) +{ + Bignum ret = newbn(number[0] + 1); + int i, maxspot = 0; + BignumDblInt carry = 0, addend = addendx; + + for (i = 1; i <= (int)ret[0]; i++) { + carry += addend & BIGNUM_INT_MASK; + carry += (i <= (int)number[0] ? number[i] : 0); + addend >>= BIGNUM_INT_BITS; + ret[i] = (BignumInt) carry & BIGNUM_INT_MASK; + carry >>= BIGNUM_INT_BITS; + if (ret[i] != 0) + maxspot = i; + } + ret[0] = maxspot; + return ret; +} + +/* + * Compute the residue of a bignum, modulo a (max 16-bit) short. + */ +unsigned short bignum_mod_short(Bignum number, unsigned short modulus) +{ + BignumDblInt mod, r; + int i; + + r = 0; + mod = modulus; + for (i = number[0]; i > 0; i--) + r = (r * (BIGNUM_TOP_BIT % mod) * 2 + number[i] % mod) % mod; + return (unsigned short) r; +} + +#ifdef DEBUG +void diagbn(char *prefix, Bignum md) +{ + int i, nibbles, morenibbles; + static const char hex[] = "0123456789ABCDEF"; + + debug(("%s0x", prefix ? prefix : "")); + + nibbles = (3 + bignum_bitcount(md)) / 4; + if (nibbles < 1) + nibbles = 1; + morenibbles = 4 * md[0] - nibbles; + for (i = 0; i < morenibbles; i++) + debug(("-")); + for (i = nibbles; i--;) + debug(("%c", + hex[(bignum_byte(md, i / 2) >> (4 * (i % 2))) & 0xF])); + + if (prefix) + debug(("\n")); +} +#endif + +/* + * Simple division. + */ +Bignum bigdiv(Bignum a, Bignum b) +{ + Bignum q = newbn(a[0]); + bigdivmod(a, b, NULL, q); + return q; +} + +/* + * Simple remainder. + */ +Bignum bigmod(Bignum a, Bignum b) +{ + Bignum r = newbn(b[0]); + bigdivmod(a, b, r, NULL); + return r; +} + +/* + * Greatest common divisor. + */ +Bignum biggcd(Bignum av, Bignum bv) +{ + Bignum a = copybn(av); + Bignum b = copybn(bv); + + while (bignum_cmp(b, Zero) != 0) { + Bignum t = newbn(b[0]); + bigdivmod(a, b, t, NULL); + while (t[0] > 1 && t[t[0]] == 0) + t[0]--; + freebn(a); + a = b; + b = t; + } + + freebn(b); + return a; +} + +/* + * Modular inverse, using Euclid's extended algorithm. + */ +Bignum modinv(Bignum number, Bignum modulus) +{ + Bignum a = copybn(modulus); + Bignum b = copybn(number); + Bignum xp = copybn(Zero); + Bignum x = copybn(One); + int sign = +1; + + while (bignum_cmp(b, One) != 0) { + Bignum t = newbn(b[0]); + Bignum q = newbn(a[0]); + bigdivmod(a, b, t, q); + while (t[0] > 1 && t[t[0]] == 0) + t[0]--; + freebn(a); + a = b; + b = t; + t = xp; + xp = x; + x = bigmuladd(q, xp, t); + sign = -sign; + freebn(t); + freebn(q); + } + + freebn(b); + freebn(a); + freebn(xp); + + /* now we know that sign * x == 1, and that x < modulus */ + if (sign < 0) { + /* set a new x to be modulus - x */ + Bignum newx = newbn(modulus[0]); + BignumInt carry = 0; + int maxspot = 1; + int i; + + for (i = 1; i <= (int)newx[0]; i++) { + BignumInt aword = (i <= (int)modulus[0] ? modulus[i] : 0); + BignumInt bword = (i <= (int)x[0] ? x[i] : 0); + newx[i] = aword - bword - carry; + bword = ~bword; + carry = carry ? (newx[i] >= bword) : (newx[i] > bword); + if (newx[i] != 0) + maxspot = i; + } + newx[0] = maxspot; + freebn(x); + x = newx; + } + + /* and return. */ + return x; +} + +/* + * Render a bignum into decimal. Return a malloced string holding + * the decimal representation. + */ +char *bignum_decimal(Bignum x) +{ + int ndigits, ndigit; + int i, iszero; + BignumDblInt carry; + char *ret; + BignumInt *workspace; + + /* + * First, estimate the number of digits. Since log(10)/log(2) + * is just greater than 93/28 (the joys of continued fraction + * approximations...) we know that for every 93 bits, we need + * at most 28 digits. This will tell us how much to malloc. + * + * Formally: if x has i bits, that means x is strictly less + * than 2^i. Since 2 is less than 10^(28/93), this is less than + * 10^(28i/93). We need an integer power of ten, so we must + * round up (rounding down might make it less than x again). + * Therefore if we multiply the bit count by 28/93, rounding + * up, we will have enough digits. + * + * i=0 (i.e., x=0) is an irritating special case. + */ + i = bignum_bitcount(x); + if (!i) + ndigits = 1; /* x = 0 */ + else + ndigits = (28 * i + 92) / 93; /* multiply by 28/93 and round up */ + ndigits++; /* allow for trailing \0 */ + ret = snewn(ndigits, char); + + /* + * Now allocate some workspace to hold the binary form as we + * repeatedly divide it by ten. Initialise this to the + * big-endian form of the number. + */ + workspace = snewn(x[0], BignumInt); + for (i = 0; i < (int)x[0]; i++) + workspace[i] = x[x[0] - i]; + + /* + * Next, write the decimal number starting with the last digit. + * We use ordinary short division, dividing 10 into the + * workspace. + */ + ndigit = ndigits - 1; + ret[ndigit] = '\0'; + do { + iszero = 1; + carry = 0; + for (i = 0; i < (int)x[0]; i++) { + carry = (carry << BIGNUM_INT_BITS) + workspace[i]; + workspace[i] = (BignumInt) (carry / 10); + if (workspace[i]) + iszero = 0; + carry %= 10; + } + ret[--ndigit] = (char) (carry + '0'); + } while (!iszero); + + /* + * There's a chance we've fallen short of the start of the + * string. Correct if so. + */ + if (ndigit > 0) + memmove(ret, ret + ndigit, ndigits - ndigit); + + /* + * Done. + */ + sfree(workspace); + return ret; +} + +#ifdef TESTBN + +#include +#include +#include + +/* + * gcc -g -O0 -DTESTBN -o testbn sshbn.c misc.c -I unix -I charset + * + * Then feed to this program's standard input the output of + * testdata/bignum.py . + */ + +void modalfatalbox(char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +#define fromxdigit(c) ( (c)>'9' ? ((c)&0xDF) - 'A' + 10 : (c) - '0' ) + +int main(int argc, char **argv) +{ + char *buf; + int line = 0; + int passes = 0, fails = 0; + + while ((buf = fgetline(stdin)) != NULL) { + int maxlen = strlen(buf); + unsigned char *data = snewn(maxlen, unsigned char); + unsigned char *ptrs[5], *q; + int ptrnum; + char *bufp = buf; + + line++; + + q = data; + ptrnum = 0; + + while (*bufp && !isspace((unsigned char)*bufp)) + bufp++; + if (bufp) + *bufp++ = '\0'; + + while (*bufp) { + char *start, *end; + int i; + + while (*bufp && !isxdigit((unsigned char)*bufp)) + bufp++; + start = bufp; + + if (!*bufp) + break; + + while (*bufp && isxdigit((unsigned char)*bufp)) + bufp++; + end = bufp; + + if (ptrnum >= lenof(ptrs)) + break; + ptrs[ptrnum++] = q; + + for (i = -((end - start) & 1); i < end-start; i += 2) { + unsigned char val = (i < 0 ? 0 : fromxdigit(start[i])); + val = val * 16 + fromxdigit(start[i+1]); + *q++ = val; + } + + ptrs[ptrnum] = q; + } + + if (!strcmp(buf, "mul")) { + Bignum a, b, c, p; + + if (ptrnum != 3) { + printf("%d: mul with %d parameters, expected 3\n", line); + exit(1); + } + a = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]); + b = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]); + c = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]); + p = bigmul(a, b); + + if (bignum_cmp(c, p) == 0) { + passes++; + } else { + char *as = bignum_decimal(a); + char *bs = bignum_decimal(b); + char *cs = bignum_decimal(c); + char *ps = bignum_decimal(p); + + printf("%d: fail: %s * %s gave %s expected %s\n", + line, as, bs, ps, cs); + fails++; + + sfree(as); + sfree(bs); + sfree(cs); + sfree(ps); + } + freebn(a); + freebn(b); + freebn(c); + freebn(p); + } else if (!strcmp(buf, "pow")) { + Bignum base, expt, modulus, expected, answer; + + if (ptrnum != 4) { + printf("%d: mul with %d parameters, expected 3\n", line); + exit(1); + } + + base = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]); + expt = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]); + modulus = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]); + expected = bignum_from_bytes(ptrs[3], ptrs[4]-ptrs[3]); + answer = modpow(base, expt, modulus); + + if (bignum_cmp(expected, answer) == 0) { + passes++; + } else { + char *as = bignum_decimal(base); + char *bs = bignum_decimal(expt); + char *cs = bignum_decimal(modulus); + char *ds = bignum_decimal(answer); + char *ps = bignum_decimal(expected); + + printf("%d: fail: %s ^ %s mod %s gave %s expected %s\n", + line, as, bs, cs, ds, ps); + fails++; + + sfree(as); + sfree(bs); + sfree(cs); + sfree(ds); + sfree(ps); + } + freebn(base); + freebn(expt); + freebn(modulus); + freebn(expected); + freebn(answer); + } else { + printf("%d: unrecognised test keyword: '%s'\n", line, buf); + exit(1); + } + + sfree(buf); + sfree(data); + } + + printf("passed %d failed %d total %d\n", passes, fails, passes+fails); + return fails != 0; +} + +#endif diff --git a/putty/SSHCRC.C b/putty/SSHCRC.C new file mode 100644 index 0000000..0a72a31 --- /dev/null +++ b/putty/SSHCRC.C @@ -0,0 +1,230 @@ +/* + * CRC32 implementation. + * + * The basic concept of a CRC is that you treat your bit-string + * abcdefg... as a ludicrously long polynomial M=a+bx+cx^2+dx^3+... + * over Z[2]. You then take a modulus polynomial P, and compute the + * remainder of M on division by P. Thus, an erroneous message N + * will only have the same CRC if the difference E = M-N is an + * exact multiple of P. (Note that as we are working over Z[2], M-N + * = N-M = M+N; but that's not very important.) + * + * What makes the CRC good is choosing P to have good properties: + * + * - If its first and last terms are both nonzero then it cannot + * be a factor of any single term x^i. Therefore if M and N + * differ by exactly one bit their CRCs will guaranteeably + * be distinct. + * + * - If it has a prime (irreducible) factor with three terms then + * it cannot divide a polynomial of the form x^i(1+x^j). + * Therefore if M and N differ by exactly _two_ bits they will + * have different CRCs. + * + * - If it has a factor (x+1) then it cannot divide a polynomial + * with an odd number of terms. Therefore if M and N differ by + * _any odd_ number of bits they will have different CRCs. + * + * - If the error term E is of the form x^i*B(x) where B(x) has + * order less than P (i.e. a short _burst_ of errors) then P + * cannot divide E (since no polynomial can divide a shorter + * one), so any such error burst will be spotted. + * + * The CRC32 standard polynomial is + * x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0 + * + * In fact, we don't compute M mod P; we compute M*x^32 mod P. + * + * The concrete implementation of the CRC is this: we maintain at + * all times a 32-bit word which is the current remainder of the + * polynomial mod P. Whenever we receive an extra bit, we multiply + * the existing remainder by x, add (XOR) the x^32 term thus + * generated to the new x^32 term caused by the incoming bit, and + * remove the resulting combined x^32 term if present by replacing + * it with (P-x^32). + * + * Bit 0 of the word is the x^31 term and bit 31 is the x^0 term. + * Thus, multiplying by x means shifting right. So the actual + * algorithm goes like this: + * + * x32term = (crcword & 1) ^ newbit; + * crcword = (crcword >> 1) ^ (x32term * 0xEDB88320); + * + * In practice, we pre-compute what will happen to crcword on any + * given sequence of eight incoming bits, and store that in a table + * which we then use at run-time to do the job: + * + * outgoingplusnew = (crcword & 0xFF) ^ newbyte; + * crcword = (crcword >> 8) ^ table[outgoingplusnew]; + * + * where table[outgoingplusnew] is computed by setting crcword=0 + * and then iterating the first code fragment eight times (taking + * the incoming byte low bit first). + * + * Note that all shifts are rightward and thus no assumption is + * made about exact word length! (Although word length must be at + * _least_ 32 bits, but ANSI C guarantees this for `unsigned long' + * anyway.) + */ + +#include + +#include "ssh.h" + +/* ---------------------------------------------------------------------- + * Multi-function module. Can be compiled three ways. + * + * - Compile with no special #defines. Will generate a table + * that's already initialised at compile time, and one function + * crc32_compute(buf,len) that uses it. Normal usage. + * + * - Compile with INITFUNC defined. Will generate an uninitialised + * array as the table, and as well as crc32_compute(buf,len) it + * will also generate void crc32_init(void) which sets up the + * table at run time. Useful if binary size is important. + * + * - Compile with GENPROGRAM defined. Will create a standalone + * program that does the initialisation and outputs the table as + * C code. + */ + +#define POLY (0xEDB88320L) + +#ifdef GENPROGRAM +#define INITFUNC /* the gen program needs the init func :-) */ +#endif + +#ifdef INITFUNC + +/* + * This variant of the code generates the table at run-time from an + * init function. + */ +static unsigned long crc32_table[256]; + +void crc32_init(void) +{ + unsigned long crcword; + int i; + + for (i = 0; i < 256; i++) { + unsigned long newbyte, x32term; + int j; + crcword = 0; + newbyte = i; + for (j = 0; j < 8; j++) { + x32term = (crcword ^ newbyte) & 1; + crcword = (crcword >> 1) ^ (x32term * POLY); + newbyte >>= 1; + } + crc32_table[i] = crcword; + } +} + +#else + +/* + * This variant of the code has the data already prepared. + */ +static const unsigned long crc32_table[256] = { + 0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL, + 0x076DC419L, 0x706AF48FL, 0xE963A535L, 0x9E6495A3L, + 0x0EDB8832L, 0x79DCB8A4L, 0xE0D5E91EL, 0x97D2D988L, + 0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, 0x90BF1D91L, + 0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL, + 0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L, + 0x136C9856L, 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL, + 0x14015C4FL, 0x63066CD9L, 0xFA0F3D63L, 0x8D080DF5L, + 0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, 0xA2677172L, + 0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL, + 0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L, + 0x32D86CE3L, 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L, + 0x26D930ACL, 0x51DE003AL, 0xC8D75180L, 0xBFD06116L, + 0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, 0xB8BDA50FL, + 0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L, + 0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL, + 0x76DC4190L, 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL, + 0x71B18589L, 0x06B6B51FL, 0x9FBFE4A5L, 0xE8B8D433L, + 0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, 0xE10E9818L, + 0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L, + 0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL, + 0x6C0695EDL, 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L, + 0x65B0D9C6L, 0x12B7E950L, 0x8BBEB8EAL, 0xFCB9887CL, + 0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, 0xFBD44C65L, + 0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L, + 0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL, + 0x4369E96AL, 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L, + 0x44042D73L, 0x33031DE5L, 0xAA0A4C5FL, 0xDD0D7CC9L, + 0x5005713CL, 0x270241AAL, 0xBE0B1010L, 0xC90C2086L, + 0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL, + 0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L, + 0x59B33D17L, 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL, + 0xEDB88320L, 0x9ABFB3B6L, 0x03B6E20CL, 0x74B1D29AL, + 0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, 0x73DC1683L, + 0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L, + 0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L, + 0xF00F9344L, 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL, + 0xF762575DL, 0x806567CBL, 0x196C3671L, 0x6E6B06E7L, + 0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, 0x67DD4ACCL, + 0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L, + 0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L, + 0xD1BB67F1L, 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL, + 0xD80D2BDAL, 0xAF0A1B4CL, 0x36034AF6L, 0x41047A60L, + 0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, 0x4669BE79L, + 0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L, + 0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL, + 0xC5BA3BBEL, 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L, + 0xC2D7FFA7L, 0xB5D0CF31L, 0x2CD99E8BL, 0x5BDEAE1DL, + 0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, 0x026D930AL, + 0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L, + 0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L, + 0x92D28E9BL, 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L, + 0x86D3D2D4L, 0xF1D4E242L, 0x68DDB3F8L, 0x1FDA836EL, + 0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, 0x18B74777L, + 0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL, + 0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L, + 0xA00AE278L, 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L, + 0xA7672661L, 0xD06016F7L, 0x4969474DL, 0x3E6E77DBL, + 0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, 0x37D83BF0L, + 0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L, + 0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L, + 0xBAD03605L, 0xCDD70693L, 0x54DE5729L, 0x23D967BFL, + 0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L, + 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL +}; + +#endif + +#ifdef GENPROGRAM +int main(void) +{ + unsigned long crcword; + int i; + + crc32_init(); + for (i = 0; i < 256; i++) { + printf("%s0x%08XL%s", + (i % 4 == 0 ? " " : " "), + crc32_table[i], + (i % 4 == 3 ? (i == 255 ? "\n" : ",\n") : ",")); + } + + return 0; +} +#endif + +unsigned long crc32_update(unsigned long crcword, const void *buf, size_t len) +{ + const unsigned char *p = (const unsigned char *) buf; + while (len--) { + unsigned long newbyte = *p++; + newbyte ^= crcword & 0xFFL; + crcword = (crcword >> 8) ^ crc32_table[newbyte]; + } + return crcword; +} + +unsigned long crc32_compute(const void *buf, size_t len) +{ + return crc32_update(0L, buf, len); +} diff --git a/putty/SSHCRCDA.C b/putty/SSHCRCDA.C new file mode 100644 index 0000000..c2cf705 --- /dev/null +++ b/putty/SSHCRCDA.C @@ -0,0 +1,172 @@ +/* $OpenBSD: deattack.c,v 1.14 2001/06/23 15:12:18 itojun Exp $ */ + +/* + * Cryptographic attack detector for ssh - source code + * + * Copyright (c) 1998 CORE SDI S.A., Buenos Aires, Argentina. + * + * All rights reserved. Redistribution and use in source and binary + * forms, with or without modification, are permitted provided that + * this copyright notice is retained. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI S.A. BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR + * CONSEQUENTIAL DAMAGES RESULTING FROM THE USE OR MISUSE OF THIS + * SOFTWARE. + * + * Ariel Futoransky + * + * + * Modified for use in PuTTY by Simon Tatham + */ + +#include +#include "misc.h" +#include "ssh.h" + +typedef unsigned char uchar; +typedef unsigned short uint16; + +/* SSH Constants */ +#define SSH_MAXBLOCKS (32 * 1024) +#define SSH_BLOCKSIZE (8) + +/* Hashing constants */ +#define HASH_MINSIZE (8 * 1024) +#define HASH_ENTRYSIZE (sizeof(uint16)) +#define HASH_FACTOR(x) ((x)*3/2) +#define HASH_UNUSEDCHAR (0xff) +#define HASH_UNUSED (0xffff) +#define HASH_IV (0xfffe) + +#define HASH_MINBLOCKS (7*SSH_BLOCKSIZE) + +/* Hash function (Input keys are cipher results) */ +#define HASH(x) GET_32BIT_MSB_FIRST(x) + +#define CMP(a, b) (memcmp(a, b, SSH_BLOCKSIZE)) + +uchar ONE[4] = { 1, 0, 0, 0 }; +uchar ZERO[4] = { 0, 0, 0, 0 }; + +struct crcda_ctx { + uint16 *h; + uint32 n; +}; + +void *crcda_make_context(void) +{ + struct crcda_ctx *ret = snew(struct crcda_ctx); + ret->h = NULL; + ret->n = HASH_MINSIZE / HASH_ENTRYSIZE; + return ret; +} + +void crcda_free_context(void *handle) +{ + struct crcda_ctx *ctx = (struct crcda_ctx *)handle; + if (ctx) { + sfree(ctx->h); + ctx->h = NULL; + sfree(ctx); + } +} + +static void crc_update(uint32 *a, void *b) +{ + *a = crc32_update(*a, b, 4); +} + +/* detect if a block is used in a particular pattern */ +static int check_crc(uchar *S, uchar *buf, uint32 len, uchar *IV) +{ + uint32 crc; + uchar *c; + + crc = 0; + if (IV && !CMP(S, IV)) { + crc_update(&crc, ONE); + crc_update(&crc, ZERO); + } + for (c = buf; c < buf + len; c += SSH_BLOCKSIZE) { + if (!CMP(S, c)) { + crc_update(&crc, ONE); + crc_update(&crc, ZERO); + } else { + crc_update(&crc, ZERO); + crc_update(&crc, ZERO); + } + } + return (crc == 0); +} + +/* Detect a crc32 compensation attack on a packet */ +int detect_attack(void *handle, uchar *buf, uint32 len, uchar *IV) +{ + struct crcda_ctx *ctx = (struct crcda_ctx *)handle; + register uint32 i, j; + uint32 l; + register uchar *c; + uchar *d; + + assert(!(len > (SSH_MAXBLOCKS * SSH_BLOCKSIZE) || + len % SSH_BLOCKSIZE != 0)); + for (l = ctx->n; l < HASH_FACTOR(len / SSH_BLOCKSIZE); l = l << 2) + ; + + if (ctx->h == NULL) { + ctx->n = l; + ctx->h = snewn(ctx->n, uint16); + } else { + if (l > ctx->n) { + ctx->n = l; + ctx->h = sresize(ctx->h, ctx->n, uint16); + } + } + + if (len <= HASH_MINBLOCKS) { + for (c = buf; c < buf + len; c += SSH_BLOCKSIZE) { + if (IV && (!CMP(c, IV))) { + if ((check_crc(c, buf, len, IV))) + return 1; /* attack detected */ + else + break; + } + for (d = buf; d < c; d += SSH_BLOCKSIZE) { + if (!CMP(c, d)) { + if ((check_crc(c, buf, len, IV))) + return 1; /* attack detected */ + else + break; + } + } + } + return 0; /* ok */ + } + memset(ctx->h, HASH_UNUSEDCHAR, ctx->n * HASH_ENTRYSIZE); + + if (IV) + ctx->h[HASH(IV) & (ctx->n - 1)] = HASH_IV; + + for (c = buf, j = 0; c < (buf + len); c += SSH_BLOCKSIZE, j++) { + for (i = HASH(c) & (ctx->n - 1); ctx->h[i] != HASH_UNUSED; + i = (i + 1) & (ctx->n - 1)) { + if (ctx->h[i] == HASH_IV) { + if (!CMP(c, IV)) { + if (check_crc(c, buf, len, IV)) + return 1; /* attack detected */ + else + break; + } + } else if (!CMP(c, buf + ctx->h[i] * SSH_BLOCKSIZE)) { + if (check_crc(c, buf, len, IV)) + return 1; /* attack detected */ + else + break; + } + } + ctx->h[i] = j; + } + return 0; /* ok */ +} diff --git a/putty/SSHDES.C b/putty/SSHDES.C new file mode 100644 index 0000000..0beb273 --- /dev/null +++ b/putty/SSHDES.C @@ -0,0 +1,1031 @@ +#include +#include "ssh.h" + + +/* des.c - implementation of DES + */ + +/* + * Description of DES + * ------------------ + * + * Unlike the description in FIPS 46, I'm going to use _sensible_ indices: + * bits in an n-bit word are numbered from 0 at the LSB to n-1 at the MSB. + * And S-boxes are indexed by six consecutive bits, not by the outer two + * followed by the middle four. + * + * The DES encryption routine requires a 64-bit input, and a key schedule K + * containing 16 48-bit elements. + * + * First the input is permuted by the initial permutation IP. + * Then the input is split into 32-bit words L and R. (L is the MSW.) + * Next, 16 rounds. In each round: + * (L, R) <- (R, L xor f(R, K[i])) + * Then the pre-output words L and R are swapped. + * Then L and R are glued back together into a 64-bit word. (L is the MSW, + * again, but since we just swapped them, the MSW is the R that came out + * of the last round.) + * The 64-bit output block is permuted by the inverse of IP and returned. + * + * Decryption is identical except that the elements of K are used in the + * opposite order. (This wouldn't work if that word swap didn't happen.) + * + * The function f, used in each round, accepts a 32-bit word R and a + * 48-bit key block K. It produces a 32-bit output. + * + * First R is expanded to 48 bits using the bit-selection function E. + * The resulting 48-bit block is XORed with the key block K to produce + * a 48-bit block X. + * This block X is split into eight groups of 6 bits. Each group of 6 + * bits is then looked up in one of the eight S-boxes to convert + * it to 4 bits. These eight groups of 4 bits are glued back + * together to produce a 32-bit preoutput block. + * The preoutput block is permuted using the permutation P and returned. + * + * Key setup maps a 64-bit key word into a 16x48-bit key schedule. Although + * the approved input format for the key is a 64-bit word, eight of the + * bits are discarded, so the actual quantity of key used is 56 bits. + * + * First the input key is converted to two 28-bit words C and D using + * the bit-selection function PC1. + * Then 16 rounds of key setup occur. In each round, C and D are each + * rotated left by either 1 or 2 bits (depending on which round), and + * then converted into a key schedule element using the bit-selection + * function PC2. + * + * That's the actual algorithm. Now for the tedious details: all those + * painful permutations and lookup tables. + * + * IP is a 64-to-64 bit permutation. Its output contains the following + * bits of its input (listed in order MSB to LSB of output). + * + * 6 14 22 30 38 46 54 62 4 12 20 28 36 44 52 60 + * 2 10 18 26 34 42 50 58 0 8 16 24 32 40 48 56 + * 7 15 23 31 39 47 55 63 5 13 21 29 37 45 53 61 + * 3 11 19 27 35 43 51 59 1 9 17 25 33 41 49 57 + * + * E is a 32-to-48 bit selection function. Its output contains the following + * bits of its input (listed in order MSB to LSB of output). + * + * 0 31 30 29 28 27 28 27 26 25 24 23 24 23 22 21 20 19 20 19 18 17 16 15 + * 16 15 14 13 12 11 12 11 10 9 8 7 8 7 6 5 4 3 4 3 2 1 0 31 + * + * The S-boxes are arbitrary table-lookups each mapping a 6-bit input to a + * 4-bit output. In other words, each S-box is an array[64] of 4-bit numbers. + * The S-boxes are listed below. The first S-box listed is applied to the + * most significant six bits of the block X; the last one is applied to the + * least significant. + * + * 14 0 4 15 13 7 1 4 2 14 15 2 11 13 8 1 + * 3 10 10 6 6 12 12 11 5 9 9 5 0 3 7 8 + * 4 15 1 12 14 8 8 2 13 4 6 9 2 1 11 7 + * 15 5 12 11 9 3 7 14 3 10 10 0 5 6 0 13 + * + * 15 3 1 13 8 4 14 7 6 15 11 2 3 8 4 14 + * 9 12 7 0 2 1 13 10 12 6 0 9 5 11 10 5 + * 0 13 14 8 7 10 11 1 10 3 4 15 13 4 1 2 + * 5 11 8 6 12 7 6 12 9 0 3 5 2 14 15 9 + * + * 10 13 0 7 9 0 14 9 6 3 3 4 15 6 5 10 + * 1 2 13 8 12 5 7 14 11 12 4 11 2 15 8 1 + * 13 1 6 10 4 13 9 0 8 6 15 9 3 8 0 7 + * 11 4 1 15 2 14 12 3 5 11 10 5 14 2 7 12 + * + * 7 13 13 8 14 11 3 5 0 6 6 15 9 0 10 3 + * 1 4 2 7 8 2 5 12 11 1 12 10 4 14 15 9 + * 10 3 6 15 9 0 0 6 12 10 11 1 7 13 13 8 + * 15 9 1 4 3 5 14 11 5 12 2 7 8 2 4 14 + * + * 2 14 12 11 4 2 1 12 7 4 10 7 11 13 6 1 + * 8 5 5 0 3 15 15 10 13 3 0 9 14 8 9 6 + * 4 11 2 8 1 12 11 7 10 1 13 14 7 2 8 13 + * 15 6 9 15 12 0 5 9 6 10 3 4 0 5 14 3 + * + * 12 10 1 15 10 4 15 2 9 7 2 12 6 9 8 5 + * 0 6 13 1 3 13 4 14 14 0 7 11 5 3 11 8 + * 9 4 14 3 15 2 5 12 2 9 8 5 12 15 3 10 + * 7 11 0 14 4 1 10 7 1 6 13 0 11 8 6 13 + * + * 4 13 11 0 2 11 14 7 15 4 0 9 8 1 13 10 + * 3 14 12 3 9 5 7 12 5 2 10 15 6 8 1 6 + * 1 6 4 11 11 13 13 8 12 1 3 4 7 10 14 7 + * 10 9 15 5 6 0 8 15 0 14 5 2 9 3 2 12 + * + * 13 1 2 15 8 13 4 8 6 10 15 3 11 7 1 4 + * 10 12 9 5 3 6 14 11 5 0 0 14 12 9 7 2 + * 7 2 11 1 4 14 1 7 9 4 12 10 14 8 2 13 + * 0 15 6 12 10 9 13 0 15 3 3 5 5 6 8 11 + * + * P is a 32-to-32 bit permutation. Its output contains the following + * bits of its input (listed in order MSB to LSB of output). + * + * 16 25 12 11 3 20 4 15 31 17 9 6 27 14 1 22 + * 30 24 8 18 0 5 29 23 13 19 2 26 10 21 28 7 + * + * PC1 is a 64-to-56 bit selection function. Its output is in two words, + * C and D. The word C contains the following bits of its input (listed + * in order MSB to LSB of output). + * + * 7 15 23 31 39 47 55 63 6 14 22 30 38 46 + * 54 62 5 13 21 29 37 45 53 61 4 12 20 28 + * + * And the word D contains these bits. + * + * 1 9 17 25 33 41 49 57 2 10 18 26 34 42 + * 50 58 3 11 19 27 35 43 51 59 36 44 52 60 + * + * PC2 is a 56-to-48 bit selection function. Its input is in two words, + * C and D. These are treated as one 56-bit word (with C more significant, + * so that bits 55 to 28 of the word are bits 27 to 0 of C, and bits 27 to + * 0 of the word are bits 27 to 0 of D). The output contains the following + * bits of this 56-bit input word (listed in order MSB to LSB of output). + * + * 42 39 45 32 55 51 53 28 41 50 35 46 33 37 44 52 30 48 40 49 29 36 43 54 + * 15 4 25 19 9 1 26 16 5 11 23 8 12 7 17 0 22 3 10 14 6 20 27 24 + */ + +/* + * Implementation details + * ---------------------- + * + * If you look at the code in this module, you'll find it looks + * nothing _like_ the above algorithm. Here I explain the + * differences... + * + * Key setup has not been heavily optimised here. We are not + * concerned with key agility: we aren't codebreakers. We don't + * mind a little delay (and it really is a little one; it may be a + * factor of five or so slower than it could be but it's still not + * an appreciable length of time) while setting up. The only tweaks + * in the key setup are ones which change the format of the key + * schedule to speed up the actual encryption. I'll describe those + * below. + * + * The first and most obvious optimisation is the S-boxes. Since + * each S-box always targets the same four bits in the final 32-bit + * word, so the output from (for example) S-box 0 must always be + * shifted left 28 bits, we can store the already-shifted outputs + * in the lookup tables. This reduces lookup-and-shift to lookup, + * so the S-box step is now just a question of ORing together eight + * table lookups. + * + * The permutation P is just a bit order change; it's invariant + * with respect to OR, in that P(x)|P(y) = P(x|y). Therefore, we + * can apply P to every entry of the S-box tables and then we don't + * have to do it in the code of f(). This yields a set of tables + * which might be called SP-boxes. + * + * The bit-selection function E is our next target. Note that E is + * immediately followed by the operation of splitting into 6-bit + * chunks. Examining the 6-bit chunks coming out of E we notice + * they're all contiguous within the word (speaking cyclically - + * the end two wrap round); so we can extract those bit strings + * individually rather than explicitly running E. This would yield + * code such as + * + * y |= SPboxes[0][ (rotl(R, 5) ^ top6bitsofK) & 0x3F ]; + * t |= SPboxes[1][ (rotl(R,11) ^ next6bitsofK) & 0x3F ]; + * + * and so on; and the key schedule preparation would have to + * provide each 6-bit chunk separately. + * + * Really we'd like to XOR in the key schedule element before + * looking up bit strings in R. This we can't do, naively, because + * the 6-bit strings we want overlap. But look at the strings: + * + * 3322222222221111111111 + * bit 10987654321098765432109876543210 + * + * box0 XXXXX X + * box1 XXXXXX + * box2 XXXXXX + * box3 XXXXXX + * box4 XXXXXX + * box5 XXXXXX + * box6 XXXXXX + * box7 X XXXXX + * + * The bit strings we need to XOR in for boxes 0, 2, 4 and 6 don't + * overlap with each other. Neither do the ones for boxes 1, 3, 5 + * and 7. So we could provide the key schedule in the form of two + * words that we can separately XOR into R, and then every S-box + * index is available as a (cyclically) contiguous 6-bit substring + * of one or the other of the results. + * + * The comments in Eric Young's libdes implementation point out + * that two of these bit strings require a rotation (rather than a + * simple shift) to extract. It's unavoidable that at least _one_ + * must do; but we can actually run the whole inner algorithm (all + * 16 rounds) rotated one bit to the left, so that what the `real' + * DES description sees as L=0x80000001 we see as L=0x00000003. + * This requires rotating all our SP-box entries one bit to the + * left, and rotating each word of the key schedule elements one to + * the left, and rotating L and R one bit left just after IP and + * one bit right again just before FP. And in each round we convert + * a rotate into a shift, so we've saved a few per cent. + * + * That's about it for the inner loop; the SP-box tables as listed + * below are what I've described here (the original S value, + * shifted to its final place in the input to P, run through P, and + * then rotated one bit left). All that remains is to optimise the + * initial permutation IP. + * + * IP is not an arbitrary permutation. It has the nice property + * that if you take any bit number, write it in binary (6 bits), + * permute those 6 bits and invert some of them, you get the final + * position of that bit. Specifically, the bit whose initial + * position is given (in binary) as fedcba ends up in position + * AcbFED (where a capital letter denotes the inverse of a bit). + * + * We have the 64-bit data in two 32-bit words L and R, where bits + * in L are those with f=1 and bits in R are those with f=0. We + * note that we can do a simple transformation: suppose we exchange + * the bits with f=1,c=0 and the bits with f=0,c=1. This will cause + * the bit fedcba to be in position cedfba - we've `swapped' bits c + * and f in the position of each bit! + * + * Better still, this transformation is easy. In the example above, + * bits in L with c=0 are bits 0x0F0F0F0F, and those in R with c=1 + * are 0xF0F0F0F0. So we can do + * + * difference = ((R >> 4) ^ L) & 0x0F0F0F0F + * R ^= (difference << 4) + * L ^= difference + * + * to perform the swap. Let's denote this by bitswap(4,0x0F0F0F0F). + * Also, we can invert the bit at the top just by exchanging L and + * R. So in a few swaps and a few of these bit operations we can + * do: + * + * Initially the position of bit fedcba is fedcba + * Swap L with R to make it Fedcba + * Perform bitswap( 4,0x0F0F0F0F) to make it cedFba + * Perform bitswap(16,0x0000FFFF) to make it ecdFba + * Swap L with R to make it EcdFba + * Perform bitswap( 2,0x33333333) to make it bcdFEa + * Perform bitswap( 8,0x00FF00FF) to make it dcbFEa + * Swap L with R to make it DcbFEa + * Perform bitswap( 1,0x55555555) to make it acbFED + * Swap L with R to make it AcbFED + * + * (In the actual code the four swaps are implicit: R and L are + * simply used the other way round in the first, second and last + * bitswap operations.) + * + * The final permutation is just the inverse of IP, so it can be + * performed by a similar set of operations. + */ + +typedef struct { + word32 k0246[16], k1357[16]; + word32 iv0, iv1; +} DESContext; + +#define rotl(x, c) ( (x << c) | (x >> (32-c)) ) +#define rotl28(x, c) ( ( (x << c) | (x >> (28-c)) ) & 0x0FFFFFFF) + +static word32 bitsel(word32 * input, const int *bitnums, int size) +{ + word32 ret = 0; + while (size--) { + int bitpos = *bitnums++; + ret <<= 1; + if (bitpos >= 0) + ret |= 1 & (input[bitpos / 32] >> (bitpos % 32)); + } + return ret; +} + +static void des_key_setup(word32 key_msw, word32 key_lsw, DESContext * sched) +{ + + static const int PC1_Cbits[] = { + 7, 15, 23, 31, 39, 47, 55, 63, 6, 14, 22, 30, 38, 46, + 54, 62, 5, 13, 21, 29, 37, 45, 53, 61, 4, 12, 20, 28 + }; + static const int PC1_Dbits[] = { + 1, 9, 17, 25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42, + 50, 58, 3, 11, 19, 27, 35, 43, 51, 59, 36, 44, 52, 60 + }; + /* + * The bit numbers in the two lists below don't correspond to + * the ones in the above description of PC2, because in the + * above description C and D are concatenated so `bit 28' means + * bit 0 of C. In this implementation we're using the standard + * `bitsel' function above and C is in the second word, so bit + * 0 of C is addressed by writing `32' here. + */ + static const int PC2_0246[] = { + 49, 36, 59, 55, -1, -1, 37, 41, 48, 56, 34, 52, -1, -1, 15, 4, + 25, 19, 9, 1, -1, -1, 12, 7, 17, 0, 22, 3, -1, -1, 46, 43 + }; + static const int PC2_1357[] = { + -1, -1, 57, 32, 45, 54, 39, 50, -1, -1, 44, 53, 33, 40, 47, 58, + -1, -1, 26, 16, 5, 11, 23, 8, -1, -1, 10, 14, 6, 20, 27, 24 + }; + static const int leftshifts[] = + { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; + + word32 C, D; + word32 buf[2]; + int i; + + buf[0] = key_lsw; + buf[1] = key_msw; + + C = bitsel(buf, PC1_Cbits, 28); + D = bitsel(buf, PC1_Dbits, 28); + + for (i = 0; i < 16; i++) { + C = rotl28(C, leftshifts[i]); + D = rotl28(D, leftshifts[i]); + buf[0] = D; + buf[1] = C; + sched->k0246[i] = bitsel(buf, PC2_0246, 32); + sched->k1357[i] = bitsel(buf, PC2_1357, 32); + } + + sched->iv0 = sched->iv1 = 0; +} + +static const word32 SPboxes[8][64] = { + {0x01010400, 0x00000000, 0x00010000, 0x01010404, + 0x01010004, 0x00010404, 0x00000004, 0x00010000, + 0x00000400, 0x01010400, 0x01010404, 0x00000400, + 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, + 0x00010400, 0x01010000, 0x01010000, 0x01000404, + 0x00010004, 0x01000004, 0x01000004, 0x00010004, + 0x00000000, 0x00000404, 0x00010404, 0x01000000, + 0x00010000, 0x01010404, 0x00000004, 0x01010000, + 0x01010400, 0x01000000, 0x01000000, 0x00000400, + 0x01010004, 0x00010000, 0x00010400, 0x01000004, + 0x00000400, 0x00000004, 0x01000404, 0x00010404, + 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, + 0x00000404, 0x01000400, 0x01000400, 0x00000000, + 0x00010004, 0x00010400, 0x00000000, 0x01010004L}, + + {0x80108020, 0x80008000, 0x00008000, 0x00108020, + 0x00100000, 0x00000020, 0x80100020, 0x80008020, + 0x80000020, 0x80108020, 0x80108000, 0x80000000, + 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, + 0x80000000, 0x00008000, 0x00108020, 0x80100000, + 0x00100020, 0x80000020, 0x00000000, 0x00108000, + 0x00008020, 0x80108000, 0x80100000, 0x00008020, + 0x00000000, 0x00108020, 0x80100020, 0x00100000, + 0x80008020, 0x80100000, 0x80108000, 0x00008000, + 0x80100000, 0x80008000, 0x00000020, 0x80108020, + 0x00108020, 0x00000020, 0x00008000, 0x80000000, + 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, + 0x00108000, 0x00000000, 0x80008000, 0x00008020, + 0x80000000, 0x80100020, 0x80108020, 0x00108000L}, + + {0x00000208, 0x08020200, 0x00000000, 0x08020008, + 0x08000200, 0x00000000, 0x00020208, 0x08000200, + 0x00020008, 0x08000008, 0x08000008, 0x00020000, + 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, + 0x00020200, 0x08020000, 0x08020008, 0x00020208, + 0x08000208, 0x00020200, 0x00020000, 0x08000208, + 0x00000008, 0x08020208, 0x00000200, 0x08000000, + 0x08020200, 0x08000000, 0x00020008, 0x00000208, + 0x00020000, 0x08020200, 0x08000200, 0x00000000, + 0x00000200, 0x00020008, 0x08020208, 0x08000200, + 0x08000008, 0x00000200, 0x00000000, 0x08020008, + 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, + 0x08020000, 0x08000208, 0x00000208, 0x08020000, + 0x00020208, 0x00000008, 0x08020008, 0x00020200L}, + + {0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802080, 0x00800081, 0x00800001, 0x00002001, + 0x00000000, 0x00802000, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002001, 0x00002080, + 0x00800081, 0x00000001, 0x00002080, 0x00800080, + 0x00002000, 0x00802080, 0x00802081, 0x00000081, + 0x00800080, 0x00800001, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00000000, 0x00802000, + 0x00002080, 0x00800080, 0x00800081, 0x00000001, + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, + 0x00002001, 0x00002080, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002000, 0x00802080L}, + + {0x00000100, 0x02080100, 0x02080000, 0x42000100, + 0x00080000, 0x00000100, 0x40000000, 0x02080000, + 0x40080100, 0x00080000, 0x02000100, 0x40080100, + 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, + 0x40000100, 0x42080100, 0x42080100, 0x02000100, + 0x42080000, 0x40000100, 0x00000000, 0x42000000, + 0x02080100, 0x02000000, 0x42000000, 0x00080100, + 0x00080000, 0x42000100, 0x00000100, 0x02000000, + 0x40000000, 0x02080000, 0x42000100, 0x40080100, + 0x02000100, 0x40000000, 0x42080000, 0x02080100, + 0x40080100, 0x00000100, 0x02000000, 0x42080000, + 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, + 0x00080100, 0x02000100, 0x40000100, 0x00080000, + 0x00000000, 0x40080000, 0x02080100, 0x40000100L}, + + {0x20000010, 0x20400000, 0x00004000, 0x20404010, + 0x20400000, 0x00000010, 0x20404010, 0x00400000, + 0x20004000, 0x00404010, 0x00400000, 0x20000010, + 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, + 0x00404000, 0x20004010, 0x00000010, 0x20400010, + 0x20400010, 0x00000000, 0x00404010, 0x20404000, + 0x00004010, 0x00404000, 0x20404000, 0x20000000, + 0x20004000, 0x00000010, 0x20400010, 0x00404000, + 0x20404010, 0x00400000, 0x00004010, 0x20000010, + 0x00400000, 0x20004000, 0x20000000, 0x00004010, + 0x20000010, 0x20404010, 0x00404000, 0x20400000, + 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, + 0x00004000, 0x00400010, 0x20004010, 0x00000000, + 0x20404000, 0x20000000, 0x00400010, 0x20004010L}, + + {0x00200000, 0x04200002, 0x04000802, 0x00000000, + 0x00000800, 0x04000802, 0x00200802, 0x04200800, + 0x04200802, 0x00200000, 0x00000000, 0x04000002, + 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, + 0x04000002, 0x04200000, 0x04200800, 0x00200002, + 0x04200000, 0x00000800, 0x00000802, 0x04200802, + 0x00200800, 0x00000002, 0x04000000, 0x00200800, + 0x04000000, 0x00200800, 0x00200000, 0x04000802, + 0x04000802, 0x04200002, 0x04200002, 0x00000002, + 0x00200002, 0x04000000, 0x04000800, 0x00200000, + 0x04200800, 0x00000802, 0x00200802, 0x04200800, + 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, + 0x00000000, 0x00200802, 0x04200000, 0x00000800, + 0x04000002, 0x04000800, 0x00000800, 0x00200002L}, + + {0x10001040, 0x00001000, 0x00040000, 0x10041040, + 0x10000000, 0x10001040, 0x00000040, 0x10000000, + 0x00040040, 0x10040000, 0x10041040, 0x00041000, + 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, + 0x00041000, 0x00040040, 0x10040040, 0x10041000, + 0x00001040, 0x00000000, 0x00000000, 0x10040040, + 0x10000040, 0x10001000, 0x00041040, 0x00040000, + 0x00041040, 0x00040000, 0x10041000, 0x00001000, + 0x00000040, 0x10040040, 0x00001000, 0x00041040, + 0x10001000, 0x00000040, 0x10000040, 0x10040000, + 0x10040040, 0x10000000, 0x00040000, 0x10001040, + 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, + 0x10041040, 0x00041000, 0x00041000, 0x00001040, + 0x00001040, 0x00040040, 0x10000000, 0x10041000L} +}; + +#define f(R, K0246, K1357) (\ + s0246 = R ^ K0246, \ + s1357 = R ^ K1357, \ + s0246 = rotl(s0246, 28), \ + SPboxes[0] [(s0246 >> 24) & 0x3F] | \ + SPboxes[1] [(s1357 >> 24) & 0x3F] | \ + SPboxes[2] [(s0246 >> 16) & 0x3F] | \ + SPboxes[3] [(s1357 >> 16) & 0x3F] | \ + SPboxes[4] [(s0246 >> 8) & 0x3F] | \ + SPboxes[5] [(s1357 >> 8) & 0x3F] | \ + SPboxes[6] [(s0246 ) & 0x3F] | \ + SPboxes[7] [(s1357 ) & 0x3F]) + +#define bitswap(L, R, n, mask) (\ + swap = mask & ( (R >> n) ^ L ), \ + R ^= swap << n, \ + L ^= swap) + +/* Initial permutation */ +#define IP(L, R) (\ + bitswap(R, L, 4, 0x0F0F0F0F), \ + bitswap(R, L, 16, 0x0000FFFF), \ + bitswap(L, R, 2, 0x33333333), \ + bitswap(L, R, 8, 0x00FF00FF), \ + bitswap(R, L, 1, 0x55555555)) + +/* Final permutation */ +#define FP(L, R) (\ + bitswap(R, L, 1, 0x55555555), \ + bitswap(L, R, 8, 0x00FF00FF), \ + bitswap(L, R, 2, 0x33333333), \ + bitswap(R, L, 16, 0x0000FFFF), \ + bitswap(R, L, 4, 0x0F0F0F0F)) + +static void des_encipher(word32 * output, word32 L, word32 R, + DESContext * sched) +{ + word32 swap, s0246, s1357; + + IP(L, R); + + L = rotl(L, 1); + R = rotl(R, 1); + + L ^= f(R, sched->k0246[0], sched->k1357[0]); + R ^= f(L, sched->k0246[1], sched->k1357[1]); + L ^= f(R, sched->k0246[2], sched->k1357[2]); + R ^= f(L, sched->k0246[3], sched->k1357[3]); + L ^= f(R, sched->k0246[4], sched->k1357[4]); + R ^= f(L, sched->k0246[5], sched->k1357[5]); + L ^= f(R, sched->k0246[6], sched->k1357[6]); + R ^= f(L, sched->k0246[7], sched->k1357[7]); + L ^= f(R, sched->k0246[8], sched->k1357[8]); + R ^= f(L, sched->k0246[9], sched->k1357[9]); + L ^= f(R, sched->k0246[10], sched->k1357[10]); + R ^= f(L, sched->k0246[11], sched->k1357[11]); + L ^= f(R, sched->k0246[12], sched->k1357[12]); + R ^= f(L, sched->k0246[13], sched->k1357[13]); + L ^= f(R, sched->k0246[14], sched->k1357[14]); + R ^= f(L, sched->k0246[15], sched->k1357[15]); + + L = rotl(L, 31); + R = rotl(R, 31); + + swap = L; + L = R; + R = swap; + + FP(L, R); + + output[0] = L; + output[1] = R; +} + +static void des_decipher(word32 * output, word32 L, word32 R, + DESContext * sched) +{ + word32 swap, s0246, s1357; + + IP(L, R); + + L = rotl(L, 1); + R = rotl(R, 1); + + L ^= f(R, sched->k0246[15], sched->k1357[15]); + R ^= f(L, sched->k0246[14], sched->k1357[14]); + L ^= f(R, sched->k0246[13], sched->k1357[13]); + R ^= f(L, sched->k0246[12], sched->k1357[12]); + L ^= f(R, sched->k0246[11], sched->k1357[11]); + R ^= f(L, sched->k0246[10], sched->k1357[10]); + L ^= f(R, sched->k0246[9], sched->k1357[9]); + R ^= f(L, sched->k0246[8], sched->k1357[8]); + L ^= f(R, sched->k0246[7], sched->k1357[7]); + R ^= f(L, sched->k0246[6], sched->k1357[6]); + L ^= f(R, sched->k0246[5], sched->k1357[5]); + R ^= f(L, sched->k0246[4], sched->k1357[4]); + L ^= f(R, sched->k0246[3], sched->k1357[3]); + R ^= f(L, sched->k0246[2], sched->k1357[2]); + L ^= f(R, sched->k0246[1], sched->k1357[1]); + R ^= f(L, sched->k0246[0], sched->k1357[0]); + + L = rotl(L, 31); + R = rotl(R, 31); + + swap = L; + L = R; + R = swap; + + FP(L, R); + + output[0] = L; + output[1] = R; +} + +static void des_cbc_encrypt(unsigned char *blk, + unsigned int len, DESContext * sched) +{ + word32 out[2], iv0, iv1; + unsigned int i; + + assert((len & 7) == 0); + + iv0 = sched->iv0; + iv1 = sched->iv1; + for (i = 0; i < len; i += 8) { + iv0 ^= GET_32BIT_MSB_FIRST(blk); + iv1 ^= GET_32BIT_MSB_FIRST(blk + 4); + des_encipher(out, iv0, iv1, sched); + iv0 = out[0]; + iv1 = out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + blk += 8; + } + sched->iv0 = iv0; + sched->iv1 = iv1; +} + +static void des_cbc_decrypt(unsigned char *blk, + unsigned int len, DESContext * sched) +{ + word32 out[2], iv0, iv1, xL, xR; + unsigned int i; + + assert((len & 7) == 0); + + iv0 = sched->iv0; + iv1 = sched->iv1; + for (i = 0; i < len; i += 8) { + xL = GET_32BIT_MSB_FIRST(blk); + xR = GET_32BIT_MSB_FIRST(blk + 4); + des_decipher(out, xL, xR, sched); + iv0 ^= out[0]; + iv1 ^= out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + blk += 8; + iv0 = xL; + iv1 = xR; + } + sched->iv0 = iv0; + sched->iv1 = iv1; +} + +static void des_3cbc_encrypt(unsigned char *blk, + unsigned int len, DESContext * scheds) +{ + des_cbc_encrypt(blk, len, &scheds[0]); + des_cbc_decrypt(blk, len, &scheds[1]); + des_cbc_encrypt(blk, len, &scheds[2]); +} + +static void des_cbc3_encrypt(unsigned char *blk, + unsigned int len, DESContext * scheds) +{ + word32 out[2], iv0, iv1; + unsigned int i; + + assert((len & 7) == 0); + + iv0 = scheds->iv0; + iv1 = scheds->iv1; + for (i = 0; i < len; i += 8) { + iv0 ^= GET_32BIT_MSB_FIRST(blk); + iv1 ^= GET_32BIT_MSB_FIRST(blk + 4); + des_encipher(out, iv0, iv1, &scheds[0]); + des_decipher(out, out[0], out[1], &scheds[1]); + des_encipher(out, out[0], out[1], &scheds[2]); + iv0 = out[0]; + iv1 = out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + blk += 8; + } + scheds->iv0 = iv0; + scheds->iv1 = iv1; +} + +static void des_3cbc_decrypt(unsigned char *blk, + unsigned int len, DESContext * scheds) +{ + des_cbc_decrypt(blk, len, &scheds[2]); + des_cbc_encrypt(blk, len, &scheds[1]); + des_cbc_decrypt(blk, len, &scheds[0]); +} + +static void des_cbc3_decrypt(unsigned char *blk, + unsigned int len, DESContext * scheds) +{ + word32 out[2], iv0, iv1, xL, xR; + unsigned int i; + + assert((len & 7) == 0); + + iv0 = scheds->iv0; + iv1 = scheds->iv1; + for (i = 0; i < len; i += 8) { + xL = GET_32BIT_MSB_FIRST(blk); + xR = GET_32BIT_MSB_FIRST(blk + 4); + des_decipher(out, xL, xR, &scheds[2]); + des_encipher(out, out[0], out[1], &scheds[1]); + des_decipher(out, out[0], out[1], &scheds[0]); + iv0 ^= out[0]; + iv1 ^= out[1]; + PUT_32BIT_MSB_FIRST(blk, iv0); + PUT_32BIT_MSB_FIRST(blk + 4, iv1); + blk += 8; + iv0 = xL; + iv1 = xR; + } + scheds->iv0 = iv0; + scheds->iv1 = iv1; +} + +static void des_sdctr3(unsigned char *blk, + unsigned int len, DESContext * scheds) +{ + word32 b[2], iv0, iv1, tmp; + unsigned int i; + + assert((len & 7) == 0); + + iv0 = scheds->iv0; + iv1 = scheds->iv1; + for (i = 0; i < len; i += 8) { + des_encipher(b, iv0, iv1, &scheds[0]); + des_decipher(b, b[0], b[1], &scheds[1]); + des_encipher(b, b[0], b[1], &scheds[2]); + tmp = GET_32BIT_MSB_FIRST(blk); + PUT_32BIT_MSB_FIRST(blk, tmp ^ b[0]); + blk += 4; + tmp = GET_32BIT_MSB_FIRST(blk); + PUT_32BIT_MSB_FIRST(blk, tmp ^ b[1]); + blk += 4; + if ((iv1 = (iv1 + 1) & 0xffffffff) == 0) + iv0 = (iv0 + 1) & 0xffffffff; + } + scheds->iv0 = iv0; + scheds->iv1 = iv1; +} + +static void *des3_make_context(void) +{ + return snewn(3, DESContext); +} + +static void *des3_ssh1_make_context(void) +{ + /* Need 3 keys for each direction, in SSH-1 */ + return snewn(6, DESContext); +} + +static void *des_make_context(void) +{ + return snew(DESContext); +} + +static void *des_ssh1_make_context(void) +{ + /* Need one key for each direction, in SSH-1 */ + return snewn(2, DESContext); +} + +static void des3_free_context(void *handle) /* used for both 3DES and DES */ +{ + sfree(handle); +} + +static void des3_key(void *handle, unsigned char *key) +{ + DESContext *keys = (DESContext *) handle; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &keys[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 8), + GET_32BIT_MSB_FIRST(key + 12), &keys[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 16), + GET_32BIT_MSB_FIRST(key + 20), &keys[2]); +} + +static void des3_iv(void *handle, unsigned char *key) +{ + DESContext *keys = (DESContext *) handle; + keys[0].iv0 = GET_32BIT_MSB_FIRST(key); + keys[0].iv1 = GET_32BIT_MSB_FIRST(key + 4); +} + +static void des_key(void *handle, unsigned char *key) +{ + DESContext *keys = (DESContext *) handle; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &keys[0]); +} + +static void des3_sesskey(void *handle, unsigned char *key) +{ + DESContext *keys = (DESContext *) handle; + des3_key(keys, key); + des3_key(keys+3, key); +} + +static void des3_encrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_3cbc_encrypt(blk, len, keys); +} + +static void des3_decrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_3cbc_decrypt(blk, len, keys+3); +} + +static void des3_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc3_encrypt(blk, len, keys); +} + +static void des3_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc3_decrypt(blk, len, keys); +} + +static void des3_ssh2_sdctr(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_sdctr3(blk, len, keys); +} + +static void des_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc_encrypt(blk, len, keys); +} + +static void des_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc_decrypt(blk, len, keys); +} + +void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +{ + DESContext ourkeys[3]; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 8), + GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[2]); + des_3cbc_decrypt(blk, len, ourkeys); + memset(ourkeys, 0, sizeof(ourkeys)); +} + +void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +{ + DESContext ourkeys[3]; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 8), + GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[2]); + des_3cbc_encrypt(blk, len, ourkeys); + memset(ourkeys, 0, sizeof(ourkeys)); +} + +void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, + unsigned char *blk, int len) +{ + DESContext ourkeys[3]; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 8), + GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 16), + GET_32BIT_MSB_FIRST(key + 20), &ourkeys[2]); + ourkeys[0].iv0 = GET_32BIT_MSB_FIRST(iv); + ourkeys[0].iv1 = GET_32BIT_MSB_FIRST(iv+4); + des_cbc3_decrypt(blk, len, ourkeys); + memset(ourkeys, 0, sizeof(ourkeys)); +} + +void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, + unsigned char *blk, int len) +{ + DESContext ourkeys[3]; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 8), + GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 16), + GET_32BIT_MSB_FIRST(key + 20), &ourkeys[2]); + ourkeys[0].iv0 = GET_32BIT_MSB_FIRST(iv); + ourkeys[0].iv1 = GET_32BIT_MSB_FIRST(iv+4); + des_cbc3_encrypt(blk, len, ourkeys); + memset(ourkeys, 0, sizeof(ourkeys)); +} + +static void des_keysetup_xdmauth(unsigned char *keydata, DESContext *dc) +{ + unsigned char key[8]; + int i, nbits, j; + unsigned int bits; + + bits = 0; + nbits = 0; + j = 0; + for (i = 0; i < 8; i++) { + if (nbits < 7) { + bits = (bits << 8) | keydata[j]; + nbits += 8; + j++; + } + key[i] = (bits >> (nbits - 7)) << 1; + bits &= ~(0x7F << (nbits - 7)); + nbits -= 7; + } + + des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), dc); +} + +void des_encrypt_xdmauth(unsigned char *keydata, unsigned char *blk, int len) +{ + DESContext dc; + des_keysetup_xdmauth(keydata, &dc); + des_cbc_encrypt(blk, 24, &dc); +} + +void des_decrypt_xdmauth(unsigned char *keydata, unsigned char *blk, int len) +{ + DESContext dc; + des_keysetup_xdmauth(keydata, &dc); + des_cbc_decrypt(blk, 24, &dc); +} + +static const struct ssh2_cipher ssh_3des_ssh2 = { + des3_make_context, des3_free_context, des3_iv, des3_key, + des3_ssh2_encrypt_blk, des3_ssh2_decrypt_blk, + "3des-cbc", + 8, 168, SSH_CIPHER_IS_CBC, "triple-DES CBC" +}; + +static const struct ssh2_cipher ssh_3des_ssh2_ctr = { + des3_make_context, des3_free_context, des3_iv, des3_key, + des3_ssh2_sdctr, des3_ssh2_sdctr, + "3des-ctr", + 8, 168, 0, "triple-DES SDCTR" +}; + +/* + * Single DES in SSH-2. "des-cbc" is marked as HISTORIC in + * RFC 4250, referring to + * FIPS-46-3. ("Single DES (i.e., DES) will be permitted + * for legacy systems only.") , but ssh.com support it and + * apparently aren't the only people to do so, so we sigh + * and implement it anyway. + */ +static const struct ssh2_cipher ssh_des_ssh2 = { + des_make_context, des3_free_context, des3_iv, des_key, + des_ssh2_encrypt_blk, des_ssh2_decrypt_blk, + "des-cbc", + 8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC" +}; + +static const struct ssh2_cipher ssh_des_sshcom_ssh2 = { + des_make_context, des3_free_context, des3_iv, des_key, + des_ssh2_encrypt_blk, des_ssh2_decrypt_blk, + "des-cbc@ssh.com", + 8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC" +}; + +static const struct ssh2_cipher *const des3_list[] = { + &ssh_3des_ssh2_ctr, + &ssh_3des_ssh2 +}; + +const struct ssh2_ciphers ssh2_3des = { + sizeof(des3_list) / sizeof(*des3_list), + des3_list +}; + +static const struct ssh2_cipher *const des_list[] = { + &ssh_des_ssh2, + &ssh_des_sshcom_ssh2 +}; + +const struct ssh2_ciphers ssh2_des = { + sizeof(des_list) / sizeof(*des_list), + des_list +}; + +const struct ssh_cipher ssh_3des = { + des3_ssh1_make_context, des3_free_context, des3_sesskey, + des3_encrypt_blk, des3_decrypt_blk, + 8, "triple-DES inner-CBC" +}; + +static void des_sesskey(void *handle, unsigned char *key) +{ + DESContext *keys = (DESContext *) handle; + des_key(keys, key); + des_key(keys+1, key); +} + +static void des_encrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc_encrypt(blk, len, keys); +} + +static void des_decrypt_blk(void *handle, unsigned char *blk, int len) +{ + DESContext *keys = (DESContext *) handle; + des_cbc_decrypt(blk, len, keys+1); +} + +const struct ssh_cipher ssh_des = { + des_ssh1_make_context, des3_free_context, des_sesskey, + des_encrypt_blk, des_decrypt_blk, + 8, "single-DES CBC" +}; diff --git a/putty/SSHDH.C b/putty/SSHDH.C new file mode 100644 index 0000000..41df756 --- /dev/null +++ b/putty/SSHDH.C @@ -0,0 +1,230 @@ +/* + * Diffie-Hellman implementation for PuTTY. + */ + +#include "ssh.h" + +/* + * The primes used in the group1 and group14 key exchange. + */ +static const unsigned char P1[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; +static const unsigned char P14[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF +}; + +/* + * The generator g = 2 (used for both group1 and group14). + */ +static const unsigned char G[] = { 2 }; + +static const struct ssh_kex ssh_diffiehellman_group1_sha1 = { + "diffie-hellman-group1-sha1", "group1", + KEXTYPE_DH, P1, G, lenof(P1), lenof(G), &ssh_sha1 +}; + +static const struct ssh_kex *const group1_list[] = { + &ssh_diffiehellman_group1_sha1 +}; + +const struct ssh_kexes ssh_diffiehellman_group1 = { + sizeof(group1_list) / sizeof(*group1_list), + group1_list +}; + +static const struct ssh_kex ssh_diffiehellman_group14_sha1 = { + "diffie-hellman-group14-sha1", "group14", + KEXTYPE_DH, P14, G, lenof(P14), lenof(G), &ssh_sha1 +}; + +static const struct ssh_kex *const group14_list[] = { + &ssh_diffiehellman_group14_sha1 +}; + +const struct ssh_kexes ssh_diffiehellman_group14 = { + sizeof(group14_list) / sizeof(*group14_list), + group14_list +}; + +static const struct ssh_kex ssh_diffiehellman_gex_sha256 = { + "diffie-hellman-group-exchange-sha256", NULL, + KEXTYPE_DH, NULL, NULL, 0, 0, &ssh_sha256 +}; + +static const struct ssh_kex ssh_diffiehellman_gex_sha1 = { + "diffie-hellman-group-exchange-sha1", NULL, + KEXTYPE_DH, NULL, NULL, 0, 0, &ssh_sha1 +}; + +static const struct ssh_kex *const gex_list[] = { + &ssh_diffiehellman_gex_sha256, + &ssh_diffiehellman_gex_sha1 +}; + +const struct ssh_kexes ssh_diffiehellman_gex = { + sizeof(gex_list) / sizeof(*gex_list), + gex_list +}; + +/* + * Variables. + */ +struct dh_ctx { + Bignum x, e, p, q, qmask, g; +}; + +/* + * Common DH initialisation. + */ +static void dh_init(struct dh_ctx *ctx) +{ + ctx->q = bignum_rshift(ctx->p, 1); + ctx->qmask = bignum_bitmask(ctx->q); + ctx->x = ctx->e = NULL; +} + +/* + * Initialise DH for a standard group. + */ +void *dh_setup_group(const struct ssh_kex *kex) +{ + struct dh_ctx *ctx = snew(struct dh_ctx); + ctx->p = bignum_from_bytes(kex->pdata, kex->plen); + ctx->g = bignum_from_bytes(kex->gdata, kex->glen); + dh_init(ctx); + return ctx; +} + +/* + * Initialise DH for a server-supplied group. + */ +void *dh_setup_gex(Bignum pval, Bignum gval) +{ + struct dh_ctx *ctx = snew(struct dh_ctx); + ctx->p = copybn(pval); + ctx->g = copybn(gval); + dh_init(ctx); + return ctx; +} + +/* + * Clean up and free a context. + */ +void dh_cleanup(void *handle) +{ + struct dh_ctx *ctx = (struct dh_ctx *)handle; + freebn(ctx->x); + freebn(ctx->e); + freebn(ctx->p); + freebn(ctx->g); + freebn(ctx->q); + freebn(ctx->qmask); + sfree(ctx); +} + +/* + * DH stage 1: invent a number x between 1 and q, and compute e = + * g^x mod p. Return e. + * + * If `nbits' is greater than zero, it is used as an upper limit + * for the number of bits in x. This is safe provided that (a) you + * use twice as many bits in x as the number of bits you expect to + * use in your session key, and (b) the DH group is a safe prime + * (which SSH demands that it must be). + * + * P. C. van Oorschot, M. J. Wiener + * "On Diffie-Hellman Key Agreement with Short Exponents". + * Advances in Cryptology: Proceedings of Eurocrypt '96 + * Springer-Verlag, May 1996. + */ +Bignum dh_create_e(void *handle, int nbits) +{ + struct dh_ctx *ctx = (struct dh_ctx *)handle; + int i; + + int nbytes; + unsigned char *buf; + + nbytes = ssh1_bignum_length(ctx->qmask); + buf = snewn(nbytes, unsigned char); + + do { + /* + * Create a potential x, by ANDing a string of random bytes + * with qmask. + */ + if (ctx->x) + freebn(ctx->x); + if (nbits == 0 || nbits > bignum_bitcount(ctx->qmask)) { + ssh1_write_bignum(buf, ctx->qmask); + for (i = 2; i < nbytes; i++) + buf[i] &= random_byte(); + ssh1_read_bignum(buf, nbytes, &ctx->x); /* can't fail */ + } else { + int b, nb; + ctx->x = bn_power_2(nbits); + b = nb = 0; + for (i = 0; i < nbits; i++) { + if (nb == 0) { + nb = 8; + b = random_byte(); + } + bignum_set_bit(ctx->x, i, b & 1); + b >>= 1; + nb--; + } + } + } while (bignum_cmp(ctx->x, One) <= 0 || bignum_cmp(ctx->x, ctx->q) >= 0); + + sfree(buf); + + /* + * Done. Now compute e = g^x mod p. + */ + ctx->e = modpow(ctx->g, ctx->x, ctx->p); + + return ctx->e; +} + +/* + * DH stage 2: given a number f, compute K = f^x mod p. + */ +Bignum dh_find_K(void *handle, Bignum f) +{ + struct dh_ctx *ctx = (struct dh_ctx *)handle; + Bignum ret; + ret = modpow(f, ctx->x, ctx->p); + return ret; +} diff --git a/putty/SSHDSS.C b/putty/SSHDSS.C new file mode 100644 index 0000000..dba1db1 --- /dev/null +++ b/putty/SSHDSS.C @@ -0,0 +1,643 @@ +/* + * Digital Signature Standard implementation for PuTTY. + */ + +#include +#include +#include + +#include "ssh.h" +#include "misc.h" + +static void sha_mpint(SHA_State * s, Bignum b) +{ + unsigned char lenbuf[4]; + int len; + len = (bignum_bitcount(b) + 8) / 8; + PUT_32BIT(lenbuf, len); + SHA_Bytes(s, lenbuf, 4); + while (len-- > 0) { + lenbuf[0] = bignum_byte(b, len); + SHA_Bytes(s, lenbuf, 1); + } + memset(lenbuf, 0, sizeof(lenbuf)); +} + +static void sha512_mpint(SHA512_State * s, Bignum b) +{ + unsigned char lenbuf[4]; + int len; + len = (bignum_bitcount(b) + 8) / 8; + PUT_32BIT(lenbuf, len); + SHA512_Bytes(s, lenbuf, 4); + while (len-- > 0) { + lenbuf[0] = bignum_byte(b, len); + SHA512_Bytes(s, lenbuf, 1); + } + memset(lenbuf, 0, sizeof(lenbuf)); +} + +static void getstring(char **data, int *datalen, char **p, int *length) +{ + *p = NULL; + if (*datalen < 4) + return; + *length = GET_32BIT(*data); + *datalen -= 4; + *data += 4; + if (*datalen < *length) + return; + *p = *data; + *data += *length; + *datalen -= *length; +} +static Bignum getmp(char **data, int *datalen) +{ + char *p; + int length; + Bignum b; + + getstring(data, datalen, &p, &length); + if (!p) + return NULL; + if (p[0] & 0x80) + return NULL; /* negative mp */ + b = bignum_from_bytes((unsigned char *)p, length); + return b; +} + +static Bignum get160(char **data, int *datalen) +{ + Bignum b; + + b = bignum_from_bytes((unsigned char *)*data, 20); + *data += 20; + *datalen -= 20; + + return b; +} + +static void *dss_newkey(char *data, int len) +{ + char *p; + int slen; + struct dss_key *dss; + + dss = snew(struct dss_key); + if (!dss) + return NULL; + getstring(&data, &len, &p, &slen); + +#ifdef DEBUG_DSS + { + int i; + printf("key:"); + for (i = 0; i < len; i++) + printf(" %02x", (unsigned char) (data[i])); + printf("\n"); + } +#endif + + if (!p || memcmp(p, "ssh-dss", 7)) { + sfree(dss); + return NULL; + } + dss->p = getmp(&data, &len); + dss->q = getmp(&data, &len); + dss->g = getmp(&data, &len); + dss->y = getmp(&data, &len); + + return dss; +} + +static void dss_freekey(void *key) +{ + struct dss_key *dss = (struct dss_key *) key; + freebn(dss->p); + freebn(dss->q); + freebn(dss->g); + freebn(dss->y); + sfree(dss); +} + +static char *dss_fmtkey(void *key) +{ + struct dss_key *dss = (struct dss_key *) key; + char *p; + int len, i, pos, nibbles; + static const char hex[] = "0123456789abcdef"; + if (!dss->p) + return NULL; + len = 8 + 4 + 1; /* 4 x "0x", punctuation, \0 */ + len += 4 * (bignum_bitcount(dss->p) + 15) / 16; + len += 4 * (bignum_bitcount(dss->q) + 15) / 16; + len += 4 * (bignum_bitcount(dss->g) + 15) / 16; + len += 4 * (bignum_bitcount(dss->y) + 15) / 16; + p = snewn(len, char); + if (!p) + return NULL; + + pos = 0; + pos += sprintf(p + pos, "0x"); + nibbles = (3 + bignum_bitcount(dss->p)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + p[pos++] = + hex[(bignum_byte(dss->p, i / 2) >> (4 * (i % 2))) & 0xF]; + pos += sprintf(p + pos, ",0x"); + nibbles = (3 + bignum_bitcount(dss->q)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + p[pos++] = + hex[(bignum_byte(dss->q, i / 2) >> (4 * (i % 2))) & 0xF]; + pos += sprintf(p + pos, ",0x"); + nibbles = (3 + bignum_bitcount(dss->g)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + p[pos++] = + hex[(bignum_byte(dss->g, i / 2) >> (4 * (i % 2))) & 0xF]; + pos += sprintf(p + pos, ",0x"); + nibbles = (3 + bignum_bitcount(dss->y)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + p[pos++] = + hex[(bignum_byte(dss->y, i / 2) >> (4 * (i % 2))) & 0xF]; + p[pos] = '\0'; + return p; +} + +static char *dss_fingerprint(void *key) +{ + struct dss_key *dss = (struct dss_key *) key; + struct MD5Context md5c; + unsigned char digest[16], lenbuf[4]; + char buffer[16 * 3 + 40]; + char *ret; + int numlen, i; + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)"\0\0\0\7ssh-dss", 11); + +#define ADD_BIGNUM(bignum) \ + numlen = (bignum_bitcount(bignum)+8)/8; \ + PUT_32BIT(lenbuf, numlen); MD5Update(&md5c, lenbuf, 4); \ + for (i = numlen; i-- ;) { \ + unsigned char c = bignum_byte(bignum, i); \ + MD5Update(&md5c, &c, 1); \ + } + ADD_BIGNUM(dss->p); + ADD_BIGNUM(dss->q); + ADD_BIGNUM(dss->g); + ADD_BIGNUM(dss->y); +#undef ADD_BIGNUM + + MD5Final(digest, &md5c); + + sprintf(buffer, "ssh-dss %d ", bignum_bitcount(dss->p)); + for (i = 0; i < 16; i++) + sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "", + digest[i]); + ret = snewn(strlen(buffer) + 1, char); + if (ret) + strcpy(ret, buffer); + return ret; +} + +static int dss_verifysig(void *key, char *sig, int siglen, + char *data, int datalen) +{ + struct dss_key *dss = (struct dss_key *) key; + char *p; + int slen; + char hash[20]; + Bignum r, s, w, gu1p, yu2p, gu1yu2p, u1, u2, sha, v; + int ret; + + if (!dss->p) + return 0; + +#ifdef DEBUG_DSS + { + int i; + printf("sig:"); + for (i = 0; i < siglen; i++) + printf(" %02x", (unsigned char) (sig[i])); + printf("\n"); + } +#endif + /* + * Commercial SSH (2.0.13) and OpenSSH disagree over the format + * of a DSA signature. OpenSSH is in line with RFC 4253: + * it uses a string "ssh-dss", followed by a 40-byte string + * containing two 160-bit integers end-to-end. Commercial SSH + * can't be bothered with the header bit, and considers a DSA + * signature blob to be _just_ the 40-byte string containing + * the two 160-bit integers. We tell them apart by measuring + * the length: length 40 means the commercial-SSH bug, anything + * else is assumed to be RFC-compliant. + */ + if (siglen != 40) { /* bug not present; read admin fields */ + getstring(&sig, &siglen, &p, &slen); + if (!p || slen != 7 || memcmp(p, "ssh-dss", 7)) { + return 0; + } + sig += 4, siglen -= 4; /* skip yet another length field */ + } + r = get160(&sig, &siglen); + s = get160(&sig, &siglen); + if (!r || !s) + return 0; + + /* + * Step 1. w <- s^-1 mod q. + */ + w = modinv(s, dss->q); + + /* + * Step 2. u1 <- SHA(message) * w mod q. + */ + SHA_Simple(data, datalen, (unsigned char *)hash); + p = hash; + slen = 20; + sha = get160(&p, &slen); + u1 = modmul(sha, w, dss->q); + + /* + * Step 3. u2 <- r * w mod q. + */ + u2 = modmul(r, w, dss->q); + + /* + * Step 4. v <- (g^u1 * y^u2 mod p) mod q. + */ + gu1p = modpow(dss->g, u1, dss->p); + yu2p = modpow(dss->y, u2, dss->p); + gu1yu2p = modmul(gu1p, yu2p, dss->p); + v = modmul(gu1yu2p, One, dss->q); + + /* + * Step 5. v should now be equal to r. + */ + + ret = !bignum_cmp(v, r); + + freebn(w); + freebn(sha); + freebn(gu1p); + freebn(yu2p); + freebn(gu1yu2p); + freebn(v); + freebn(r); + freebn(s); + + return ret; +} + +static unsigned char *dss_public_blob(void *key, int *len) +{ + struct dss_key *dss = (struct dss_key *) key; + int plen, qlen, glen, ylen, bloblen; + int i; + unsigned char *blob, *p; + + plen = (bignum_bitcount(dss->p) + 8) / 8; + qlen = (bignum_bitcount(dss->q) + 8) / 8; + glen = (bignum_bitcount(dss->g) + 8) / 8; + ylen = (bignum_bitcount(dss->y) + 8) / 8; + + /* + * string "ssh-dss", mpint p, mpint q, mpint g, mpint y. Total + * 27 + sum of lengths. (five length fields, 20+7=27). + */ + bloblen = 27 + plen + qlen + glen + ylen; + blob = snewn(bloblen, unsigned char); + p = blob; + PUT_32BIT(p, 7); + p += 4; + memcpy(p, "ssh-dss", 7); + p += 7; + PUT_32BIT(p, plen); + p += 4; + for (i = plen; i--;) + *p++ = bignum_byte(dss->p, i); + PUT_32BIT(p, qlen); + p += 4; + for (i = qlen; i--;) + *p++ = bignum_byte(dss->q, i); + PUT_32BIT(p, glen); + p += 4; + for (i = glen; i--;) + *p++ = bignum_byte(dss->g, i); + PUT_32BIT(p, ylen); + p += 4; + for (i = ylen; i--;) + *p++ = bignum_byte(dss->y, i); + assert(p == blob + bloblen); + *len = bloblen; + return blob; +} + +static unsigned char *dss_private_blob(void *key, int *len) +{ + struct dss_key *dss = (struct dss_key *) key; + int xlen, bloblen; + int i; + unsigned char *blob, *p; + + xlen = (bignum_bitcount(dss->x) + 8) / 8; + + /* + * mpint x, string[20] the SHA of p||q||g. Total 4 + xlen. + */ + bloblen = 4 + xlen; + blob = snewn(bloblen, unsigned char); + p = blob; + PUT_32BIT(p, xlen); + p += 4; + for (i = xlen; i--;) + *p++ = bignum_byte(dss->x, i); + assert(p == blob + bloblen); + *len = bloblen; + return blob; +} + +static void *dss_createkey(unsigned char *pub_blob, int pub_len, + unsigned char *priv_blob, int priv_len) +{ + struct dss_key *dss; + char *pb = (char *) priv_blob; + char *hash; + int hashlen; + SHA_State s; + unsigned char digest[20]; + Bignum ytest; + + dss = dss_newkey((char *) pub_blob, pub_len); + dss->x = getmp(&pb, &priv_len); + + /* + * Check the obsolete hash in the old DSS key format. + */ + hashlen = -1; + getstring(&pb, &priv_len, &hash, &hashlen); + if (hashlen == 20) { + SHA_Init(&s); + sha_mpint(&s, dss->p); + sha_mpint(&s, dss->q); + sha_mpint(&s, dss->g); + SHA_Final(&s, digest); + if (0 != memcmp(hash, digest, 20)) { + dss_freekey(dss); + return NULL; + } + } + + /* + * Now ensure g^x mod p really is y. + */ + ytest = modpow(dss->g, dss->x, dss->p); + if (0 != bignum_cmp(ytest, dss->y)) { + dss_freekey(dss); + return NULL; + } + freebn(ytest); + + return dss; +} + +static void *dss_openssh_createkey(unsigned char **blob, int *len) +{ + char **b = (char **) blob; + struct dss_key *dss; + + dss = snew(struct dss_key); + if (!dss) + return NULL; + + dss->p = getmp(b, len); + dss->q = getmp(b, len); + dss->g = getmp(b, len); + dss->y = getmp(b, len); + dss->x = getmp(b, len); + + if (!dss->p || !dss->q || !dss->g || !dss->y || !dss->x) { + sfree(dss->p); + sfree(dss->q); + sfree(dss->g); + sfree(dss->y); + sfree(dss->x); + sfree(dss); + return NULL; + } + + return dss; +} + +static int dss_openssh_fmtkey(void *key, unsigned char *blob, int len) +{ + struct dss_key *dss = (struct dss_key *) key; + int bloblen, i; + + bloblen = + ssh2_bignum_length(dss->p) + + ssh2_bignum_length(dss->q) + + ssh2_bignum_length(dss->g) + + ssh2_bignum_length(dss->y) + + ssh2_bignum_length(dss->x); + + if (bloblen > len) + return bloblen; + + bloblen = 0; +#define ENC(x) \ + PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \ + for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i); + ENC(dss->p); + ENC(dss->q); + ENC(dss->g); + ENC(dss->y); + ENC(dss->x); + + return bloblen; +} + +static int dss_pubkey_bits(void *blob, int len) +{ + struct dss_key *dss; + int ret; + + dss = dss_newkey((char *) blob, len); + ret = bignum_bitcount(dss->p); + dss_freekey(dss); + + return ret; +} + +static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen) +{ + /* + * 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. + */ + struct dss_key *dss = (struct dss_key *) key; + SHA512_State ss; + unsigned char digest[20], digest512[64]; + Bignum proto_k, k, gkp, hash, kinv, hxr, r, s; + unsigned char *bytes; + int nbytes, i; + + SHA_Simple(data, datalen, digest); + + /* + * Hash some identifying text plus x. + */ + SHA512_Init(&ss); + SHA512_Bytes(&ss, "DSA deterministic k generator", 30); + sha512_mpint(&ss, dss->x); + SHA512_Final(&ss, digest512); + + /* + * Now hash that digest plus the message hash. + */ + SHA512_Init(&ss); + SHA512_Bytes(&ss, digest512, sizeof(digest512)); + SHA512_Bytes(&ss, digest, sizeof(digest)); + SHA512_Final(&ss, digest512); + + memset(&ss, 0, sizeof(ss)); + + /* + * Now convert the result into a bignum, and reduce it mod q. + */ + proto_k = bignum_from_bytes(digest512, 64); + k = bigmod(proto_k, dss->q); + freebn(proto_k); + + memset(digest512, 0, sizeof(digest512)); + + /* + * Now we have k, so just go ahead and compute the signature. + */ + gkp = modpow(dss->g, k, dss->p); /* g^k mod p */ + r = bigmod(gkp, dss->q); /* r = (g^k mod p) mod q */ + freebn(gkp); + + hash = bignum_from_bytes(digest, 20); + kinv = modinv(k, dss->q); /* k^-1 mod q */ + hxr = bigmuladd(dss->x, r, hash); /* hash + x*r */ + s = modmul(kinv, hxr, dss->q); /* s = k^-1 * (hash + x*r) mod q */ + freebn(hxr); + freebn(kinv); + freebn(hash); + + /* + * Signature blob is + * + * string "ssh-dss" + * string two 20-byte numbers r and s, end to end + * + * i.e. 4+7 + 4+40 bytes. + */ + nbytes = 4 + 7 + 4 + 40; + bytes = snewn(nbytes, unsigned char); + PUT_32BIT(bytes, 7); + memcpy(bytes + 4, "ssh-dss", 7); + PUT_32BIT(bytes + 4 + 7, 40); + for (i = 0; i < 20; i++) { + bytes[4 + 7 + 4 + i] = bignum_byte(r, 19 - i); + bytes[4 + 7 + 4 + 20 + i] = bignum_byte(s, 19 - i); + } + freebn(r); + freebn(s); + + *siglen = nbytes; + return bytes; +} + +const struct ssh_signkey ssh_dss = { + dss_newkey, + dss_freekey, + dss_fmtkey, + dss_public_blob, + dss_private_blob, + dss_createkey, + dss_openssh_createkey, + dss_openssh_fmtkey, + dss_pubkey_bits, + dss_fingerprint, + dss_verifysig, + dss_sign, + "ssh-dss", + "dss" +}; diff --git a/putty/SSHDSSG.C b/putty/SSHDSSG.C new file mode 100644 index 0000000..a19bc27 --- /dev/null +++ b/putty/SSHDSSG.C @@ -0,0 +1,143 @@ +/* + * DSS key generation. + */ + +#include "misc.h" +#include "ssh.h" + +int dsa_generate(struct dss_key *key, int bits, progfn_t pfn, + void *pfnparam) +{ + Bignum qm1, power, g, h, tmp; + int progress; + + /* + * Set up the phase limits for the progress report. We do this + * by passing minus the phase number. + * + * For prime generation: our initial filter finds things + * coprime to everything below 2^16. Computing the product of + * (p-1)/p for all prime p below 2^16 gives about 20.33; so + * among B-bit integers, one in every 20.33 will get through + * the initial filter to be a candidate prime. + * + * Meanwhile, we are searching for primes in the region of 2^B; + * since pi(x) ~ x/log(x), when x is in the region of 2^B, the + * prime density will be d/dx pi(x) ~ 1/log(B), i.e. about + * 1/0.6931B. So the chance of any given candidate being prime + * is 20.33/0.6931B, which is roughly 29.34 divided by B. + * + * So now we have this probability P, we're looking at an + * exponential distribution with parameter P: we will manage in + * one attempt with probability P, in two with probability + * P(1-P), in three with probability P(1-P)^2, etc. The + * probability that we have still not managed to find a prime + * after N attempts is (1-P)^N. + * + * We therefore inform the progress indicator of the number B + * (29.34/B), so that it knows how much to increment by each + * time. We do this in 16-bit fixed point, so 29.34 becomes + * 0x1D.57C4. + */ + pfn(pfnparam, PROGFN_PHASE_EXTENT, 1, 0x2800); + pfn(pfnparam, PROGFN_EXP_PHASE, 1, -0x1D57C4 / 160); + pfn(pfnparam, PROGFN_PHASE_EXTENT, 2, 0x40 * bits); + pfn(pfnparam, PROGFN_EXP_PHASE, 2, -0x1D57C4 / bits); + + /* + * In phase three we are finding an order-q element of the + * multiplicative group of p, by finding an element whose order + * is _divisible_ by q and raising it to the power of (p-1)/q. + * _Most_ elements will have order divisible by q, since for a + * start phi(p) of them will be primitive roots. So + * realistically we don't need to set this much below 1 (64K). + * Still, we'll set it to 1/2 (32K) to be on the safe side. + */ + pfn(pfnparam, PROGFN_PHASE_EXTENT, 3, 0x2000); + pfn(pfnparam, PROGFN_EXP_PHASE, 3, -32768); + + /* + * In phase four we are finding an element x between 1 and q-1 + * (exclusive), by inventing 160 random bits and hoping they + * come out to a plausible number; so assuming q is uniformly + * distributed between 2^159 and 2^160, the chance of any given + * attempt succeeding is somewhere between 0.5 and 1. Lacking + * the energy to arrange to be able to specify this probability + * _after_ generating q, we'll just set it to 0.75. + */ + pfn(pfnparam, PROGFN_PHASE_EXTENT, 4, 0x2000); + pfn(pfnparam, PROGFN_EXP_PHASE, 4, -49152); + + pfn(pfnparam, PROGFN_READY, 0, 0); + + /* + * Generate q: a prime of length 160. + */ + key->q = primegen(160, 2, 2, NULL, 1, pfn, pfnparam); + /* + * Now generate p: a prime of length `bits', such that p-1 is + * divisible by q. + */ + key->p = primegen(bits-160, 2, 2, key->q, 2, pfn, pfnparam); + + /* + * Next we need g. Raise 2 to the power (p-1)/q modulo p, and + * if that comes out to one then try 3, then 4 and so on. As + * soon as we hit a non-unit (and non-zero!) one, that'll do + * for g. + */ + power = bigdiv(key->p, key->q); /* this is floor(p/q) == (p-1)/q */ + h = bignum_from_long(1); + progress = 0; + while (1) { + pfn(pfnparam, PROGFN_PROGRESS, 3, ++progress); + g = modpow(h, power, key->p); + if (bignum_cmp(g, One) > 0) + break; /* got one */ + tmp = h; + h = bignum_add_long(h, 1); + freebn(tmp); + } + key->g = g; + freebn(h); + + /* + * Now we're nearly done. All we need now is our private key x, + * which should be a number between 1 and q-1 exclusive, and + * our public key y = g^x mod p. + */ + qm1 = copybn(key->q); + decbn(qm1); + progress = 0; + while (1) { + int i, v, byte, bitsleft; + Bignum x; + + pfn(pfnparam, PROGFN_PROGRESS, 4, ++progress); + x = bn_power_2(159); + byte = 0; + bitsleft = 0; + + for (i = 0; i < 160; i++) { + if (bitsleft <= 0) + bitsleft = 8, byte = random_byte(); + v = byte & 1; + byte >>= 1; + bitsleft--; + bignum_set_bit(x, i, v); + } + + if (bignum_cmp(x, One) <= 0 || bignum_cmp(x, qm1) >= 0) { + freebn(x); + continue; + } else { + key->x = x; + break; + } + } + freebn(qm1); + + key->y = modpow(key->g, key->x, key->p); + + return 1; +} diff --git a/putty/SSHGSS.H b/putty/SSHGSS.H new file mode 100644 index 0000000..5d8fca1 --- /dev/null +++ b/putty/SSHGSS.H @@ -0,0 +1,188 @@ +#ifndef PUTTY_SSHGSS_H +#define PUTTY_SSHGSS_H +#include "putty.h" +#include "pgssapi.h" + +#ifndef NO_GSSAPI + +#define SSH2_GSS_OIDTYPE 0x06 +typedef void *Ssh_gss_ctx; + +typedef enum Ssh_gss_stat { + SSH_GSS_OK = 0, + SSH_GSS_S_CONTINUE_NEEDED, + SSH_GSS_NO_MEM, + SSH_GSS_BAD_HOST_NAME, + SSH_GSS_FAILURE +} Ssh_gss_stat; + +#define SSH_GSS_S_COMPLETE SSH_GSS_OK + +#define SSH_GSS_CLEAR_BUF(buf) do { \ + (*buf).length = 0; \ + (*buf).value = NULL; \ +} while (0) + +typedef gss_buffer_desc Ssh_gss_buf; +typedef gss_name_t Ssh_gss_name; + +/* Functions, provided by either wingss.c or sshgssc.c */ + +struct ssh_gss_library; + +/* + * Prepare a collection of GSSAPI libraries for use in a single SSH + * connection. Returns a structure containing a list of libraries, + * with their ids (see struct ssh_gss_library below) filled in so + * that the client can go through them in the SSH user's preferred + * order. + * + * Must always return non-NULL. (Even if no libraries are available, + * it must return an empty structure.) + * + * The free function cleans up the structure, and its associated + * libraries (if any). + */ +struct ssh_gss_liblist { + struct ssh_gss_library *libraries; + int nlibraries; +}; +struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg); +void ssh_gss_cleanup(struct ssh_gss_liblist *list); + +/* + * Fills in buf with a string describing the GSSAPI mechanism in + * use. buf->data is not dynamically allocated. + */ +typedef Ssh_gss_stat (*t_ssh_gss_indicate_mech)(struct ssh_gss_library *lib, + Ssh_gss_buf *buf); + +/* + * Converts a name such as a hostname into a GSSAPI internal form, + * which is placed in "out". The result should be freed by + * ssh_gss_release_name(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_import_name)(struct ssh_gss_library *lib, + char *in, Ssh_gss_name *out); + +/* + * Frees the contents of an Ssh_gss_name structure filled in by + * ssh_gss_import_name(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_release_name)(struct ssh_gss_library *lib, + Ssh_gss_name *name); + +/* + * The main GSSAPI security context setup function. The "out" + * parameter will need to be freed by ssh_gss_free_tok. + */ +typedef Ssh_gss_stat (*t_ssh_gss_init_sec_context) + (struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx, Ssh_gss_name name, int delegate, + Ssh_gss_buf *in, Ssh_gss_buf *out); + +/* + * Frees the contents of an Ssh_gss_buf filled in by + * ssh_gss_init_sec_context(). Do not accidentally call this on + * something filled in by ssh_gss_get_mic() (which requires a + * different free function) or something filled in by any other + * way. + */ +typedef Ssh_gss_stat (*t_ssh_gss_free_tok)(struct ssh_gss_library *lib, + Ssh_gss_buf *); + +/* + * Acquires the credentials to perform authentication in the first + * place. Needs to be freed by ssh_gss_release_cred(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_acquire_cred)(struct ssh_gss_library *lib, + Ssh_gss_ctx *); + +/* + * Frees the contents of an Ssh_gss_ctx filled in by + * ssh_gss_acquire_cred(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_release_cred)(struct ssh_gss_library *lib, + Ssh_gss_ctx *); + +/* + * Gets a MIC for some input data. "out" needs to be freed by + * ssh_gss_free_mic(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_get_mic)(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *in, + Ssh_gss_buf *out); + +/* + * Frees the contents of an Ssh_gss_buf filled in by + * ssh_gss_get_mic(). Do not accidentally call this on something + * filled in by ssh_gss_init_sec_context() (which requires a + * different free function) or something filled in by any other + * way. + */ +typedef Ssh_gss_stat (*t_ssh_gss_free_mic)(struct ssh_gss_library *lib, + Ssh_gss_buf *); + +/* + * Return an error message after authentication failed. The + * message string is returned in "buf", with buf->len giving the + * number of characters of printable message text and buf->data + * containing one more character which is a trailing NUL. + * buf->data should be manually freed by the caller. + */ +typedef Ssh_gss_stat (*t_ssh_gss_display_status)(struct ssh_gss_library *lib, + Ssh_gss_ctx, Ssh_gss_buf *buf); + +struct ssh_gss_library { + /* + * Identifying number in the enumeration used by the + * configuration code to specify a preference order. + */ + int id; + + /* + * Filled in at initialisation time, if there's anything + * interesting to say about how GSSAPI was initialised (e.g. + * which of a number of alternative libraries was used). + */ + const char *gsslogmsg; + + /* + * Function pointers implementing the SSH wrapper layer on top + * of GSSAPI. (Defined in sshgssc, typically, though Windows + * provides an alternative layer to sit on top of the annoyingly + * different SSPI.) + */ + t_ssh_gss_indicate_mech indicate_mech; + t_ssh_gss_import_name import_name; + t_ssh_gss_release_name release_name; + t_ssh_gss_init_sec_context init_sec_context; + t_ssh_gss_free_tok free_tok; + t_ssh_gss_acquire_cred acquire_cred; + t_ssh_gss_release_cred release_cred; + t_ssh_gss_get_mic get_mic; + t_ssh_gss_free_mic free_mic; + t_ssh_gss_display_status display_status; + + /* + * Additional data for the wrapper layers. + */ + union { + struct gssapi_functions gssapi; + /* + * The SSPI wrappers don't need to store their Windows API + * function pointers in this structure, because there can't + * be more than one set of them available. + */ + } u; + + /* + * Wrapper layers will often also need to store a library handle + * of some sort for cleanup time. + */ + void *handle; +}; + +#endif /* NO_GSSAPI */ + +#endif /*PUTTY_SSHGSS_H*/ diff --git a/putty/SSHGSSC.C b/putty/SSHGSSC.C new file mode 100644 index 0000000..87e2236 --- /dev/null +++ b/putty/SSHGSSC.C @@ -0,0 +1,209 @@ +#include "putty.h" + +#include +#include "sshgssc.h" +#include "misc.h" + +#ifndef NO_GSSAPI + +static Ssh_gss_stat ssh_gssapi_indicate_mech(struct ssh_gss_library *lib, + Ssh_gss_buf *mech) +{ + /* Copy constant into mech */ + mech->length = GSS_MECH_KRB5->length; + mech->value = GSS_MECH_KRB5->elements; + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_gssapi_import_name(struct ssh_gss_library *lib, + char *host, + Ssh_gss_name *srv_name) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + OM_uint32 min_stat,maj_stat; + gss_buffer_desc host_buf; + char *pStr; + + pStr = dupcat("host@", host, NULL); + + host_buf.value = pStr; + host_buf.length = strlen(pStr); + + maj_stat = gss->import_name(&min_stat, &host_buf, + GSS_C_NT_HOSTBASED_SERVICE, srv_name); + /* Release buffer */ + sfree(pStr); + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx) +{ + gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx); + + gssctx->maj_stat = gssctx->min_stat = GSS_S_COMPLETE; + gssctx->ctx = GSS_C_NO_CONTEXT; + *ctx = (Ssh_gss_ctx) gssctx; + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx, + Ssh_gss_name srv_name, + int to_deleg, + Ssh_gss_buf *recv_tok, + Ssh_gss_buf *send_tok) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx; + OM_uint32 ret_flags; + + if (to_deleg) to_deleg = GSS_C_DELEG_FLAG; + gssctx->maj_stat = gss->init_sec_context(&gssctx->min_stat, + GSS_C_NO_CREDENTIAL, + &gssctx->ctx, + srv_name, + (gss_OID) GSS_MECH_KRB5, + GSS_C_MUTUAL_FLAG | + GSS_C_INTEG_FLAG | to_deleg, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + recv_tok, + NULL, /* ignore mech type */ + send_tok, + &ret_flags, + NULL); /* ignore time_rec */ + + if (gssctx->maj_stat == GSS_S_COMPLETE) return SSH_GSS_S_COMPLETE; + if (gssctx->maj_stat == GSS_S_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_display_status(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, + Ssh_gss_buf *buf) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx; + OM_uint32 lmin,lmax; + OM_uint32 ccc; + gss_buffer_desc msg_maj=GSS_C_EMPTY_BUFFER; + gss_buffer_desc msg_min=GSS_C_EMPTY_BUFFER; + + /* Return empty buffer in case of failure */ + SSH_GSS_CLEAR_BUF(buf); + + /* get first mesg from GSS */ + ccc=0; + lmax=gss->display_status(&lmin,gssctx->maj_stat,GSS_C_GSS_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_maj); + + if (lmax != GSS_S_COMPLETE) return SSH_GSS_FAILURE; + + /* get first mesg from Kerberos */ + ccc=0; + lmax=gss->display_status(&lmin,gssctx->min_stat,GSS_C_MECH_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_min); + + if (lmax != GSS_S_COMPLETE) { + gss->release_buffer(&lmin, &msg_maj); + return SSH_GSS_FAILURE; + } + + /* copy data into buffer */ + buf->length = msg_maj.length + msg_min.length + 1; + buf->value = snewn(buf->length + 1, char); + + /* copy mem */ + memcpy((char *)buf->value, msg_maj.value, msg_maj.length); + ((char *)buf->value)[msg_maj.length] = ' '; + memcpy((char *)buf->value + msg_maj.length + 1, msg_min.value, msg_min.length); + ((char *)buf->value)[buf->length] = 0; + /* free mem & exit */ + gss->release_buffer(&lmin, &msg_maj); + gss->release_buffer(&lmin, &msg_min); + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_gssapi_free_tok(struct ssh_gss_library *lib, + Ssh_gss_buf *send_tok) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + OM_uint32 min_stat,maj_stat; + maj_stat = gss->release_buffer(&min_stat, send_tok); + + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_release_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) *ctx; + OM_uint32 min_stat; + OM_uint32 maj_stat=GSS_S_COMPLETE; + + if (gssctx == NULL) return SSH_GSS_FAILURE; + if (gssctx->ctx != GSS_C_NO_CONTEXT) + maj_stat = gss->delete_sec_context(&min_stat,&gssctx->ctx,GSS_C_NO_BUFFER); + sfree(gssctx); + + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + + +static Ssh_gss_stat ssh_gssapi_release_name(struct ssh_gss_library *lib, + Ssh_gss_name *srv_name) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + OM_uint32 min_stat,maj_stat; + maj_stat = gss->release_name(&min_stat, srv_name); + + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_get_mic(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *buf, + Ssh_gss_buf *hash) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx; + if (gssctx == NULL) return SSH_GSS_FAILURE; + return gss->get_mic(&(gssctx->min_stat), gssctx->ctx, 0, buf, hash); +} + +static Ssh_gss_stat ssh_gssapi_free_mic(struct ssh_gss_library *lib, + Ssh_gss_buf *hash) +{ + /* On Unix this is the same freeing process as ssh_gssapi_free_tok. */ + return ssh_gssapi_free_tok(lib, hash); +} + +void ssh_gssapi_bind_fns(struct ssh_gss_library *lib) +{ + lib->indicate_mech = ssh_gssapi_indicate_mech; + lib->import_name = ssh_gssapi_import_name; + lib->release_name = ssh_gssapi_release_name; + lib->init_sec_context = ssh_gssapi_init_sec_context; + lib->free_tok = ssh_gssapi_free_tok; + lib->acquire_cred = ssh_gssapi_acquire_cred; + lib->release_cred = ssh_gssapi_release_cred; + lib->get_mic = ssh_gssapi_get_mic; + lib->free_mic = ssh_gssapi_free_mic; + lib->display_status = ssh_gssapi_display_status; +} + +#else + +/* Dummy function so this source file defines something if NO_GSSAPI + is defined. */ + +int ssh_gssapi_init(void) +{ + return 0; +} + +#endif diff --git a/putty/SSHGSSC.H b/putty/SSHGSSC.H new file mode 100644 index 0000000..0f271f8 --- /dev/null +++ b/putty/SSHGSSC.H @@ -0,0 +1,23 @@ +#ifndef PUTTY_SSHGSSC_H +#define PUTTY_SSHGSSC_H +#include "putty.h" +#ifndef NO_GSSAPI + +#include "pgssapi.h" +#include "sshgss.h" + +typedef struct gssapi_ssh_gss_ctx { + OM_uint32 maj_stat; + OM_uint32 min_stat; + gss_ctx_id_t ctx; +} gssapi_ssh_gss_ctx; + +void ssh_gssapi_bind_fns(struct ssh_gss_library *lib); + +#else + +int ssh_gssapi_init(void); + +#endif /*NO_GSSAPI*/ + +#endif /*PUTTY_SSHGSSC_H*/ diff --git a/putty/SSHMD5.C b/putty/SSHMD5.C new file mode 100644 index 0000000..80474df --- /dev/null +++ b/putty/SSHMD5.C @@ -0,0 +1,344 @@ +#include "ssh.h" + +/* + * MD5 implementation for PuTTY. Written directly from the spec by + * Simon Tatham. + */ + +/* ---------------------------------------------------------------------- + * Core MD5 algorithm: processes 16-word blocks into a message digest. + */ + +#define F(x,y,z) ( ((x) & (y)) | ((~(x)) & (z)) ) +#define G(x,y,z) ( ((x) & (z)) | ((~(z)) & (y)) ) +#define H(x,y,z) ( (x) ^ (y) ^ (z) ) +#define I(x,y,z) ( (y) ^ ( (x) | ~(z) ) ) + +#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) ) + +#define subround(f,w,x,y,z,k,s,ti) \ + w = x + rol(w + f(x,y,z) + block[k] + ti, s) + +static void MD5_Core_Init(MD5_Core_State * s) +{ + s->h[0] = 0x67452301; + s->h[1] = 0xefcdab89; + s->h[2] = 0x98badcfe; + s->h[3] = 0x10325476; +} + +static void MD5_Block(MD5_Core_State * s, uint32 * block) +{ + uint32 a, b, c, d; + + a = s->h[0]; + b = s->h[1]; + c = s->h[2]; + d = s->h[3]; + + subround(F, a, b, c, d, 0, 7, 0xd76aa478); + subround(F, d, a, b, c, 1, 12, 0xe8c7b756); + subround(F, c, d, a, b, 2, 17, 0x242070db); + subround(F, b, c, d, a, 3, 22, 0xc1bdceee); + subround(F, a, b, c, d, 4, 7, 0xf57c0faf); + subround(F, d, a, b, c, 5, 12, 0x4787c62a); + subround(F, c, d, a, b, 6, 17, 0xa8304613); + subround(F, b, c, d, a, 7, 22, 0xfd469501); + subround(F, a, b, c, d, 8, 7, 0x698098d8); + subround(F, d, a, b, c, 9, 12, 0x8b44f7af); + subround(F, c, d, a, b, 10, 17, 0xffff5bb1); + subround(F, b, c, d, a, 11, 22, 0x895cd7be); + subround(F, a, b, c, d, 12, 7, 0x6b901122); + subround(F, d, a, b, c, 13, 12, 0xfd987193); + subround(F, c, d, a, b, 14, 17, 0xa679438e); + subround(F, b, c, d, a, 15, 22, 0x49b40821); + subround(G, a, b, c, d, 1, 5, 0xf61e2562); + subround(G, d, a, b, c, 6, 9, 0xc040b340); + subround(G, c, d, a, b, 11, 14, 0x265e5a51); + subround(G, b, c, d, a, 0, 20, 0xe9b6c7aa); + subround(G, a, b, c, d, 5, 5, 0xd62f105d); + subround(G, d, a, b, c, 10, 9, 0x02441453); + subround(G, c, d, a, b, 15, 14, 0xd8a1e681); + subround(G, b, c, d, a, 4, 20, 0xe7d3fbc8); + subround(G, a, b, c, d, 9, 5, 0x21e1cde6); + subround(G, d, a, b, c, 14, 9, 0xc33707d6); + subround(G, c, d, a, b, 3, 14, 0xf4d50d87); + subround(G, b, c, d, a, 8, 20, 0x455a14ed); + subround(G, a, b, c, d, 13, 5, 0xa9e3e905); + subround(G, d, a, b, c, 2, 9, 0xfcefa3f8); + subround(G, c, d, a, b, 7, 14, 0x676f02d9); + subround(G, b, c, d, a, 12, 20, 0x8d2a4c8a); + subround(H, a, b, c, d, 5, 4, 0xfffa3942); + subround(H, d, a, b, c, 8, 11, 0x8771f681); + subround(H, c, d, a, b, 11, 16, 0x6d9d6122); + subround(H, b, c, d, a, 14, 23, 0xfde5380c); + subround(H, a, b, c, d, 1, 4, 0xa4beea44); + subround(H, d, a, b, c, 4, 11, 0x4bdecfa9); + subround(H, c, d, a, b, 7, 16, 0xf6bb4b60); + subround(H, b, c, d, a, 10, 23, 0xbebfbc70); + subround(H, a, b, c, d, 13, 4, 0x289b7ec6); + subround(H, d, a, b, c, 0, 11, 0xeaa127fa); + subround(H, c, d, a, b, 3, 16, 0xd4ef3085); + subround(H, b, c, d, a, 6, 23, 0x04881d05); + subround(H, a, b, c, d, 9, 4, 0xd9d4d039); + subround(H, d, a, b, c, 12, 11, 0xe6db99e5); + subround(H, c, d, a, b, 15, 16, 0x1fa27cf8); + subround(H, b, c, d, a, 2, 23, 0xc4ac5665); + subround(I, a, b, c, d, 0, 6, 0xf4292244); + subround(I, d, a, b, c, 7, 10, 0x432aff97); + subround(I, c, d, a, b, 14, 15, 0xab9423a7); + subround(I, b, c, d, a, 5, 21, 0xfc93a039); + subround(I, a, b, c, d, 12, 6, 0x655b59c3); + subround(I, d, a, b, c, 3, 10, 0x8f0ccc92); + subround(I, c, d, a, b, 10, 15, 0xffeff47d); + subround(I, b, c, d, a, 1, 21, 0x85845dd1); + subround(I, a, b, c, d, 8, 6, 0x6fa87e4f); + subround(I, d, a, b, c, 15, 10, 0xfe2ce6e0); + subround(I, c, d, a, b, 6, 15, 0xa3014314); + subround(I, b, c, d, a, 13, 21, 0x4e0811a1); + subround(I, a, b, c, d, 4, 6, 0xf7537e82); + subround(I, d, a, b, c, 11, 10, 0xbd3af235); + subround(I, c, d, a, b, 2, 15, 0x2ad7d2bb); + subround(I, b, c, d, a, 9, 21, 0xeb86d391); + + s->h[0] += a; + s->h[1] += b; + s->h[2] += c; + s->h[3] += d; +} + +/* ---------------------------------------------------------------------- + * Outer MD5 algorithm: take an arbitrary length byte string, + * convert it into 16-word blocks with the prescribed padding at + * the end, and pass those blocks to the core MD5 algorithm. + */ + +#define BLKSIZE 64 + +void MD5Init(struct MD5Context *s) +{ + MD5_Core_Init(&s->core); + s->blkused = 0; + s->lenhi = s->lenlo = 0; +} + +void MD5Update(struct MD5Context *s, unsigned char const *p, unsigned len) +{ + unsigned char *q = (unsigned char *) p; + uint32 wordblock[16]; + uint32 lenw = len; + int i; + + /* + * Update the length field. + */ + s->lenlo += lenw; + s->lenhi += (s->lenlo < lenw); + + if (s->blkused + len < BLKSIZE) { + /* + * Trivial case: just add to the block. + */ + memcpy(s->block + s->blkused, q, len); + s->blkused += len; + } else { + /* + * We must complete and process at least one block. + */ + while (s->blkused + len >= BLKSIZE) { + memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused); + q += BLKSIZE - s->blkused; + len -= BLKSIZE - s->blkused; + /* Now process the block. Gather bytes little-endian into words */ + for (i = 0; i < 16; i++) { + wordblock[i] = + (((uint32) s->block[i * 4 + 3]) << 24) | + (((uint32) s->block[i * 4 + 2]) << 16) | + (((uint32) s->block[i * 4 + 1]) << 8) | + (((uint32) s->block[i * 4 + 0]) << 0); + } + MD5_Block(&s->core, wordblock); + s->blkused = 0; + } + memcpy(s->block, q, len); + s->blkused = len; + } +} + +void MD5Final(unsigned char output[16], struct MD5Context *s) +{ + int i; + unsigned pad; + unsigned char c[64]; + uint32 lenhi, lenlo; + + if (s->blkused >= 56) + pad = 56 + 64 - s->blkused; + else + pad = 56 - s->blkused; + + lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3)); + lenlo = (s->lenlo << 3); + + memset(c, 0, pad); + c[0] = 0x80; + MD5Update(s, c, pad); + + c[7] = (lenhi >> 24) & 0xFF; + c[6] = (lenhi >> 16) & 0xFF; + c[5] = (lenhi >> 8) & 0xFF; + c[4] = (lenhi >> 0) & 0xFF; + c[3] = (lenlo >> 24) & 0xFF; + c[2] = (lenlo >> 16) & 0xFF; + c[1] = (lenlo >> 8) & 0xFF; + c[0] = (lenlo >> 0) & 0xFF; + + MD5Update(s, c, 8); + + for (i = 0; i < 4; i++) { + output[4 * i + 3] = (s->core.h[i] >> 24) & 0xFF; + output[4 * i + 2] = (s->core.h[i] >> 16) & 0xFF; + output[4 * i + 1] = (s->core.h[i] >> 8) & 0xFF; + output[4 * i + 0] = (s->core.h[i] >> 0) & 0xFF; + } +} + +void MD5Simple(void const *p, unsigned len, unsigned char output[16]) +{ + struct MD5Context s; + + MD5Init(&s); + MD5Update(&s, (unsigned char const *)p, len); + MD5Final(output, &s); +} + +/* ---------------------------------------------------------------------- + * The above is the MD5 algorithm itself. Now we implement the + * HMAC wrapper on it. + * + * Some of these functions are exported directly, because they are + * useful elsewhere (SOCKS5 CHAP authentication uses HMAC-MD5). + */ + +void *hmacmd5_make_context(void) +{ + return snewn(3, struct MD5Context); +} + +void hmacmd5_free_context(void *handle) +{ + sfree(handle); +} + +void hmacmd5_key(void *handle, void const *keyv, int len) +{ + struct MD5Context *keys = (struct MD5Context *)handle; + unsigned char foo[64]; + unsigned char const *key = (unsigned char const *)keyv; + int i; + + memset(foo, 0x36, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + MD5Init(&keys[0]); + MD5Update(&keys[0], foo, 64); + + memset(foo, 0x5C, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + MD5Init(&keys[1]); + MD5Update(&keys[1], foo, 64); + + memset(foo, 0, 64); /* burn the evidence */ +} + +static void hmacmd5_key_16(void *handle, unsigned char *key) +{ + hmacmd5_key(handle, key, 16); +} + +static void hmacmd5_start(void *handle) +{ + struct MD5Context *keys = (struct MD5Context *)handle; + + keys[2] = keys[0]; /* structure copy */ +} + +static void hmacmd5_bytes(void *handle, unsigned char const *blk, int len) +{ + struct MD5Context *keys = (struct MD5Context *)handle; + MD5Update(&keys[2], blk, len); +} + +static void hmacmd5_genresult(void *handle, unsigned char *hmac) +{ + struct MD5Context *keys = (struct MD5Context *)handle; + struct MD5Context s; + unsigned char intermediate[16]; + + s = keys[2]; /* structure copy */ + MD5Final(intermediate, &s); + s = keys[1]; /* structure copy */ + MD5Update(&s, intermediate, 16); + MD5Final(hmac, &s); +} + +static int hmacmd5_verresult(void *handle, unsigned char const *hmac) +{ + unsigned char correct[16]; + hmacmd5_genresult(handle, correct); + return !memcmp(correct, hmac, 16); +} + +static void hmacmd5_do_hmac_internal(void *handle, + unsigned char const *blk, int len, + unsigned char const *blk2, int len2, + unsigned char *hmac) +{ + hmacmd5_start(handle); + hmacmd5_bytes(handle, blk, len); + if (blk2) hmacmd5_bytes(handle, blk2, len2); + hmacmd5_genresult(handle, hmac); +} + +void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len, + unsigned char *hmac) +{ + hmacmd5_do_hmac_internal(handle, blk, len, NULL, 0, hmac); +} + +static void hmacmd5_do_hmac_ssh(void *handle, unsigned char const *blk, int len, + unsigned long seq, unsigned char *hmac) +{ + unsigned char seqbuf[16]; + + seqbuf[0] = (unsigned char) ((seq >> 24) & 0xFF); + seqbuf[1] = (unsigned char) ((seq >> 16) & 0xFF); + seqbuf[2] = (unsigned char) ((seq >> 8) & 0xFF); + seqbuf[3] = (unsigned char) ((seq) & 0xFF); + + hmacmd5_do_hmac_internal(handle, seqbuf, 4, blk, len, hmac); +} + +static void hmacmd5_generate(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + hmacmd5_do_hmac_ssh(handle, blk, len, seq, blk + len); +} + +static int hmacmd5_verify(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + unsigned char correct[16]; + hmacmd5_do_hmac_ssh(handle, blk, len, seq, correct); + return !memcmp(correct, blk + len, 16); +} + +const struct ssh_mac ssh_hmac_md5 = { + hmacmd5_make_context, hmacmd5_free_context, hmacmd5_key_16, + hmacmd5_generate, hmacmd5_verify, + hmacmd5_start, hmacmd5_bytes, hmacmd5_genresult, hmacmd5_verresult, + "hmac-md5", + 16, + "HMAC-MD5" +}; diff --git a/putty/SSHNOGSS.C b/putty/SSHNOGSS.C new file mode 100644 index 0000000..27ec760 --- /dev/null +++ b/putty/SSHNOGSS.C @@ -0,0 +1,19 @@ +#include "putty.h" +#ifndef NO_GSSAPI + +/* For platforms not supporting GSSAPI */ + +struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg) +{ + 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/putty/SSHPRIME.C b/putty/SSHPRIME.C new file mode 100644 index 0000000..e283b52 --- /dev/null +++ b/putty/SSHPRIME.C @@ -0,0 +1,1398 @@ +/* + * Prime generation. + */ + +#include +#include "ssh.h" + +/* + * This prime generation algorithm is pretty much cribbed from + * OpenSSL. The algorithm is: + * + * - invent a B-bit random number and ensure the top and bottom + * bits are set (so it's definitely B-bit, and it's definitely + * odd) + * + * - see if it's coprime to all primes below 2^16; increment it by + * two until it is (this shouldn't take long in general) + * + * - perform the Miller-Rabin primality test enough times to + * ensure the probability of it being composite is 2^-80 or + * less + * + * - go back to square one if any M-R test fails. + */ + +/* + * The Miller-Rabin primality test is an extension to the Fermat + * test. The Fermat test just checks that a^(p-1) == 1 mod p; this + * is vulnerable to Carmichael numbers. Miller-Rabin considers how + * that 1 is derived as well. + * + * Lemma: if a^2 == 1 (mod p), and p is prime, then either a == 1 + * or a == -1 (mod p). + * + * Proof: p divides a^2-1, i.e. p divides (a+1)(a-1). Hence, + * since p is prime, either p divides (a+1) or p divides (a-1). + * But this is the same as saying that either a is congruent to + * -1 mod p or a is congruent to +1 mod p. [] + * + * Comment: This fails when p is not prime. Consider p=mn, so + * that mn divides (a+1)(a-1). Now we could have m dividing (a+1) + * and n dividing (a-1), without the whole of mn dividing either. + * For example, consider a=10 and p=99. 99 = 9 * 11; 9 divides + * 10-1 and 11 divides 10+1, so a^2 is congruent to 1 mod p + * without a having to be congruent to either 1 or -1. + * + * So the Miller-Rabin test, as well as considering a^(p-1), + * considers a^((p-1)/2), a^((p-1)/4), and so on as far as it can + * go. In other words. we write p-1 as q * 2^k, with k as large as + * possible (i.e. q must be odd), and we consider the powers + * + * a^(q*2^0) a^(q*2^1) ... a^(q*2^(k-1)) a^(q*2^k) + * i.e. a^((n-1)/2^k) a^((n-1)/2^(k-1)) ... a^((n-1)/2) a^(n-1) + * + * If p is to be prime, the last of these must be 1. Therefore, by + * the above lemma, the one before it must be either 1 or -1. And + * _if_ it's 1, then the one before that must be either 1 or -1, + * and so on ... In other words, we expect to see a trailing chain + * of 1s preceded by a -1. (If we're unlucky, our trailing chain of + * 1s will be as long as the list so we'll never get to see what + * lies before it. This doesn't count as a test failure because it + * hasn't _proved_ that p is not prime.) + * + * For example, consider a=2 and p=1729. 1729 is a Carmichael + * number: although it's not prime, it satisfies a^(p-1) == 1 mod p + * for any a coprime to it. So the Fermat test wouldn't have a + * problem with it at all, unless we happened to stumble on an a + * which had a common factor. + * + * So. 1729 - 1 equals 27 * 2^6. So we look at + * + * 2^27 mod 1729 == 645 + * 2^108 mod 1729 == 1065 + * 2^216 mod 1729 == 1 + * 2^432 mod 1729 == 1 + * 2^864 mod 1729 == 1 + * 2^1728 mod 1729 == 1 + * + * We do have a trailing string of 1s, so the Fermat test would + * have been happy. But this trailing string of 1s is preceded by + * 1065; whereas if 1729 were prime, we'd expect to see it preceded + * by -1 (i.e. 1728.). Guards! Seize this impostor. + * + * (If we were unlucky, we might have tried a=16 instead of a=2; + * now 16^27 mod 1729 == 1, so we would have seen a long string of + * 1s and wouldn't have seen the thing _before_ the 1s. So, just + * like the Fermat test, for a given p there may well exist values + * of a which fail to show up its compositeness. So we try several, + * just like the Fermat test. The difference is that Miller-Rabin + * is not _in general_ fooled by Carmichael numbers.) + * + * Put simply, then, the Miller-Rabin test requires us to: + * + * 1. write p-1 as q * 2^k, with q odd + * 2. compute z = (a^q) mod p. + * 3. report success if z == 1 or z == -1. + * 4. square z at most k-1 times, and report success if it becomes + * -1 at any point. + * 5. report failure otherwise. + * + * (We expect z to become -1 after at most k-1 squarings, because + * if it became -1 after k squarings then a^(p-1) would fail to be + * 1. And we don't need to investigate what happens after we see a + * -1, because we _know_ that -1 squared is 1 modulo anything at + * all, so after we've seen a -1 we can be sure of seeing nothing + * but 1s.) + */ + +/* + * The first few odd primes. + * + * import sys + * def sieve(n): + * z = [] + * list = [] + * for i in range(n): z.append(1) + * for i in range(2,n): + * if z[i]: + * list.append(i) + * for j in range(i,n,i): z[j] = 0 + * return list + * list = sieve(65535) + * for i in list[1:]: sys.stdout.write("%d," % i) + */ +static const unsigned short primes[] = { + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, + 71, 73, 79, 83, 89, 97, 101, + 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, + 179, 181, 191, 193, + 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, + 277, 281, 283, 293, + 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, + 389, 397, 401, 409, + 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, + 499, 503, 509, 521, + 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, + 617, 619, 631, 641, + 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, + 739, 743, 751, 757, + 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, + 859, 863, 877, 881, + 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, + 991, 997, 1009, + 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, + 1091, 1093, + 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, + 1193, 1201, + 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, + 1291, 1297, + 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, + 1423, 1427, + 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, + 1493, 1499, + 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, + 1601, 1607, + 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, + 1699, 1709, + 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, + 1811, 1823, + 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, + 1931, 1933, + 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, + 2029, 2039, + 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, + 2137, 2141, + 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, + 2267, 2269, + 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, + 2357, 2371, + 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, + 2459, 2467, + 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, + 2593, 2609, + 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, + 2693, 2699, + 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, + 2791, 2797, + 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, + 2903, 2909, + 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, + 3023, 3037, + 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, + 3167, 3169, + 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, + 3271, 3299, + 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, + 3373, 3389, + 3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, + 3511, 3517, + 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, + 3607, 3613, + 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, + 3709, 3719, + 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, + 3833, 3847, + 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, + 3931, 3943, + 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, + 4057, 4073, + 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, + 4177, 4201, + 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, + 4283, 4289, + 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, + 4423, 4441, + 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, + 4547, 4549, + 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, + 4657, 4663, + 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, + 4789, 4793, + 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, + 4931, 4933, + 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, + 5011, 5021, + 5023, 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, + 5147, 5153, + 5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, + 5279, 5281, + 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, + 5413, 5417, + 5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, + 5507, 5519, + 5521, 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, 5641, + 5647, 5651, + 5653, 5657, 5659, 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, + 5743, 5749, + 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, + 5857, 5861, + 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, + 6007, 6011, + 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, + 6121, 6131, + 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, + 6247, 6257, + 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337, + 6343, 6353, + 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, + 6473, 6481, + 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, + 6607, 6619, + 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, + 6733, 6737, + 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, + 6857, 6863, + 6869, 6871, 6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, + 6971, 6977, + 6983, 6991, 6997, 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, + 7103, 7109, + 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, + 7229, 7237, + 7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, + 7369, 7393, + 7411, 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, + 7517, 7523, + 7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, 7591, + 7603, 7607, + 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, + 7723, 7727, + 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, + 7873, 7877, + 7879, 7883, 7901, 7907, 7919, 7927, 7933, 7937, 7949, 7951, 7963, 7993, + 8009, 8011, + 8017, 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, 8117, + 8123, 8147, + 8161, 8167, 8171, 8179, 8191, 8209, 8219, 8221, 8231, 8233, 8237, 8243, + 8263, 8269, + 8273, 8287, 8291, 8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, + 8387, 8389, + 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501, 8513, 8521, 8527, + 8537, 8539, + 8543, 8563, 8573, 8581, 8597, 8599, 8609, 8623, 8627, 8629, 8641, 8647, + 8663, 8669, + 8677, 8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741, 8747, + 8753, 8761, + 8779, 8783, 8803, 8807, 8819, 8821, 8831, 8837, 8839, 8849, 8861, 8863, + 8867, 8887, + 8893, 8923, 8929, 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, + 9011, 9013, + 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, 9127, 9133, 9137, + 9151, 9157, + 9161, 9173, 9181, 9187, 9199, 9203, 9209, 9221, 9227, 9239, 9241, 9257, + 9277, 9281, + 9283, 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377, 9391, + 9397, 9403, + 9413, 9419, 9421, 9431, 9433, 9437, 9439, 9461, 9463, 9467, 9473, 9479, + 9491, 9497, + 9511, 9521, 9533, 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, + 9631, 9643, + 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733, 9739, 9743, 9749, + 9767, 9769, + 9781, 9787, 9791, 9803, 9811, 9817, 9829, 9833, 9839, 9851, 9857, 9859, + 9871, 9883, + 9887, 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007, + 10009, 10037, + 10039, 10061, 10067, 10069, 10079, 10091, 10093, 10099, 10103, 10111, + 10133, 10139, + 10141, 10151, 10159, 10163, 10169, 10177, 10181, 10193, 10211, 10223, + 10243, 10247, + 10253, 10259, 10267, 10271, 10273, 10289, 10301, 10303, 10313, 10321, + 10331, 10333, + 10337, 10343, 10357, 10369, 10391, 10399, 10427, 10429, 10433, 10453, + 10457, 10459, + 10463, 10477, 10487, 10499, 10501, 10513, 10529, 10531, 10559, 10567, + 10589, 10597, + 10601, 10607, 10613, 10627, 10631, 10639, 10651, 10657, 10663, 10667, + 10687, 10691, + 10709, 10711, 10723, 10729, 10733, 10739, 10753, 10771, 10781, 10789, + 10799, 10831, + 10837, 10847, 10853, 10859, 10861, 10867, 10883, 10889, 10891, 10903, + 10909, 10937, + 10939, 10949, 10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, + 11057, 11059, + 11069, 11071, 11083, 11087, 11093, 11113, 11117, 11119, 11131, 11149, + 11159, 11161, + 11171, 11173, 11177, 11197, 11213, 11239, 11243, 11251, 11257, 11261, + 11273, 11279, + 11287, 11299, 11311, 11317, 11321, 11329, 11351, 11353, 11369, 11383, + 11393, 11399, + 11411, 11423, 11437, 11443, 11447, 11467, 11471, 11483, 11489, 11491, + 11497, 11503, + 11519, 11527, 11549, 11551, 11579, 11587, 11593, 11597, 11617, 11621, + 11633, 11657, + 11677, 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777, + 11779, 11783, + 11789, 11801, 11807, 11813, 11821, 11827, 11831, 11833, 11839, 11863, + 11867, 11887, + 11897, 11903, 11909, 11923, 11927, 11933, 11939, 11941, 11953, 11959, + 11969, 11971, + 11981, 11987, 12007, 12011, 12037, 12041, 12043, 12049, 12071, 12073, + 12097, 12101, + 12107, 12109, 12113, 12119, 12143, 12149, 12157, 12161, 12163, 12197, + 12203, 12211, + 12227, 12239, 12241, 12251, 12253, 12263, 12269, 12277, 12281, 12289, + 12301, 12323, + 12329, 12343, 12347, 12373, 12377, 12379, 12391, 12401, 12409, 12413, + 12421, 12433, + 12437, 12451, 12457, 12473, 12479, 12487, 12491, 12497, 12503, 12511, + 12517, 12527, + 12539, 12541, 12547, 12553, 12569, 12577, 12583, 12589, 12601, 12611, + 12613, 12619, + 12637, 12641, 12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, + 12721, 12739, + 12743, 12757, 12763, 12781, 12791, 12799, 12809, 12821, 12823, 12829, + 12841, 12853, + 12889, 12893, 12899, 12907, 12911, 12917, 12919, 12923, 12941, 12953, + 12959, 12967, + 12973, 12979, 12983, 13001, 13003, 13007, 13009, 13033, 13037, 13043, + 13049, 13063, + 13093, 13099, 13103, 13109, 13121, 13127, 13147, 13151, 13159, 13163, + 13171, 13177, + 13183, 13187, 13217, 13219, 13229, 13241, 13249, 13259, 13267, 13291, + 13297, 13309, + 13313, 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411, + 13417, 13421, + 13441, 13451, 13457, 13463, 13469, 13477, 13487, 13499, 13513, 13523, + 13537, 13553, + 13567, 13577, 13591, 13597, 13613, 13619, 13627, 13633, 13649, 13669, + 13679, 13681, + 13687, 13691, 13693, 13697, 13709, 13711, 13721, 13723, 13729, 13751, + 13757, 13759, + 13763, 13781, 13789, 13799, 13807, 13829, 13831, 13841, 13859, 13873, + 13877, 13879, + 13883, 13901, 13903, 13907, 13913, 13921, 13931, 13933, 13963, 13967, + 13997, 13999, + 14009, 14011, 14029, 14033, 14051, 14057, 14071, 14081, 14083, 14087, + 14107, 14143, + 14149, 14153, 14159, 14173, 14177, 14197, 14207, 14221, 14243, 14249, + 14251, 14281, + 14293, 14303, 14321, 14323, 14327, 14341, 14347, 14369, 14387, 14389, + 14401, 14407, + 14411, 14419, 14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, + 14503, 14519, + 14533, 14537, 14543, 14549, 14551, 14557, 14561, 14563, 14591, 14593, + 14621, 14627, + 14629, 14633, 14639, 14653, 14657, 14669, 14683, 14699, 14713, 14717, + 14723, 14731, + 14737, 14741, 14747, 14753, 14759, 14767, 14771, 14779, 14783, 14797, + 14813, 14821, + 14827, 14831, 14843, 14851, 14867, 14869, 14879, 14887, 14891, 14897, + 14923, 14929, + 14939, 14947, 14951, 14957, 14969, 14983, 15013, 15017, 15031, 15053, + 15061, 15073, + 15077, 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149, + 15161, 15173, + 15187, 15193, 15199, 15217, 15227, 15233, 15241, 15259, 15263, 15269, + 15271, 15277, + 15287, 15289, 15299, 15307, 15313, 15319, 15329, 15331, 15349, 15359, + 15361, 15373, + 15377, 15383, 15391, 15401, 15413, 15427, 15439, 15443, 15451, 15461, + 15467, 15473, + 15493, 15497, 15511, 15527, 15541, 15551, 15559, 15569, 15581, 15583, + 15601, 15607, + 15619, 15629, 15641, 15643, 15647, 15649, 15661, 15667, 15671, 15679, + 15683, 15727, + 15731, 15733, 15737, 15739, 15749, 15761, 15767, 15773, 15787, 15791, + 15797, 15803, + 15809, 15817, 15823, 15859, 15877, 15881, 15887, 15889, 15901, 15907, + 15913, 15919, + 15923, 15937, 15959, 15971, 15973, 15991, 16001, 16007, 16033, 16057, + 16061, 16063, + 16067, 16069, 16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, + 16141, 16183, + 16187, 16189, 16193, 16217, 16223, 16229, 16231, 16249, 16253, 16267, + 16273, 16301, + 16319, 16333, 16339, 16349, 16361, 16363, 16369, 16381, 16411, 16417, + 16421, 16427, + 16433, 16447, 16451, 16453, 16477, 16481, 16487, 16493, 16519, 16529, + 16547, 16553, + 16561, 16567, 16573, 16603, 16607, 16619, 16631, 16633, 16649, 16651, + 16657, 16661, + 16673, 16691, 16693, 16699, 16703, 16729, 16741, 16747, 16759, 16763, + 16787, 16811, + 16823, 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903, + 16921, 16927, + 16931, 16937, 16943, 16963, 16979, 16981, 16987, 16993, 17011, 17021, + 17027, 17029, + 17033, 17041, 17047, 17053, 17077, 17093, 17099, 17107, 17117, 17123, + 17137, 17159, + 17167, 17183, 17189, 17191, 17203, 17207, 17209, 17231, 17239, 17257, + 17291, 17293, + 17299, 17317, 17321, 17327, 17333, 17341, 17351, 17359, 17377, 17383, + 17387, 17389, + 17393, 17401, 17417, 17419, 17431, 17443, 17449, 17467, 17471, 17477, + 17483, 17489, + 17491, 17497, 17509, 17519, 17539, 17551, 17569, 17573, 17579, 17581, + 17597, 17599, + 17609, 17623, 17627, 17657, 17659, 17669, 17681, 17683, 17707, 17713, + 17729, 17737, + 17747, 17749, 17761, 17783, 17789, 17791, 17807, 17827, 17837, 17839, + 17851, 17863, + 17881, 17891, 17903, 17909, 17911, 17921, 17923, 17929, 17939, 17957, + 17959, 17971, + 17977, 17981, 17987, 17989, 18013, 18041, 18043, 18047, 18049, 18059, + 18061, 18077, + 18089, 18097, 18119, 18121, 18127, 18131, 18133, 18143, 18149, 18169, + 18181, 18191, + 18199, 18211, 18217, 18223, 18229, 18233, 18251, 18253, 18257, 18269, + 18287, 18289, + 18301, 18307, 18311, 18313, 18329, 18341, 18353, 18367, 18371, 18379, + 18397, 18401, + 18413, 18427, 18433, 18439, 18443, 18451, 18457, 18461, 18481, 18493, + 18503, 18517, + 18521, 18523, 18539, 18541, 18553, 18583, 18587, 18593, 18617, 18637, + 18661, 18671, + 18679, 18691, 18701, 18713, 18719, 18731, 18743, 18749, 18757, 18773, + 18787, 18793, + 18797, 18803, 18839, 18859, 18869, 18899, 18911, 18913, 18917, 18919, + 18947, 18959, + 18973, 18979, 19001, 19009, 19013, 19031, 19037, 19051, 19069, 19073, + 19079, 19081, + 19087, 19121, 19139, 19141, 19157, 19163, 19181, 19183, 19207, 19211, + 19213, 19219, + 19231, 19237, 19249, 19259, 19267, 19273, 19289, 19301, 19309, 19319, + 19333, 19373, + 19379, 19381, 19387, 19391, 19403, 19417, 19421, 19423, 19427, 19429, + 19433, 19441, + 19447, 19457, 19463, 19469, 19471, 19477, 19483, 19489, 19501, 19507, + 19531, 19541, + 19543, 19553, 19559, 19571, 19577, 19583, 19597, 19603, 19609, 19661, + 19681, 19687, + 19697, 19699, 19709, 19717, 19727, 19739, 19751, 19753, 19759, 19763, + 19777, 19793, + 19801, 19813, 19819, 19841, 19843, 19853, 19861, 19867, 19889, 19891, + 19913, 19919, + 19927, 19937, 19949, 19961, 19963, 19973, 19979, 19991, 19993, 19997, + 20011, 20021, + 20023, 20029, 20047, 20051, 20063, 20071, 20089, 20101, 20107, 20113, + 20117, 20123, + 20129, 20143, 20147, 20149, 20161, 20173, 20177, 20183, 20201, 20219, + 20231, 20233, + 20249, 20261, 20269, 20287, 20297, 20323, 20327, 20333, 20341, 20347, + 20353, 20357, + 20359, 20369, 20389, 20393, 20399, 20407, 20411, 20431, 20441, 20443, + 20477, 20479, + 20483, 20507, 20509, 20521, 20533, 20543, 20549, 20551, 20563, 20593, + 20599, 20611, + 20627, 20639, 20641, 20663, 20681, 20693, 20707, 20717, 20719, 20731, + 20743, 20747, + 20749, 20753, 20759, 20771, 20773, 20789, 20807, 20809, 20849, 20857, + 20873, 20879, + 20887, 20897, 20899, 20903, 20921, 20929, 20939, 20947, 20959, 20963, + 20981, 20983, + 21001, 21011, 21013, 21017, 21019, 21023, 21031, 21059, 21061, 21067, + 21089, 21101, + 21107, 21121, 21139, 21143, 21149, 21157, 21163, 21169, 21179, 21187, + 21191, 21193, + 21211, 21221, 21227, 21247, 21269, 21277, 21283, 21313, 21317, 21319, + 21323, 21341, + 21347, 21377, 21379, 21383, 21391, 21397, 21401, 21407, 21419, 21433, + 21467, 21481, + 21487, 21491, 21493, 21499, 21503, 21517, 21521, 21523, 21529, 21557, + 21559, 21563, + 21569, 21577, 21587, 21589, 21599, 21601, 21611, 21613, 21617, 21647, + 21649, 21661, + 21673, 21683, 21701, 21713, 21727, 21737, 21739, 21751, 21757, 21767, + 21773, 21787, + 21799, 21803, 21817, 21821, 21839, 21841, 21851, 21859, 21863, 21871, + 21881, 21893, + 21911, 21929, 21937, 21943, 21961, 21977, 21991, 21997, 22003, 22013, + 22027, 22031, + 22037, 22039, 22051, 22063, 22067, 22073, 22079, 22091, 22093, 22109, + 22111, 22123, + 22129, 22133, 22147, 22153, 22157, 22159, 22171, 22189, 22193, 22229, + 22247, 22259, + 22271, 22273, 22277, 22279, 22283, 22291, 22303, 22307, 22343, 22349, + 22367, 22369, + 22381, 22391, 22397, 22409, 22433, 22441, 22447, 22453, 22469, 22481, + 22483, 22501, + 22511, 22531, 22541, 22543, 22549, 22567, 22571, 22573, 22613, 22619, + 22621, 22637, + 22639, 22643, 22651, 22669, 22679, 22691, 22697, 22699, 22709, 22717, + 22721, 22727, + 22739, 22741, 22751, 22769, 22777, 22783, 22787, 22807, 22811, 22817, + 22853, 22859, + 22861, 22871, 22877, 22901, 22907, 22921, 22937, 22943, 22961, 22963, + 22973, 22993, + 23003, 23011, 23017, 23021, 23027, 23029, 23039, 23041, 23053, 23057, + 23059, 23063, + 23071, 23081, 23087, 23099, 23117, 23131, 23143, 23159, 23167, 23173, + 23189, 23197, + 23201, 23203, 23209, 23227, 23251, 23269, 23279, 23291, 23293, 23297, + 23311, 23321, + 23327, 23333, 23339, 23357, 23369, 23371, 23399, 23417, 23431, 23447, + 23459, 23473, + 23497, 23509, 23531, 23537, 23539, 23549, 23557, 23561, 23563, 23567, + 23581, 23593, + 23599, 23603, 23609, 23623, 23627, 23629, 23633, 23663, 23669, 23671, + 23677, 23687, + 23689, 23719, 23741, 23743, 23747, 23753, 23761, 23767, 23773, 23789, + 23801, 23813, + 23819, 23827, 23831, 23833, 23857, 23869, 23873, 23879, 23887, 23893, + 23899, 23909, + 23911, 23917, 23929, 23957, 23971, 23977, 23981, 23993, 24001, 24007, + 24019, 24023, + 24029, 24043, 24049, 24061, 24071, 24077, 24083, 24091, 24097, 24103, + 24107, 24109, + 24113, 24121, 24133, 24137, 24151, 24169, 24179, 24181, 24197, 24203, + 24223, 24229, + 24239, 24247, 24251, 24281, 24317, 24329, 24337, 24359, 24371, 24373, + 24379, 24391, + 24407, 24413, 24419, 24421, 24439, 24443, 24469, 24473, 24481, 24499, + 24509, 24517, + 24527, 24533, 24547, 24551, 24571, 24593, 24611, 24623, 24631, 24659, + 24671, 24677, + 24683, 24691, 24697, 24709, 24733, 24749, 24763, 24767, 24781, 24793, + 24799, 24809, + 24821, 24841, 24847, 24851, 24859, 24877, 24889, 24907, 24917, 24919, + 24923, 24943, + 24953, 24967, 24971, 24977, 24979, 24989, 25013, 25031, 25033, 25037, + 25057, 25073, + 25087, 25097, 25111, 25117, 25121, 25127, 25147, 25153, 25163, 25169, + 25171, 25183, + 25189, 25219, 25229, 25237, 25243, 25247, 25253, 25261, 25301, 25303, + 25307, 25309, + 25321, 25339, 25343, 25349, 25357, 25367, 25373, 25391, 25409, 25411, + 25423, 25439, + 25447, 25453, 25457, 25463, 25469, 25471, 25523, 25537, 25541, 25561, + 25577, 25579, + 25583, 25589, 25601, 25603, 25609, 25621, 25633, 25639, 25643, 25657, + 25667, 25673, + 25679, 25693, 25703, 25717, 25733, 25741, 25747, 25759, 25763, 25771, + 25793, 25799, + 25801, 25819, 25841, 25847, 25849, 25867, 25873, 25889, 25903, 25913, + 25919, 25931, + 25933, 25939, 25943, 25951, 25969, 25981, 25997, 25999, 26003, 26017, + 26021, 26029, + 26041, 26053, 26083, 26099, 26107, 26111, 26113, 26119, 26141, 26153, + 26161, 26171, + 26177, 26183, 26189, 26203, 26209, 26227, 26237, 26249, 26251, 26261, + 26263, 26267, + 26293, 26297, 26309, 26317, 26321, 26339, 26347, 26357, 26371, 26387, + 26393, 26399, + 26407, 26417, 26423, 26431, 26437, 26449, 26459, 26479, 26489, 26497, + 26501, 26513, + 26539, 26557, 26561, 26573, 26591, 26597, 26627, 26633, 26641, 26647, + 26669, 26681, + 26683, 26687, 26693, 26699, 26701, 26711, 26713, 26717, 26723, 26729, + 26731, 26737, + 26759, 26777, 26783, 26801, 26813, 26821, 26833, 26839, 26849, 26861, + 26863, 26879, + 26881, 26891, 26893, 26903, 26921, 26927, 26947, 26951, 26953, 26959, + 26981, 26987, + 26993, 27011, 27017, 27031, 27043, 27059, 27061, 27067, 27073, 27077, + 27091, 27103, + 27107, 27109, 27127, 27143, 27179, 27191, 27197, 27211, 27239, 27241, + 27253, 27259, + 27271, 27277, 27281, 27283, 27299, 27329, 27337, 27361, 27367, 27397, + 27407, 27409, + 27427, 27431, 27437, 27449, 27457, 27479, 27481, 27487, 27509, 27527, + 27529, 27539, + 27541, 27551, 27581, 27583, 27611, 27617, 27631, 27647, 27653, 27673, + 27689, 27691, + 27697, 27701, 27733, 27737, 27739, 27743, 27749, 27751, 27763, 27767, + 27773, 27779, + 27791, 27793, 27799, 27803, 27809, 27817, 27823, 27827, 27847, 27851, + 27883, 27893, + 27901, 27917, 27919, 27941, 27943, 27947, 27953, 27961, 27967, 27983, + 27997, 28001, + 28019, 28027, 28031, 28051, 28057, 28069, 28081, 28087, 28097, 28099, + 28109, 28111, + 28123, 28151, 28163, 28181, 28183, 28201, 28211, 28219, 28229, 28277, + 28279, 28283, + 28289, 28297, 28307, 28309, 28319, 28349, 28351, 28387, 28393, 28403, + 28409, 28411, + 28429, 28433, 28439, 28447, 28463, 28477, 28493, 28499, 28513, 28517, + 28537, 28541, + 28547, 28549, 28559, 28571, 28573, 28579, 28591, 28597, 28603, 28607, + 28619, 28621, + 28627, 28631, 28643, 28649, 28657, 28661, 28663, 28669, 28687, 28697, + 28703, 28711, + 28723, 28729, 28751, 28753, 28759, 28771, 28789, 28793, 28807, 28813, + 28817, 28837, + 28843, 28859, 28867, 28871, 28879, 28901, 28909, 28921, 28927, 28933, + 28949, 28961, + 28979, 29009, 29017, 29021, 29023, 29027, 29033, 29059, 29063, 29077, + 29101, 29123, + 29129, 29131, 29137, 29147, 29153, 29167, 29173, 29179, 29191, 29201, + 29207, 29209, + 29221, 29231, 29243, 29251, 29269, 29287, 29297, 29303, 29311, 29327, + 29333, 29339, + 29347, 29363, 29383, 29387, 29389, 29399, 29401, 29411, 29423, 29429, + 29437, 29443, + 29453, 29473, 29483, 29501, 29527, 29531, 29537, 29567, 29569, 29573, + 29581, 29587, + 29599, 29611, 29629, 29633, 29641, 29663, 29669, 29671, 29683, 29717, + 29723, 29741, + 29753, 29759, 29761, 29789, 29803, 29819, 29833, 29837, 29851, 29863, + 29867, 29873, + 29879, 29881, 29917, 29921, 29927, 29947, 29959, 29983, 29989, 30011, + 30013, 30029, + 30047, 30059, 30071, 30089, 30091, 30097, 30103, 30109, 30113, 30119, + 30133, 30137, + 30139, 30161, 30169, 30181, 30187, 30197, 30203, 30211, 30223, 30241, + 30253, 30259, + 30269, 30271, 30293, 30307, 30313, 30319, 30323, 30341, 30347, 30367, + 30389, 30391, + 30403, 30427, 30431, 30449, 30467, 30469, 30491, 30493, 30497, 30509, + 30517, 30529, + 30539, 30553, 30557, 30559, 30577, 30593, 30631, 30637, 30643, 30649, + 30661, 30671, + 30677, 30689, 30697, 30703, 30707, 30713, 30727, 30757, 30763, 30773, + 30781, 30803, + 30809, 30817, 30829, 30839, 30841, 30851, 30853, 30859, 30869, 30871, + 30881, 30893, + 30911, 30931, 30937, 30941, 30949, 30971, 30977, 30983, 31013, 31019, + 31033, 31039, + 31051, 31063, 31069, 31079, 31081, 31091, 31121, 31123, 31139, 31147, + 31151, 31153, + 31159, 31177, 31181, 31183, 31189, 31193, 31219, 31223, 31231, 31237, + 31247, 31249, + 31253, 31259, 31267, 31271, 31277, 31307, 31319, 31321, 31327, 31333, + 31337, 31357, + 31379, 31387, 31391, 31393, 31397, 31469, 31477, 31481, 31489, 31511, + 31513, 31517, + 31531, 31541, 31543, 31547, 31567, 31573, 31583, 31601, 31607, 31627, + 31643, 31649, + 31657, 31663, 31667, 31687, 31699, 31721, 31723, 31727, 31729, 31741, + 31751, 31769, + 31771, 31793, 31799, 31817, 31847, 31849, 31859, 31873, 31883, 31891, + 31907, 31957, + 31963, 31973, 31981, 31991, 32003, 32009, 32027, 32029, 32051, 32057, + 32059, 32063, + 32069, 32077, 32083, 32089, 32099, 32117, 32119, 32141, 32143, 32159, + 32173, 32183, + 32189, 32191, 32203, 32213, 32233, 32237, 32251, 32257, 32261, 32297, + 32299, 32303, + 32309, 32321, 32323, 32327, 32341, 32353, 32359, 32363, 32369, 32371, + 32377, 32381, + 32401, 32411, 32413, 32423, 32429, 32441, 32443, 32467, 32479, 32491, + 32497, 32503, + 32507, 32531, 32533, 32537, 32561, 32563, 32569, 32573, 32579, 32587, + 32603, 32609, + 32611, 32621, 32633, 32647, 32653, 32687, 32693, 32707, 32713, 32717, + 32719, 32749, + 32771, 32779, 32783, 32789, 32797, 32801, 32803, 32831, 32833, 32839, + 32843, 32869, + 32887, 32909, 32911, 32917, 32933, 32939, 32941, 32957, 32969, 32971, + 32983, 32987, + 32993, 32999, 33013, 33023, 33029, 33037, 33049, 33053, 33071, 33073, + 33083, 33091, + 33107, 33113, 33119, 33149, 33151, 33161, 33179, 33181, 33191, 33199, + 33203, 33211, + 33223, 33247, 33287, 33289, 33301, 33311, 33317, 33329, 33331, 33343, + 33347, 33349, + 33353, 33359, 33377, 33391, 33403, 33409, 33413, 33427, 33457, 33461, + 33469, 33479, + 33487, 33493, 33503, 33521, 33529, 33533, 33547, 33563, 33569, 33577, + 33581, 33587, + 33589, 33599, 33601, 33613, 33617, 33619, 33623, 33629, 33637, 33641, + 33647, 33679, + 33703, 33713, 33721, 33739, 33749, 33751, 33757, 33767, 33769, 33773, + 33791, 33797, + 33809, 33811, 33827, 33829, 33851, 33857, 33863, 33871, 33889, 33893, + 33911, 33923, + 33931, 33937, 33941, 33961, 33967, 33997, 34019, 34031, 34033, 34039, + 34057, 34061, + 34123, 34127, 34129, 34141, 34147, 34157, 34159, 34171, 34183, 34211, + 34213, 34217, + 34231, 34253, 34259, 34261, 34267, 34273, 34283, 34297, 34301, 34303, + 34313, 34319, + 34327, 34337, 34351, 34361, 34367, 34369, 34381, 34403, 34421, 34429, + 34439, 34457, + 34469, 34471, 34483, 34487, 34499, 34501, 34511, 34513, 34519, 34537, + 34543, 34549, + 34583, 34589, 34591, 34603, 34607, 34613, 34631, 34649, 34651, 34667, + 34673, 34679, + 34687, 34693, 34703, 34721, 34729, 34739, 34747, 34757, 34759, 34763, + 34781, 34807, + 34819, 34841, 34843, 34847, 34849, 34871, 34877, 34883, 34897, 34913, + 34919, 34939, + 34949, 34961, 34963, 34981, 35023, 35027, 35051, 35053, 35059, 35069, + 35081, 35083, + 35089, 35099, 35107, 35111, 35117, 35129, 35141, 35149, 35153, 35159, + 35171, 35201, + 35221, 35227, 35251, 35257, 35267, 35279, 35281, 35291, 35311, 35317, + 35323, 35327, + 35339, 35353, 35363, 35381, 35393, 35401, 35407, 35419, 35423, 35437, + 35447, 35449, + 35461, 35491, 35507, 35509, 35521, 35527, 35531, 35533, 35537, 35543, + 35569, 35573, + 35591, 35593, 35597, 35603, 35617, 35671, 35677, 35729, 35731, 35747, + 35753, 35759, + 35771, 35797, 35801, 35803, 35809, 35831, 35837, 35839, 35851, 35863, + 35869, 35879, + 35897, 35899, 35911, 35923, 35933, 35951, 35963, 35969, 35977, 35983, + 35993, 35999, + 36007, 36011, 36013, 36017, 36037, 36061, 36067, 36073, 36083, 36097, + 36107, 36109, + 36131, 36137, 36151, 36161, 36187, 36191, 36209, 36217, 36229, 36241, + 36251, 36263, + 36269, 36277, 36293, 36299, 36307, 36313, 36319, 36341, 36343, 36353, + 36373, 36383, + 36389, 36433, 36451, 36457, 36467, 36469, 36473, 36479, 36493, 36497, + 36523, 36527, + 36529, 36541, 36551, 36559, 36563, 36571, 36583, 36587, 36599, 36607, + 36629, 36637, + 36643, 36653, 36671, 36677, 36683, 36691, 36697, 36709, 36713, 36721, + 36739, 36749, + 36761, 36767, 36779, 36781, 36787, 36791, 36793, 36809, 36821, 36833, + 36847, 36857, + 36871, 36877, 36887, 36899, 36901, 36913, 36919, 36923, 36929, 36931, + 36943, 36947, + 36973, 36979, 36997, 37003, 37013, 37019, 37021, 37039, 37049, 37057, + 37061, 37087, + 37097, 37117, 37123, 37139, 37159, 37171, 37181, 37189, 37199, 37201, + 37217, 37223, + 37243, 37253, 37273, 37277, 37307, 37309, 37313, 37321, 37337, 37339, + 37357, 37361, + 37363, 37369, 37379, 37397, 37409, 37423, 37441, 37447, 37463, 37483, + 37489, 37493, + 37501, 37507, 37511, 37517, 37529, 37537, 37547, 37549, 37561, 37567, + 37571, 37573, + 37579, 37589, 37591, 37607, 37619, 37633, 37643, 37649, 37657, 37663, + 37691, 37693, + 37699, 37717, 37747, 37781, 37783, 37799, 37811, 37813, 37831, 37847, + 37853, 37861, + 37871, 37879, 37889, 37897, 37907, 37951, 37957, 37963, 37967, 37987, + 37991, 37993, + 37997, 38011, 38039, 38047, 38053, 38069, 38083, 38113, 38119, 38149, + 38153, 38167, + 38177, 38183, 38189, 38197, 38201, 38219, 38231, 38237, 38239, 38261, + 38273, 38281, + 38287, 38299, 38303, 38317, 38321, 38327, 38329, 38333, 38351, 38371, + 38377, 38393, + 38431, 38447, 38449, 38453, 38459, 38461, 38501, 38543, 38557, 38561, + 38567, 38569, + 38593, 38603, 38609, 38611, 38629, 38639, 38651, 38653, 38669, 38671, + 38677, 38693, + 38699, 38707, 38711, 38713, 38723, 38729, 38737, 38747, 38749, 38767, + 38783, 38791, + 38803, 38821, 38833, 38839, 38851, 38861, 38867, 38873, 38891, 38903, + 38917, 38921, + 38923, 38933, 38953, 38959, 38971, 38977, 38993, 39019, 39023, 39041, + 39043, 39047, + 39079, 39089, 39097, 39103, 39107, 39113, 39119, 39133, 39139, 39157, + 39161, 39163, + 39181, 39191, 39199, 39209, 39217, 39227, 39229, 39233, 39239, 39241, + 39251, 39293, + 39301, 39313, 39317, 39323, 39341, 39343, 39359, 39367, 39371, 39373, + 39383, 39397, + 39409, 39419, 39439, 39443, 39451, 39461, 39499, 39503, 39509, 39511, + 39521, 39541, + 39551, 39563, 39569, 39581, 39607, 39619, 39623, 39631, 39659, 39667, + 39671, 39679, + 39703, 39709, 39719, 39727, 39733, 39749, 39761, 39769, 39779, 39791, + 39799, 39821, + 39827, 39829, 39839, 39841, 39847, 39857, 39863, 39869, 39877, 39883, + 39887, 39901, + 39929, 39937, 39953, 39971, 39979, 39983, 39989, 40009, 40013, 40031, + 40037, 40039, + 40063, 40087, 40093, 40099, 40111, 40123, 40127, 40129, 40151, 40153, + 40163, 40169, + 40177, 40189, 40193, 40213, 40231, 40237, 40241, 40253, 40277, 40283, + 40289, 40343, + 40351, 40357, 40361, 40387, 40423, 40427, 40429, 40433, 40459, 40471, + 40483, 40487, + 40493, 40499, 40507, 40519, 40529, 40531, 40543, 40559, 40577, 40583, + 40591, 40597, + 40609, 40627, 40637, 40639, 40693, 40697, 40699, 40709, 40739, 40751, + 40759, 40763, + 40771, 40787, 40801, 40813, 40819, 40823, 40829, 40841, 40847, 40849, + 40853, 40867, + 40879, 40883, 40897, 40903, 40927, 40933, 40939, 40949, 40961, 40973, + 40993, 41011, + 41017, 41023, 41039, 41047, 41051, 41057, 41077, 41081, 41113, 41117, + 41131, 41141, + 41143, 41149, 41161, 41177, 41179, 41183, 41189, 41201, 41203, 41213, + 41221, 41227, + 41231, 41233, 41243, 41257, 41263, 41269, 41281, 41299, 41333, 41341, + 41351, 41357, + 41381, 41387, 41389, 41399, 41411, 41413, 41443, 41453, 41467, 41479, + 41491, 41507, + 41513, 41519, 41521, 41539, 41543, 41549, 41579, 41593, 41597, 41603, + 41609, 41611, + 41617, 41621, 41627, 41641, 41647, 41651, 41659, 41669, 41681, 41687, + 41719, 41729, + 41737, 41759, 41761, 41771, 41777, 41801, 41809, 41813, 41843, 41849, + 41851, 41863, + 41879, 41887, 41893, 41897, 41903, 41911, 41927, 41941, 41947, 41953, + 41957, 41959, + 41969, 41981, 41983, 41999, 42013, 42017, 42019, 42023, 42043, 42061, + 42071, 42073, + 42083, 42089, 42101, 42131, 42139, 42157, 42169, 42179, 42181, 42187, + 42193, 42197, + 42209, 42221, 42223, 42227, 42239, 42257, 42281, 42283, 42293, 42299, + 42307, 42323, + 42331, 42337, 42349, 42359, 42373, 42379, 42391, 42397, 42403, 42407, + 42409, 42433, + 42437, 42443, 42451, 42457, 42461, 42463, 42467, 42473, 42487, 42491, + 42499, 42509, + 42533, 42557, 42569, 42571, 42577, 42589, 42611, 42641, 42643, 42649, + 42667, 42677, + 42683, 42689, 42697, 42701, 42703, 42709, 42719, 42727, 42737, 42743, + 42751, 42767, + 42773, 42787, 42793, 42797, 42821, 42829, 42839, 42841, 42853, 42859, + 42863, 42899, + 42901, 42923, 42929, 42937, 42943, 42953, 42961, 42967, 42979, 42989, + 43003, 43013, + 43019, 43037, 43049, 43051, 43063, 43067, 43093, 43103, 43117, 43133, + 43151, 43159, + 43177, 43189, 43201, 43207, 43223, 43237, 43261, 43271, 43283, 43291, + 43313, 43319, + 43321, 43331, 43391, 43397, 43399, 43403, 43411, 43427, 43441, 43451, + 43457, 43481, + 43487, 43499, 43517, 43541, 43543, 43573, 43577, 43579, 43591, 43597, + 43607, 43609, + 43613, 43627, 43633, 43649, 43651, 43661, 43669, 43691, 43711, 43717, + 43721, 43753, + 43759, 43777, 43781, 43783, 43787, 43789, 43793, 43801, 43853, 43867, + 43889, 43891, + 43913, 43933, 43943, 43951, 43961, 43963, 43969, 43973, 43987, 43991, + 43997, 44017, + 44021, 44027, 44029, 44041, 44053, 44059, 44071, 44087, 44089, 44101, + 44111, 44119, + 44123, 44129, 44131, 44159, 44171, 44179, 44189, 44201, 44203, 44207, + 44221, 44249, + 44257, 44263, 44267, 44269, 44273, 44279, 44281, 44293, 44351, 44357, + 44371, 44381, + 44383, 44389, 44417, 44449, 44453, 44483, 44491, 44497, 44501, 44507, + 44519, 44531, + 44533, 44537, 44543, 44549, 44563, 44579, 44587, 44617, 44621, 44623, + 44633, 44641, + 44647, 44651, 44657, 44683, 44687, 44699, 44701, 44711, 44729, 44741, + 44753, 44771, + 44773, 44777, 44789, 44797, 44809, 44819, 44839, 44843, 44851, 44867, + 44879, 44887, + 44893, 44909, 44917, 44927, 44939, 44953, 44959, 44963, 44971, 44983, + 44987, 45007, + 45013, 45053, 45061, 45077, 45083, 45119, 45121, 45127, 45131, 45137, + 45139, 45161, + 45179, 45181, 45191, 45197, 45233, 45247, 45259, 45263, 45281, 45289, + 45293, 45307, + 45317, 45319, 45329, 45337, 45341, 45343, 45361, 45377, 45389, 45403, + 45413, 45427, + 45433, 45439, 45481, 45491, 45497, 45503, 45523, 45533, 45541, 45553, + 45557, 45569, + 45587, 45589, 45599, 45613, 45631, 45641, 45659, 45667, 45673, 45677, + 45691, 45697, + 45707, 45737, 45751, 45757, 45763, 45767, 45779, 45817, 45821, 45823, + 45827, 45833, + 45841, 45853, 45863, 45869, 45887, 45893, 45943, 45949, 45953, 45959, + 45971, 45979, + 45989, 46021, 46027, 46049, 46051, 46061, 46073, 46091, 46093, 46099, + 46103, 46133, + 46141, 46147, 46153, 46171, 46181, 46183, 46187, 46199, 46219, 46229, + 46237, 46261, + 46271, 46273, 46279, 46301, 46307, 46309, 46327, 46337, 46349, 46351, + 46381, 46399, + 46411, 46439, 46441, 46447, 46451, 46457, 46471, 46477, 46489, 46499, + 46507, 46511, + 46523, 46549, 46559, 46567, 46573, 46589, 46591, 46601, 46619, 46633, + 46639, 46643, + 46649, 46663, 46679, 46681, 46687, 46691, 46703, 46723, 46727, 46747, + 46751, 46757, + 46769, 46771, 46807, 46811, 46817, 46819, 46829, 46831, 46853, 46861, + 46867, 46877, + 46889, 46901, 46919, 46933, 46957, 46993, 46997, 47017, 47041, 47051, + 47057, 47059, + 47087, 47093, 47111, 47119, 47123, 47129, 47137, 47143, 47147, 47149, + 47161, 47189, + 47207, 47221, 47237, 47251, 47269, 47279, 47287, 47293, 47297, 47303, + 47309, 47317, + 47339, 47351, 47353, 47363, 47381, 47387, 47389, 47407, 47417, 47419, + 47431, 47441, + 47459, 47491, 47497, 47501, 47507, 47513, 47521, 47527, 47533, 47543, + 47563, 47569, + 47581, 47591, 47599, 47609, 47623, 47629, 47639, 47653, 47657, 47659, + 47681, 47699, + 47701, 47711, 47713, 47717, 47737, 47741, 47743, 47777, 47779, 47791, + 47797, 47807, + 47809, 47819, 47837, 47843, 47857, 47869, 47881, 47903, 47911, 47917, + 47933, 47939, + 47947, 47951, 47963, 47969, 47977, 47981, 48017, 48023, 48029, 48049, + 48073, 48079, + 48091, 48109, 48119, 48121, 48131, 48157, 48163, 48179, 48187, 48193, + 48197, 48221, + 48239, 48247, 48259, 48271, 48281, 48299, 48311, 48313, 48337, 48341, + 48353, 48371, + 48383, 48397, 48407, 48409, 48413, 48437, 48449, 48463, 48473, 48479, + 48481, 48487, + 48491, 48497, 48523, 48527, 48533, 48539, 48541, 48563, 48571, 48589, + 48593, 48611, + 48619, 48623, 48647, 48649, 48661, 48673, 48677, 48679, 48731, 48733, + 48751, 48757, + 48761, 48767, 48779, 48781, 48787, 48799, 48809, 48817, 48821, 48823, + 48847, 48857, + 48859, 48869, 48871, 48883, 48889, 48907, 48947, 48953, 48973, 48989, + 48991, 49003, + 49009, 49019, 49031, 49033, 49037, 49043, 49057, 49069, 49081, 49103, + 49109, 49117, + 49121, 49123, 49139, 49157, 49169, 49171, 49177, 49193, 49199, 49201, + 49207, 49211, + 49223, 49253, 49261, 49277, 49279, 49297, 49307, 49331, 49333, 49339, + 49363, 49367, + 49369, 49391, 49393, 49409, 49411, 49417, 49429, 49433, 49451, 49459, + 49463, 49477, + 49481, 49499, 49523, 49529, 49531, 49537, 49547, 49549, 49559, 49597, + 49603, 49613, + 49627, 49633, 49639, 49663, 49667, 49669, 49681, 49697, 49711, 49727, + 49739, 49741, + 49747, 49757, 49783, 49787, 49789, 49801, 49807, 49811, 49823, 49831, + 49843, 49853, + 49871, 49877, 49891, 49919, 49921, 49927, 49937, 49939, 49943, 49957, + 49991, 49993, + 49999, 50021, 50023, 50033, 50047, 50051, 50053, 50069, 50077, 50087, + 50093, 50101, + 50111, 50119, 50123, 50129, 50131, 50147, 50153, 50159, 50177, 50207, + 50221, 50227, + 50231, 50261, 50263, 50273, 50287, 50291, 50311, 50321, 50329, 50333, + 50341, 50359, + 50363, 50377, 50383, 50387, 50411, 50417, 50423, 50441, 50459, 50461, + 50497, 50503, + 50513, 50527, 50539, 50543, 50549, 50551, 50581, 50587, 50591, 50593, + 50599, 50627, + 50647, 50651, 50671, 50683, 50707, 50723, 50741, 50753, 50767, 50773, + 50777, 50789, + 50821, 50833, 50839, 50849, 50857, 50867, 50873, 50891, 50893, 50909, + 50923, 50929, + 50951, 50957, 50969, 50971, 50989, 50993, 51001, 51031, 51043, 51047, + 51059, 51061, + 51071, 51109, 51131, 51133, 51137, 51151, 51157, 51169, 51193, 51197, + 51199, 51203, + 51217, 51229, 51239, 51241, 51257, 51263, 51283, 51287, 51307, 51329, + 51341, 51343, + 51347, 51349, 51361, 51383, 51407, 51413, 51419, 51421, 51427, 51431, + 51437, 51439, + 51449, 51461, 51473, 51479, 51481, 51487, 51503, 51511, 51517, 51521, + 51539, 51551, + 51563, 51577, 51581, 51593, 51599, 51607, 51613, 51631, 51637, 51647, + 51659, 51673, + 51679, 51683, 51691, 51713, 51719, 51721, 51749, 51767, 51769, 51787, + 51797, 51803, + 51817, 51827, 51829, 51839, 51853, 51859, 51869, 51871, 51893, 51899, + 51907, 51913, + 51929, 51941, 51949, 51971, 51973, 51977, 51991, 52009, 52021, 52027, + 52051, 52057, + 52067, 52069, 52081, 52103, 52121, 52127, 52147, 52153, 52163, 52177, + 52181, 52183, + 52189, 52201, 52223, 52237, 52249, 52253, 52259, 52267, 52289, 52291, + 52301, 52313, + 52321, 52361, 52363, 52369, 52379, 52387, 52391, 52433, 52453, 52457, + 52489, 52501, + 52511, 52517, 52529, 52541, 52543, 52553, 52561, 52567, 52571, 52579, + 52583, 52609, + 52627, 52631, 52639, 52667, 52673, 52691, 52697, 52709, 52711, 52721, + 52727, 52733, + 52747, 52757, 52769, 52783, 52807, 52813, 52817, 52837, 52859, 52861, + 52879, 52883, + 52889, 52901, 52903, 52919, 52937, 52951, 52957, 52963, 52967, 52973, + 52981, 52999, + 53003, 53017, 53047, 53051, 53069, 53077, 53087, 53089, 53093, 53101, + 53113, 53117, + 53129, 53147, 53149, 53161, 53171, 53173, 53189, 53197, 53201, 53231, + 53233, 53239, + 53267, 53269, 53279, 53281, 53299, 53309, 53323, 53327, 53353, 53359, + 53377, 53381, + 53401, 53407, 53411, 53419, 53437, 53441, 53453, 53479, 53503, 53507, + 53527, 53549, + 53551, 53569, 53591, 53593, 53597, 53609, 53611, 53617, 53623, 53629, + 53633, 53639, + 53653, 53657, 53681, 53693, 53699, 53717, 53719, 53731, 53759, 53773, + 53777, 53783, + 53791, 53813, 53819, 53831, 53849, 53857, 53861, 53881, 53887, 53891, + 53897, 53899, + 53917, 53923, 53927, 53939, 53951, 53959, 53987, 53993, 54001, 54011, + 54013, 54037, + 54049, 54059, 54083, 54091, 54101, 54121, 54133, 54139, 54151, 54163, + 54167, 54181, + 54193, 54217, 54251, 54269, 54277, 54287, 54293, 54311, 54319, 54323, + 54331, 54347, + 54361, 54367, 54371, 54377, 54401, 54403, 54409, 54413, 54419, 54421, + 54437, 54443, + 54449, 54469, 54493, 54497, 54499, 54503, 54517, 54521, 54539, 54541, + 54547, 54559, + 54563, 54577, 54581, 54583, 54601, 54617, 54623, 54629, 54631, 54647, + 54667, 54673, + 54679, 54709, 54713, 54721, 54727, 54751, 54767, 54773, 54779, 54787, + 54799, 54829, + 54833, 54851, 54869, 54877, 54881, 54907, 54917, 54919, 54941, 54949, + 54959, 54973, + 54979, 54983, 55001, 55009, 55021, 55049, 55051, 55057, 55061, 55073, + 55079, 55103, + 55109, 55117, 55127, 55147, 55163, 55171, 55201, 55207, 55213, 55217, + 55219, 55229, + 55243, 55249, 55259, 55291, 55313, 55331, 55333, 55337, 55339, 55343, + 55351, 55373, + 55381, 55399, 55411, 55439, 55441, 55457, 55469, 55487, 55501, 55511, + 55529, 55541, + 55547, 55579, 55589, 55603, 55609, 55619, 55621, 55631, 55633, 55639, + 55661, 55663, + 55667, 55673, 55681, 55691, 55697, 55711, 55717, 55721, 55733, 55763, + 55787, 55793, + 55799, 55807, 55813, 55817, 55819, 55823, 55829, 55837, 55843, 55849, + 55871, 55889, + 55897, 55901, 55903, 55921, 55927, 55931, 55933, 55949, 55967, 55987, + 55997, 56003, + 56009, 56039, 56041, 56053, 56081, 56087, 56093, 56099, 56101, 56113, + 56123, 56131, + 56149, 56167, 56171, 56179, 56197, 56207, 56209, 56237, 56239, 56249, + 56263, 56267, + 56269, 56299, 56311, 56333, 56359, 56369, 56377, 56383, 56393, 56401, + 56417, 56431, + 56437, 56443, 56453, 56467, 56473, 56477, 56479, 56489, 56501, 56503, + 56509, 56519, + 56527, 56531, 56533, 56543, 56569, 56591, 56597, 56599, 56611, 56629, + 56633, 56659, + 56663, 56671, 56681, 56687, 56701, 56711, 56713, 56731, 56737, 56747, + 56767, 56773, + 56779, 56783, 56807, 56809, 56813, 56821, 56827, 56843, 56857, 56873, + 56891, 56893, + 56897, 56909, 56911, 56921, 56923, 56929, 56941, 56951, 56957, 56963, + 56983, 56989, + 56993, 56999, 57037, 57041, 57047, 57059, 57073, 57077, 57089, 57097, + 57107, 57119, + 57131, 57139, 57143, 57149, 57163, 57173, 57179, 57191, 57193, 57203, + 57221, 57223, + 57241, 57251, 57259, 57269, 57271, 57283, 57287, 57301, 57329, 57331, + 57347, 57349, + 57367, 57373, 57383, 57389, 57397, 57413, 57427, 57457, 57467, 57487, + 57493, 57503, + 57527, 57529, 57557, 57559, 57571, 57587, 57593, 57601, 57637, 57641, + 57649, 57653, + 57667, 57679, 57689, 57697, 57709, 57713, 57719, 57727, 57731, 57737, + 57751, 57773, + 57781, 57787, 57791, 57793, 57803, 57809, 57829, 57839, 57847, 57853, + 57859, 57881, + 57899, 57901, 57917, 57923, 57943, 57947, 57973, 57977, 57991, 58013, + 58027, 58031, + 58043, 58049, 58057, 58061, 58067, 58073, 58099, 58109, 58111, 58129, + 58147, 58151, + 58153, 58169, 58171, 58189, 58193, 58199, 58207, 58211, 58217, 58229, + 58231, 58237, + 58243, 58271, 58309, 58313, 58321, 58337, 58363, 58367, 58369, 58379, + 58391, 58393, + 58403, 58411, 58417, 58427, 58439, 58441, 58451, 58453, 58477, 58481, + 58511, 58537, + 58543, 58549, 58567, 58573, 58579, 58601, 58603, 58613, 58631, 58657, + 58661, 58679, + 58687, 58693, 58699, 58711, 58727, 58733, 58741, 58757, 58763, 58771, + 58787, 58789, + 58831, 58889, 58897, 58901, 58907, 58909, 58913, 58921, 58937, 58943, + 58963, 58967, + 58979, 58991, 58997, 59009, 59011, 59021, 59023, 59029, 59051, 59053, + 59063, 59069, + 59077, 59083, 59093, 59107, 59113, 59119, 59123, 59141, 59149, 59159, + 59167, 59183, + 59197, 59207, 59209, 59219, 59221, 59233, 59239, 59243, 59263, 59273, + 59281, 59333, + 59341, 59351, 59357, 59359, 59369, 59377, 59387, 59393, 59399, 59407, + 59417, 59419, + 59441, 59443, 59447, 59453, 59467, 59471, 59473, 59497, 59509, 59513, + 59539, 59557, + 59561, 59567, 59581, 59611, 59617, 59621, 59627, 59629, 59651, 59659, + 59663, 59669, + 59671, 59693, 59699, 59707, 59723, 59729, 59743, 59747, 59753, 59771, + 59779, 59791, + 59797, 59809, 59833, 59863, 59879, 59887, 59921, 59929, 59951, 59957, + 59971, 59981, + 59999, 60013, 60017, 60029, 60037, 60041, 60077, 60083, 60089, 60091, + 60101, 60103, + 60107, 60127, 60133, 60139, 60149, 60161, 60167, 60169, 60209, 60217, + 60223, 60251, + 60257, 60259, 60271, 60289, 60293, 60317, 60331, 60337, 60343, 60353, + 60373, 60383, + 60397, 60413, 60427, 60443, 60449, 60457, 60493, 60497, 60509, 60521, + 60527, 60539, + 60589, 60601, 60607, 60611, 60617, 60623, 60631, 60637, 60647, 60649, + 60659, 60661, + 60679, 60689, 60703, 60719, 60727, 60733, 60737, 60757, 60761, 60763, + 60773, 60779, + 60793, 60811, 60821, 60859, 60869, 60887, 60889, 60899, 60901, 60913, + 60917, 60919, + 60923, 60937, 60943, 60953, 60961, 61001, 61007, 61027, 61031, 61043, + 61051, 61057, + 61091, 61099, 61121, 61129, 61141, 61151, 61153, 61169, 61211, 61223, + 61231, 61253, + 61261, 61283, 61291, 61297, 61331, 61333, 61339, 61343, 61357, 61363, + 61379, 61381, + 61403, 61409, 61417, 61441, 61463, 61469, 61471, 61483, 61487, 61493, + 61507, 61511, + 61519, 61543, 61547, 61553, 61559, 61561, 61583, 61603, 61609, 61613, + 61627, 61631, + 61637, 61643, 61651, 61657, 61667, 61673, 61681, 61687, 61703, 61717, + 61723, 61729, + 61751, 61757, 61781, 61813, 61819, 61837, 61843, 61861, 61871, 61879, + 61909, 61927, + 61933, 61949, 61961, 61967, 61979, 61981, 61987, 61991, 62003, 62011, + 62017, 62039, + 62047, 62053, 62057, 62071, 62081, 62099, 62119, 62129, 62131, 62137, + 62141, 62143, + 62171, 62189, 62191, 62201, 62207, 62213, 62219, 62233, 62273, 62297, + 62299, 62303, + 62311, 62323, 62327, 62347, 62351, 62383, 62401, 62417, 62423, 62459, + 62467, 62473, + 62477, 62483, 62497, 62501, 62507, 62533, 62539, 62549, 62563, 62581, + 62591, 62597, + 62603, 62617, 62627, 62633, 62639, 62653, 62659, 62683, 62687, 62701, + 62723, 62731, + 62743, 62753, 62761, 62773, 62791, 62801, 62819, 62827, 62851, 62861, + 62869, 62873, + 62897, 62903, 62921, 62927, 62929, 62939, 62969, 62971, 62981, 62983, + 62987, 62989, + 63029, 63031, 63059, 63067, 63073, 63079, 63097, 63103, 63113, 63127, + 63131, 63149, + 63179, 63197, 63199, 63211, 63241, 63247, 63277, 63281, 63299, 63311, + 63313, 63317, + 63331, 63337, 63347, 63353, 63361, 63367, 63377, 63389, 63391, 63397, + 63409, 63419, + 63421, 63439, 63443, 63463, 63467, 63473, 63487, 63493, 63499, 63521, + 63527, 63533, + 63541, 63559, 63577, 63587, 63589, 63599, 63601, 63607, 63611, 63617, + 63629, 63647, + 63649, 63659, 63667, 63671, 63689, 63691, 63697, 63703, 63709, 63719, + 63727, 63737, + 63743, 63761, 63773, 63781, 63793, 63799, 63803, 63809, 63823, 63839, + 63841, 63853, + 63857, 63863, 63901, 63907, 63913, 63929, 63949, 63977, 63997, 64007, + 64013, 64019, + 64033, 64037, 64063, 64067, 64081, 64091, 64109, 64123, 64151, 64153, + 64157, 64171, + 64187, 64189, 64217, 64223, 64231, 64237, 64271, 64279, 64283, 64301, + 64303, 64319, + 64327, 64333, 64373, 64381, 64399, 64403, 64433, 64439, 64451, 64453, + 64483, 64489, + 64499, 64513, 64553, 64567, 64577, 64579, 64591, 64601, 64609, 64613, + 64621, 64627, + 64633, 64661, 64663, 64667, 64679, 64693, 64709, 64717, 64747, 64763, + 64781, 64783, + 64793, 64811, 64817, 64849, 64853, 64871, 64877, 64879, 64891, 64901, + 64919, 64921, + 64927, 64937, 64951, 64969, 64997, 65003, 65011, 65027, 65029, 65033, + 65053, 65063, + 65071, 65089, 65099, 65101, 65111, 65119, 65123, 65129, 65141, 65147, + 65167, 65171, + 65173, 65179, 65183, 65203, 65213, 65239, 65257, 65267, 65269, 65287, + 65293, 65309, + 65323, 65327, 65353, 65357, 65371, 65381, 65393, 65407, 65413, 65419, + 65423, 65437, + 65447, 65449, 65479, 65497, 65519, 65521, +}; + +#define NPRIMES (sizeof(primes) / sizeof(*primes)) + +/* + * Generate a prime. We can deal with various extra properties of + * the prime: + * + * - to speed up use in RSA, we can arrange to select a prime with + * the property (prime % modulus) != residue. + * + * - for use in DSA, we can arrange to select a prime which is one + * more than a multiple of a dirty great bignum. In this case + * `bits' gives the size of the factor by which we _multiply_ + * that bignum, rather than the size of the whole number. + */ +Bignum primegen(int bits, int modulus, int residue, Bignum factor, + int phase, progfn_t pfn, void *pfnparam) +{ + int i, k, v, byte, bitsleft, check, checks; + unsigned long delta; + unsigned long moduli[NPRIMES + 1]; + unsigned long residues[NPRIMES + 1]; + unsigned long multipliers[NPRIMES + 1]; + Bignum p, pm1, q, wqp, wqp2; + int progress = 0; + + byte = 0; + bitsleft = 0; + + STARTOVER: + + pfn(pfnparam, PROGFN_PROGRESS, phase, ++progress); + + /* + * Generate a k-bit random number with top and bottom bits set. + * Alternatively, if `factor' is nonzero, generate a k-bit + * random number with the top bit set and the bottom bit clear, + * multiply it by `factor', and add one. + */ + p = bn_power_2(bits - 1); + for (i = 0; i < bits; i++) { + if (i == 0 || i == bits - 1) + v = (i != 0 || !factor) ? 1 : 0; + else { + if (bitsleft <= 0) + bitsleft = 8, byte = random_byte(); + v = byte & 1; + byte >>= 1; + bitsleft--; + } + bignum_set_bit(p, i, v); + } + if (factor) { + Bignum tmp = p; + p = bigmul(tmp, factor); + freebn(tmp); + assert(bignum_bit(p, 0) == 0); + bignum_set_bit(p, 0, 1); + } + + /* + * Ensure this random number is coprime to the first few + * primes, by repeatedly adding either 2 or 2*factor to it + * until it is. + */ + for (i = 0; i < NPRIMES; i++) { + moduli[i] = primes[i]; + residues[i] = bignum_mod_short(p, primes[i]); + if (factor) + multipliers[i] = bignum_mod_short(factor, primes[i]); + else + multipliers[i] = 1; + } + moduli[NPRIMES] = modulus; + residues[NPRIMES] = (bignum_mod_short(p, (unsigned short) modulus) + + modulus - residue); + if (factor) + multipliers[NPRIMES] = bignum_mod_short(factor, modulus); + else + multipliers[NPRIMES] = 1; + delta = 0; + while (1) { + for (i = 0; i < (sizeof(moduli) / sizeof(*moduli)); i++) + if (!((residues[i] + delta * multipliers[i]) % moduli[i])) + break; + if (i < (sizeof(moduli) / sizeof(*moduli))) { /* we broke */ + delta += 2; + if (delta > 65536) { + freebn(p); + goto STARTOVER; + } + continue; + } + break; + } + q = p; + if (factor) { + Bignum tmp; + tmp = bignum_from_long(delta); + p = bigmuladd(tmp, factor, q); + freebn(tmp); + } else { + p = bignum_add_long(q, delta); + } + freebn(q); + + /* + * Now apply the Miller-Rabin primality test a few times. First + * work out how many checks are needed. + */ + checks = 27; + if (bits >= 150) + checks = 18; + if (bits >= 200) + checks = 15; + if (bits >= 250) + checks = 12; + if (bits >= 300) + checks = 9; + if (bits >= 350) + checks = 8; + if (bits >= 400) + checks = 7; + if (bits >= 450) + checks = 6; + if (bits >= 550) + checks = 5; + if (bits >= 650) + checks = 4; + if (bits >= 850) + checks = 3; + if (bits >= 1300) + checks = 2; + + /* + * Next, write p-1 as q*2^k. + */ + for (k = 0; bignum_bit(p, k) == !k; k++) + continue; /* find first 1 bit in p-1 */ + q = bignum_rshift(p, k); + /* And store p-1 itself, which we'll need. */ + pm1 = copybn(p); + decbn(pm1); + + /* + * Now, for each check ... + */ + for (check = 0; check < checks; check++) { + Bignum w; + + /* + * Invent a random number between 1 and p-1 inclusive. + */ + while (1) { + w = bn_power_2(bits - 1); + for (i = 0; i < bits; i++) { + if (bitsleft <= 0) + bitsleft = 8, byte = random_byte(); + v = byte & 1; + byte >>= 1; + bitsleft--; + bignum_set_bit(w, i, v); + } + bn_restore_invariant(w); + if (bignum_cmp(w, p) >= 0 || bignum_cmp(w, Zero) == 0) { + freebn(w); + continue; + } + break; + } + + pfn(pfnparam, PROGFN_PROGRESS, phase, ++progress); + + /* + * Compute w^q mod p. + */ + wqp = modpow(w, q, p); + freebn(w); + + /* + * See if this is 1, or if it is -1, or if it becomes -1 + * when squared at most k-1 times. + */ + if (bignum_cmp(wqp, One) == 0 || bignum_cmp(wqp, pm1) == 0) { + freebn(wqp); + continue; + } + for (i = 0; i < k - 1; i++) { + wqp2 = modmul(wqp, wqp, p); + freebn(wqp); + wqp = wqp2; + if (bignum_cmp(wqp, pm1) == 0) + break; + } + if (i < k - 1) { + freebn(wqp); + continue; + } + + /* + * It didn't. Therefore, w is a witness for the + * compositeness of p. + */ + freebn(wqp); + freebn(p); + freebn(pm1); + freebn(q); + goto STARTOVER; + } + + /* + * We have a prime! + */ + freebn(q); + freebn(pm1); + return p; +} diff --git a/putty/SSHPUBK.C b/putty/SSHPUBK.C new file mode 100644 index 0000000..7b5a690 --- /dev/null +++ b/putty/SSHPUBK.C @@ -0,0 +1,1217 @@ +/* + * Generic SSH public-key handling operations. In particular, + * reading of SSH public-key files, and also the generic `sign' + * operation for SSH-2 (which checks the type of the key and + * dispatches to the appropriate key-type specific function). + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "misc.h" + +#define rsa_signature "SSH PRIVATE KEY FILE FORMAT 1.1\n" + +#define BASE64_TOINT(x) ( (x)-'A'<26 ? (x)-'A'+0 :\ + (x)-'a'<26 ? (x)-'a'+26 :\ + (x)-'0'<10 ? (x)-'0'+52 :\ + (x)=='+' ? 62 : \ + (x)=='/' ? 63 : 0 ) + +static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only, + char **commentptr, char *passphrase, + const char **error) +{ + unsigned char buf[16384]; + unsigned char keybuf[16]; + int len; + int i, j, ciphertype; + int ret = 0; + struct MD5Context md5c; + char *comment; + + *error = NULL; + + /* Slurp the whole file (minus the header) into a buffer. */ + len = fread(buf, 1, sizeof(buf), fp); + fclose(fp); + if (len < 0 || len == sizeof(buf)) { + *error = "error reading file"; + goto end; /* file too big or not read */ + } + + i = 0; + *error = "file format error"; + + /* + * A zero byte. (The signature includes a terminating NUL.) + */ + if (len - i < 1 || buf[i] != 0) + goto end; + i++; + + /* One byte giving encryption type, and one reserved uint32. */ + if (len - i < 1) + goto end; + ciphertype = buf[i]; + if (ciphertype != 0 && ciphertype != SSH_CIPHER_3DES) + goto end; + i++; + if (len - i < 4) + goto end; /* reserved field not present */ + if (buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0 + || buf[i + 3] != 0) goto end; /* reserved field nonzero, panic! */ + i += 4; + + /* Now the serious stuff. An ordinary SSH-1 public key. */ + i += makekey(buf + i, len, key, NULL, 1); + if (i < 0) + goto end; /* overran */ + + /* Next, the comment field. */ + j = GET_32BIT(buf + i); + i += 4; + if (len - i < j) + goto end; + comment = snewn(j + 1, char); + if (comment) { + memcpy(comment, buf + i, j); + comment[j] = '\0'; + } + i += j; + if (commentptr) + *commentptr = dupstr(comment); + if (key) + key->comment = comment; + else + sfree(comment); + + if (pub_only) { + ret = 1; + goto end; + } + + if (!key) { + ret = ciphertype != 0; + *error = NULL; + goto end; + } + + /* + * Decrypt remainder of buffer. + */ + if (ciphertype) { + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Final(keybuf, &md5c); + des3_decrypt_pubkey(keybuf, buf + i, (len - i + 7) & ~7); + memset(keybuf, 0, sizeof(keybuf)); /* burn the evidence */ + } + + /* + * We are now in the secret part of the key. The first four + * bytes should be of the form a, b, a, b. + */ + if (len - i < 4) + goto end; + if (buf[i] != buf[i + 2] || buf[i + 1] != buf[i + 3]) { + *error = "wrong passphrase"; + ret = -1; + goto end; + } + i += 4; + + /* + * After that, we have one further bignum which is our + * decryption exponent, and then the three auxiliary values + * (iqmp, q, p). + */ + j = makeprivate(buf + i, len - i, key); + if (j < 0) goto end; + i += j; + j = ssh1_read_bignum(buf + i, len - i, &key->iqmp); + if (j < 0) goto end; + i += j; + j = ssh1_read_bignum(buf + i, len - i, &key->q); + if (j < 0) goto end; + i += j; + j = ssh1_read_bignum(buf + i, len - i, &key->p); + if (j < 0) goto end; + i += j; + + if (!rsa_verify(key)) { + *error = "rsa_verify failed"; + freersakey(key); + ret = 0; + } else + ret = 1; + + end: + memset(buf, 0, sizeof(buf)); /* burn the evidence */ + return ret; +} + +int loadrsakey(const Filename *filename, struct RSAKey *key, char *passphrase, + const char **errorstr) +{ + FILE *fp; + char buf[64]; + int ret = 0; + const char *error = NULL; + + fp = f_open(*filename, "rb", FALSE); + if (!fp) { + error = "can't open file"; + goto end; + } + + /* + * Read the first line of the file and see if it's a v1 private + * key file. + */ + if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) { + /* + * This routine will take care of calling fclose() for us. + */ + ret = loadrsakey_main(fp, key, FALSE, NULL, passphrase, &error); + fp = NULL; + goto end; + } + + /* + * Otherwise, we have nothing. Return empty-handed. + */ + error = "not an SSH-1 RSA file"; + + end: + if (fp) + fclose(fp); + if ((ret != 1) && errorstr) + *errorstr = error; + return ret; +} + +/* + * See whether an RSA key is encrypted. Return its comment field as + * well. + */ +int rsakey_encrypted(const Filename *filename, char **comment) +{ + FILE *fp; + char buf[64]; + + fp = f_open(*filename, "rb", FALSE); + if (!fp) + return 0; /* doesn't even exist */ + + /* + * Read the first line of the file and see if it's a v1 private + * key file. + */ + if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) { + const char *dummy; + /* + * This routine will take care of calling fclose() for us. + */ + return loadrsakey_main(fp, NULL, FALSE, comment, NULL, &dummy); + } + fclose(fp); + return 0; /* wasn't the right kind of file */ +} + +/* + * Return a malloc'ed chunk of memory containing the public blob of + * an RSA key, as given in the agent protocol (modulus bits, + * exponent, modulus). + */ +int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen, + char **commentptr, const char **errorstr) +{ + FILE *fp; + char buf[64]; + struct RSAKey key; + int ret; + const char *error = NULL; + + /* Default return if we fail. */ + *blob = NULL; + *bloblen = 0; + ret = 0; + + fp = f_open(*filename, "rb", FALSE); + if (!fp) { + error = "can't open file"; + goto end; + } + + /* + * Read the first line of the file and see if it's a v1 private + * key file. + */ + if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) { + memset(&key, 0, sizeof(key)); + if (loadrsakey_main(fp, &key, TRUE, commentptr, NULL, &error)) { + *blob = rsa_public_blob(&key, bloblen); + freersakey(&key); + ret = 1; + fp = NULL; + } + } else { + error = "not an SSH-1 RSA file"; + } + + end: + if (fp) + fclose(fp); + if ((ret != 1) && errorstr) + *errorstr = error; + return ret; +} + +/* + * Save an RSA key file. Return nonzero on success. + */ +int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase) +{ + unsigned char buf[16384]; + unsigned char keybuf[16]; + struct MD5Context md5c; + unsigned char *p, *estart; + FILE *fp; + + /* + * Write the initial signature. + */ + p = buf; + memcpy(p, rsa_signature, sizeof(rsa_signature)); + p += sizeof(rsa_signature); + + /* + * One byte giving encryption type, and one reserved (zero) + * uint32. + */ + *p++ = (passphrase ? SSH_CIPHER_3DES : 0); + PUT_32BIT(p, 0); + p += 4; + + /* + * An ordinary SSH-1 public key consists of: a uint32 + * containing the bit count, then two bignums containing the + * modulus and exponent respectively. + */ + PUT_32BIT(p, bignum_bitcount(key->modulus)); + p += 4; + p += ssh1_write_bignum(p, key->modulus); + p += ssh1_write_bignum(p, key->exponent); + + /* + * A string containing the comment field. + */ + if (key->comment) { + PUT_32BIT(p, strlen(key->comment)); + p += 4; + memcpy(p, key->comment, strlen(key->comment)); + p += strlen(key->comment); + } else { + PUT_32BIT(p, 0); + p += 4; + } + + /* + * The encrypted portion starts here. + */ + estart = p; + + /* + * Two bytes, then the same two bytes repeated. + */ + *p++ = random_byte(); + *p++ = random_byte(); + p[0] = p[-2]; + p[1] = p[-1]; + p += 2; + + /* + * Four more bignums: the decryption exponent, then iqmp, then + * q, then p. + */ + p += ssh1_write_bignum(p, key->private_exponent); + p += ssh1_write_bignum(p, key->iqmp); + p += ssh1_write_bignum(p, key->q); + p += ssh1_write_bignum(p, key->p); + + /* + * Now write zeros until the encrypted portion is a multiple of + * 8 bytes. + */ + while ((p - estart) % 8) + *p++ = '\0'; + + /* + * Now encrypt the encrypted portion. + */ + if (passphrase) { + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + MD5Final(keybuf, &md5c); + des3_encrypt_pubkey(keybuf, estart, p - estart); + memset(keybuf, 0, sizeof(keybuf)); /* burn the evidence */ + } + + /* + * Done. Write the result to the file. + */ + fp = f_open(*filename, "wb", TRUE); + if (fp) { + int ret = (fwrite(buf, 1, p - buf, fp) == (size_t) (p - buf)); + if (fclose(fp)) + ret = 0; + return ret; + } else + return 0; +} + +/* ---------------------------------------------------------------------- + * SSH-2 private key load/store functions. + */ + +/* + * PuTTY's own format for SSH-2 keys is as follows: + * + * The file is text. Lines are terminated by CRLF, although CR-only + * and LF-only are tolerated on input. + * + * The first line says "PuTTY-User-Key-File-2: " plus the name of the + * algorithm ("ssh-dss", "ssh-rsa" etc). + * + * The next line says "Encryption: " plus an encryption type. + * Currently the only supported encryption types are "aes256-cbc" + * and "none". + * + * The next line says "Comment: " plus the comment string. + * + * Next there is a line saying "Public-Lines: " plus a number N. + * The following N lines contain a base64 encoding of the public + * part of the key. This is encoded as the standard SSH-2 public key + * blob (with no initial length): so for RSA, for example, it will + * read + * + * string "ssh-rsa" + * mpint exponent + * mpint modulus + * + * Next, there is a line saying "Private-Lines: " plus a number N, + * and then N lines containing the (potentially encrypted) private + * part of the key. For the key type "ssh-rsa", this will be + * composed of + * + * mpint private_exponent + * mpint p (the larger of the two primes) + * mpint q (the smaller prime) + * mpint iqmp (the inverse of q modulo p) + * data padding (to reach a multiple of the cipher block size) + * + * And for "ssh-dss", it will be composed of + * + * mpint x (the private key parameter) + * [ string hash 20-byte hash of mpints p || q || g only in old format ] + * + * Finally, there is a line saying "Private-MAC: " plus a hex + * representation of a HMAC-SHA-1 of: + * + * string name of algorithm ("ssh-dss", "ssh-rsa") + * string encryption type + * string comment + * string public-blob + * string private-plaintext (the plaintext version of the + * private part, including the final + * padding) + * + * The key to the MAC is itself a SHA-1 hash of: + * + * data "putty-private-key-file-mac-key" + * data passphrase + * + * (An empty passphrase is used for unencrypted keys.) + * + * If the key is encrypted, the encryption key is derived from the + * passphrase by means of a succession of SHA-1 hashes. Each hash + * is the hash of: + * + * uint32 sequence-number + * data passphrase + * + * where the sequence-number increases from zero. As many of these + * hashes are used as necessary. + * + * For backwards compatibility with snapshots between 0.51 and + * 0.52, we also support the older key file format, which begins + * with "PuTTY-User-Key-File-1" (version number differs). In this + * format the Private-MAC: field only covers the private-plaintext + * field and nothing else (and without the 4-byte string length on + * the front too). Moreover, the Private-MAC: field can be replaced + * with a Private-Hash: field which is a plain SHA-1 hash instead of + * an HMAC (this was generated for unencrypted keys). + */ + +static int read_header(FILE * fp, char *header) +{ + int len = 39; + int c; + + while (len > 0) { + c = fgetc(fp); + if (c == '\n' || c == '\r' || c == EOF) + return 0; /* failure */ + if (c == ':') { + c = fgetc(fp); + if (c != ' ') + return 0; + *header = '\0'; + return 1; /* success! */ + } + if (len == 0) + return 0; /* failure */ + *header++ = c; + len--; + } + return 0; /* failure */ +} + +static char *read_body(FILE * fp) +{ + char *text; + int len; + int size; + int c; + + size = 128; + text = snewn(size, char); + len = 0; + text[len] = '\0'; + + while (1) { + c = fgetc(fp); + if (c == '\r' || c == '\n' || c == EOF) { + if (c != EOF) { + c = fgetc(fp); + if (c != '\r' && c != '\n') + ungetc(c, fp); + } + return text; + } + if (len + 1 >= size) { + size += 128; + text = sresize(text, size, char); + } + text[len++] = c; + text[len] = '\0'; + } +} + +int base64_decode_atom(char *atom, unsigned char *out) +{ + int vals[4]; + int i, v, len; + unsigned word; + char c; + + for (i = 0; i < 4; i++) { + c = atom[i]; + if (c >= 'A' && c <= 'Z') + v = c - 'A'; + else if (c >= 'a' && c <= 'z') + v = c - 'a' + 26; + else if (c >= '0' && c <= '9') + v = c - '0' + 52; + else if (c == '+') + v = 62; + else if (c == '/') + v = 63; + else if (c == '=') + v = -1; + else + return 0; /* invalid atom */ + vals[i] = v; + } + + if (vals[0] == -1 || vals[1] == -1) + return 0; + if (vals[2] == -1 && vals[3] != -1) + return 0; + + if (vals[3] != -1) + len = 3; + else if (vals[2] != -1) + len = 2; + else + len = 1; + + word = ((vals[0] << 18) | + (vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F)); + out[0] = (word >> 16) & 0xFF; + if (len > 1) + out[1] = (word >> 8) & 0xFF; + if (len > 2) + out[2] = word & 0xFF; + return len; +} + +static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen) +{ + unsigned char *blob; + char *line; + int linelen, len; + int i, j, k; + + /* We expect at most 64 base64 characters, ie 48 real bytes, per line. */ + blob = snewn(48 * nlines, unsigned char); + len = 0; + for (i = 0; i < nlines; i++) { + line = read_body(fp); + if (!line) { + sfree(blob); + return NULL; + } + linelen = strlen(line); + if (linelen % 4 != 0 || linelen > 64) { + sfree(blob); + sfree(line); + return NULL; + } + for (j = 0; j < linelen; j += 4) { + k = base64_decode_atom(line + j, blob + len); + if (!k) { + sfree(line); + sfree(blob); + return NULL; + } + len += k; + } + sfree(line); + } + *bloblen = len; + return blob; +} + +/* + * Magic error return value for when the passphrase is wrong. + */ +struct ssh2_userkey ssh2_wrong_passphrase = { + NULL, NULL, NULL +}; + +const struct ssh_signkey *find_pubkey_alg(const char *name) +{ + if (!strcmp(name, "ssh-rsa")) + return &ssh_rsa; + else if (!strcmp(name, "ssh-dss")) + return &ssh_dss; + else + return NULL; +} + +struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, + char *passphrase, const char **errorstr) +{ + FILE *fp; + char header[40], *b, *encryption, *comment, *mac; + const struct ssh_signkey *alg; + struct ssh2_userkey *ret; + int cipher, cipherblk; + unsigned char *public_blob, *private_blob; + int public_blob_len, private_blob_len; + int i, is_mac, old_fmt; + int passlen = passphrase ? strlen(passphrase) : 0; + const char *error = NULL; + + ret = NULL; /* return NULL for most errors */ + encryption = comment = mac = NULL; + public_blob = private_blob = NULL; + + fp = f_open(*filename, "rb", FALSE); + if (!fp) { + error = "can't open file"; + goto error; + } + + /* Read the first header line which contains the key type. */ + if (!read_header(fp, header)) + goto error; + if (0 == strcmp(header, "PuTTY-User-Key-File-2")) { + old_fmt = 0; + } else if (0 == strcmp(header, "PuTTY-User-Key-File-1")) { + /* this is an old key file; warn and then continue */ + old_keyfile_warning(); + old_fmt = 1; + } else { + error = "not a PuTTY SSH-2 private key"; + goto error; + } + error = "file format error"; + if ((b = read_body(fp)) == NULL) + goto error; + /* Select key algorithm structure. */ + alg = find_pubkey_alg(b); + if (!alg) { + sfree(b); + goto error; + } + sfree(b); + + /* Read the Encryption header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) + goto error; + if ((encryption = read_body(fp)) == NULL) + goto error; + if (!strcmp(encryption, "aes256-cbc")) { + cipher = 1; + cipherblk = 16; + } else if (!strcmp(encryption, "none")) { + cipher = 0; + cipherblk = 1; + } else { + sfree(encryption); + goto error; + } + + /* Read the Comment header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Comment")) + goto error; + if ((comment = read_body(fp)) == NULL) + goto error; + + /* Read the Public-Lines header line and the public blob. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Public-Lines")) + goto error; + if ((b = read_body(fp)) == NULL) + goto error; + i = atoi(b); + sfree(b); + if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL) + goto error; + + /* Read the Private-Lines header line and the Private blob. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Private-Lines")) + goto error; + if ((b = read_body(fp)) == NULL) + goto error; + i = atoi(b); + sfree(b); + if ((private_blob = read_blob(fp, i, &private_blob_len)) == NULL) + goto error; + + /* Read the Private-MAC or Private-Hash header line. */ + if (!read_header(fp, header)) + goto error; + if (0 == strcmp(header, "Private-MAC")) { + if ((mac = read_body(fp)) == NULL) + goto error; + is_mac = 1; + } else if (0 == strcmp(header, "Private-Hash") && old_fmt) { + if ((mac = read_body(fp)) == NULL) + goto error; + is_mac = 0; + } else + goto error; + + fclose(fp); + fp = NULL; + + /* + * Decrypt the private blob. + */ + if (cipher) { + unsigned char key[40]; + SHA_State s; + + if (!passphrase) + goto error; + if (private_blob_len % cipherblk) + goto error; + + SHA_Init(&s); + SHA_Bytes(&s, "\0\0\0\0", 4); + SHA_Bytes(&s, passphrase, passlen); + SHA_Final(&s, key + 0); + SHA_Init(&s); + SHA_Bytes(&s, "\0\0\0\1", 4); + SHA_Bytes(&s, passphrase, passlen); + SHA_Final(&s, key + 20); + aes256_decrypt_pubkey(key, private_blob, private_blob_len); + } + + /* + * Verify the MAC. + */ + { + char realmac[41]; + unsigned char binary[20]; + unsigned char *macdata; + int maclen; + int free_macdata; + + if (old_fmt) { + /* MAC (or hash) only covers the private blob. */ + macdata = private_blob; + maclen = private_blob_len; + free_macdata = 0; + } else { + unsigned char *p; + int namelen = strlen(alg->name); + int enclen = strlen(encryption); + int commlen = strlen(comment); + maclen = (4 + namelen + + 4 + enclen + + 4 + commlen + + 4 + public_blob_len + + 4 + private_blob_len); + macdata = snewn(maclen, unsigned char); + p = macdata; +#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len) + DO_STR(alg->name, namelen); + DO_STR(encryption, enclen); + DO_STR(comment, commlen); + DO_STR(public_blob, public_blob_len); + DO_STR(private_blob, private_blob_len); + + free_macdata = 1; + } + + if (is_mac) { + SHA_State s; + unsigned char mackey[20]; + char header[] = "putty-private-key-file-mac-key"; + + SHA_Init(&s); + SHA_Bytes(&s, header, sizeof(header)-1); + if (cipher && passphrase) + SHA_Bytes(&s, passphrase, passlen); + SHA_Final(&s, mackey); + + hmac_sha1_simple(mackey, 20, macdata, maclen, binary); + + memset(mackey, 0, sizeof(mackey)); + memset(&s, 0, sizeof(s)); + } else { + SHA_Simple(macdata, maclen, binary); + } + + if (free_macdata) { + memset(macdata, 0, maclen); + sfree(macdata); + } + + for (i = 0; i < 20; i++) + sprintf(realmac + 2 * i, "%02x", binary[i]); + + if (strcmp(mac, realmac)) { + /* An incorrect MAC is an unconditional Error if the key is + * unencrypted. Otherwise, it means Wrong Passphrase. */ + if (cipher) { + error = "wrong passphrase"; + ret = SSH2_WRONG_PASSPHRASE; + } else { + error = "MAC failed"; + ret = NULL; + } + goto error; + } + } + sfree(mac); + + /* + * Create and return the key. + */ + ret = snew(struct ssh2_userkey); + ret->alg = alg; + ret->comment = comment; + ret->data = alg->createkey(public_blob, public_blob_len, + private_blob, private_blob_len); + if (!ret->data) { + sfree(ret->comment); + sfree(ret); + ret = NULL; + error = "createkey failed"; + goto error; + } + sfree(public_blob); + sfree(private_blob); + sfree(encryption); + if (errorstr) + *errorstr = NULL; + return ret; + + /* + * Error processing. + */ + error: + if (fp) + fclose(fp); + if (comment) + sfree(comment); + if (encryption) + sfree(encryption); + if (mac) + sfree(mac); + if (public_blob) + sfree(public_blob); + if (private_blob) + sfree(private_blob); + if (errorstr) + *errorstr = error; + return ret; +} + +unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm, + int *pub_blob_len, char **commentptr, + const char **errorstr) +{ + FILE *fp; + char header[40], *b; + const struct ssh_signkey *alg; + unsigned char *public_blob; + int public_blob_len; + int i; + const char *error = NULL; + char *comment; + + public_blob = NULL; + + fp = f_open(*filename, "rb", FALSE); + if (!fp) { + error = "can't open file"; + goto error; + } + + /* Read the first header line which contains the key type. */ + if (!read_header(fp, header) + || (0 != strcmp(header, "PuTTY-User-Key-File-2") && + 0 != strcmp(header, "PuTTY-User-Key-File-1"))) { + error = "not a PuTTY SSH-2 private key"; + goto error; + } + error = "file format error"; + if ((b = read_body(fp)) == NULL) + goto error; + /* Select key algorithm structure. */ + alg = find_pubkey_alg(b); + if (!alg) { + sfree(b); + goto error; + } + sfree(b); + + /* Read the Encryption header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) + goto error; + if ((b = read_body(fp)) == NULL) + goto error; + sfree(b); /* we don't care */ + + /* Read the Comment header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Comment")) + goto error; + if ((comment = read_body(fp)) == NULL) + goto error; + + if (commentptr) + *commentptr = comment; + else + sfree(comment); + + /* Read the Public-Lines header line and the public blob. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Public-Lines")) + goto error; + if ((b = read_body(fp)) == NULL) + goto error; + i = atoi(b); + sfree(b); + if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL) + goto error; + + fclose(fp); + if (pub_blob_len) + *pub_blob_len = public_blob_len; + if (algorithm) + *algorithm = alg->name; + return public_blob; + + /* + * Error processing. + */ + error: + if (fp) + fclose(fp); + if (public_blob) + sfree(public_blob); + if (errorstr) + *errorstr = error; + return NULL; +} + +int ssh2_userkey_encrypted(const Filename *filename, char **commentptr) +{ + FILE *fp; + char header[40], *b, *comment; + int ret; + + if (commentptr) + *commentptr = NULL; + + fp = f_open(*filename, "rb", FALSE); + if (!fp) + return 0; + if (!read_header(fp, header) + || (0 != strcmp(header, "PuTTY-User-Key-File-2") && + 0 != strcmp(header, "PuTTY-User-Key-File-1"))) { + fclose(fp); + return 0; + } + if ((b = read_body(fp)) == NULL) { + fclose(fp); + return 0; + } + sfree(b); /* we don't care about key type here */ + /* Read the Encryption header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) { + fclose(fp); + return 0; + } + if ((b = read_body(fp)) == NULL) { + fclose(fp); + return 0; + } + + /* Read the Comment header line. */ + if (!read_header(fp, header) || 0 != strcmp(header, "Comment")) { + fclose(fp); + sfree(b); + return 1; + } + if ((comment = read_body(fp)) == NULL) { + fclose(fp); + sfree(b); + return 1; + } + + if (commentptr) + *commentptr = comment; + + fclose(fp); + if (!strcmp(b, "aes256-cbc")) + ret = 1; + else + ret = 0; + sfree(b); + return ret; +} + +int base64_lines(int datalen) +{ + /* When encoding, we use 64 chars/line, which equals 48 real chars. */ + return (datalen + 47) / 48; +} + +void base64_encode(FILE * fp, unsigned char *data, int datalen, int cpl) +{ + int linelen = 0; + char out[4]; + int n, i; + + while (datalen > 0) { + n = (datalen < 3 ? datalen : 3); + base64_encode_atom(data, n, out); + data += n; + datalen -= n; + for (i = 0; i < 4; i++) { + if (linelen >= cpl) { + linelen = 0; + fputc('\n', fp); + } + fputc(out[i], fp); + linelen++; + } + } + fputc('\n', fp); +} + +int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, + char *passphrase) +{ + FILE *fp; + unsigned char *pub_blob, *priv_blob, *priv_blob_encrypted; + int pub_blob_len, priv_blob_len, priv_encrypted_len; + int passlen; + int cipherblk; + int i; + char *cipherstr; + unsigned char priv_mac[20]; + + /* + * Fetch the key component blobs. + */ + pub_blob = key->alg->public_blob(key->data, &pub_blob_len); + priv_blob = key->alg->private_blob(key->data, &priv_blob_len); + if (!pub_blob || !priv_blob) { + sfree(pub_blob); + sfree(priv_blob); + return 0; + } + + /* + * Determine encryption details, and encrypt the private blob. + */ + if (passphrase) { + cipherstr = "aes256-cbc"; + cipherblk = 16; + } else { + cipherstr = "none"; + cipherblk = 1; + } + priv_encrypted_len = priv_blob_len + cipherblk - 1; + priv_encrypted_len -= priv_encrypted_len % cipherblk; + priv_blob_encrypted = snewn(priv_encrypted_len, unsigned char); + memset(priv_blob_encrypted, 0, priv_encrypted_len); + memcpy(priv_blob_encrypted, priv_blob, priv_blob_len); + /* Create padding based on the SHA hash of the unpadded blob. This prevents + * too easy a known-plaintext attack on the last block. */ + SHA_Simple(priv_blob, priv_blob_len, priv_mac); + assert(priv_encrypted_len - priv_blob_len < 20); + memcpy(priv_blob_encrypted + priv_blob_len, priv_mac, + priv_encrypted_len - priv_blob_len); + + /* Now create the MAC. */ + { + unsigned char *macdata; + int maclen; + unsigned char *p; + int namelen = strlen(key->alg->name); + int enclen = strlen(cipherstr); + int commlen = strlen(key->comment); + SHA_State s; + unsigned char mackey[20]; + char header[] = "putty-private-key-file-mac-key"; + + maclen = (4 + namelen + + 4 + enclen + + 4 + commlen + + 4 + pub_blob_len + + 4 + priv_encrypted_len); + macdata = snewn(maclen, unsigned char); + p = macdata; +#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len) + DO_STR(key->alg->name, namelen); + DO_STR(cipherstr, enclen); + DO_STR(key->comment, commlen); + DO_STR(pub_blob, pub_blob_len); + DO_STR(priv_blob_encrypted, priv_encrypted_len); + + SHA_Init(&s); + SHA_Bytes(&s, header, sizeof(header)-1); + if (passphrase) + SHA_Bytes(&s, passphrase, strlen(passphrase)); + SHA_Final(&s, mackey); + hmac_sha1_simple(mackey, 20, macdata, maclen, priv_mac); + memset(macdata, 0, maclen); + sfree(macdata); + memset(mackey, 0, sizeof(mackey)); + memset(&s, 0, sizeof(s)); + } + + if (passphrase) { + unsigned char key[40]; + SHA_State s; + + passlen = strlen(passphrase); + + SHA_Init(&s); + SHA_Bytes(&s, "\0\0\0\0", 4); + SHA_Bytes(&s, passphrase, passlen); + SHA_Final(&s, key + 0); + SHA_Init(&s); + SHA_Bytes(&s, "\0\0\0\1", 4); + SHA_Bytes(&s, passphrase, passlen); + SHA_Final(&s, key + 20); + aes256_encrypt_pubkey(key, priv_blob_encrypted, + priv_encrypted_len); + + memset(key, 0, sizeof(key)); + memset(&s, 0, sizeof(s)); + } + + fp = f_open(*filename, "w", TRUE); + if (!fp) + return 0; + fprintf(fp, "PuTTY-User-Key-File-2: %s\n", key->alg->name); + fprintf(fp, "Encryption: %s\n", cipherstr); + fprintf(fp, "Comment: %s\n", key->comment); + fprintf(fp, "Public-Lines: %d\n", base64_lines(pub_blob_len)); + base64_encode(fp, pub_blob, pub_blob_len, 64); + fprintf(fp, "Private-Lines: %d\n", base64_lines(priv_encrypted_len)); + base64_encode(fp, priv_blob_encrypted, priv_encrypted_len, 64); + fprintf(fp, "Private-MAC: "); + for (i = 0; i < 20; i++) + fprintf(fp, "%02x", priv_mac[i]); + fprintf(fp, "\n"); + fclose(fp); + + sfree(pub_blob); + memset(priv_blob, 0, priv_blob_len); + sfree(priv_blob); + sfree(priv_blob_encrypted); + return 1; +} + +/* ---------------------------------------------------------------------- + * A function to determine the type of a private key file. Returns + * 0 on failure, 1 or 2 on success. + */ +int key_type(const Filename *filename) +{ + FILE *fp; + char buf[32]; + const char putty2_sig[] = "PuTTY-User-Key-File-"; + const char sshcom_sig[] = "---- BEGIN SSH2 ENCRYPTED PRIVAT"; + const char openssh_sig[] = "-----BEGIN "; + int i; + + fp = f_open(*filename, "r", FALSE); + if (!fp) + return SSH_KEYTYPE_UNOPENABLE; + i = fread(buf, 1, sizeof(buf), fp); + fclose(fp); + if (i < 0) + return SSH_KEYTYPE_UNOPENABLE; + if (i < 32) + return SSH_KEYTYPE_UNKNOWN; + if (!memcmp(buf, rsa_signature, sizeof(rsa_signature)-1)) + return SSH_KEYTYPE_SSH1; + if (!memcmp(buf, putty2_sig, sizeof(putty2_sig)-1)) + return SSH_KEYTYPE_SSH2; + if (!memcmp(buf, openssh_sig, sizeof(openssh_sig)-1)) + return SSH_KEYTYPE_OPENSSH; + if (!memcmp(buf, sshcom_sig, sizeof(sshcom_sig)-1)) + return SSH_KEYTYPE_SSHCOM; + return SSH_KEYTYPE_UNKNOWN; /* unrecognised or EOF */ +} + +/* + * Convert the type word to a string, for `wrong type' error + * messages. + */ +char *key_type_to_str(int type) +{ + switch (type) { + case SSH_KEYTYPE_UNOPENABLE: return "unable to open file"; break; + case SSH_KEYTYPE_UNKNOWN: return "not a private key"; break; + case SSH_KEYTYPE_SSH1: return "SSH-1 private key"; break; + case SSH_KEYTYPE_SSH2: return "PuTTY SSH-2 private key"; break; + case SSH_KEYTYPE_OPENSSH: return "OpenSSH SSH-2 private key"; break; + case SSH_KEYTYPE_SSHCOM: return "ssh.com SSH-2 private key"; break; + default: return "INTERNAL ERROR"; break; + } +} diff --git a/putty/SSHRAND.C b/putty/SSHRAND.C new file mode 100644 index 0000000..91d9b37 --- /dev/null +++ b/putty/SSHRAND.C @@ -0,0 +1,251 @@ +/* + * cryptographic random number generator for PuTTY's ssh client + */ + +#include "putty.h" +#include "ssh.h" +#include + +/* Collect environmental noise every 5 minutes */ +#define NOISE_REGULAR_INTERVAL (5*60*TICKSPERSEC) + +void noise_get_heavy(void (*func) (void *, int)); +void noise_get_light(void (*func) (void *, int)); + +/* + * `pool' itself is a pool of random data which we actually use: we + * return bytes from `pool', at position `poolpos', until `poolpos' + * reaches the end of the pool. At this point we generate more + * random data, by adding noise, stirring well, and resetting + * `poolpos' to point to just past the beginning of the pool (not + * _the_ beginning, since otherwise we'd give away the whole + * contents of our pool, and attackers would just have to guess the + * next lot of noise). + * + * `incomingb' buffers acquired noise data, until it gets full, at + * which point the acquired noise is SHA'ed into `incoming' and + * `incomingb' is cleared. The noise in `incoming' is used as part + * of the noise for each stirring of the pool, in addition to local + * time, process listings, and other such stuff. + */ + +#define HASHINPUT 64 /* 64 bytes SHA input */ +#define HASHSIZE 20 /* 160 bits SHA output */ +#define POOLSIZE 1200 /* size of random pool */ + +struct RandPool { + unsigned char pool[POOLSIZE]; + int poolpos; + + unsigned char incoming[HASHSIZE]; + + unsigned char incomingb[HASHINPUT]; + int incomingpos; + + int stir_pending; +}; + +static struct RandPool pool; +int random_active = 0; +long next_noise_collection; + +static void random_stir(void) +{ + word32 block[HASHINPUT / sizeof(word32)]; + word32 digest[HASHSIZE / sizeof(word32)]; + int i, j, k; + + /* + * noise_get_light will call random_add_noise, which may call + * back to here. Prevent recursive stirs. + */ + if (pool.stir_pending) + return; + pool.stir_pending = TRUE; + + noise_get_light(random_add_noise); + + SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb); + pool.incomingpos = 0; + + /* + * Chunks of this code are blatantly endianness-dependent, but + * as it's all random bits anyway, WHO CARES? + */ + memcpy(digest, pool.incoming, sizeof(digest)); + + /* + * Make two passes over the pool. + */ + for (i = 0; i < 2; i++) { + + /* + * We operate SHA in CFB mode, repeatedly adding the same + * block of data to the digest. But we're also fiddling + * with the digest-so-far, so this shouldn't be Bad or + * anything. + */ + memcpy(block, pool.pool, sizeof(block)); + + /* + * Each pass processes the pool backwards in blocks of + * HASHSIZE, just so that in general we get the output of + * SHA before the corresponding input, in the hope that + * things will be that much less predictable that way + * round, when we subsequently return bytes ... + */ + for (j = POOLSIZE; (j -= HASHSIZE) >= 0;) { + /* + * XOR the bit of the pool we're processing into the + * digest. + */ + + for (k = 0; k < sizeof(digest) / sizeof(*digest); k++) + digest[k] ^= ((word32 *) (pool.pool + j))[k]; + + /* + * Munge our unrevealed first block of the pool into + * it. + */ + SHATransform(digest, block); + + /* + * Stick the result back into the pool. + */ + + for (k = 0; k < sizeof(digest) / sizeof(*digest); k++) + ((word32 *) (pool.pool + j))[k] = digest[k]; + } + } + + /* + * Might as well save this value back into `incoming', just so + * there'll be some extra bizarreness there. + */ + SHATransform(digest, block); + memcpy(pool.incoming, digest, sizeof(digest)); + + pool.poolpos = sizeof(pool.incoming); + + pool.stir_pending = FALSE; +} + +void random_add_noise(void *noise, int length) +{ + unsigned char *p = noise; + int i; + + if (!random_active) + return; + + /* + * This function processes HASHINPUT bytes into only HASHSIZE + * bytes, so _if_ we were getting incredibly high entropy + * sources then we would be throwing away valuable stuff. + */ + while (length >= (HASHINPUT - pool.incomingpos)) { + memcpy(pool.incomingb + pool.incomingpos, p, + HASHINPUT - pool.incomingpos); + p += HASHINPUT - pool.incomingpos; + length -= HASHINPUT - pool.incomingpos; + SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb); + for (i = 0; i < HASHSIZE; i++) { + pool.pool[pool.poolpos++] ^= pool.incomingb[i]; + if (pool.poolpos >= POOLSIZE) + pool.poolpos = 0; + } + if (pool.poolpos < HASHSIZE) + random_stir(); + + pool.incomingpos = 0; + } + + memcpy(pool.incomingb + pool.incomingpos, p, length); + pool.incomingpos += length; +} + +void random_add_heavynoise(void *noise, int length) +{ + unsigned char *p = noise; + int i; + + while (length >= POOLSIZE) { + for (i = 0; i < POOLSIZE; i++) + pool.pool[i] ^= *p++; + random_stir(); + length -= POOLSIZE; + } + + for (i = 0; i < length; i++) + pool.pool[i] ^= *p++; + random_stir(); +} + +static void random_add_heavynoise_bitbybit(void *noise, int length) +{ + unsigned char *p = noise; + int i; + + while (length >= POOLSIZE - pool.poolpos) { + for (i = 0; i < POOLSIZE - pool.poolpos; i++) + pool.pool[pool.poolpos + i] ^= *p++; + random_stir(); + length -= POOLSIZE - pool.poolpos; + pool.poolpos = 0; + } + + for (i = 0; i < length; i++) + pool.pool[i] ^= *p++; + pool.poolpos = i; +} + +static void random_timer(void *ctx, long now) +{ + if (random_active > 0 && now - next_noise_collection >= 0) { + noise_regular(); + next_noise_collection = + schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool); + } +} + +void random_ref(void) +{ + if (!random_active) { + memset(&pool, 0, sizeof(pool)); /* just to start with */ + + noise_get_heavy(random_add_heavynoise_bitbybit); + random_stir(); + + next_noise_collection = + schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool); + } + + random_active++; +} + +void random_unref(void) +{ + random_active--; + assert(random_active >= 0); + if (random_active) return; + + expire_timer_context(&pool); +} + +int random_byte(void) +{ + if (pool.poolpos >= POOLSIZE) + random_stir(); + + return pool.pool[pool.poolpos++]; +} + +void random_get_savedata(void **data, int *len) +{ + void *buf = snewn(POOLSIZE / 2, char); + random_stir(); + memcpy(buf, pool.pool + pool.poolpos, POOLSIZE / 2); + *len = POOLSIZE / 2; + *data = buf; + random_stir(); +} diff --git a/putty/SSHRSA.C b/putty/SSHRSA.C new file mode 100644 index 0000000..ea6440b --- /dev/null +++ b/putty/SSHRSA.C @@ -0,0 +1,1086 @@ +/* + * RSA implementation for PuTTY. + */ + +#include +#include +#include +#include + +#include "ssh.h" +#include "misc.h" + +int makekey(unsigned char *data, int len, struct RSAKey *result, + unsigned char **keystr, int order) +{ + unsigned char *p = data; + int i, n; + + if (len < 4) + return -1; + + if (result) { + result->bits = 0; + for (i = 0; i < 4; i++) + result->bits = (result->bits << 8) + *p++; + } else + p += 4; + + len -= 4; + + /* + * order=0 means exponent then modulus (the keys sent by the + * server). order=1 means modulus then exponent (the keys + * stored in a keyfile). + */ + + if (order == 0) { + n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL); + if (n < 0) return -1; + p += n; + len -= n; + } + + n = ssh1_read_bignum(p, len, result ? &result->modulus : NULL); + if (n < 0 || (result && bignum_bitcount(result->modulus) == 0)) return -1; + if (result) + result->bytes = n - 2; + if (keystr) + *keystr = p + 2; + p += n; + len -= n; + + if (order == 1) { + n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL); + if (n < 0) return -1; + p += n; + len -= n; + } + return p - data; +} + +int makeprivate(unsigned char *data, int len, struct RSAKey *result) +{ + return ssh1_read_bignum(data, len, &result->private_exponent); +} + +int rsaencrypt(unsigned char *data, int length, struct RSAKey *key) +{ + Bignum b1, b2; + int i; + unsigned char *p; + + if (key->bytes < length + 4) + return 0; /* RSA key too short! */ + + memmove(data + key->bytes - length, data, length); + data[0] = 0; + data[1] = 2; + + for (i = 2; i < key->bytes - length - 1; i++) { + do { + data[i] = random_byte(); + } while (data[i] == 0); + } + data[key->bytes - length - 1] = 0; + + b1 = bignum_from_bytes(data, key->bytes); + + b2 = modpow(b1, key->exponent, key->modulus); + + p = data; + for (i = key->bytes; i--;) { + *p++ = bignum_byte(b2, i); + } + + freebn(b1); + freebn(b2); + + return 1; +} + +static void sha512_mpint(SHA512_State * s, Bignum b) +{ + unsigned char lenbuf[4]; + int len; + len = (bignum_bitcount(b) + 8) / 8; + PUT_32BIT(lenbuf, len); + SHA512_Bytes(s, lenbuf, 4); + while (len-- > 0) { + lenbuf[0] = bignum_byte(b, len); + SHA512_Bytes(s, lenbuf, 1); + } + memset(lenbuf, 0, sizeof(lenbuf)); +} + +/* + * Compute (base ^ exp) % mod, provided mod == p * q, with p,q + * distinct primes, and iqmp is the multiplicative inverse of q mod p. + * Uses Chinese Remainder Theorem to speed computation up over the + * obvious implementation of a single big modpow. + */ +Bignum crt_modpow(Bignum base, Bignum exp, Bignum mod, + Bignum p, Bignum q, Bignum iqmp) +{ + Bignum pm1, qm1, pexp, qexp, presult, qresult, diff, multiplier, ret0, ret; + + /* + * Reduce the exponent mod phi(p) and phi(q), to save time when + * exponentiating mod p and mod q respectively. Of course, since p + * and q are prime, phi(p) == p-1 and similarly for q. + */ + pm1 = copybn(p); + decbn(pm1); + qm1 = copybn(q); + decbn(qm1); + pexp = bigmod(exp, pm1); + qexp = bigmod(exp, qm1); + + /* + * Do the two modpows. + */ + presult = modpow(base, pexp, p); + qresult = modpow(base, qexp, q); + + /* + * Recombine the results. We want a value which is congruent to + * qresult mod q, and to presult mod p. + * + * We know that iqmp * q is congruent to 1 * mod p (by definition + * of iqmp) and to 0 mod q (obviously). So we start with qresult + * (which is congruent to qresult mod both primes), and add on + * (presult-qresult) * (iqmp * q) which adjusts it to be congruent + * to presult mod p without affecting its value mod q. + */ + if (bignum_cmp(presult, qresult) < 0) { + /* + * Can't subtract presult from qresult without first adding on + * p. + */ + Bignum tmp = presult; + presult = bigadd(presult, p); + freebn(tmp); + } + diff = bigsub(presult, qresult); + multiplier = bigmul(iqmp, q); + ret0 = bigmuladd(multiplier, diff, qresult); + + /* + * Finally, reduce the result mod n. + */ + ret = bigmod(ret0, mod); + + /* + * Free all the intermediate results before returning. + */ + freebn(pm1); + freebn(qm1); + freebn(pexp); + freebn(qexp); + freebn(presult); + freebn(qresult); + freebn(diff); + freebn(multiplier); + freebn(ret0); + + return ret; +} + +/* + * This function is a wrapper on modpow(). It has the same effect as + * modpow(), but employs RSA blinding to protect against timing + * attacks and also uses the Chinese Remainder Theorem (implemented + * above, in crt_modpow()) to speed up the main operation. + */ +static Bignum rsa_privkey_op(Bignum input, struct RSAKey *key) +{ + Bignum random, random_encrypted, random_inverse; + Bignum input_blinded, ret_blinded; + Bignum ret; + + SHA512_State ss; + unsigned char digest512[64]; + int digestused = lenof(digest512); + int hashseq = 0; + + /* + * Start by inventing a random number chosen uniformly from the + * range 2..modulus-1. (We do this by preparing a random number + * of the right length and retrying if it's greater than the + * modulus, to prevent any potential Bleichenbacher-like + * attacks making use of the uneven distribution within the + * range that would arise from just reducing our number mod n. + * There are timing implications to the potential retries, of + * course, but all they tell you is the modulus, which you + * already knew.) + * + * To preserve determinism and avoid Pageant needing to share + * the random number pool, we actually generate this `random' + * number by hashing stuff with the private key. + */ + while (1) { + int bits, byte, bitsleft, v; + random = copybn(key->modulus); + /* + * Find the topmost set bit. (This function will return its + * index plus one.) Then we'll set all bits from that one + * downwards randomly. + */ + bits = bignum_bitcount(random); + byte = 0; + bitsleft = 0; + while (bits--) { + if (bitsleft <= 0) { + bitsleft = 8; + /* + * Conceptually the following few lines are equivalent to + * byte = random_byte(); + */ + if (digestused >= lenof(digest512)) { + unsigned char seqbuf[4]; + PUT_32BIT(seqbuf, hashseq); + SHA512_Init(&ss); + SHA512_Bytes(&ss, "RSA deterministic blinding", 26); + SHA512_Bytes(&ss, seqbuf, sizeof(seqbuf)); + sha512_mpint(&ss, key->private_exponent); + SHA512_Final(&ss, digest512); + hashseq++; + + /* + * Now hash that digest plus the signature + * input. + */ + SHA512_Init(&ss); + SHA512_Bytes(&ss, digest512, sizeof(digest512)); + sha512_mpint(&ss, input); + SHA512_Final(&ss, digest512); + + digestused = 0; + } + byte = digest512[digestused++]; + } + v = byte & 1; + byte >>= 1; + bitsleft--; + bignum_set_bit(random, bits, v); + } + + /* + * Now check that this number is strictly greater than + * zero, and strictly less than modulus. + */ + if (bignum_cmp(random, Zero) <= 0 || + bignum_cmp(random, key->modulus) >= 0) { + freebn(random); + continue; + } else { + break; + } + } + + /* + * RSA blinding relies on the fact that (xy)^d mod n is equal + * to (x^d mod n) * (y^d mod n) mod n. We invent a random pair + * y and y^d; then we multiply x by y, raise to the power d mod + * n as usual, and divide by y^d to recover x^d. Thus an + * attacker can't correlate the timing of the modpow with the + * input, because they don't know anything about the number + * that was input to the actual modpow. + * + * The clever bit is that we don't have to do a huge modpow to + * get y and y^d; we will use the number we just invented as + * _y^d_, and use the _public_ exponent to compute (y^d)^e = y + * from it, which is much faster to do. + */ + random_encrypted = crt_modpow(random, key->exponent, + key->modulus, key->p, key->q, key->iqmp); + random_inverse = modinv(random, key->modulus); + input_blinded = modmul(input, random_encrypted, key->modulus); + ret_blinded = crt_modpow(input_blinded, key->private_exponent, + key->modulus, key->p, key->q, key->iqmp); + ret = modmul(ret_blinded, random_inverse, key->modulus); + + freebn(ret_blinded); + freebn(input_blinded); + freebn(random_inverse); + freebn(random_encrypted); + freebn(random); + + return ret; +} + +Bignum rsadecrypt(Bignum input, struct RSAKey *key) +{ + return rsa_privkey_op(input, key); +} + +int rsastr_len(struct RSAKey *key) +{ + Bignum md, ex; + int mdlen, exlen; + + md = key->modulus; + ex = key->exponent; + mdlen = (bignum_bitcount(md) + 15) / 16; + exlen = (bignum_bitcount(ex) + 15) / 16; + return 4 * (mdlen + exlen) + 20; +} + +void rsastr_fmt(char *str, struct RSAKey *key) +{ + Bignum md, ex; + int len = 0, i, nibbles; + static const char hex[] = "0123456789abcdef"; + + md = key->modulus; + ex = key->exponent; + + len += sprintf(str + len, "0x"); + + nibbles = (3 + bignum_bitcount(ex)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + str[len++] = hex[(bignum_byte(ex, i / 2) >> (4 * (i % 2))) & 0xF]; + + len += sprintf(str + len, ",0x"); + + nibbles = (3 + bignum_bitcount(md)) / 4; + if (nibbles < 1) + nibbles = 1; + for (i = nibbles; i--;) + str[len++] = hex[(bignum_byte(md, i / 2) >> (4 * (i % 2))) & 0xF]; + + str[len] = '\0'; +} + +/* + * Generate a fingerprint string for the key. Compatible with the + * OpenSSH fingerprint code. + */ +void rsa_fingerprint(char *str, int len, struct RSAKey *key) +{ + struct MD5Context md5c; + unsigned char digest[16]; + char buffer[16 * 3 + 40]; + int numlen, slen, i; + + MD5Init(&md5c); + numlen = ssh1_bignum_length(key->modulus) - 2; + for (i = numlen; i--;) { + unsigned char c = bignum_byte(key->modulus, i); + MD5Update(&md5c, &c, 1); + } + numlen = ssh1_bignum_length(key->exponent) - 2; + for (i = numlen; i--;) { + unsigned char c = bignum_byte(key->exponent, i); + MD5Update(&md5c, &c, 1); + } + MD5Final(digest, &md5c); + + sprintf(buffer, "%d ", bignum_bitcount(key->modulus)); + for (i = 0; i < 16; i++) + sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "", + digest[i]); + strncpy(str, buffer, len); + str[len - 1] = '\0'; + slen = strlen(str); + if (key->comment && slen < len - 1) { + str[slen] = ' '; + strncpy(str + slen + 1, key->comment, len - slen - 1); + str[len - 1] = '\0'; + } +} + +/* + * Verify that the public data in an RSA key matches the private + * data. We also check the private data itself: we ensure that p > + * q and that iqmp really is the inverse of q mod p. + */ +int rsa_verify(struct RSAKey *key) +{ + Bignum n, ed, pm1, qm1; + int cmp; + + /* n must equal pq. */ + n = bigmul(key->p, key->q); + cmp = bignum_cmp(n, key->modulus); + freebn(n); + if (cmp != 0) + return 0; + + /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */ + pm1 = copybn(key->p); + decbn(pm1); + ed = modmul(key->exponent, key->private_exponent, pm1); + cmp = bignum_cmp(ed, One); + sfree(ed); + if (cmp != 0) + return 0; + + qm1 = copybn(key->q); + decbn(qm1); + ed = modmul(key->exponent, key->private_exponent, qm1); + cmp = bignum_cmp(ed, One); + sfree(ed); + if (cmp != 0) + return 0; + + /* + * Ensure p > q. + * + * I have seen key blobs in the wild which were generated with + * p < q, so instead of rejecting the key in this case we + * should instead flip them round into the canonical order of + * p > q. This also involves regenerating iqmp. + */ + if (bignum_cmp(key->p, key->q) <= 0) { + Bignum tmp = key->p; + key->p = key->q; + key->q = tmp; + + freebn(key->iqmp); + key->iqmp = modinv(key->q, key->p); + } + + /* + * Ensure iqmp * q is congruent to 1, modulo p. + */ + n = modmul(key->iqmp, key->q, key->p); + cmp = bignum_cmp(n, One); + sfree(n); + if (cmp != 0) + return 0; + + return 1; +} + +/* Public key blob as used by Pageant: exponent before modulus. */ +unsigned char *rsa_public_blob(struct RSAKey *key, int *len) +{ + int length, pos; + unsigned char *ret; + + length = (ssh1_bignum_length(key->modulus) + + ssh1_bignum_length(key->exponent) + 4); + ret = snewn(length, unsigned char); + + PUT_32BIT(ret, bignum_bitcount(key->modulus)); + pos = 4; + pos += ssh1_write_bignum(ret + pos, key->exponent); + pos += ssh1_write_bignum(ret + pos, key->modulus); + + *len = length; + return ret; +} + +/* Given a public blob, determine its length. */ +int rsa_public_blob_len(void *data, int maxlen) +{ + unsigned char *p = (unsigned char *)data; + int n; + + if (maxlen < 4) + return -1; + p += 4; /* length word */ + maxlen -= 4; + + n = ssh1_read_bignum(p, maxlen, NULL); /* exponent */ + if (n < 0) + return -1; + p += n; + + n = ssh1_read_bignum(p, maxlen, NULL); /* modulus */ + if (n < 0) + return -1; + p += n; + + return p - (unsigned char *)data; +} + +void freersakey(struct RSAKey *key) +{ + if (key->modulus) + freebn(key->modulus); + if (key->exponent) + freebn(key->exponent); + if (key->private_exponent) + freebn(key->private_exponent); + if (key->p) + freebn(key->p); + if (key->q) + freebn(key->q); + if (key->iqmp) + freebn(key->iqmp); + if (key->comment) + sfree(key->comment); +} + +/* ---------------------------------------------------------------------- + * Implementation of the ssh-rsa signing key type. + */ + +static void getstring(char **data, int *datalen, char **p, int *length) +{ + *p = NULL; + if (*datalen < 4) + return; + *length = GET_32BIT(*data); + *datalen -= 4; + *data += 4; + if (*datalen < *length) + return; + *p = *data; + *data += *length; + *datalen -= *length; +} +static Bignum getmp(char **data, int *datalen) +{ + char *p; + int length; + Bignum b; + + getstring(data, datalen, &p, &length); + if (!p) + return NULL; + b = bignum_from_bytes((unsigned char *)p, length); + return b; +} + +static void *rsa2_newkey(char *data, int len) +{ + char *p; + int slen; + struct RSAKey *rsa; + + rsa = snew(struct RSAKey); + if (!rsa) + return NULL; + getstring(&data, &len, &p, &slen); + + if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) { + sfree(rsa); + return NULL; + } + rsa->exponent = getmp(&data, &len); + rsa->modulus = getmp(&data, &len); + rsa->private_exponent = NULL; + rsa->p = rsa->q = rsa->iqmp = NULL; + rsa->comment = NULL; + + return rsa; +} + +static void rsa2_freekey(void *key) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + freersakey(rsa); + sfree(rsa); +} + +static char *rsa2_fmtkey(void *key) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + char *p; + int len; + + len = rsastr_len(rsa); + p = snewn(len, char); + rsastr_fmt(p, rsa); + return p; +} + +static unsigned char *rsa2_public_blob(void *key, int *len) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + int elen, mlen, bloblen; + int i; + unsigned char *blob, *p; + + elen = (bignum_bitcount(rsa->exponent) + 8) / 8; + mlen = (bignum_bitcount(rsa->modulus) + 8) / 8; + + /* + * string "ssh-rsa", mpint exp, mpint mod. Total 19+elen+mlen. + * (three length fields, 12+7=19). + */ + bloblen = 19 + elen + mlen; + blob = snewn(bloblen, unsigned char); + p = blob; + PUT_32BIT(p, 7); + p += 4; + memcpy(p, "ssh-rsa", 7); + p += 7; + PUT_32BIT(p, elen); + p += 4; + for (i = elen; i--;) + *p++ = bignum_byte(rsa->exponent, i); + PUT_32BIT(p, mlen); + p += 4; + for (i = mlen; i--;) + *p++ = bignum_byte(rsa->modulus, i); + assert(p == blob + bloblen); + *len = bloblen; + return blob; +} + +static unsigned char *rsa2_private_blob(void *key, int *len) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + int dlen, plen, qlen, ulen, bloblen; + int i; + unsigned char *blob, *p; + + dlen = (bignum_bitcount(rsa->private_exponent) + 8) / 8; + plen = (bignum_bitcount(rsa->p) + 8) / 8; + qlen = (bignum_bitcount(rsa->q) + 8) / 8; + ulen = (bignum_bitcount(rsa->iqmp) + 8) / 8; + + /* + * mpint private_exp, mpint p, mpint q, mpint iqmp. Total 16 + + * sum of lengths. + */ + bloblen = 16 + dlen + plen + qlen + ulen; + blob = snewn(bloblen, unsigned char); + p = blob; + PUT_32BIT(p, dlen); + p += 4; + for (i = dlen; i--;) + *p++ = bignum_byte(rsa->private_exponent, i); + PUT_32BIT(p, plen); + p += 4; + for (i = plen; i--;) + *p++ = bignum_byte(rsa->p, i); + PUT_32BIT(p, qlen); + p += 4; + for (i = qlen; i--;) + *p++ = bignum_byte(rsa->q, i); + PUT_32BIT(p, ulen); + p += 4; + for (i = ulen; i--;) + *p++ = bignum_byte(rsa->iqmp, i); + assert(p == blob + bloblen); + *len = bloblen; + return blob; +} + +static void *rsa2_createkey(unsigned char *pub_blob, int pub_len, + unsigned char *priv_blob, int priv_len) +{ + struct RSAKey *rsa; + char *pb = (char *) priv_blob; + + rsa = rsa2_newkey((char *) pub_blob, pub_len); + rsa->private_exponent = getmp(&pb, &priv_len); + rsa->p = getmp(&pb, &priv_len); + rsa->q = getmp(&pb, &priv_len); + rsa->iqmp = getmp(&pb, &priv_len); + + if (!rsa_verify(rsa)) { + rsa2_freekey(rsa); + return NULL; + } + + return rsa; +} + +static void *rsa2_openssh_createkey(unsigned char **blob, int *len) +{ + char **b = (char **) blob; + struct RSAKey *rsa; + + rsa = snew(struct RSAKey); + if (!rsa) + return NULL; + rsa->comment = NULL; + + rsa->modulus = getmp(b, len); + rsa->exponent = getmp(b, len); + rsa->private_exponent = getmp(b, len); + rsa->iqmp = getmp(b, len); + rsa->p = getmp(b, len); + rsa->q = getmp(b, len); + + if (!rsa->modulus || !rsa->exponent || !rsa->private_exponent || + !rsa->iqmp || !rsa->p || !rsa->q) { + sfree(rsa->modulus); + sfree(rsa->exponent); + sfree(rsa->private_exponent); + sfree(rsa->iqmp); + sfree(rsa->p); + sfree(rsa->q); + sfree(rsa); + return NULL; + } + + return rsa; +} + +static int rsa2_openssh_fmtkey(void *key, unsigned char *blob, int len) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + int bloblen, i; + + bloblen = + ssh2_bignum_length(rsa->modulus) + + ssh2_bignum_length(rsa->exponent) + + ssh2_bignum_length(rsa->private_exponent) + + ssh2_bignum_length(rsa->iqmp) + + ssh2_bignum_length(rsa->p) + ssh2_bignum_length(rsa->q); + + if (bloblen > len) + return bloblen; + + bloblen = 0; +#define ENC(x) \ + PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \ + for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i); + ENC(rsa->modulus); + ENC(rsa->exponent); + ENC(rsa->private_exponent); + ENC(rsa->iqmp); + ENC(rsa->p); + ENC(rsa->q); + + return bloblen; +} + +static int rsa2_pubkey_bits(void *blob, int len) +{ + struct RSAKey *rsa; + int ret; + + rsa = rsa2_newkey((char *) blob, len); + ret = bignum_bitcount(rsa->modulus); + rsa2_freekey(rsa); + + return ret; +} + +static char *rsa2_fingerprint(void *key) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + struct MD5Context md5c; + unsigned char digest[16], lenbuf[4]; + char buffer[16 * 3 + 40]; + char *ret; + int numlen, i; + + MD5Init(&md5c); + MD5Update(&md5c, (unsigned char *)"\0\0\0\7ssh-rsa", 11); + +#define ADD_BIGNUM(bignum) \ + numlen = (bignum_bitcount(bignum)+8)/8; \ + PUT_32BIT(lenbuf, numlen); MD5Update(&md5c, lenbuf, 4); \ + for (i = numlen; i-- ;) { \ + unsigned char c = bignum_byte(bignum, i); \ + MD5Update(&md5c, &c, 1); \ + } + ADD_BIGNUM(rsa->exponent); + ADD_BIGNUM(rsa->modulus); +#undef ADD_BIGNUM + + MD5Final(digest, &md5c); + + sprintf(buffer, "ssh-rsa %d ", bignum_bitcount(rsa->modulus)); + for (i = 0; i < 16; i++) + sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "", + digest[i]); + ret = snewn(strlen(buffer) + 1, char); + if (ret) + strcpy(ret, buffer); + return ret; +} + +/* + * This is the magic ASN.1/DER prefix that goes in the decoded + * signature, between the string of FFs and the actual SHA hash + * value. The meaning of it is: + * + * 00 -- this marks the end of the FFs; not part of the ASN.1 bit itself + * + * 30 21 -- a constructed SEQUENCE of length 0x21 + * 30 09 -- a constructed sub-SEQUENCE of length 9 + * 06 05 -- an object identifier, length 5 + * 2B 0E 03 02 1A -- object id { 1 3 14 3 2 26 } + * (the 1,3 comes from 0x2B = 43 = 40*1+3) + * 05 00 -- NULL + * 04 14 -- a primitive OCTET STRING of length 0x14 + * [0x14 bytes of hash data follows] + * + * The object id in the middle there is listed as `id-sha1' in + * ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1d2.asn (the + * ASN module for PKCS #1) and its expanded form is as follows: + * + * id-sha1 OBJECT IDENTIFIER ::= { + * iso(1) identified-organization(3) oiw(14) secsig(3) + * algorithms(2) 26 } + */ +static const unsigned char asn1_weird_stuff[] = { + 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, + 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, +}; + +#define ASN1_LEN ( (int) sizeof(asn1_weird_stuff) ) + +static int rsa2_verifysig(void *key, char *sig, int siglen, + char *data, int datalen) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + Bignum in, out; + char *p; + int slen; + int bytes, i, j, ret; + unsigned char hash[20]; + + getstring(&sig, &siglen, &p, &slen); + if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) { + return 0; + } + in = getmp(&sig, &siglen); + out = modpow(in, rsa->exponent, rsa->modulus); + freebn(in); + + ret = 1; + + bytes = (bignum_bitcount(rsa->modulus)+7) / 8; + /* Top (partial) byte should be zero. */ + if (bignum_byte(out, bytes - 1) != 0) + ret = 0; + /* First whole byte should be 1. */ + if (bignum_byte(out, bytes - 2) != 1) + ret = 0; + /* Most of the rest should be FF. */ + for (i = bytes - 3; i >= 20 + ASN1_LEN; i--) { + if (bignum_byte(out, i) != 0xFF) + ret = 0; + } + /* Then we expect to see the asn1_weird_stuff. */ + for (i = 20 + ASN1_LEN - 1, j = 0; i >= 20; i--, j++) { + if (bignum_byte(out, i) != asn1_weird_stuff[j]) + ret = 0; + } + /* Finally, we expect to see the SHA-1 hash of the signed data. */ + SHA_Simple(data, datalen, hash); + for (i = 19, j = 0; i >= 0; i--, j++) { + if (bignum_byte(out, i) != hash[j]) + ret = 0; + } + freebn(out); + + return ret; +} + +static unsigned char *rsa2_sign(void *key, char *data, int datalen, + int *siglen) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + unsigned char *bytes; + int nbytes; + unsigned char hash[20]; + Bignum in, out; + int i, j; + + SHA_Simple(data, datalen, hash); + + nbytes = (bignum_bitcount(rsa->modulus) - 1) / 8; + assert(1 <= nbytes - 20 - ASN1_LEN); + bytes = snewn(nbytes, unsigned char); + + bytes[0] = 1; + for (i = 1; i < nbytes - 20 - ASN1_LEN; i++) + bytes[i] = 0xFF; + for (i = nbytes - 20 - ASN1_LEN, j = 0; i < nbytes - 20; i++, j++) + bytes[i] = asn1_weird_stuff[j]; + for (i = nbytes - 20, j = 0; i < nbytes; i++, j++) + bytes[i] = hash[j]; + + in = bignum_from_bytes(bytes, nbytes); + sfree(bytes); + + out = rsa_privkey_op(in, rsa); + freebn(in); + + nbytes = (bignum_bitcount(out) + 7) / 8; + bytes = snewn(4 + 7 + 4 + nbytes, unsigned char); + PUT_32BIT(bytes, 7); + memcpy(bytes + 4, "ssh-rsa", 7); + PUT_32BIT(bytes + 4 + 7, nbytes); + for (i = 0; i < nbytes; i++) + bytes[4 + 7 + 4 + i] = bignum_byte(out, nbytes - 1 - i); + freebn(out); + + *siglen = 4 + 7 + 4 + nbytes; + return bytes; +} + +const struct ssh_signkey ssh_rsa = { + rsa2_newkey, + rsa2_freekey, + rsa2_fmtkey, + rsa2_public_blob, + rsa2_private_blob, + rsa2_createkey, + rsa2_openssh_createkey, + rsa2_openssh_fmtkey, + rsa2_pubkey_bits, + rsa2_fingerprint, + rsa2_verifysig, + rsa2_sign, + "ssh-rsa", + "rsa2" +}; + +void *ssh_rsakex_newkey(char *data, int len) +{ + return rsa2_newkey(data, len); +} + +void ssh_rsakex_freekey(void *key) +{ + rsa2_freekey(key); +} + +int ssh_rsakex_klen(void *key) +{ + struct RSAKey *rsa = (struct RSAKey *) key; + + return bignum_bitcount(rsa->modulus); +} + +static void oaep_mask(const struct ssh_hash *h, void *seed, int seedlen, + void *vdata, int datalen) +{ + unsigned char *data = (unsigned char *)vdata; + unsigned count = 0; + + while (datalen > 0) { + int i, max = (datalen > h->hlen ? h->hlen : datalen); + void *s; + unsigned char counter[4], hash[SSH2_KEX_MAX_HASH_LEN]; + + assert(h->hlen <= SSH2_KEX_MAX_HASH_LEN); + PUT_32BIT(counter, count); + s = h->init(); + h->bytes(s, seed, seedlen); + h->bytes(s, counter, 4); + h->final(s, hash); + count++; + + for (i = 0; i < max; i++) + data[i] ^= hash[i]; + + data += max; + datalen -= max; + } +} + +void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen, + unsigned char *out, int outlen, + void *key) +{ + Bignum b1, b2; + struct RSAKey *rsa = (struct RSAKey *) key; + int k, i; + char *p; + const int HLEN = h->hlen; + + /* + * Here we encrypt using RSAES-OAEP. Essentially this means: + * + * - we have a SHA-based `mask generation function' which + * creates a pseudo-random stream of mask data + * deterministically from an input chunk of data. + * + * - we have a random chunk of data called a seed. + * + * - we use the seed to generate a mask which we XOR with our + * plaintext. + * + * - then we use _the masked plaintext_ to generate a mask + * which we XOR with the seed. + * + * - then we concatenate the masked seed and the masked + * plaintext, and RSA-encrypt that lot. + * + * The result is that the data input to the encryption function + * is random-looking and (hopefully) contains no exploitable + * structure such as PKCS1-v1_5 does. + * + * For a precise specification, see RFC 3447, section 7.1.1. + * Some of the variable names below are derived from that, so + * it'd probably help to read it anyway. + */ + + /* k denotes the length in octets of the RSA modulus. */ + k = (7 + bignum_bitcount(rsa->modulus)) / 8; + + /* The length of the input data must be at most k - 2hLen - 2. */ + assert(inlen > 0 && inlen <= k - 2*HLEN - 2); + + /* The length of the output data wants to be precisely k. */ + assert(outlen == k); + + /* + * Now perform EME-OAEP encoding. First set up all the unmasked + * output data. + */ + /* Leading byte zero. */ + out[0] = 0; + /* At position 1, the seed: HLEN bytes of random data. */ + for (i = 0; i < HLEN; i++) + out[i + 1] = random_byte(); + /* At position 1+HLEN, the data block DB, consisting of: */ + /* The hash of the label (we only support an empty label here) */ + h->final(h->init(), out + HLEN + 1); + /* A bunch of zero octets */ + memset(out + 2*HLEN + 1, 0, outlen - (2*HLEN + 1)); + /* A single 1 octet, followed by the input message data. */ + out[outlen - inlen - 1] = 1; + memcpy(out + outlen - inlen, in, inlen); + + /* + * Now use the seed data to mask the block DB. + */ + oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1); + + /* + * And now use the masked DB to mask the seed itself. + */ + oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN); + + /* + * Now `out' contains precisely the data we want to + * RSA-encrypt. + */ + b1 = bignum_from_bytes(out, outlen); + b2 = modpow(b1, rsa->exponent, rsa->modulus); + p = (char *)out; + for (i = outlen; i--;) { + *p++ = bignum_byte(b2, i); + } + freebn(b1); + freebn(b2); + + /* + * And we're done. + */ +} + +static const struct ssh_kex ssh_rsa_kex_sha1 = { + "rsa1024-sha1", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha1 +}; + +static const struct ssh_kex ssh_rsa_kex_sha256 = { + "rsa2048-sha256", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha256 +}; + +static const struct ssh_kex *const rsa_kex_list[] = { + &ssh_rsa_kex_sha256, + &ssh_rsa_kex_sha1 +}; + +const struct ssh_kexes ssh_rsa_kex = { + sizeof(rsa_kex_list) / sizeof(*rsa_kex_list), + rsa_kex_list +}; diff --git a/putty/SSHRSAG.C b/putty/SSHRSAG.C new file mode 100644 index 0000000..b19d3c1 --- /dev/null +++ b/putty/SSHRSAG.C @@ -0,0 +1,103 @@ +/* + * RSA key generation. + */ + +#include "ssh.h" + +#define RSA_EXPONENT 37 /* we like this prime */ + +int rsa_generate(struct RSAKey *key, int bits, progfn_t pfn, + void *pfnparam) +{ + Bignum pm1, qm1, phi_n; + + /* + * Set up the phase limits for the progress report. We do this + * by passing minus the phase number. + * + * For prime generation: our initial filter finds things + * coprime to everything below 2^16. Computing the product of + * (p-1)/p for all prime p below 2^16 gives about 20.33; so + * among B-bit integers, one in every 20.33 will get through + * the initial filter to be a candidate prime. + * + * Meanwhile, we are searching for primes in the region of 2^B; + * since pi(x) ~ x/log(x), when x is in the region of 2^B, the + * prime density will be d/dx pi(x) ~ 1/log(B), i.e. about + * 1/0.6931B. So the chance of any given candidate being prime + * is 20.33/0.6931B, which is roughly 29.34 divided by B. + * + * So now we have this probability P, we're looking at an + * exponential distribution with parameter P: we will manage in + * one attempt with probability P, in two with probability + * P(1-P), in three with probability P(1-P)^2, etc. The + * probability that we have still not managed to find a prime + * after N attempts is (1-P)^N. + * + * We therefore inform the progress indicator of the number B + * (29.34/B), so that it knows how much to increment by each + * time. We do this in 16-bit fixed point, so 29.34 becomes + * 0x1D.57C4. + */ + pfn(pfnparam, PROGFN_PHASE_EXTENT, 1, 0x10000); + pfn(pfnparam, PROGFN_EXP_PHASE, 1, -0x1D57C4 / (bits / 2)); + pfn(pfnparam, PROGFN_PHASE_EXTENT, 2, 0x10000); + pfn(pfnparam, PROGFN_EXP_PHASE, 2, -0x1D57C4 / (bits - bits / 2)); + pfn(pfnparam, PROGFN_PHASE_EXTENT, 3, 0x4000); + pfn(pfnparam, PROGFN_LIN_PHASE, 3, 5); + pfn(pfnparam, PROGFN_READY, 0, 0); + + /* + * We don't generate e; we just use a standard one always. + */ + key->exponent = bignum_from_long(RSA_EXPONENT); + + /* + * Generate p and q: primes with combined length `bits', not + * congruent to 1 modulo e. (Strictly speaking, we wanted (p-1) + * and e to be coprime, and (q-1) and e to be coprime, but in + * general that's slightly more fiddly to arrange. By choosing + * a prime e, we can simplify the criterion.) + */ + key->p = primegen(bits / 2, RSA_EXPONENT, 1, NULL, + 1, pfn, pfnparam); + key->q = primegen(bits - bits / 2, RSA_EXPONENT, 1, NULL, + 2, pfn, pfnparam); + + /* + * Ensure p > q, by swapping them if not. + */ + if (bignum_cmp(key->p, key->q) < 0) { + Bignum t = key->p; + key->p = key->q; + key->q = t; + } + + /* + * Now we have p, q and e. All we need to do now is work out + * the other helpful quantities: n=pq, d=e^-1 mod (p-1)(q-1), + * and (q^-1 mod p). + */ + pfn(pfnparam, PROGFN_PROGRESS, 3, 1); + key->modulus = bigmul(key->p, key->q); + pfn(pfnparam, PROGFN_PROGRESS, 3, 2); + pm1 = copybn(key->p); + decbn(pm1); + qm1 = copybn(key->q); + decbn(qm1); + phi_n = bigmul(pm1, qm1); + pfn(pfnparam, PROGFN_PROGRESS, 3, 3); + freebn(pm1); + freebn(qm1); + key->private_exponent = modinv(key->exponent, phi_n); + pfn(pfnparam, PROGFN_PROGRESS, 3, 4); + key->iqmp = modinv(key->q, key->p); + pfn(pfnparam, PROGFN_PROGRESS, 3, 5); + + /* + * Clean up temporary numbers. + */ + freebn(phi_n); + + return 1; +} diff --git a/putty/SSHSH256.C b/putty/SSHSH256.C new file mode 100644 index 0000000..538982a --- /dev/null +++ b/putty/SSHSH256.C @@ -0,0 +1,269 @@ +/* + * SHA-256 algorithm as described at + * + * http://csrc.nist.gov/cryptval/shs.html + */ + +#include "ssh.h" + +/* ---------------------------------------------------------------------- + * Core SHA256 algorithm: processes 16-word blocks into a message digest. + */ + +#define ror(x,y) ( ((x) << (32-y)) | (((uint32)(x)) >> (y)) ) +#define shr(x,y) ( (((uint32)(x)) >> (y)) ) +#define Ch(x,y,z) ( ((x) & (y)) ^ (~(x) & (z)) ) +#define Maj(x,y,z) ( ((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)) ) +#define bigsigma0(x) ( ror((x),2) ^ ror((x),13) ^ ror((x),22) ) +#define bigsigma1(x) ( ror((x),6) ^ ror((x),11) ^ ror((x),25) ) +#define smallsigma0(x) ( ror((x),7) ^ ror((x),18) ^ shr((x),3) ) +#define smallsigma1(x) ( ror((x),17) ^ ror((x),19) ^ shr((x),10) ) + +void SHA256_Core_Init(SHA256_State *s) { + s->h[0] = 0x6a09e667; + s->h[1] = 0xbb67ae85; + s->h[2] = 0x3c6ef372; + s->h[3] = 0xa54ff53a; + s->h[4] = 0x510e527f; + s->h[5] = 0x9b05688c; + s->h[6] = 0x1f83d9ab; + s->h[7] = 0x5be0cd19; +} + +void SHA256_Block(SHA256_State *s, uint32 *block) { + uint32 w[80]; + uint32 a,b,c,d,e,f,g,h; + static const int k[] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + + int t; + + for (t = 0; t < 16; t++) + w[t] = block[t]; + + for (t = 16; t < 64; t++) + w[t] = smallsigma1(w[t-2]) + w[t-7] + smallsigma0(w[t-15]) + w[t-16]; + + a = s->h[0]; b = s->h[1]; c = s->h[2]; d = s->h[3]; + e = s->h[4]; f = s->h[5]; g = s->h[6]; h = s->h[7]; + + for (t = 0; t < 64; t+=8) { + uint32 t1, t2; + +#define ROUND(j,a,b,c,d,e,f,g,h) \ + t1 = h + bigsigma1(e) + Ch(e,f,g) + k[j] + w[j]; \ + t2 = bigsigma0(a) + Maj(a,b,c); \ + d = d + t1; h = t1 + t2; + + ROUND(t+0, a,b,c,d,e,f,g,h); + ROUND(t+1, h,a,b,c,d,e,f,g); + ROUND(t+2, g,h,a,b,c,d,e,f); + ROUND(t+3, f,g,h,a,b,c,d,e); + ROUND(t+4, e,f,g,h,a,b,c,d); + ROUND(t+5, d,e,f,g,h,a,b,c); + ROUND(t+6, c,d,e,f,g,h,a,b); + ROUND(t+7, b,c,d,e,f,g,h,a); + } + + s->h[0] += a; s->h[1] += b; s->h[2] += c; s->h[3] += d; + s->h[4] += e; s->h[5] += f; s->h[6] += g; s->h[7] += h; +} + +/* ---------------------------------------------------------------------- + * Outer SHA256 algorithm: take an arbitrary length byte string, + * convert it into 16-word blocks with the prescribed padding at + * the end, and pass those blocks to the core SHA256 algorithm. + */ + +#define BLKSIZE 64 + +void SHA256_Init(SHA256_State *s) { + SHA256_Core_Init(s); + s->blkused = 0; + s->lenhi = s->lenlo = 0; +} + +void SHA256_Bytes(SHA256_State *s, const void *p, int len) { + unsigned char *q = (unsigned char *)p; + uint32 wordblock[16]; + uint32 lenw = len; + int i; + + /* + * Update the length field. + */ + s->lenlo += lenw; + s->lenhi += (s->lenlo < lenw); + + if (s->blkused && s->blkused+len < BLKSIZE) { + /* + * Trivial case: just add to the block. + */ + memcpy(s->block + s->blkused, q, len); + s->blkused += len; + } else { + /* + * We must complete and process at least one block. + */ + while (s->blkused + len >= BLKSIZE) { + memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused); + q += BLKSIZE - s->blkused; + len -= BLKSIZE - s->blkused; + /* Now process the block. Gather bytes big-endian into words */ + for (i = 0; i < 16; i++) { + wordblock[i] = + ( ((uint32)s->block[i*4+0]) << 24 ) | + ( ((uint32)s->block[i*4+1]) << 16 ) | + ( ((uint32)s->block[i*4+2]) << 8 ) | + ( ((uint32)s->block[i*4+3]) << 0 ); + } + SHA256_Block(s, wordblock); + s->blkused = 0; + } + memcpy(s->block, q, len); + s->blkused = len; + } +} + +void SHA256_Final(SHA256_State *s, unsigned char *digest) { + int i; + int pad; + unsigned char c[64]; + uint32 lenhi, lenlo; + + if (s->blkused >= 56) + pad = 56 + 64 - s->blkused; + else + pad = 56 - s->blkused; + + lenhi = (s->lenhi << 3) | (s->lenlo >> (32-3)); + lenlo = (s->lenlo << 3); + + memset(c, 0, pad); + c[0] = 0x80; + SHA256_Bytes(s, &c, pad); + + c[0] = (lenhi >> 24) & 0xFF; + c[1] = (lenhi >> 16) & 0xFF; + c[2] = (lenhi >> 8) & 0xFF; + c[3] = (lenhi >> 0) & 0xFF; + c[4] = (lenlo >> 24) & 0xFF; + c[5] = (lenlo >> 16) & 0xFF; + c[6] = (lenlo >> 8) & 0xFF; + c[7] = (lenlo >> 0) & 0xFF; + + SHA256_Bytes(s, &c, 8); + + for (i = 0; i < 8; i++) { + digest[i*4+0] = (s->h[i] >> 24) & 0xFF; + digest[i*4+1] = (s->h[i] >> 16) & 0xFF; + digest[i*4+2] = (s->h[i] >> 8) & 0xFF; + digest[i*4+3] = (s->h[i] >> 0) & 0xFF; + } +} + +void SHA256_Simple(const void *p, int len, unsigned char *output) { + SHA256_State s; + + SHA256_Init(&s); + SHA256_Bytes(&s, p, len); + SHA256_Final(&s, output); +} + +/* + * Thin abstraction for things where hashes are pluggable. + */ + +static void *sha256_init(void) +{ + SHA256_State *s; + + s = snew(SHA256_State); + SHA256_Init(s); + return s; +} + +static void sha256_bytes(void *handle, void *p, int len) +{ + SHA256_State *s = handle; + + SHA256_Bytes(s, p, len); +} + +static void sha256_final(void *handle, unsigned char *output) +{ + SHA256_State *s = handle; + + SHA256_Final(s, output); + sfree(s); +} + +const struct ssh_hash ssh_sha256 = { + sha256_init, sha256_bytes, sha256_final, 32, "SHA-256" +}; + +#ifdef TEST + +#include +#include +#include + +int main(void) { + unsigned char digest[32]; + int i, j, errors; + + struct { + const char *teststring; + unsigned char digest[32]; + } tests[] = { + { "abc", { + 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad, + } }, + { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", { + 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, + 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39, + 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, + 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1, + } }, + }; + + errors = 0; + + for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + SHA256_Simple(tests[i].teststring, + strlen(tests[i].teststring), digest); + for (j = 0; j < 32; j++) { + if (digest[j] != tests[i].digest[j]) { + fprintf(stderr, + "\"%s\" digest byte %d should be 0x%02x, is 0x%02x\n", + tests[i].teststring, j, tests[i].digest[j], digest[j]); + errors++; + } + } + } + + printf("%d errors\n", errors); + + return 0; +} + +#endif diff --git a/putty/SSHSH512.C b/putty/SSHSH512.C new file mode 100644 index 0000000..bdb126f --- /dev/null +++ b/putty/SSHSH512.C @@ -0,0 +1,358 @@ +/* + * SHA-512 algorithm as described at + * + * http://csrc.nist.gov/cryptval/shs.html + */ + +#include "ssh.h" + +#define BLKSIZE 128 + +/* + * Arithmetic implementations. Note that AND, XOR and NOT can + * overlap destination with one source, but the others can't. + */ +#define add(r,x,y) ( r.lo = y.lo + x.lo, \ + r.hi = y.hi + x.hi + ((uint32)r.lo < (uint32)y.lo) ) +#define rorB(r,x,y) ( r.lo = ((uint32)x.hi >> ((y)-32)) | ((uint32)x.lo << (64-(y))), \ + r.hi = ((uint32)x.lo >> ((y)-32)) | ((uint32)x.hi << (64-(y))) ) +#define rorL(r,x,y) ( r.lo = ((uint32)x.lo >> (y)) | ((uint32)x.hi << (32-(y))), \ + r.hi = ((uint32)x.hi >> (y)) | ((uint32)x.lo << (32-(y))) ) +#define shrB(r,x,y) ( r.lo = (uint32)x.hi >> ((y)-32), r.hi = 0 ) +#define shrL(r,x,y) ( r.lo = ((uint32)x.lo >> (y)) | ((uint32)x.hi << (32-(y))), \ + r.hi = (uint32)x.hi >> (y) ) +#define and(r,x,y) ( r.lo = x.lo & y.lo, r.hi = x.hi & y.hi ) +#define xor(r,x,y) ( r.lo = x.lo ^ y.lo, r.hi = x.hi ^ y.hi ) +#define not(r,x) ( r.lo = ~x.lo, r.hi = ~x.hi ) +#define INIT(h,l) { h, l } +#define BUILD(r,h,l) ( r.hi = h, r.lo = l ) +#define EXTRACT(h,l,r) ( h = r.hi, l = r.lo ) + +/* ---------------------------------------------------------------------- + * Core SHA512 algorithm: processes 16-doubleword blocks into a + * message digest. + */ + +#define Ch(r,t,x,y,z) ( not(t,x), and(r,t,z), and(t,x,y), xor(r,r,t) ) +#define Maj(r,t,x,y,z) ( and(r,x,y), and(t,x,z), xor(r,r,t), \ + and(t,y,z), xor(r,r,t) ) +#define bigsigma0(r,t,x) ( rorL(r,x,28), rorB(t,x,34), xor(r,r,t), \ + rorB(t,x,39), xor(r,r,t) ) +#define bigsigma1(r,t,x) ( rorL(r,x,14), rorL(t,x,18), xor(r,r,t), \ + rorB(t,x,41), xor(r,r,t) ) +#define smallsigma0(r,t,x) ( rorL(r,x,1), rorL(t,x,8), xor(r,r,t), \ + shrL(t,x,7), xor(r,r,t) ) +#define smallsigma1(r,t,x) ( rorL(r,x,19), rorB(t,x,61), xor(r,r,t), \ + shrL(t,x,6), xor(r,r,t) ) + +static void SHA512_Core_Init(SHA512_State *s) { + static const uint64 iv[] = { + INIT(0x6a09e667, 0xf3bcc908), + INIT(0xbb67ae85, 0x84caa73b), + INIT(0x3c6ef372, 0xfe94f82b), + INIT(0xa54ff53a, 0x5f1d36f1), + INIT(0x510e527f, 0xade682d1), + INIT(0x9b05688c, 0x2b3e6c1f), + INIT(0x1f83d9ab, 0xfb41bd6b), + INIT(0x5be0cd19, 0x137e2179), + }; + int i; + for (i = 0; i < 8; i++) + s->h[i] = iv[i]; +} + +static void SHA512_Block(SHA512_State *s, uint64 *block) { + uint64 w[80]; + uint64 a,b,c,d,e,f,g,h; + static const uint64 k[] = { + INIT(0x428a2f98, 0xd728ae22), INIT(0x71374491, 0x23ef65cd), + INIT(0xb5c0fbcf, 0xec4d3b2f), INIT(0xe9b5dba5, 0x8189dbbc), + INIT(0x3956c25b, 0xf348b538), INIT(0x59f111f1, 0xb605d019), + INIT(0x923f82a4, 0xaf194f9b), INIT(0xab1c5ed5, 0xda6d8118), + INIT(0xd807aa98, 0xa3030242), INIT(0x12835b01, 0x45706fbe), + INIT(0x243185be, 0x4ee4b28c), INIT(0x550c7dc3, 0xd5ffb4e2), + INIT(0x72be5d74, 0xf27b896f), INIT(0x80deb1fe, 0x3b1696b1), + INIT(0x9bdc06a7, 0x25c71235), INIT(0xc19bf174, 0xcf692694), + INIT(0xe49b69c1, 0x9ef14ad2), INIT(0xefbe4786, 0x384f25e3), + INIT(0x0fc19dc6, 0x8b8cd5b5), INIT(0x240ca1cc, 0x77ac9c65), + INIT(0x2de92c6f, 0x592b0275), INIT(0x4a7484aa, 0x6ea6e483), + INIT(0x5cb0a9dc, 0xbd41fbd4), INIT(0x76f988da, 0x831153b5), + INIT(0x983e5152, 0xee66dfab), INIT(0xa831c66d, 0x2db43210), + INIT(0xb00327c8, 0x98fb213f), INIT(0xbf597fc7, 0xbeef0ee4), + INIT(0xc6e00bf3, 0x3da88fc2), INIT(0xd5a79147, 0x930aa725), + INIT(0x06ca6351, 0xe003826f), INIT(0x14292967, 0x0a0e6e70), + INIT(0x27b70a85, 0x46d22ffc), INIT(0x2e1b2138, 0x5c26c926), + INIT(0x4d2c6dfc, 0x5ac42aed), INIT(0x53380d13, 0x9d95b3df), + INIT(0x650a7354, 0x8baf63de), INIT(0x766a0abb, 0x3c77b2a8), + INIT(0x81c2c92e, 0x47edaee6), INIT(0x92722c85, 0x1482353b), + INIT(0xa2bfe8a1, 0x4cf10364), INIT(0xa81a664b, 0xbc423001), + INIT(0xc24b8b70, 0xd0f89791), INIT(0xc76c51a3, 0x0654be30), + INIT(0xd192e819, 0xd6ef5218), INIT(0xd6990624, 0x5565a910), + INIT(0xf40e3585, 0x5771202a), INIT(0x106aa070, 0x32bbd1b8), + INIT(0x19a4c116, 0xb8d2d0c8), INIT(0x1e376c08, 0x5141ab53), + INIT(0x2748774c, 0xdf8eeb99), INIT(0x34b0bcb5, 0xe19b48a8), + INIT(0x391c0cb3, 0xc5c95a63), INIT(0x4ed8aa4a, 0xe3418acb), + INIT(0x5b9cca4f, 0x7763e373), INIT(0x682e6ff3, 0xd6b2b8a3), + INIT(0x748f82ee, 0x5defb2fc), INIT(0x78a5636f, 0x43172f60), + INIT(0x84c87814, 0xa1f0ab72), INIT(0x8cc70208, 0x1a6439ec), + INIT(0x90befffa, 0x23631e28), INIT(0xa4506ceb, 0xde82bde9), + INIT(0xbef9a3f7, 0xb2c67915), INIT(0xc67178f2, 0xe372532b), + INIT(0xca273ece, 0xea26619c), INIT(0xd186b8c7, 0x21c0c207), + INIT(0xeada7dd6, 0xcde0eb1e), INIT(0xf57d4f7f, 0xee6ed178), + INIT(0x06f067aa, 0x72176fba), INIT(0x0a637dc5, 0xa2c898a6), + INIT(0x113f9804, 0xbef90dae), INIT(0x1b710b35, 0x131c471b), + INIT(0x28db77f5, 0x23047d84), INIT(0x32caab7b, 0x40c72493), + INIT(0x3c9ebe0a, 0x15c9bebc), INIT(0x431d67c4, 0x9c100d4c), + INIT(0x4cc5d4be, 0xcb3e42b6), INIT(0x597f299c, 0xfc657e2a), + INIT(0x5fcb6fab, 0x3ad6faec), INIT(0x6c44198c, 0x4a475817), + }; + + int t; + + for (t = 0; t < 16; t++) + w[t] = block[t]; + + for (t = 16; t < 80; t++) { + uint64 p, q, r, tmp; + smallsigma1(p, tmp, w[t-2]); + smallsigma0(q, tmp, w[t-15]); + add(r, p, q); + add(p, r, w[t-7]); + add(w[t], p, w[t-16]); + } + + a = s->h[0]; b = s->h[1]; c = s->h[2]; d = s->h[3]; + e = s->h[4]; f = s->h[5]; g = s->h[6]; h = s->h[7]; + + for (t = 0; t < 80; t+=8) { + uint64 tmp, p, q, r; + +#define ROUND(j,a,b,c,d,e,f,g,h) \ + bigsigma1(p, tmp, e); \ + Ch(q, tmp, e, f, g); \ + add(r, p, q); \ + add(p, r, k[j]) ; \ + add(q, p, w[j]); \ + add(r, q, h); \ + bigsigma0(p, tmp, a); \ + Maj(tmp, q, a, b, c); \ + add(q, tmp, p); \ + add(p, r, d); \ + d = p; \ + add(h, q, r); + + ROUND(t+0, a,b,c,d,e,f,g,h); + ROUND(t+1, h,a,b,c,d,e,f,g); + ROUND(t+2, g,h,a,b,c,d,e,f); + ROUND(t+3, f,g,h,a,b,c,d,e); + ROUND(t+4, e,f,g,h,a,b,c,d); + ROUND(t+5, d,e,f,g,h,a,b,c); + ROUND(t+6, c,d,e,f,g,h,a,b); + ROUND(t+7, b,c,d,e,f,g,h,a); + } + + { + uint64 tmp; +#define UPDATE(state, local) ( tmp = state, add(state, tmp, local) ) + UPDATE(s->h[0], a); UPDATE(s->h[1], b); + UPDATE(s->h[2], c); UPDATE(s->h[3], d); + UPDATE(s->h[4], e); UPDATE(s->h[5], f); + UPDATE(s->h[6], g); UPDATE(s->h[7], h); + } +} + +/* ---------------------------------------------------------------------- + * Outer SHA512 algorithm: take an arbitrary length byte string, + * convert it into 16-doubleword blocks with the prescribed padding + * at the end, and pass those blocks to the core SHA512 algorithm. + */ + +void SHA512_Init(SHA512_State *s) { + int i; + SHA512_Core_Init(s); + s->blkused = 0; + for (i = 0; i < 4; i++) + s->len[i] = 0; +} + +void SHA512_Bytes(SHA512_State *s, const void *p, int len) { + unsigned char *q = (unsigned char *)p; + uint64 wordblock[16]; + uint32 lenw = len; + int i; + + /* + * Update the length field. + */ + for (i = 0; i < 4; i++) { + s->len[i] += lenw; + lenw = (s->len[i] < lenw); + } + + if (s->blkused && s->blkused+len < BLKSIZE) { + /* + * Trivial case: just add to the block. + */ + memcpy(s->block + s->blkused, q, len); + s->blkused += len; + } else { + /* + * We must complete and process at least one block. + */ + while (s->blkused + len >= BLKSIZE) { + memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused); + q += BLKSIZE - s->blkused; + len -= BLKSIZE - s->blkused; + /* Now process the block. Gather bytes big-endian into words */ + for (i = 0; i < 16; i++) { + uint32 h, l; + h = ( ((uint32)s->block[i*8+0]) << 24 ) | + ( ((uint32)s->block[i*8+1]) << 16 ) | + ( ((uint32)s->block[i*8+2]) << 8 ) | + ( ((uint32)s->block[i*8+3]) << 0 ); + l = ( ((uint32)s->block[i*8+4]) << 24 ) | + ( ((uint32)s->block[i*8+5]) << 16 ) | + ( ((uint32)s->block[i*8+6]) << 8 ) | + ( ((uint32)s->block[i*8+7]) << 0 ); + BUILD(wordblock[i], h, l); + } + SHA512_Block(s, wordblock); + s->blkused = 0; + } + memcpy(s->block, q, len); + s->blkused = len; + } +} + +void SHA512_Final(SHA512_State *s, unsigned char *digest) { + int i; + int pad; + unsigned char c[BLKSIZE]; + uint32 len[4]; + + if (s->blkused >= BLKSIZE-16) + pad = (BLKSIZE-16) + BLKSIZE - s->blkused; + else + pad = (BLKSIZE-16) - s->blkused; + + for (i = 4; i-- ;) { + uint32 lenhi = s->len[i]; + uint32 lenlo = i > 0 ? s->len[i-1] : 0; + len[i] = (lenhi << 3) | (lenlo >> (32-3)); + } + + memset(c, 0, pad); + c[0] = 0x80; + SHA512_Bytes(s, &c, pad); + + for (i = 0; i < 4; i++) { + c[i*4+0] = (len[3-i] >> 24) & 0xFF; + c[i*4+1] = (len[3-i] >> 16) & 0xFF; + c[i*4+2] = (len[3-i] >> 8) & 0xFF; + c[i*4+3] = (len[3-i] >> 0) & 0xFF; + } + + SHA512_Bytes(s, &c, 16); + + for (i = 0; i < 8; i++) { + uint32 h, l; + EXTRACT(h, l, s->h[i]); + digest[i*8+0] = (h >> 24) & 0xFF; + digest[i*8+1] = (h >> 16) & 0xFF; + digest[i*8+2] = (h >> 8) & 0xFF; + digest[i*8+3] = (h >> 0) & 0xFF; + digest[i*8+4] = (l >> 24) & 0xFF; + digest[i*8+5] = (l >> 16) & 0xFF; + digest[i*8+6] = (l >> 8) & 0xFF; + digest[i*8+7] = (l >> 0) & 0xFF; + } +} + +void SHA512_Simple(const void *p, int len, unsigned char *output) { + SHA512_State s; + + SHA512_Init(&s); + SHA512_Bytes(&s, p, len); + SHA512_Final(&s, output); +} + +#ifdef TEST + +#include +#include +#include + +int main(void) { + unsigned char digest[64]; + int i, j, errors; + + struct { + const char *teststring; + unsigned char digest512[64]; + } tests[] = { + { "abc", { + 0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba, + 0xcc, 0x41, 0x73, 0x49, 0xae, 0x20, 0x41, 0x31, + 0x12, 0xe6, 0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2, + 0x0a, 0x9e, 0xee, 0xe6, 0x4b, 0x55, 0xd3, 0x9a, + 0x21, 0x92, 0x99, 0x2a, 0x27, 0x4f, 0xc1, 0xa8, + 0x36, 0xba, 0x3c, 0x23, 0xa3, 0xfe, 0xeb, 0xbd, + 0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c, 0xe8, 0x0e, + 0x2a, 0x9a, 0xc9, 0x4f, 0xa5, 0x4c, 0xa4, 0x9f, + } }, + { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", { + 0x8e, 0x95, 0x9b, 0x75, 0xda, 0xe3, 0x13, 0xda, + 0x8c, 0xf4, 0xf7, 0x28, 0x14, 0xfc, 0x14, 0x3f, + 0x8f, 0x77, 0x79, 0xc6, 0xeb, 0x9f, 0x7f, 0xa1, + 0x72, 0x99, 0xae, 0xad, 0xb6, 0x88, 0x90, 0x18, + 0x50, 0x1d, 0x28, 0x9e, 0x49, 0x00, 0xf7, 0xe4, + 0x33, 0x1b, 0x99, 0xde, 0xc4, 0xb5, 0x43, 0x3a, + 0xc7, 0xd3, 0x29, 0xee, 0xb6, 0xdd, 0x26, 0x54, + 0x5e, 0x96, 0xe5, 0x5b, 0x87, 0x4b, 0xe9, 0x09, + } }, + { NULL, { + 0xe7, 0x18, 0x48, 0x3d, 0x0c, 0xe7, 0x69, 0x64, + 0x4e, 0x2e, 0x42, 0xc7, 0xbc, 0x15, 0xb4, 0x63, + 0x8e, 0x1f, 0x98, 0xb1, 0x3b, 0x20, 0x44, 0x28, + 0x56, 0x32, 0xa8, 0x03, 0xaf, 0xa9, 0x73, 0xeb, + 0xde, 0x0f, 0xf2, 0x44, 0x87, 0x7e, 0xa6, 0x0a, + 0x4c, 0xb0, 0x43, 0x2c, 0xe5, 0x77, 0xc3, 0x1b, + 0xeb, 0x00, 0x9c, 0x5c, 0x2c, 0x49, 0xaa, 0x2e, + 0x4e, 0xad, 0xb2, 0x17, 0xad, 0x8c, 0xc0, 0x9b, + } }, + }; + + errors = 0; + + for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + if (tests[i].teststring) { + SHA512_Simple(tests[i].teststring, + strlen(tests[i].teststring), digest); + } else { + SHA512_State s; + int n; + SHA512_Init(&s); + for (n = 0; n < 1000000 / 40; n++) + SHA512_Bytes(&s, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 40); + SHA512_Final(&s, digest); + } + for (j = 0; j < 64; j++) { + if (digest[j] != tests[i].digest512[j]) { + fprintf(stderr, + "\"%s\" digest512 byte %d should be 0x%02x, is 0x%02x\n", + tests[i].teststring, j, tests[i].digest512[j], + digest[j]); + errors++; + } + } + + } + + printf("%d errors\n", errors); + + return 0; +} + +#endif diff --git a/putty/SSHSHA.C b/putty/SSHSHA.C new file mode 100644 index 0000000..d1c7981 --- /dev/null +++ b/putty/SSHSHA.C @@ -0,0 +1,411 @@ +/* + * SHA1 hash algorithm. Used in SSH-2 as a MAC, and the transform is + * also used as a `stirring' function for the PuTTY random number + * pool. Implemented directly from the specification by Simon + * Tatham. + */ + +#include "ssh.h" + +/* ---------------------------------------------------------------------- + * Core SHA algorithm: processes 16-word blocks into a message digest. + */ + +#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) ) + +static void SHA_Core_Init(uint32 h[5]) +{ + h[0] = 0x67452301; + h[1] = 0xefcdab89; + h[2] = 0x98badcfe; + h[3] = 0x10325476; + h[4] = 0xc3d2e1f0; +} + +void SHATransform(word32 * digest, word32 * block) +{ + word32 w[80]; + word32 a, b, c, d, e; + int t; + + for (t = 0; t < 16; t++) + w[t] = block[t]; + + for (t = 16; t < 80; t++) { + word32 tmp = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]; + w[t] = rol(tmp, 1); + } + + a = digest[0]; + b = digest[1]; + c = digest[2]; + d = digest[3]; + e = digest[4]; + + for (t = 0; t < 20; t++) { + word32 tmp = + rol(a, 5) + ((b & c) | (d & ~b)) + e + w[t] + 0x5a827999; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + for (t = 20; t < 40; t++) { + word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0x6ed9eba1; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + for (t = 40; t < 60; t++) { + word32 tmp = rol(a, + 5) + ((b & c) | (b & d) | (c & d)) + e + w[t] + + 0x8f1bbcdc; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + for (t = 60; t < 80; t++) { + word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0xca62c1d6; + e = d; + d = c; + c = rol(b, 30); + b = a; + a = tmp; + } + + digest[0] += a; + digest[1] += b; + digest[2] += c; + digest[3] += d; + digest[4] += e; +} + +/* ---------------------------------------------------------------------- + * Outer SHA algorithm: take an arbitrary length byte string, + * convert it into 16-word blocks with the prescribed padding at + * the end, and pass those blocks to the core SHA algorithm. + */ + +void SHA_Init(SHA_State * s) +{ + SHA_Core_Init(s->h); + s->blkused = 0; + s->lenhi = s->lenlo = 0; +} + +void SHA_Bytes(SHA_State * s, void *p, int len) +{ + unsigned char *q = (unsigned char *) p; + uint32 wordblock[16]; + uint32 lenw = len; + int i; + + /* + * Update the length field. + */ + s->lenlo += lenw; + s->lenhi += (s->lenlo < lenw); + + if (s->blkused && s->blkused + len < 64) { + /* + * Trivial case: just add to the block. + */ + memcpy(s->block + s->blkused, q, len); + s->blkused += len; + } else { + /* + * We must complete and process at least one block. + */ + while (s->blkused + len >= 64) { + memcpy(s->block + s->blkused, q, 64 - s->blkused); + q += 64 - s->blkused; + len -= 64 - s->blkused; + /* Now process the block. Gather bytes big-endian into words */ + for (i = 0; i < 16; i++) { + wordblock[i] = + (((uint32) s->block[i * 4 + 0]) << 24) | + (((uint32) s->block[i * 4 + 1]) << 16) | + (((uint32) s->block[i * 4 + 2]) << 8) | + (((uint32) s->block[i * 4 + 3]) << 0); + } + SHATransform(s->h, wordblock); + s->blkused = 0; + } + memcpy(s->block, q, len); + s->blkused = len; + } +} + +void SHA_Final(SHA_State * s, unsigned char *output) +{ + int i; + int pad; + unsigned char c[64]; + uint32 lenhi, lenlo; + + if (s->blkused >= 56) + pad = 56 + 64 - s->blkused; + else + pad = 56 - s->blkused; + + lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3)); + lenlo = (s->lenlo << 3); + + memset(c, 0, pad); + c[0] = 0x80; + SHA_Bytes(s, &c, pad); + + c[0] = (lenhi >> 24) & 0xFF; + c[1] = (lenhi >> 16) & 0xFF; + c[2] = (lenhi >> 8) & 0xFF; + c[3] = (lenhi >> 0) & 0xFF; + c[4] = (lenlo >> 24) & 0xFF; + c[5] = (lenlo >> 16) & 0xFF; + c[6] = (lenlo >> 8) & 0xFF; + c[7] = (lenlo >> 0) & 0xFF; + + SHA_Bytes(s, &c, 8); + + for (i = 0; i < 5; i++) { + output[i * 4] = (s->h[i] >> 24) & 0xFF; + output[i * 4 + 1] = (s->h[i] >> 16) & 0xFF; + output[i * 4 + 2] = (s->h[i] >> 8) & 0xFF; + output[i * 4 + 3] = (s->h[i]) & 0xFF; + } +} + +void SHA_Simple(void *p, int len, unsigned char *output) +{ + SHA_State s; + + SHA_Init(&s); + SHA_Bytes(&s, p, len); + SHA_Final(&s, output); +} + +/* + * Thin abstraction for things where hashes are pluggable. + */ + +static void *sha1_init(void) +{ + SHA_State *s; + + s = snew(SHA_State); + SHA_Init(s); + return s; +} + +static void sha1_bytes(void *handle, void *p, int len) +{ + SHA_State *s = handle; + + SHA_Bytes(s, p, len); +} + +static void sha1_final(void *handle, unsigned char *output) +{ + SHA_State *s = handle; + + SHA_Final(s, output); + sfree(s); +} + +const struct ssh_hash ssh_sha1 = { + sha1_init, sha1_bytes, sha1_final, 20, "SHA-1" +}; + +/* ---------------------------------------------------------------------- + * The above is the SHA-1 algorithm itself. Now we implement the + * HMAC wrapper on it. + */ + +static void *sha1_make_context(void) +{ + return snewn(3, SHA_State); +} + +static void sha1_free_context(void *handle) +{ + sfree(handle); +} + +static void sha1_key_internal(void *handle, unsigned char *key, int len) +{ + SHA_State *keys = (SHA_State *)handle; + unsigned char foo[64]; + int i; + + memset(foo, 0x36, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + SHA_Init(&keys[0]); + SHA_Bytes(&keys[0], foo, 64); + + memset(foo, 0x5C, 64); + for (i = 0; i < len && i < 64; i++) + foo[i] ^= key[i]; + SHA_Init(&keys[1]); + SHA_Bytes(&keys[1], foo, 64); + + memset(foo, 0, 64); /* burn the evidence */ +} + +static void sha1_key(void *handle, unsigned char *key) +{ + sha1_key_internal(handle, key, 20); +} + +static void sha1_key_buggy(void *handle, unsigned char *key) +{ + sha1_key_internal(handle, key, 16); +} + +static void hmacsha1_start(void *handle) +{ + SHA_State *keys = (SHA_State *)handle; + + keys[2] = keys[0]; /* structure copy */ +} + +static void hmacsha1_bytes(void *handle, unsigned char const *blk, int len) +{ + SHA_State *keys = (SHA_State *)handle; + SHA_Bytes(&keys[2], (void *)blk, len); +} + +static void hmacsha1_genresult(void *handle, unsigned char *hmac) +{ + SHA_State *keys = (SHA_State *)handle; + SHA_State s; + unsigned char intermediate[20]; + + s = keys[2]; /* structure copy */ + SHA_Final(&s, intermediate); + s = keys[1]; /* structure copy */ + SHA_Bytes(&s, intermediate, 20); + SHA_Final(&s, hmac); +} + +static void sha1_do_hmac(void *handle, unsigned char *blk, int len, + unsigned long seq, unsigned char *hmac) +{ + unsigned char seqbuf[4]; + + seqbuf[0] = (unsigned char) ((seq >> 24) & 0xFF); + seqbuf[1] = (unsigned char) ((seq >> 16) & 0xFF); + seqbuf[2] = (unsigned char) ((seq >> 8) & 0xFF); + seqbuf[3] = (unsigned char) ((seq) & 0xFF); + + hmacsha1_start(handle); + hmacsha1_bytes(handle, seqbuf, 4); + hmacsha1_bytes(handle, blk, len); + hmacsha1_genresult(handle, hmac); +} + +static void sha1_generate(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + sha1_do_hmac(handle, blk, len, seq, blk + len); +} + +static int hmacsha1_verresult(void *handle, unsigned char const *hmac) +{ + unsigned char correct[20]; + hmacsha1_genresult(handle, correct); + return !memcmp(correct, hmac, 20); +} + +static int sha1_verify(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + unsigned char correct[20]; + sha1_do_hmac(handle, blk, len, seq, correct); + return !memcmp(correct, blk + len, 20); +} + +static void hmacsha1_96_genresult(void *handle, unsigned char *hmac) +{ + unsigned char full[20]; + hmacsha1_genresult(handle, full); + memcpy(hmac, full, 12); +} + +static void sha1_96_generate(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + unsigned char full[20]; + sha1_do_hmac(handle, blk, len, seq, full); + memcpy(blk + len, full, 12); +} + +static int hmacsha1_96_verresult(void *handle, unsigned char const *hmac) +{ + unsigned char correct[20]; + hmacsha1_genresult(handle, correct); + return !memcmp(correct, hmac, 12); +} + +static int sha1_96_verify(void *handle, unsigned char *blk, int len, + unsigned long seq) +{ + unsigned char correct[20]; + sha1_do_hmac(handle, blk, len, seq, correct); + return !memcmp(correct, blk + len, 12); +} + +void hmac_sha1_simple(void *key, int keylen, void *data, int datalen, + unsigned char *output) { + SHA_State states[2]; + unsigned char intermediate[20]; + + sha1_key_internal(states, key, keylen); + SHA_Bytes(&states[0], data, datalen); + SHA_Final(&states[0], intermediate); + + SHA_Bytes(&states[1], intermediate, 20); + SHA_Final(&states[1], output); +} + +const struct ssh_mac ssh_hmac_sha1 = { + sha1_make_context, sha1_free_context, sha1_key, + sha1_generate, sha1_verify, + hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult, + "hmac-sha1", + 20, + "HMAC-SHA1" +}; + +const struct ssh_mac ssh_hmac_sha1_96 = { + sha1_make_context, sha1_free_context, sha1_key, + sha1_96_generate, sha1_96_verify, + hmacsha1_start, hmacsha1_bytes, + hmacsha1_96_genresult, hmacsha1_96_verresult, + "hmac-sha1-96", + 12, + "HMAC-SHA1-96" +}; + +const struct ssh_mac ssh_hmac_sha1_buggy = { + sha1_make_context, sha1_free_context, sha1_key_buggy, + sha1_generate, sha1_verify, + hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult, + "hmac-sha1", + 20, + "bug-compatible HMAC-SHA1" +}; + +const struct ssh_mac ssh_hmac_sha1_96_buggy = { + sha1_make_context, sha1_free_context, sha1_key_buggy, + sha1_96_generate, sha1_96_verify, + hmacsha1_start, hmacsha1_bytes, + hmacsha1_96_genresult, hmacsha1_96_verresult, + "hmac-sha1-96", + 12, + "bug-compatible HMAC-SHA1-96" +}; diff --git a/putty/SSHZLIB.C b/putty/SSHZLIB.C new file mode 100644 index 0000000..9c780a4 --- /dev/null +++ b/putty/SSHZLIB.C @@ -0,0 +1,1385 @@ +/* + * Zlib (RFC1950 / RFC1951) compression for PuTTY. + * + * There will no doubt be criticism of my decision to reimplement + * Zlib compression from scratch instead of using the existing zlib + * code. People will cry `reinventing the wheel'; they'll claim + * that the `fundamental basis of OSS' is code reuse; they'll want + * to see a really good reason for me having chosen not to use the + * existing code. + * + * Well, here are my reasons. Firstly, I don't want to link the + * whole of zlib into the PuTTY binary; PuTTY is justifiably proud + * of its small size and I think zlib contains a lot of unnecessary + * baggage for the kind of compression that SSH requires. + * + * Secondly, I also don't like the alternative of using zlib.dll. + * Another thing PuTTY is justifiably proud of is its ease of + * installation, and the last thing I want to do is to start + * mandating DLLs. Not only that, but there are two _kinds_ of + * zlib.dll kicking around, one with C calling conventions on the + * exported functions and another with WINAPI conventions, and + * there would be a significant danger of getting the wrong one. + * + * Thirdly, there seems to be a difference of opinion on the IETF + * secsh mailing list about the correct way to round off a + * compressed packet and start the next. In particular, there's + * some talk of switching to a mechanism zlib isn't currently + * capable of supporting (see below for an explanation). Given that + * sort of uncertainty, I thought it might be better to have code + * that will support even the zlib-incompatible worst case. + * + * Fourthly, it's a _second implementation_. Second implementations + * are fundamentally a Good Thing in standardisation efforts. The + * difference of opinion mentioned above has arisen _precisely_ + * because there has been only one zlib implementation and + * everybody has used it. I don't intend that this should happen + * again. + */ + +#include +#include + +#ifdef ZLIB_STANDALONE + +/* + * This module also makes a handy zlib decoding tool for when + * you're picking apart Zip files or PDFs or PNGs. If you compile + * it with ZLIB_STANDALONE defined, it builds on its own and + * becomes a command-line utility. + * + * Therefore, here I provide a self-contained implementation of the + * macros required from the rest of the PuTTY sources. + */ +#define snew(type) ( (type *) malloc(sizeof(type)) ) +#define snewn(n, type) ( (type *) malloc((n) * sizeof(type)) ) +#define sresize(x, n, type) ( (type *) realloc((x), (n) * sizeof(type)) ) +#define sfree(x) ( free((x)) ) + +#else +#include "ssh.h" +#endif + +#ifndef FALSE +#define FALSE 0 +#define TRUE (!FALSE) +#endif + +/* ---------------------------------------------------------------------- + * Basic LZ77 code. This bit is designed modularly, so it could be + * ripped out and used in a different LZ77 compressor. Go to it, + * and good luck :-) + */ + +struct LZ77InternalContext; +struct LZ77Context { + struct LZ77InternalContext *ictx; + void *userdata; + void (*literal) (struct LZ77Context * ctx, unsigned char c); + void (*match) (struct LZ77Context * ctx, int distance, int len); +}; + +/* + * Initialise the private fields of an LZ77Context. It's up to the + * user to initialise the public fields. + */ +static int lz77_init(struct LZ77Context *ctx); + +/* + * Supply data to be compressed. Will update the private fields of + * the LZ77Context, and will call literal() and match() to output. + * If `compress' is FALSE, it will never emit a match, but will + * instead call literal() for everything. + */ +static void lz77_compress(struct LZ77Context *ctx, + unsigned char *data, int len, int compress); + +/* + * Modifiable parameters. + */ +#define WINSIZE 32768 /* window size. Must be power of 2! */ +#define HASHMAX 2039 /* one more than max hash value */ +#define MAXMATCH 32 /* how many matches we track */ +#define HASHCHARS 3 /* how many chars make a hash */ + +/* + * This compressor takes a less slapdash approach than the + * gzip/zlib one. Rather than allowing our hash chains to fall into + * disuse near the far end, we keep them doubly linked so we can + * _find_ the far end, and then every time we add a new byte to the + * window (thus rolling round by one and removing the previous + * byte), we can carefully remove the hash chain entry. + */ + +#define INVALID -1 /* invalid hash _and_ invalid offset */ +struct WindowEntry { + short next, prev; /* array indices within the window */ + short hashval; +}; + +struct HashEntry { + short first; /* window index of first in chain */ +}; + +struct Match { + int distance, len; +}; + +struct LZ77InternalContext { + struct WindowEntry win[WINSIZE]; + unsigned char data[WINSIZE]; + int winpos; + struct HashEntry hashtab[HASHMAX]; + unsigned char pending[HASHCHARS]; + int npending; +}; + +static int lz77_hash(unsigned char *data) +{ + return (257 * data[0] + 263 * data[1] + 269 * data[2]) % HASHMAX; +} + +static int lz77_init(struct LZ77Context *ctx) +{ + struct LZ77InternalContext *st; + int i; + + st = snew(struct LZ77InternalContext); + if (!st) + return 0; + + ctx->ictx = st; + + for (i = 0; i < WINSIZE; i++) + st->win[i].next = st->win[i].prev = st->win[i].hashval = INVALID; + for (i = 0; i < HASHMAX; i++) + st->hashtab[i].first = INVALID; + st->winpos = 0; + + st->npending = 0; + + return 1; +} + +static void lz77_advance(struct LZ77InternalContext *st, + unsigned char c, int hash) +{ + int off; + + /* + * Remove the hash entry at winpos from the tail of its chain, + * or empty the chain if it's the only thing on the chain. + */ + if (st->win[st->winpos].prev != INVALID) { + st->win[st->win[st->winpos].prev].next = INVALID; + } else if (st->win[st->winpos].hashval != INVALID) { + st->hashtab[st->win[st->winpos].hashval].first = INVALID; + } + + /* + * Create a new entry at winpos and add it to the head of its + * hash chain. + */ + st->win[st->winpos].hashval = hash; + st->win[st->winpos].prev = INVALID; + off = st->win[st->winpos].next = st->hashtab[hash].first; + st->hashtab[hash].first = st->winpos; + if (off != INVALID) + st->win[off].prev = st->winpos; + st->data[st->winpos] = c; + + /* + * Advance the window pointer. + */ + st->winpos = (st->winpos + 1) & (WINSIZE - 1); +} + +#define CHARAT(k) ( (k)<0 ? st->data[(st->winpos+k)&(WINSIZE-1)] : data[k] ) + +static void lz77_compress(struct LZ77Context *ctx, + unsigned char *data, int len, int compress) +{ + struct LZ77InternalContext *st = ctx->ictx; + int i, hash, distance, off, nmatch, matchlen, advance; + struct Match defermatch, matches[MAXMATCH]; + int deferchr; + + /* + * Add any pending characters from last time to the window. (We + * might not be able to.) + */ + for (i = 0; i < st->npending; i++) { + unsigned char foo[HASHCHARS]; + int j; + if (len + st->npending - i < HASHCHARS) { + /* Update the pending array. */ + for (j = i; j < st->npending; j++) + st->pending[j - i] = st->pending[j]; + break; + } + for (j = 0; j < HASHCHARS; j++) + foo[j] = (i + j < st->npending ? st->pending[i + j] : + data[i + j - st->npending]); + lz77_advance(st, foo[0], lz77_hash(foo)); + } + st->npending -= i; + + defermatch.distance = 0; /* appease compiler */ + defermatch.len = 0; + deferchr = '\0'; + while (len > 0) { + + /* Don't even look for a match, if we're not compressing. */ + if (compress && len >= HASHCHARS) { + /* + * Hash the next few characters. + */ + hash = lz77_hash(data); + + /* + * Look the hash up in the corresponding hash chain and see + * what we can find. + */ + nmatch = 0; + for (off = st->hashtab[hash].first; + off != INVALID; off = st->win[off].next) { + /* distance = 1 if off == st->winpos-1 */ + /* distance = WINSIZE if off == st->winpos */ + distance = + WINSIZE - (off + WINSIZE - st->winpos) % WINSIZE; + for (i = 0; i < HASHCHARS; i++) + if (CHARAT(i) != CHARAT(i - distance)) + break; + if (i == HASHCHARS) { + matches[nmatch].distance = distance; + matches[nmatch].len = 3; + if (++nmatch >= MAXMATCH) + break; + } + } + } else { + nmatch = 0; + hash = INVALID; + } + + if (nmatch > 0) { + /* + * We've now filled up matches[] with nmatch potential + * matches. Follow them down to find the longest. (We + * assume here that it's always worth favouring a + * longer match over a shorter one.) + */ + matchlen = HASHCHARS; + while (matchlen < len) { + int j; + for (i = j = 0; i < nmatch; i++) { + if (CHARAT(matchlen) == + CHARAT(matchlen - matches[i].distance)) { + matches[j++] = matches[i]; + } + } + if (j == 0) + break; + matchlen++; + nmatch = j; + } + + /* + * We've now got all the longest matches. We favour the + * shorter distances, which means we go with matches[0]. + * So see if we want to defer it or throw it away. + */ + matches[0].len = matchlen; + if (defermatch.len > 0) { + if (matches[0].len > defermatch.len + 1) { + /* We have a better match. Emit the deferred char, + * and defer this match. */ + ctx->literal(ctx, (unsigned char) deferchr); + defermatch = matches[0]; + deferchr = data[0]; + advance = 1; + } else { + /* We don't have a better match. Do the deferred one. */ + ctx->match(ctx, defermatch.distance, defermatch.len); + advance = defermatch.len - 1; + defermatch.len = 0; + } + } else { + /* There was no deferred match. Defer this one. */ + defermatch = matches[0]; + deferchr = data[0]; + advance = 1; + } + } else { + /* + * We found no matches. Emit the deferred match, if + * any; otherwise emit a literal. + */ + if (defermatch.len > 0) { + ctx->match(ctx, defermatch.distance, defermatch.len); + advance = defermatch.len - 1; + defermatch.len = 0; + } else { + ctx->literal(ctx, data[0]); + advance = 1; + } + } + + /* + * Now advance the position by `advance' characters, + * keeping the window and hash chains consistent. + */ + while (advance > 0) { + if (len >= HASHCHARS) { + lz77_advance(st, *data, lz77_hash(data)); + } else { + st->pending[st->npending++] = *data; + } + data++; + len--; + advance--; + } + } +} + +/* ---------------------------------------------------------------------- + * Zlib compression. We always use the static Huffman tree option. + * Mostly this is because it's hard to scan a block in advance to + * work out better trees; dynamic trees are great when you're + * compressing a large file under no significant time constraint, + * but when you're compressing little bits in real time, things get + * hairier. + * + * I suppose it's possible that I could compute Huffman trees based + * on the frequencies in the _previous_ block, as a sort of + * heuristic, but I'm not confident that the gain would balance out + * having to transmit the trees. + */ + +struct Outbuf { + unsigned char *outbuf; + int outlen, outsize; + unsigned long outbits; + int noutbits; + int firstblock; + int comp_disabled; +}; + +static void outbits(struct Outbuf *out, unsigned long bits, int nbits) +{ + assert(out->noutbits + nbits <= 32); + out->outbits |= bits << out->noutbits; + out->noutbits += nbits; + while (out->noutbits >= 8) { + if (out->outlen >= out->outsize) { + out->outsize = out->outlen + 64; + out->outbuf = sresize(out->outbuf, out->outsize, unsigned char); + } + out->outbuf[out->outlen++] = (unsigned char) (out->outbits & 0xFF); + out->outbits >>= 8; + out->noutbits -= 8; + } +} + +static const unsigned char mirrorbytes[256] = { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, + 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, + 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, + 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, + 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, + 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, + 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, + 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, + 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, + 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, + 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, + 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, + 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, +}; + +typedef struct { + short code, extrabits; + int min, max; +} coderecord; + +static const coderecord lencodes[] = { + {257, 0, 3, 3}, + {258, 0, 4, 4}, + {259, 0, 5, 5}, + {260, 0, 6, 6}, + {261, 0, 7, 7}, + {262, 0, 8, 8}, + {263, 0, 9, 9}, + {264, 0, 10, 10}, + {265, 1, 11, 12}, + {266, 1, 13, 14}, + {267, 1, 15, 16}, + {268, 1, 17, 18}, + {269, 2, 19, 22}, + {270, 2, 23, 26}, + {271, 2, 27, 30}, + {272, 2, 31, 34}, + {273, 3, 35, 42}, + {274, 3, 43, 50}, + {275, 3, 51, 58}, + {276, 3, 59, 66}, + {277, 4, 67, 82}, + {278, 4, 83, 98}, + {279, 4, 99, 114}, + {280, 4, 115, 130}, + {281, 5, 131, 162}, + {282, 5, 163, 194}, + {283, 5, 195, 226}, + {284, 5, 227, 257}, + {285, 0, 258, 258}, +}; + +static const coderecord distcodes[] = { + {0, 0, 1, 1}, + {1, 0, 2, 2}, + {2, 0, 3, 3}, + {3, 0, 4, 4}, + {4, 1, 5, 6}, + {5, 1, 7, 8}, + {6, 2, 9, 12}, + {7, 2, 13, 16}, + {8, 3, 17, 24}, + {9, 3, 25, 32}, + {10, 4, 33, 48}, + {11, 4, 49, 64}, + {12, 5, 65, 96}, + {13, 5, 97, 128}, + {14, 6, 129, 192}, + {15, 6, 193, 256}, + {16, 7, 257, 384}, + {17, 7, 385, 512}, + {18, 8, 513, 768}, + {19, 8, 769, 1024}, + {20, 9, 1025, 1536}, + {21, 9, 1537, 2048}, + {22, 10, 2049, 3072}, + {23, 10, 3073, 4096}, + {24, 11, 4097, 6144}, + {25, 11, 6145, 8192}, + {26, 12, 8193, 12288}, + {27, 12, 12289, 16384}, + {28, 13, 16385, 24576}, + {29, 13, 24577, 32768}, +}; + +static void zlib_literal(struct LZ77Context *ectx, unsigned char c) +{ + struct Outbuf *out = (struct Outbuf *) ectx->userdata; + + if (out->comp_disabled) { + /* + * We're in an uncompressed block, so just output the byte. + */ + outbits(out, c, 8); + return; + } + + if (c <= 143) { + /* 0 through 143 are 8 bits long starting at 00110000. */ + outbits(out, mirrorbytes[0x30 + c], 8); + } else { + /* 144 through 255 are 9 bits long starting at 110010000. */ + outbits(out, 1 + 2 * mirrorbytes[0x90 - 144 + c], 9); + } +} + +static void zlib_match(struct LZ77Context *ectx, int distance, int len) +{ + const coderecord *d, *l; + int i, j, k; + struct Outbuf *out = (struct Outbuf *) ectx->userdata; + + assert(!out->comp_disabled); + + while (len > 0) { + int thislen; + + /* + * We can transmit matches of lengths 3 through 258 + * inclusive. So if len exceeds 258, we must transmit in + * several steps, with 258 or less in each step. + * + * Specifically: if len >= 261, we can transmit 258 and be + * sure of having at least 3 left for the next step. And if + * len <= 258, we can just transmit len. But if len == 259 + * or 260, we must transmit len-3. + */ + thislen = (len > 260 ? 258 : len <= 258 ? len : len - 3); + len -= thislen; + + /* + * Binary-search to find which length code we're + * transmitting. + */ + i = -1; + j = sizeof(lencodes) / sizeof(*lencodes); + while (1) { + assert(j - i >= 2); + k = (j + i) / 2; + if (thislen < lencodes[k].min) + j = k; + else if (thislen > lencodes[k].max) + i = k; + else { + l = &lencodes[k]; + break; /* found it! */ + } + } + + /* + * Transmit the length code. 256-279 are seven bits + * starting at 0000000; 280-287 are eight bits starting at + * 11000000. + */ + if (l->code <= 279) { + outbits(out, mirrorbytes[(l->code - 256) * 2], 7); + } else { + outbits(out, mirrorbytes[0xc0 - 280 + l->code], 8); + } + + /* + * Transmit the extra bits. + */ + if (l->extrabits) + outbits(out, thislen - l->min, l->extrabits); + + /* + * Binary-search to find which distance code we're + * transmitting. + */ + i = -1; + j = sizeof(distcodes) / sizeof(*distcodes); + while (1) { + assert(j - i >= 2); + k = (j + i) / 2; + if (distance < distcodes[k].min) + j = k; + else if (distance > distcodes[k].max) + i = k; + else { + d = &distcodes[k]; + break; /* found it! */ + } + } + + /* + * Transmit the distance code. Five bits starting at 00000. + */ + outbits(out, mirrorbytes[d->code * 8], 5); + + /* + * Transmit the extra bits. + */ + if (d->extrabits) + outbits(out, distance - d->min, d->extrabits); + } +} + +void *zlib_compress_init(void) +{ + struct Outbuf *out; + struct LZ77Context *ectx = snew(struct LZ77Context); + + lz77_init(ectx); + ectx->literal = zlib_literal; + ectx->match = zlib_match; + + out = snew(struct Outbuf); + out->outbits = out->noutbits = 0; + out->firstblock = 1; + out->comp_disabled = FALSE; + ectx->userdata = out; + + return ectx; +} + +void zlib_compress_cleanup(void *handle) +{ + struct LZ77Context *ectx = (struct LZ77Context *)handle; + sfree(ectx->userdata); + sfree(ectx->ictx); + sfree(ectx); +} + +/* + * Turn off actual LZ77 analysis for one block, to facilitate + * construction of a precise-length IGNORE packet. Returns the + * length adjustment (which is only valid for packets < 65536 + * bytes, but that seems reasonable enough). + */ +static int zlib_disable_compression(void *handle) +{ + struct LZ77Context *ectx = (struct LZ77Context *)handle; + struct Outbuf *out = (struct Outbuf *) ectx->userdata; + int n; + + out->comp_disabled = TRUE; + + n = 0; + /* + * If this is the first block, we will start by outputting two + * header bytes, and then three bits to begin an uncompressed + * block. This will cost three bytes (because we will start on + * a byte boundary, this is certain). + */ + if (out->firstblock) { + n = 3; + } else { + /* + * Otherwise, we will output seven bits to close the + * previous static block, and _then_ three bits to begin an + * uncompressed block, and then flush the current byte. + * This may cost two bytes or three, depending on noutbits. + */ + n += (out->noutbits + 10) / 8; + } + + /* + * Now we output four bytes for the length / ~length pair in + * the uncompressed block. + */ + n += 4; + + return n; +} + +int zlib_compress_block(void *handle, unsigned char *block, int len, + unsigned char **outblock, int *outlen) +{ + struct LZ77Context *ectx = (struct LZ77Context *)handle; + struct Outbuf *out = (struct Outbuf *) ectx->userdata; + int in_block; + + out->outbuf = NULL; + out->outlen = out->outsize = 0; + + /* + * If this is the first block, output the Zlib (RFC1950) header + * bytes 78 9C. (Deflate compression, 32K window size, default + * algorithm.) + */ + if (out->firstblock) { + outbits(out, 0x9C78, 16); + out->firstblock = 0; + + in_block = FALSE; + } else + in_block = TRUE; + + if (out->comp_disabled) { + if (in_block) + outbits(out, 0, 7); /* close static block */ + + while (len > 0) { + int blen = (len < 65535 ? len : 65535); + + /* + * Start a Deflate (RFC1951) uncompressed block. We + * transmit a zero bit (BFINAL=0), followed by two more + * zero bits (BTYPE=00). Of course these are in the + * wrong order (00 0), not that it matters. + */ + outbits(out, 0, 3); + + /* + * Output zero bits to align to a byte boundary. + */ + if (out->noutbits) + outbits(out, 0, 8 - out->noutbits); + + /* + * Output the block length, and then its one's + * complement. They're little-endian, so all we need to + * do is pass them straight to outbits() with bit count + * 16. + */ + outbits(out, blen, 16); + outbits(out, blen ^ 0xFFFF, 16); + + /* + * Do the `compression': we need to pass the data to + * lz77_compress so that it will be taken into account + * for subsequent (distance,length) pairs. But + * lz77_compress is passed FALSE, which means it won't + * actually find (or even look for) any matches; so + * every character will be passed straight to + * zlib_literal which will spot out->comp_disabled and + * emit in the uncompressed format. + */ + lz77_compress(ectx, block, blen, FALSE); + + len -= blen; + block += blen; + } + outbits(out, 2, 3); /* open new block */ + } else { + if (!in_block) { + /* + * Start a Deflate (RFC1951) fixed-trees block. We + * transmit a zero bit (BFINAL=0), followed by a zero + * bit and a one bit (BTYPE=01). Of course these are in + * the wrong order (01 0). + */ + outbits(out, 2, 3); + } + + /* + * Do the compression. + */ + lz77_compress(ectx, block, len, TRUE); + + /* + * End the block (by transmitting code 256, which is + * 0000000 in fixed-tree mode), and transmit some empty + * blocks to ensure we have emitted the byte containing the + * last piece of genuine data. There are three ways we can + * do this: + * + * - Minimal flush. Output end-of-block and then open a + * new static block. This takes 9 bits, which is + * guaranteed to flush out the last genuine code in the + * closed block; but allegedly zlib can't handle it. + * + * - Zlib partial flush. Output EOB, open and close an + * empty static block, and _then_ open the new block. + * This is the best zlib can handle. + * + * - Zlib sync flush. Output EOB, then an empty + * _uncompressed_ block (000, then sync to byte + * boundary, then send bytes 00 00 FF FF). Then open the + * new block. + * + * For the moment, we will use Zlib partial flush. + */ + outbits(out, 0, 7); /* close block */ + outbits(out, 2, 3 + 7); /* empty static block */ + outbits(out, 2, 3); /* open new block */ + } + + out->comp_disabled = FALSE; + + *outblock = out->outbuf; + *outlen = out->outlen; + + return 1; +} + +/* ---------------------------------------------------------------------- + * Zlib decompression. Of course, even though our compressor always + * uses static trees, our _decompressor_ has to be capable of + * handling dynamic trees if it sees them. + */ + +/* + * The way we work the Huffman decode is to have a table lookup on + * the first N bits of the input stream (in the order they arrive, + * of course, i.e. the first bit of the Huffman code is in bit 0). + * Each table entry lists the number of bits to consume, plus + * either an output code or a pointer to a secondary table. + */ +struct zlib_table; +struct zlib_tableentry; + +struct zlib_tableentry { + unsigned char nbits; + short code; + struct zlib_table *nexttable; +}; + +struct zlib_table { + int mask; /* mask applied to input bit stream */ + struct zlib_tableentry *table; +}; + +#define MAXCODELEN 16 +#define MAXSYMS 288 + +/* + * Build a single-level decode table for elements + * [minlength,maxlength) of the provided code/length tables, and + * recurse to build subtables. + */ +static struct zlib_table *zlib_mkonetab(int *codes, unsigned char *lengths, + int nsyms, + int pfx, int pfxbits, int bits) +{ + struct zlib_table *tab = snew(struct zlib_table); + int pfxmask = (1 << pfxbits) - 1; + int nbits, i, j, code; + + tab->table = snewn(1 << bits, struct zlib_tableentry); + tab->mask = (1 << bits) - 1; + + for (code = 0; code <= tab->mask; code++) { + tab->table[code].code = -1; + tab->table[code].nbits = 0; + tab->table[code].nexttable = NULL; + } + + for (i = 0; i < nsyms; i++) { + if (lengths[i] <= pfxbits || (codes[i] & pfxmask) != pfx) + continue; + code = (codes[i] >> pfxbits) & tab->mask; + for (j = code; j <= tab->mask; j += 1 << (lengths[i] - pfxbits)) { + tab->table[j].code = i; + nbits = lengths[i] - pfxbits; + if (tab->table[j].nbits < nbits) + tab->table[j].nbits = nbits; + } + } + for (code = 0; code <= tab->mask; code++) { + if (tab->table[code].nbits <= bits) + continue; + /* Generate a subtable. */ + tab->table[code].code = -1; + nbits = tab->table[code].nbits - bits; + if (nbits > 7) + nbits = 7; + tab->table[code].nbits = bits; + tab->table[code].nexttable = zlib_mkonetab(codes, lengths, nsyms, + pfx | (code << pfxbits), + pfxbits + bits, nbits); + } + + return tab; +} + +/* + * Build a decode table, given a set of Huffman tree lengths. + */ +static struct zlib_table *zlib_mktable(unsigned char *lengths, + int nlengths) +{ + int count[MAXCODELEN], startcode[MAXCODELEN], codes[MAXSYMS]; + int code, maxlen; + int i, j; + + /* Count the codes of each length. */ + maxlen = 0; + for (i = 1; i < MAXCODELEN; i++) + count[i] = 0; + for (i = 0; i < nlengths; i++) { + count[lengths[i]]++; + if (maxlen < lengths[i]) + maxlen = lengths[i]; + } + /* Determine the starting code for each length block. */ + code = 0; + for (i = 1; i < MAXCODELEN; i++) { + startcode[i] = code; + code += count[i]; + code <<= 1; + } + /* Determine the code for each symbol. Mirrored, of course. */ + for (i = 0; i < nlengths; i++) { + code = startcode[lengths[i]]++; + codes[i] = 0; + for (j = 0; j < lengths[i]; j++) { + codes[i] = (codes[i] << 1) | (code & 1); + code >>= 1; + } + } + + /* + * Now we have the complete list of Huffman codes. Build a + * table. + */ + return zlib_mkonetab(codes, lengths, nlengths, 0, 0, + maxlen < 9 ? maxlen : 9); +} + +static int zlib_freetable(struct zlib_table **ztab) +{ + struct zlib_table *tab; + int code; + + if (ztab == NULL) + return -1; + + if (*ztab == NULL) + return 0; + + tab = *ztab; + + for (code = 0; code <= tab->mask; code++) + if (tab->table[code].nexttable != NULL) + zlib_freetable(&tab->table[code].nexttable); + + sfree(tab->table); + tab->table = NULL; + + sfree(tab); + *ztab = NULL; + + return (0); +} + +struct zlib_decompress_ctx { + struct zlib_table *staticlentable, *staticdisttable; + struct zlib_table *currlentable, *currdisttable, *lenlentable; + enum { + START, OUTSIDEBLK, + TREES_HDR, TREES_LENLEN, TREES_LEN, TREES_LENREP, + INBLK, GOTLENSYM, GOTLEN, GOTDISTSYM, + UNCOMP_LEN, UNCOMP_NLEN, UNCOMP_DATA + } state; + int sym, hlit, hdist, hclen, lenptr, lenextrabits, lenaddon, len, + lenrep; + int uncomplen; + unsigned char lenlen[19]; + unsigned char lengths[286 + 32]; + unsigned long bits; + int nbits; + unsigned char window[WINSIZE]; + int winpos; + unsigned char *outblk; + int outlen, outsize; +}; + +void *zlib_decompress_init(void) +{ + struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx); + unsigned char lengths[288]; + + memset(lengths, 8, 144); + memset(lengths + 144, 9, 256 - 144); + memset(lengths + 256, 7, 280 - 256); + memset(lengths + 280, 8, 288 - 280); + dctx->staticlentable = zlib_mktable(lengths, 288); + memset(lengths, 5, 32); + dctx->staticdisttable = zlib_mktable(lengths, 32); + dctx->state = START; /* even before header */ + dctx->currlentable = dctx->currdisttable = dctx->lenlentable = NULL; + dctx->bits = 0; + dctx->nbits = 0; + dctx->winpos = 0; + + return dctx; +} + +void zlib_decompress_cleanup(void *handle) +{ + struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle; + + if (dctx->currlentable && dctx->currlentable != dctx->staticlentable) + zlib_freetable(&dctx->currlentable); + if (dctx->currdisttable && dctx->currdisttable != dctx->staticdisttable) + zlib_freetable(&dctx->currdisttable); + if (dctx->lenlentable) + zlib_freetable(&dctx->lenlentable); + zlib_freetable(&dctx->staticlentable); + zlib_freetable(&dctx->staticdisttable); + sfree(dctx); +} + +static int zlib_huflookup(unsigned long *bitsp, int *nbitsp, + struct zlib_table *tab) +{ + unsigned long bits = *bitsp; + int nbits = *nbitsp; + while (1) { + struct zlib_tableentry *ent; + ent = &tab->table[bits & tab->mask]; + if (ent->nbits > nbits) + return -1; /* not enough data */ + bits >>= ent->nbits; + nbits -= ent->nbits; + if (ent->code == -1) + tab = ent->nexttable; + else { + *bitsp = bits; + *nbitsp = nbits; + return ent->code; + } + + if (!tab) { + /* + * There was a missing entry in the table, presumably + * due to an invalid Huffman table description, and the + * subsequent data has attempted to use the missing + * entry. Return a decoding failure. + */ + return -2; + } + } +} + +static void zlib_emit_char(struct zlib_decompress_ctx *dctx, int c) +{ + dctx->window[dctx->winpos] = c; + dctx->winpos = (dctx->winpos + 1) & (WINSIZE - 1); + if (dctx->outlen >= dctx->outsize) { + dctx->outsize = dctx->outlen + 512; + dctx->outblk = sresize(dctx->outblk, dctx->outsize, unsigned char); + } + dctx->outblk[dctx->outlen++] = c; +} + +#define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) ) + +int zlib_decompress_block(void *handle, unsigned char *block, int len, + unsigned char **outblock, int *outlen) +{ + struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle; + const coderecord *rec; + int code, blktype, rep, dist, nlen, header; + static const unsigned char lenlenmap[] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + dctx->outblk = snewn(256, unsigned char); + dctx->outsize = 256; + dctx->outlen = 0; + + while (len > 0 || dctx->nbits > 0) { + while (dctx->nbits < 24 && len > 0) { + dctx->bits |= (*block++) << dctx->nbits; + dctx->nbits += 8; + len--; + } + switch (dctx->state) { + case START: + /* Expect 16-bit zlib header. */ + if (dctx->nbits < 16) + goto finished; /* done all we can */ + + /* + * The header is stored as a big-endian 16-bit integer, + * in contrast to the general little-endian policy in + * the rest of the format :-( + */ + header = (((dctx->bits & 0xFF00) >> 8) | + ((dctx->bits & 0x00FF) << 8)); + EATBITS(16); + + /* + * Check the header: + * + * - bits 8-11 should be 1000 (Deflate/RFC1951) + * - bits 12-15 should be at most 0111 (window size) + * - bit 5 should be zero (no dictionary present) + * - we don't care about bits 6-7 (compression rate) + * - bits 0-4 should be set up to make the whole thing + * a multiple of 31 (checksum). + */ + if ((header & 0x0F00) != 0x0800 || + (header & 0xF000) > 0x7000 || + (header & 0x0020) != 0x0000 || + (header % 31) != 0) + goto decode_error; + + dctx->state = OUTSIDEBLK; + break; + case OUTSIDEBLK: + /* Expect 3-bit block header. */ + if (dctx->nbits < 3) + goto finished; /* done all we can */ + EATBITS(1); + blktype = dctx->bits & 3; + EATBITS(2); + if (blktype == 0) { + int to_eat = dctx->nbits & 7; + dctx->state = UNCOMP_LEN; + EATBITS(to_eat); /* align to byte boundary */ + } else if (blktype == 1) { + dctx->currlentable = dctx->staticlentable; + dctx->currdisttable = dctx->staticdisttable; + dctx->state = INBLK; + } else if (blktype == 2) { + dctx->state = TREES_HDR; + } + break; + case TREES_HDR: + /* + * Dynamic block header. Five bits of HLIT, five of + * HDIST, four of HCLEN. + */ + if (dctx->nbits < 5 + 5 + 4) + goto finished; /* done all we can */ + dctx->hlit = 257 + (dctx->bits & 31); + EATBITS(5); + dctx->hdist = 1 + (dctx->bits & 31); + EATBITS(5); + dctx->hclen = 4 + (dctx->bits & 15); + EATBITS(4); + dctx->lenptr = 0; + dctx->state = TREES_LENLEN; + memset(dctx->lenlen, 0, sizeof(dctx->lenlen)); + break; + case TREES_LENLEN: + if (dctx->nbits < 3) + goto finished; + while (dctx->lenptr < dctx->hclen && dctx->nbits >= 3) { + dctx->lenlen[lenlenmap[dctx->lenptr++]] = + (unsigned char) (dctx->bits & 7); + EATBITS(3); + } + if (dctx->lenptr == dctx->hclen) { + dctx->lenlentable = zlib_mktable(dctx->lenlen, 19); + dctx->state = TREES_LEN; + dctx->lenptr = 0; + } + break; + case TREES_LEN: + if (dctx->lenptr >= dctx->hlit + dctx->hdist) { + dctx->currlentable = zlib_mktable(dctx->lengths, dctx->hlit); + dctx->currdisttable = zlib_mktable(dctx->lengths + dctx->hlit, + dctx->hdist); + zlib_freetable(&dctx->lenlentable); + dctx->lenlentable = NULL; + dctx->state = INBLK; + break; + } + code = + zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->lenlentable); + if (code == -1) + goto finished; + if (code == -2) + goto decode_error; + if (code < 16) + dctx->lengths[dctx->lenptr++] = code; + else { + dctx->lenextrabits = (code == 16 ? 2 : code == 17 ? 3 : 7); + dctx->lenaddon = (code == 18 ? 11 : 3); + dctx->lenrep = (code == 16 && dctx->lenptr > 0 ? + dctx->lengths[dctx->lenptr - 1] : 0); + dctx->state = TREES_LENREP; + } + break; + case TREES_LENREP: + if (dctx->nbits < dctx->lenextrabits) + goto finished; + rep = + dctx->lenaddon + + (dctx->bits & ((1 << dctx->lenextrabits) - 1)); + EATBITS(dctx->lenextrabits); + while (rep > 0 && dctx->lenptr < dctx->hlit + dctx->hdist) { + dctx->lengths[dctx->lenptr] = dctx->lenrep; + dctx->lenptr++; + rep--; + } + dctx->state = TREES_LEN; + break; + case INBLK: + code = + zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->currlentable); + if (code == -1) + goto finished; + if (code == -2) + goto decode_error; + if (code < 256) + zlib_emit_char(dctx, code); + else if (code == 256) { + dctx->state = OUTSIDEBLK; + if (dctx->currlentable != dctx->staticlentable) { + zlib_freetable(&dctx->currlentable); + dctx->currlentable = NULL; + } + if (dctx->currdisttable != dctx->staticdisttable) { + zlib_freetable(&dctx->currdisttable); + dctx->currdisttable = NULL; + } + } else if (code < 286) { /* static tree can give >285; ignore */ + dctx->state = GOTLENSYM; + dctx->sym = code; + } + break; + case GOTLENSYM: + rec = &lencodes[dctx->sym - 257]; + if (dctx->nbits < rec->extrabits) + goto finished; + dctx->len = + rec->min + (dctx->bits & ((1 << rec->extrabits) - 1)); + EATBITS(rec->extrabits); + dctx->state = GOTLEN; + break; + case GOTLEN: + code = + zlib_huflookup(&dctx->bits, &dctx->nbits, + dctx->currdisttable); + if (code == -1) + goto finished; + if (code == -2) + goto decode_error; + dctx->state = GOTDISTSYM; + dctx->sym = code; + break; + case GOTDISTSYM: + rec = &distcodes[dctx->sym]; + if (dctx->nbits < rec->extrabits) + goto finished; + dist = rec->min + (dctx->bits & ((1 << rec->extrabits) - 1)); + EATBITS(rec->extrabits); + dctx->state = INBLK; + while (dctx->len--) + zlib_emit_char(dctx, dctx->window[(dctx->winpos - dist) & + (WINSIZE - 1)]); + break; + case UNCOMP_LEN: + /* + * Uncompressed block. We expect to see a 16-bit LEN. + */ + if (dctx->nbits < 16) + goto finished; + dctx->uncomplen = dctx->bits & 0xFFFF; + EATBITS(16); + dctx->state = UNCOMP_NLEN; + break; + case UNCOMP_NLEN: + /* + * Uncompressed block. We expect to see a 16-bit NLEN, + * which should be the one's complement of the previous + * LEN. + */ + if (dctx->nbits < 16) + goto finished; + nlen = dctx->bits & 0xFFFF; + EATBITS(16); + if (dctx->uncomplen != (nlen ^ 0xFFFF)) + goto decode_error; + if (dctx->uncomplen == 0) + dctx->state = OUTSIDEBLK; /* block is empty */ + else + dctx->state = UNCOMP_DATA; + break; + case UNCOMP_DATA: + if (dctx->nbits < 8) + goto finished; + zlib_emit_char(dctx, dctx->bits & 0xFF); + EATBITS(8); + if (--dctx->uncomplen == 0) + dctx->state = OUTSIDEBLK; /* end of uncompressed block */ + break; + } + } + + finished: + *outblock = dctx->outblk; + *outlen = dctx->outlen; + return 1; + + decode_error: + sfree(dctx->outblk); + *outblock = dctx->outblk = NULL; + *outlen = 0; + return 0; +} + +#ifdef ZLIB_STANDALONE + +#include +#include + +int main(int argc, char **argv) +{ + unsigned char buf[16], *outbuf; + int ret, outlen; + void *handle; + int noheader = FALSE, opts = TRUE; + char *filename = NULL; + FILE *fp; + + while (--argc) { + char *p = *++argv; + + if (p[0] == '-' && opts) { + if (!strcmp(p, "-d")) + noheader = TRUE; + else if (!strcmp(p, "--")) + opts = FALSE; /* next thing is filename */ + else { + fprintf(stderr, "unknown command line option '%s'\n", p); + return 1; + } + } else if (!filename) { + filename = p; + } else { + fprintf(stderr, "can only handle one filename\n"); + return 1; + } + } + + handle = zlib_decompress_init(); + + if (noheader) { + /* + * Provide missing zlib header if -d was specified. + */ + zlib_decompress_block(handle, "\x78\x9C", 2, &outbuf, &outlen); + assert(outlen == 0); + } + + if (filename) + fp = fopen(filename, "rb"); + else + fp = stdin; + + if (!fp) { + assert(filename); + fprintf(stderr, "unable to open '%s'\n", filename); + return 1; + } + + while (1) { + ret = fread(buf, 1, sizeof(buf), fp); + if (ret <= 0) + break; + zlib_decompress_block(handle, buf, ret, &outbuf, &outlen); + if (outbuf) { + if (outlen) + fwrite(outbuf, 1, outlen, stdout); + sfree(outbuf); + } else { + fprintf(stderr, "decoding error\n"); + return 1; + } + } + + zlib_decompress_cleanup(handle); + + if (filename) + fclose(fp); + + return 0; +} + +#else + +const struct ssh_compress ssh_zlib = { + "zlib", + "zlib@openssh.com", /* delayed version */ + zlib_compress_init, + zlib_compress_cleanup, + zlib_compress_block, + zlib_decompress_init, + zlib_decompress_cleanup, + zlib_decompress_block, + zlib_disable_compression, + "zlib (RFC1950)" +}; + +#endif diff --git a/putty/STORAGE.H b/putty/STORAGE.H new file mode 100644 index 0000000..0e0a7c0 --- /dev/null +++ b/putty/STORAGE.H @@ -0,0 +1,115 @@ +/* + * storage.h: interface defining functions for storage and recovery + * of PuTTY's persistent data. + */ + +#ifndef PUTTY_STORAGE_H +#define PUTTY_STORAGE_H + +/* ---------------------------------------------------------------------- + * Functions to save and restore PuTTY sessions. Note that this is + * only the low-level code to do the reading and writing. The + * higher-level code that translates a Config structure into a set + * of (key,value) pairs is elsewhere, since it doesn't (mostly) + * change between platforms. + */ + +/* + * Write a saved session. The caller is expected to call + * open_setting_w() to get a `void *' handle, then pass that to a + * number of calls to write_setting_s() and write_setting_i(), and + * then close it using close_settings_w(). At the end of this call + * sequence the settings should have been written to the PuTTY + * persistent storage area. + * + * A given key will be written at most once while saving a session. + * Keys may be up to 255 characters long. String values have no length + * limit. + * + * Any returned error message must be freed after use. + */ +void *open_settings_w(const char *sessionname, char **errmsg); +void write_setting_s(void *handle, const char *key, const char *value); +void write_setting_i(void *handle, const char *key, int value); +void write_setting_filename(void *handle, const char *key, Filename value); +void write_setting_fontspec(void *handle, const char *key, FontSpec font); +void close_settings_w(void *handle); + +/* + * Read a saved session. The caller is expected to call + * open_setting_r() to get a `void *' handle, then pass that to a + * number of calls to read_setting_s() and read_setting_i(), and + * then close it using close_settings_r(). + * + * read_setting_s() writes into the provided buffer and returns a + * pointer to the same buffer. + * + * If a particular string setting is not present in the session, + * read_setting_s() can return NULL, in which case the caller + * should invent a sensible default. If an integer setting is not + * present, read_setting_i() returns its provided default. + * + * read_setting_filename() and read_setting_fontspec() each read into + * the provided buffer, and return zero if they failed to. + */ +void *open_settings_r(const char *sessionname); +char *read_setting_s(void *handle, const char *key, char *buffer, int buflen); +int read_setting_i(void *handle, const char *key, int defvalue); +int read_setting_filename(void *handle, const char *key, Filename *value); +int read_setting_fontspec(void *handle, const char *key, FontSpec *font); +void close_settings_r(void *handle); + +/* + * Delete a whole saved session. + */ +void del_settings(const char *sessionname); + +/* + * Enumerate all saved sessions. + */ +void *enum_settings_start(void); +char *enum_settings_next(void *handle, char *buffer, int buflen); +void enum_settings_finish(void *handle); + +/* ---------------------------------------------------------------------- + * Functions to access PuTTY's host key database. + */ + +/* + * See if a host key matches the database entry. Return values can + * be 0 (entry matches database), 1 (entry is absent in database), + * or 2 (entry exists in database and is different). + */ +int verify_host_key(const char *hostname, int port, + const char *keytype, const char *key); + +/* + * Write a host key into the database, overwriting any previous + * entry that might have been there. + */ +void store_host_key(const char *hostname, int port, + const char *keytype, const char *key); + +/* ---------------------------------------------------------------------- + * Functions to access PuTTY's random number seed file. + */ + +typedef void (*noise_consumer_t) (void *data, int len); + +/* + * Read PuTTY's random seed file and pass its contents to a noise + * consumer function. + */ +void read_random_seed(noise_consumer_t consumer); + +/* + * Write PuTTY's random seed file from a given chunk of noise. + */ +void write_random_seed(void *data, int len); + +/* ---------------------------------------------------------------------- + * Cleanup function: remove all of PuTTY's persistent state. + */ +void cleanup_all(void); + +#endif diff --git a/putty/TELNET.C b/putty/TELNET.C new file mode 100644 index 0000000..8fbe886 --- /dev/null +++ b/putty/TELNET.C @@ -0,0 +1,1091 @@ +/* + * Telnet backend. + */ + +#include +#include + +#include "putty.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#define IAC 255 /* interpret as command: */ +#define DONT 254 /* you are not to use option */ +#define DO 253 /* please, you use option */ +#define WONT 252 /* I won't use option */ +#define WILL 251 /* I will use option */ +#define SB 250 /* interpret as subnegotiation */ +#define SE 240 /* end sub negotiation */ + +#define GA 249 /* you may reverse the line */ +#define EL 248 /* erase the current line */ +#define EC 247 /* erase the current character */ +#define AYT 246 /* are you there */ +#define AO 245 /* abort output--but let prog finish */ +#define IP 244 /* interrupt process--permanently */ +#define BREAK 243 /* break */ +#define DM 242 /* data mark--for connect. cleaning */ +#define NOP 241 /* nop */ +#define EOR 239 /* end of record (transparent mode) */ +#define ABORT 238 /* Abort process */ +#define SUSP 237 /* Suspend process */ +#define xEOF 236 /* End of file: EOF is already used... */ + +#define TELOPTS(X) \ + X(BINARY, 0) /* 8-bit data path */ \ + X(ECHO, 1) /* echo */ \ + X(RCP, 2) /* prepare to reconnect */ \ + X(SGA, 3) /* suppress go ahead */ \ + X(NAMS, 4) /* approximate message size */ \ + X(STATUS, 5) /* give status */ \ + X(TM, 6) /* timing mark */ \ + X(RCTE, 7) /* remote controlled transmission and echo */ \ + X(NAOL, 8) /* negotiate about output line width */ \ + X(NAOP, 9) /* negotiate about output page size */ \ + X(NAOCRD, 10) /* negotiate about CR disposition */ \ + X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \ + X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \ + X(NAOFFD, 13) /* negotiate about formfeed disposition */ \ + X(NAOVTS, 14) /* negotiate about vertical tab stops */ \ + X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \ + X(NAOLFD, 16) /* negotiate about output LF disposition */ \ + X(XASCII, 17) /* extended ascic character set */ \ + X(LOGOUT, 18) /* force logout */ \ + X(BM, 19) /* byte macro */ \ + X(DET, 20) /* data entry terminal */ \ + X(SUPDUP, 21) /* supdup protocol */ \ + X(SUPDUPOUTPUT, 22) /* supdup output */ \ + X(SNDLOC, 23) /* send location */ \ + X(TTYPE, 24) /* terminal type */ \ + X(EOR, 25) /* end or record */ \ + X(TUID, 26) /* TACACS user identification */ \ + X(OUTMRK, 27) /* output marking */ \ + X(TTYLOC, 28) /* terminal location number */ \ + X(3270REGIME, 29) /* 3270 regime */ \ + X(X3PAD, 30) /* X.3 PAD */ \ + X(NAWS, 31) /* window size */ \ + X(TSPEED, 32) /* terminal speed */ \ + X(LFLOW, 33) /* remote flow control */ \ + X(LINEMODE, 34) /* Linemode option */ \ + X(XDISPLOC, 35) /* X Display Location */ \ + X(OLD_ENVIRON, 36) /* Old - Environment variables */ \ + X(AUTHENTICATION, 37) /* Authenticate */ \ + X(ENCRYPT, 38) /* Encryption option */ \ + X(NEW_ENVIRON, 39) /* New - Environment variables */ \ + X(TN3270E, 40) /* TN3270 enhancements */ \ + X(XAUTH, 41) \ + X(CHARSET, 42) /* Character set */ \ + X(RSP, 43) /* Remote serial port */ \ + X(COM_PORT_OPTION, 44) /* Com port control */ \ + X(SLE, 45) /* Suppress local echo */ \ + X(STARTTLS, 46) /* Start TLS */ \ + X(KERMIT, 47) /* Automatic Kermit file transfer */ \ + X(SEND_URL, 48) \ + X(FORWARD_X, 49) \ + X(PRAGMA_LOGON, 138) \ + X(SSPI_LOGON, 139) \ + X(PRAGMA_HEARTBEAT, 140) \ + X(EXOPL, 255) /* extended-options-list */ + +#define telnet_enum(x,y) TELOPT_##x = y, +enum { TELOPTS(telnet_enum) dummy=0 }; +#undef telnet_enum + +#define TELQUAL_IS 0 /* option is... */ +#define TELQUAL_SEND 1 /* send option */ +#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */ +#define BSD_VAR 1 +#define BSD_VALUE 0 +#define RFC_VAR 0 +#define RFC_VALUE 1 + +#define CR 13 +#define LF 10 +#define NUL 0 + +#define iswritable(x) \ + ( (x) != IAC && \ + (telnet->opt_states[o_we_bin.index] == ACTIVE || (x) != CR)) + +static char *telopt(int opt) +{ +#define telnet_str(x,y) case TELOPT_##x: return #x; + switch (opt) { + TELOPTS(telnet_str) + default: + return ""; + } +#undef telnet_str +} + +static void telnet_size(void *handle, int width, int height); + +struct Opt { + int send; /* what we initially send */ + int nsend; /* -ve send if requested to stop it */ + int ack, nak; /* +ve and -ve acknowledgements */ + int option; /* the option code */ + int index; /* index into telnet->opt_states[] */ + enum { + REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE + } initial_state; +}; + +enum { + OPTINDEX_NAWS, + OPTINDEX_TSPEED, + OPTINDEX_TTYPE, + OPTINDEX_OENV, + OPTINDEX_NENV, + OPTINDEX_ECHO, + OPTINDEX_WE_SGA, + OPTINDEX_THEY_SGA, + OPTINDEX_WE_BIN, + OPTINDEX_THEY_BIN, + NUM_OPTS +}; + +static const struct Opt o_naws = + { WILL, WONT, DO, DONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED }; +static const struct Opt o_tspeed = + { WILL, WONT, DO, DONT, TELOPT_TSPEED, OPTINDEX_TSPEED, REQUESTED }; +static const struct Opt o_ttype = + { WILL, WONT, DO, DONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED }; +static const struct Opt o_oenv = + { WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE }; +static const struct Opt o_nenv = + { WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED }; +static const struct Opt o_echo = + { DO, DONT, WILL, WONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED }; +static const struct Opt o_we_sga = + { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED }; +static const struct Opt o_they_sga = + { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED }; +static const struct Opt o_we_bin = + { WILL, WONT, DO, DONT, TELOPT_BINARY, OPTINDEX_WE_BIN, INACTIVE }; +static const struct Opt o_they_bin = + { DO, DONT, WILL, WONT, TELOPT_BINARY, OPTINDEX_THEY_BIN, INACTIVE }; + +static const struct Opt *const opts[] = { + &o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo, + &o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL +}; + +typedef struct telnet_tag { + const struct plug_function_table *fn; + /* the above field _must_ be first in the structure */ + + Socket s; + + void *frontend; + void *ldisc; + int term_width, term_height; + + int opt_states[NUM_OPTS]; + + int echoing, editing; + int activated; + int bufsize; + int in_synch; + int sb_opt, sb_len; + unsigned char *sb_buf; + int sb_size; + + enum { + TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, + SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR + } state; + + Config cfg; + + Pinger pinger; +} *Telnet; + +#define TELNET_MAX_BACKLOG 4096 + +#define SB_DELTA 1024 + +static void c_write(Telnet telnet, char *buf, int len) +{ + int backlog; + backlog = from_backend(telnet->frontend, 0, buf, len); + sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); +} + +static void log_option(Telnet telnet, char *sender, int cmd, int option) +{ + char *buf; + /* + * The strange-looking "" below is there to avoid a + * trigraph - a double question mark followed by > maps to a + * closing brace character! + */ + buf = dupprintf("%s:\t%s %s", sender, + (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" : + cmd == DO ? "DO" : cmd == DONT ? "DONT" : ""), + telopt(option)); + logevent(telnet->frontend, buf); + sfree(buf); +} + +static void send_opt(Telnet telnet, int cmd, int option) +{ + unsigned char b[3]; + + b[0] = IAC; + b[1] = cmd; + b[2] = option; + telnet->bufsize = sk_write(telnet->s, (char *)b, 3); + log_option(telnet, "client", cmd, option); +} + +static void deactivate_option(Telnet telnet, const struct Opt *o) +{ + if (telnet->opt_states[o->index] == REQUESTED || + telnet->opt_states[o->index] == ACTIVE) + send_opt(telnet, o->nsend, o->option); + telnet->opt_states[o->index] = REALLY_INACTIVE; +} + +/* + * Generate side effects of enabling or disabling an option. + */ +static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled) +{ + if (o->option == TELOPT_ECHO && o->send == DO) + telnet->echoing = !enabled; + else if (o->option == TELOPT_SGA && o->send == DO) + telnet->editing = !enabled; + if (telnet->ldisc) /* cause ldisc to notice the change */ + ldisc_send(telnet->ldisc, NULL, 0, 0); + + /* Ensure we get the minimum options */ + if (!telnet->activated) { + if (telnet->opt_states[o_echo.index] == INACTIVE) { + telnet->opt_states[o_echo.index] = REQUESTED; + send_opt(telnet, o_echo.send, o_echo.option); + } + if (telnet->opt_states[o_we_sga.index] == INACTIVE) { + telnet->opt_states[o_we_sga.index] = REQUESTED; + send_opt(telnet, o_we_sga.send, o_we_sga.option); + } + if (telnet->opt_states[o_they_sga.index] == INACTIVE) { + telnet->opt_states[o_they_sga.index] = REQUESTED; + send_opt(telnet, o_they_sga.send, o_they_sga.option); + } + telnet->activated = TRUE; + } +} + +static void activate_option(Telnet telnet, const struct Opt *o) +{ + if (o->send == WILL && o->option == TELOPT_NAWS) + telnet_size(telnet, telnet->term_width, telnet->term_height); + if (o->send == WILL && + (o->option == TELOPT_NEW_ENVIRON || + o->option == TELOPT_OLD_ENVIRON)) { + /* + * We may only have one kind of ENVIRON going at a time. + * This is a hack, but who cares. + */ + deactivate_option(telnet, o->option == + TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv); + } + option_side_effects(telnet, o, 1); +} + +static void refused_option(Telnet telnet, const struct Opt *o) +{ + if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON && + telnet->opt_states[o_oenv.index] == INACTIVE) { + send_opt(telnet, WILL, TELOPT_OLD_ENVIRON); + telnet->opt_states[o_oenv.index] = REQUESTED; + } + option_side_effects(telnet, o, 0); +} + +static void proc_rec_opt(Telnet telnet, int cmd, int option) +{ + const struct Opt *const *o; + + log_option(telnet, "server", cmd, option); + for (o = opts; *o; o++) { + if ((*o)->option == option && (*o)->ack == cmd) { + switch (telnet->opt_states[(*o)->index]) { + case REQUESTED: + telnet->opt_states[(*o)->index] = ACTIVE; + activate_option(telnet, *o); + break; + case ACTIVE: + break; + case INACTIVE: + telnet->opt_states[(*o)->index] = ACTIVE; + send_opt(telnet, (*o)->send, option); + activate_option(telnet, *o); + break; + case REALLY_INACTIVE: + send_opt(telnet, (*o)->nsend, option); + break; + } + return; + } else if ((*o)->option == option && (*o)->nak == cmd) { + switch (telnet->opt_states[(*o)->index]) { + case REQUESTED: + telnet->opt_states[(*o)->index] = INACTIVE; + refused_option(telnet, *o); + break; + case ACTIVE: + telnet->opt_states[(*o)->index] = INACTIVE; + send_opt(telnet, (*o)->nsend, option); + option_side_effects(telnet, *o, 0); + break; + case INACTIVE: + case REALLY_INACTIVE: + break; + } + return; + } + } + /* + * If we reach here, the option was one we weren't prepared to + * cope with. If the request was positive (WILL or DO), we send + * a negative ack to indicate refusal. If the request was + * negative (WONT / DONT), we must do nothing. + */ + if (cmd == WILL || cmd == DO) + send_opt(telnet, (cmd == WILL ? DONT : WONT), option); +} + +static void process_subneg(Telnet telnet) +{ + unsigned char b[2048], *p, *q; + int var, value, n; + char *e; + + switch (telnet->sb_opt) { + case TELOPT_TSPEED: + if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) { + char *logbuf; + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TSPEED; + b[3] = TELQUAL_IS; + strcpy((char *)(b + 4), telnet->cfg.termspeed); + n = 4 + strlen(telnet->cfg.termspeed); + b[n] = IAC; + b[n + 1] = SE; + telnet->bufsize = sk_write(telnet->s, (char *)b, n + 2); + logevent(telnet->frontend, "server:\tSB TSPEED SEND"); + logbuf = dupprintf("client:\tSB TSPEED IS %s", telnet->cfg.termspeed); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + } else + logevent(telnet->frontend, "server:\tSB TSPEED "); + break; + case TELOPT_TTYPE: + if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) { + char *logbuf; + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TTYPE; + b[3] = TELQUAL_IS; + for (n = 0; telnet->cfg.termtype[n]; n++) + b[n + 4] = (telnet->cfg.termtype[n] >= 'a' + && telnet->cfg.termtype[n] <= + 'z' ? telnet->cfg.termtype[n] + 'A' - + 'a' : telnet->cfg.termtype[n]); + b[n + 4] = IAC; + b[n + 5] = SE; + telnet->bufsize = sk_write(telnet->s, (char *)b, n + 6); + b[n + 4] = 0; + logevent(telnet->frontend, "server:\tSB TTYPE SEND"); + logbuf = dupprintf("client:\tSB TTYPE IS %s", b + 4); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + } else + logevent(telnet->frontend, "server:\tSB TTYPE \r\n"); + break; + case TELOPT_OLD_ENVIRON: + case TELOPT_NEW_ENVIRON: + p = telnet->sb_buf; + q = p + telnet->sb_len; + if (p < q && *p == TELQUAL_SEND) { + char *logbuf; + p++; + logbuf = dupprintf("server:\tSB %s SEND", telopt(telnet->sb_opt)); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + if (telnet->sb_opt == TELOPT_OLD_ENVIRON) { + if (telnet->cfg.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; + } + b[0] = IAC; + b[1] = SB; + b[2] = telnet->sb_opt; + b[3] = TELQUAL_IS; + n = 4; + e = telnet->cfg.environmt; + while (*e) { + b[n++] = var; + while (*e && *e != '\t') + b[n++] = *e++; + if (*e == '\t') + e++; + b[n++] = value; + while (*e) + b[n++] = *e++; + e++; + } + { + char user[sizeof(telnet->cfg.username)]; + (void) get_remote_username(&telnet->cfg, user, sizeof(user)); + if (*user) { + b[n++] = var; + b[n++] = 'U'; + b[n++] = 'S'; + b[n++] = 'E'; + b[n++] = 'R'; + b[n++] = value; + e = user; + while (*e) + b[n++] = *e++; + } + b[n++] = IAC; + b[n++] = SE; + telnet->bufsize = sk_write(telnet->s, (char *)b, n); + logbuf = dupprintf("client:\tSB %s IS %s%s%s%s", + telopt(telnet->sb_opt), + *user ? "USER=" : "", + user, + *user ? " " : "", + n == 6 ? "" : + (*telnet->cfg.environmt ? "" : "")); + logevent(telnet->frontend, logbuf); + sfree(logbuf); + } + } + break; + } +} + +static void do_telnet_read(Telnet telnet, char *buf, int len) +{ + char *outbuf = NULL; + int outbuflen = 0, outbufsize = 0; + +#define ADDTOBUF(c) do { \ + if (outbuflen >= outbufsize) { \ + outbufsize = outbuflen + 256; \ + outbuf = sresize(outbuf, outbufsize, char); \ + } \ + outbuf[outbuflen++] = (c); \ +} while (0) + + while (len--) { + int c = (unsigned char) *buf++; + + switch (telnet->state) { + case TOP_LEVEL: + case SEENCR: + if (c == NUL && telnet->state == SEENCR) + telnet->state = TOP_LEVEL; + else if (c == IAC) + telnet->state = SEENIAC; + else { + if (!telnet->in_synch) + ADDTOBUF(c); + +#if 1 + /* I can't get the F***ing winsock to insert the urgent IAC + * into the right position! Even with SO_OOBINLINE it gives + * it to recv too soon. And of course the DM byte (that + * arrives in the same packet!) appears several K later!! + * + * Oh well, we do get the DM in the right place so I'll + * just stop hiding on the next 0xf2 and hope for the best. + */ + else if (c == DM) + telnet->in_synch = 0; +#endif + if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE) + telnet->state = SEENCR; + else + telnet->state = TOP_LEVEL; + } + break; + case SEENIAC: + if (c == DO) + telnet->state = SEENDO; + else if (c == DONT) + telnet->state = SEENDONT; + else if (c == WILL) + telnet->state = SEENWILL; + else if (c == WONT) + telnet->state = SEENWONT; + else if (c == SB) + telnet->state = SEENSB; + else if (c == DM) { + telnet->in_synch = 0; + telnet->state = TOP_LEVEL; + } else { + /* ignore everything else; print it if it's IAC */ + if (c == IAC) { + ADDTOBUF(c); + } + telnet->state = TOP_LEVEL; + } + break; + case SEENWILL: + proc_rec_opt(telnet, WILL, c); + telnet->state = TOP_LEVEL; + break; + case SEENWONT: + proc_rec_opt(telnet, WONT, c); + telnet->state = TOP_LEVEL; + break; + case SEENDO: + proc_rec_opt(telnet, DO, c); + telnet->state = TOP_LEVEL; + break; + case SEENDONT: + proc_rec_opt(telnet, DONT, c); + telnet->state = TOP_LEVEL; + break; + case SEENSB: + telnet->sb_opt = c; + telnet->sb_len = 0; + telnet->state = SUBNEGOT; + break; + case SUBNEGOT: + if (c == IAC) + telnet->state = SUBNEG_IAC; + else { + subneg_addchar: + if (telnet->sb_len >= telnet->sb_size) { + telnet->sb_size += SB_DELTA; + telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size, + unsigned char); + } + telnet->sb_buf[telnet->sb_len++] = c; + telnet->state = SUBNEGOT; /* in case we came here by goto */ + } + break; + case SUBNEG_IAC: + if (c != SE) + goto subneg_addchar; /* yes, it's a hack, I know, but... */ + else { + process_subneg(telnet); + telnet->state = TOP_LEVEL; + } + break; + } + } + + if (outbuflen) + c_write(telnet, outbuf, outbuflen); + sfree(outbuf); +} + +static void telnet_log(Plug plug, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + Telnet telnet = (Telnet) plug; + char addrbuf[256], *msg; + + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + + if (type == 0) + msg = dupprintf("Connecting to %s port %d", addrbuf, port); + else + msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); + + logevent(telnet->frontend, msg); +} + +static int telnet_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + Telnet telnet = (Telnet) plug; + + if (telnet->s) { + sk_close(telnet->s); + telnet->s = NULL; + notify_remote_exit(telnet->frontend); + } + if (error_msg) { + logevent(telnet->frontend, error_msg); + connection_fatal(telnet->frontend, "%s", error_msg); + } + /* Otherwise, the remote side closed the connection normally. */ + return 0; +} + +static int telnet_receive(Plug plug, int urgent, char *data, int len) +{ + Telnet telnet = (Telnet) plug; + if (urgent) + telnet->in_synch = TRUE; + do_telnet_read(telnet, data, len); + return 1; +} + +static void telnet_sent(Plug plug, int bufsize) +{ + Telnet telnet = (Telnet) plug; + telnet->bufsize = bufsize; +} + +/* + * Called to set up the Telnet connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *telnet_init(void *frontend_handle, void **backend_handle, + Config *cfg, + char *host, int port, char **realhost, + int nodelay, int keepalive) +{ + static const struct plug_function_table fn_table = { + telnet_log, + telnet_closing, + telnet_receive, + telnet_sent + }; + SockAddr addr; + const char *err; + Telnet telnet; + + telnet = snew(struct telnet_tag); + telnet->fn = &fn_table; + telnet->cfg = *cfg; /* STRUCTURE COPY */ + telnet->s = NULL; + telnet->echoing = TRUE; + telnet->editing = TRUE; + telnet->activated = FALSE; + telnet->sb_buf = NULL; + telnet->sb_size = 0; + telnet->frontend = frontend_handle; + telnet->term_width = telnet->cfg.width; + telnet->term_height = telnet->cfg.height; + telnet->state = TOP_LEVEL; + telnet->ldisc = NULL; + telnet->pinger = NULL; + *backend_handle = telnet; + + /* + * Try to find host. + */ + { + char *buf; + buf = dupprintf("Looking up host \"%s\"%s", host, + (cfg->addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (cfg->addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + ""))); + logevent(telnet->frontend, buf); + sfree(buf); + } + addr = name_lookup(host, port, realhost, &telnet->cfg, cfg->addressfamily); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + + if (port < 0) + port = 23; /* default telnet port */ + + /* + * Open socket. + */ + telnet->s = new_connection(addr, *realhost, port, 0, 1, + nodelay, keepalive, (Plug) telnet, &telnet->cfg); + if ((err = sk_socket_error(telnet->s)) != NULL) + return err; + + telnet->pinger = pinger_new(&telnet->cfg, &telnet_backend, telnet); + + /* + * Initialise option states. + */ + if (telnet->cfg.passive_telnet) { + const struct Opt *const *o; + + for (o = opts; *o; o++) + telnet->opt_states[(*o)->index] = INACTIVE; + } else { + const struct Opt *const *o; + + for (o = opts; *o; o++) { + telnet->opt_states[(*o)->index] = (*o)->initial_state; + if (telnet->opt_states[(*o)->index] == REQUESTED) + send_opt(telnet, (*o)->send, (*o)->option); + } + telnet->activated = TRUE; + } + + /* + * Set up SYNCH state. + */ + telnet->in_synch = FALSE; + + /* + * We can send special commands from the start. + */ + update_specials_menu(telnet->frontend); + + /* + * loghost overrides realhost, if specified. + */ + if (*telnet->cfg.loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(telnet->cfg.loghost); + colon = strrchr(*realhost, ':'); + if (colon) { + /* + * FIXME: if we ever update this aspect of ssh.c for + * IPv6 literal management, this should change in line + * with it. + */ + *colon++ = '\0'; + } + } + + return NULL; +} + +static void telnet_free(void *handle) +{ + Telnet telnet = (Telnet) handle; + + sfree(telnet->sb_buf); + if (telnet->s) + sk_close(telnet->s); + if (telnet->pinger) + pinger_free(telnet->pinger); + sfree(telnet); +} +/* + * Reconfigure the Telnet backend. There's no immediate action + * necessary, in this backend: we just save the fresh config for + * any subsequent negotiations. + */ +static void telnet_reconfig(void *handle, Config *cfg) +{ + Telnet telnet = (Telnet) handle; + pinger_reconfig(telnet->pinger, &telnet->cfg, cfg); + telnet->cfg = *cfg; /* STRUCTURE COPY */ +} + +/* + * Called to send data down the Telnet connection. + */ +static int telnet_send(void *handle, char *buf, int len) +{ + Telnet telnet = (Telnet) handle; + unsigned char *p, *end; + static const unsigned char iac[2] = { IAC, IAC }; + static const unsigned char cr[2] = { CR, NUL }; +#if 0 + static const unsigned char nl[2] = { CR, LF }; +#endif + + if (telnet->s == NULL) + return 0; + + p = (unsigned char *)buf; + end = (unsigned char *)(buf + len); + while (p < end) { + unsigned char *q = p; + + while (p < end && iswritable(*p)) + p++; + telnet->bufsize = sk_write(telnet->s, (char *)q, p - q); + + while (p < end && !iswritable(*p)) { + telnet->bufsize = + sk_write(telnet->s, (char *)(*p == IAC ? iac : cr), 2); + p++; + } + } + + return telnet->bufsize; +} + +/* + * Called to query the current socket sendability status. + */ +static int telnet_sendbuffer(void *handle) +{ + Telnet telnet = (Telnet) handle; + return telnet->bufsize; +} + +/* + * Called to set the size of the window from Telnet's POV. + */ +static void telnet_size(void *handle, int width, int height) +{ + Telnet telnet = (Telnet) handle; + unsigned char b[24]; + int n; + char *logbuf; + + telnet->term_width = width; + telnet->term_height = height; + + if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE) + return; + n = 0; + b[n++] = IAC; + b[n++] = SB; + b[n++] = TELOPT_NAWS; + b[n++] = telnet->term_width >> 8; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_width & 0xFF; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_height >> 8; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_height & 0xFF; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = IAC; + b[n++] = SE; + telnet->bufsize = sk_write(telnet->s, (char *)b, n); + logbuf = dupprintf("client:\tSB NAWS %d,%d", + telnet->term_width, telnet->term_height); + logevent(telnet->frontend, logbuf); + sfree(logbuf); +} + +/* + * Send Telnet special codes. + */ +static void telnet_special(void *handle, Telnet_Special code) +{ + Telnet telnet = (Telnet) handle; + unsigned char b[2]; + + if (telnet->s == NULL) + return; + + b[0] = IAC; + switch (code) { + case TS_AYT: + b[1] = AYT; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_BRK: + b[1] = BREAK; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_EC: + b[1] = EC; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_EL: + b[1] = EL; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_GA: + b[1] = GA; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_NOP: + b[1] = NOP; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_ABORT: + b[1] = ABORT; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_AO: + b[1] = AO; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_IP: + b[1] = IP; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_SUSP: + b[1] = SUSP; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_EOR: + b[1] = EOR; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_EOF: + b[1] = xEOF; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + break; + case TS_EOL: + /* In BINARY mode, CR-LF becomes just CR - + * and without the NUL suffix too. */ + if (telnet->opt_states[o_we_bin.index] == ACTIVE) + telnet->bufsize = sk_write(telnet->s, "\r", 1); + else + telnet->bufsize = sk_write(telnet->s, "\r\n", 2); + break; + case TS_SYNCH: + b[1] = DM; + telnet->bufsize = sk_write(telnet->s, (char *)b, 1); + telnet->bufsize = sk_write_oob(telnet->s, (char *)(b + 1), 1); + break; + case TS_RECHO: + if (telnet->opt_states[o_echo.index] == INACTIVE || + telnet->opt_states[o_echo.index] == REALLY_INACTIVE) { + telnet->opt_states[o_echo.index] = REQUESTED; + send_opt(telnet, o_echo.send, o_echo.option); + } + break; + case TS_LECHO: + if (telnet->opt_states[o_echo.index] == ACTIVE) { + telnet->opt_states[o_echo.index] = REQUESTED; + send_opt(telnet, o_echo.nsend, o_echo.option); + } + break; + case TS_PING: + if (telnet->opt_states[o_they_sga.index] == ACTIVE) { + b[1] = NOP; + telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + } + break; + default: + break; /* never heard of it */ + } +} + +static const struct telnet_special *telnet_get_specials(void *handle) +{ + static const struct telnet_special specials[] = { + {"Are You There", TS_AYT}, + {"Break", TS_BRK}, + {"Synch", TS_SYNCH}, + {"Erase Character", TS_EC}, + {"Erase Line", TS_EL}, + {"Go Ahead", TS_GA}, + {"No Operation", TS_NOP}, + {NULL, TS_SEP}, + {"Abort Process", TS_ABORT}, + {"Abort Output", TS_AO}, + {"Interrupt Process", TS_IP}, + {"Suspend Process", TS_SUSP}, + {NULL, TS_SEP}, + {"End Of Record", TS_EOR}, + {"End Of File", TS_EOF}, + {NULL, TS_EXITMENU} + }; + return specials; +} + +static int telnet_connected(void *handle) +{ + Telnet telnet = (Telnet) handle; + return telnet->s != NULL; +} + +static int telnet_sendok(void *handle) +{ + /* Telnet telnet = (Telnet) handle; */ + return 1; +} + +static void telnet_unthrottle(void *handle, int backlog) +{ + Telnet telnet = (Telnet) handle; + sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); +} + +static int telnet_ldisc(void *handle, int option) +{ + Telnet telnet = (Telnet) handle; + if (option == LD_ECHO) + return telnet->echoing; + if (option == LD_EDIT) + return telnet->editing; + return FALSE; +} + +static void telnet_provide_ldisc(void *handle, void *ldisc) +{ + Telnet telnet = (Telnet) handle; + telnet->ldisc = ldisc; +} + +static void telnet_provide_logctx(void *handle, void *logctx) +{ + /* This is a stub. */ +} + +static int telnet_exitcode(void *handle) +{ + Telnet telnet = (Telnet) handle; + if (telnet->s != NULL) + return -1; /* still connected */ + else + /* Telnet doesn't transmit exit codes back to the client */ + return 0; +} + +/* + * cfg_info for Telnet does nothing at all. + */ +static int telnet_cfg_info(void *handle) +{ + return 0; +} + +Backend telnet_backend = { + telnet_init, + telnet_free, + telnet_reconfig, + telnet_send, + telnet_sendbuffer, + telnet_size, + telnet_special, + telnet_get_specials, + telnet_connected, + telnet_exitcode, + telnet_sendok, + telnet_ldisc, + telnet_provide_ldisc, + telnet_provide_logctx, + telnet_unthrottle, + telnet_cfg_info, + "telnet", + PROT_TELNET, + 23 +}; diff --git a/putty/TERMINAL.C b/putty/TERMINAL.C new file mode 100644 index 0000000..baadad4 --- /dev/null +++ b/putty/TERMINAL.C @@ -0,0 +1,6613 @@ +/* + * Terminal emulator. + */ + +#include +#include +#include + +#include +#include +#include "putty.h" +#include "terminal.h" + +#define poslt(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x < (p2).x ) ) +#define posle(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x <= (p2).x ) ) +#define poseq(p1,p2) ( (p1).y == (p2).y && (p1).x == (p2).x ) +#define posdiff(p1,p2) ( ((p1).y - (p2).y) * (term->cols+1) + (p1).x - (p2).x ) + +/* Product-order comparisons for rectangular block selection. */ +#define posPlt(p1,p2) ( (p1).y <= (p2).y && (p1).x < (p2).x ) +#define posPle(p1,p2) ( (p1).y <= (p2).y && (p1).x <= (p2).x ) + +#define incpos(p) ( (p).x == term->cols ? ((p).x = 0, (p).y++, 1) : ((p).x++, 0) ) +#define decpos(p) ( (p).x == 0 ? ((p).x = term->cols, (p).y--, 1) : ((p).x--, 0) ) + +#define VT52_PLUS + +#define CL_ANSIMIN 0x0001 /* Codes in all ANSI like terminals. */ +#define CL_VT100 0x0002 /* VT100 */ +#define CL_VT100AVO 0x0004 /* VT100 +AVO; 132x24 (not 132x14) & attrs */ +#define CL_VT102 0x0008 /* VT102 */ +#define CL_VT220 0x0010 /* VT220 */ +#define CL_VT320 0x0020 /* VT320 */ +#define CL_VT420 0x0040 /* VT420 */ +#define CL_VT510 0x0080 /* VT510, NB VT510 includes ANSI */ +#define CL_VT340TEXT 0x0100 /* VT340 extensions that appear in the VT420 */ +#define CL_SCOANSI 0x1000 /* SCOANSI not in ANSIMIN. */ +#define CL_ANSI 0x2000 /* ANSI ECMA-48 not in the VT100..VT420 */ +#define CL_OTHER 0x4000 /* Others, Xterm, linux, putty, dunno, etc */ + +#define TM_VT100 (CL_ANSIMIN|CL_VT100) +#define TM_VT100AVO (TM_VT100|CL_VT100AVO) +#define TM_VT102 (TM_VT100AVO|CL_VT102) +#define TM_VT220 (TM_VT102|CL_VT220) +#define TM_VTXXX (TM_VT220|CL_VT340TEXT|CL_VT510|CL_VT420|CL_VT320) +#define TM_SCOANSI (CL_ANSIMIN|CL_SCOANSI) + +#define TM_PUTTY (0xFFFF) + +#define UPDATE_DELAY ((TICKSPERSEC+49)/50)/* ticks to defer window update */ +#define TBLINK_DELAY ((TICKSPERSEC*9+19)/20)/* ticks between text blinks*/ +#define CBLINK_DELAY (CURSORBLINK) /* ticks between cursor blinks */ +#define VBELL_DELAY (VBELL_TIMEOUT) /* visual bell timeout in ticks */ + +#define compatibility(x) \ + if ( ((CL_##x)&term->compatibility_level) == 0 ) { \ + term->termstate=TOPLEVEL; \ + break; \ + } +#define compatibility2(x,y) \ + if ( ((CL_##x|CL_##y)&term->compatibility_level) == 0 ) { \ + term->termstate=TOPLEVEL; \ + break; \ + } + +#define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 ) + +char *EMPTY_WINDOW_TITLE = ""; + +const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + +#define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t)) +const wchar_t sel_nl[] = SEL_NL; + +/* + * Fetch the character at a particular position in a line array, + * for purposes of `wordtype'. The reason this isn't just a simple + * array reference is that if the character we find is UCSWIDE, + * then we must look one space further to the left. + */ +#define UCSGET(a, x) \ + ( (x)>0 && (a)[(x)].chr == UCSWIDE ? (a)[(x)-1].chr : (a)[(x)].chr ) + +/* + * Detect the various aliases of U+0020 SPACE. + */ +#define IS_SPACE_CHR(chr) \ + ((chr) == 0x20 || (DIRECT_CHAR(chr) && ((chr) & 0xFF) == 0x20)) + +/* + * Spot magic CSETs. + */ +#define CSET_OF(chr) (DIRECT_CHAR(chr)||DIRECT_FONT(chr) ? (chr)&CSET_MASK : 0) + +/* + * Internal prototypes. + */ +static void resizeline(Terminal *, termline *, int); +static termline *lineptr(Terminal *, int, int, int); +static void unlineptr(termline *); +static void do_paint(Terminal *, Context, int); +static void erase_lots(Terminal *, int, int, int); +static int find_last_nonempty_line(Terminal *, tree234 *); +static void swap_screen(Terminal *, int, int, int); +static void update_sbar(Terminal *); +static void deselect(Terminal *); +static void term_print_finish(Terminal *); +static void scroll(Terminal *, int, int, int, int); +#ifdef OPTIMISE_SCROLL +static void scroll_display(Terminal *, int, int, int); +#endif /* OPTIMISE_SCROLL */ + +static termline *newline(Terminal *term, int cols, int bce) +{ + termline *line; + int j; + + line = snew(termline); + line->chars = snewn(cols, termchar); + for (j = 0; j < cols; j++) + line->chars[j] = (bce ? term->erase_char : term->basic_erase_char); + line->cols = line->size = cols; + line->lattr = LATTR_NORM; + line->temporary = FALSE; + line->cc_free = 0; + + return line; +} + +static void freeline(termline *line) +{ + if (line) { + sfree(line->chars); + sfree(line); + } +} + +static void unlineptr(termline *line) +{ + if (line->temporary) + freeline(line); +} + +#ifdef TERM_CC_DIAGS +/* + * Diagnostic function: verify that a termline has a correct + * combining character structure. + * + * This is a performance-intensive check, so it's no longer enabled + * by default. + */ +static void cc_check(termline *line) +{ + unsigned char *flags; + int i, j; + + assert(line->size >= line->cols); + + flags = snewn(line->size, unsigned char); + + for (i = 0; i < line->size; i++) + flags[i] = (i < line->cols); + + for (i = 0; i < line->cols; i++) { + j = i; + while (line->chars[j].cc_next) { + j += line->chars[j].cc_next; + assert(j >= line->cols && j < line->size); + assert(!flags[j]); + flags[j] = TRUE; + } + } + + j = line->cc_free; + if (j) { + while (1) { + assert(j >= line->cols && j < line->size); + assert(!flags[j]); + flags[j] = TRUE; + if (line->chars[j].cc_next) + j += line->chars[j].cc_next; + else + break; + } + } + + j = 0; + for (i = 0; i < line->size; i++) + j += (flags[i] != 0); + + assert(j == line->size); + + sfree(flags); +} +#endif + +/* + * Add a combining character to a character cell. + */ +static void add_cc(termline *line, int col, unsigned long chr) +{ + int newcc; + + assert(col >= 0 && col < line->cols); + + /* + * Start by extending the cols array if the free list is empty. + */ + if (!line->cc_free) { + int n = line->size; + line->size += 16 + (line->size - line->cols) / 2; + line->chars = sresize(line->chars, line->size, termchar); + line->cc_free = n; + while (n < line->size) { + if (n+1 < line->size) + line->chars[n].cc_next = 1; + else + line->chars[n].cc_next = 0; + n++; + } + } + + /* + * Now walk the cc list of the cell in question. + */ + while (line->chars[col].cc_next) + col += line->chars[col].cc_next; + + /* + * `col' now points at the last cc currently in this cell; so + * we simply add another one. + */ + newcc = line->cc_free; + if (line->chars[newcc].cc_next) + line->cc_free = newcc + line->chars[newcc].cc_next; + else + line->cc_free = 0; + line->chars[newcc].cc_next = 0; + line->chars[newcc].chr = chr; + line->chars[col].cc_next = newcc - col; + +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif +} + +/* + * Clear the combining character list in a character cell. + */ +static void clear_cc(termline *line, int col) +{ + int oldfree, origcol = col; + + assert(col >= 0 && col < line->cols); + + if (!line->chars[col].cc_next) + return; /* nothing needs doing */ + + oldfree = line->cc_free; + line->cc_free = col + line->chars[col].cc_next; + while (line->chars[col].cc_next) + col += line->chars[col].cc_next; + if (oldfree) + line->chars[col].cc_next = oldfree - col; + else + line->chars[col].cc_next = 0; + + line->chars[origcol].cc_next = 0; + +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif +} + +/* + * Compare two character cells for equality. Special case required + * in do_paint() where we override what we expect the chr and attr + * fields to be. + */ +static int termchars_equal_override(termchar *a, termchar *b, + unsigned long bchr, unsigned long battr) +{ + /* FULL-TERMCHAR */ + if (a->chr != bchr) + return FALSE; + if ((a->attr &~ DATTR_MASK) != (battr &~ DATTR_MASK)) + return FALSE; + while (a->cc_next || b->cc_next) { + if (!a->cc_next || !b->cc_next) + return FALSE; /* one cc-list ends, other does not */ + a += a->cc_next; + b += b->cc_next; + if (a->chr != b->chr) + return FALSE; + } + return TRUE; +} + +static int termchars_equal(termchar *a, termchar *b) +{ + return termchars_equal_override(a, b, b->chr, b->attr); +} + +/* + * Copy a character cell. (Requires a pointer to the destination + * termline, so as to access its free list.) + */ +static void copy_termchar(termline *destline, int x, termchar *src) +{ + clear_cc(destline, x); + + destline->chars[x] = *src; /* copy everything except cc-list */ + destline->chars[x].cc_next = 0; /* and make sure this is zero */ + + while (src->cc_next) { + src += src->cc_next; + add_cc(destline, x, src->chr); + } + +#ifdef TERM_CC_DIAGS + cc_check(destline); +#endif +} + +/* + * Move a character cell within its termline. + */ +static void move_termchar(termline *line, termchar *dest, termchar *src) +{ + /* First clear the cc list from the original char, just in case. */ + clear_cc(line, dest - line->chars); + + /* Move the character cell and adjust its cc_next. */ + *dest = *src; /* copy everything except cc-list */ + if (src->cc_next) + dest->cc_next = src->cc_next - (dest-src); + + /* Ensure the original cell doesn't have a cc list. */ + src->cc_next = 0; + +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif +} + +/* + * Compress and decompress a termline into an RLE-based format for + * storing in scrollback. (Since scrollback almost never needs to + * be modified and exists in huge quantities, this is a sensible + * tradeoff, particularly since it allows us to continue adding + * features to the main termchar structure without proportionally + * bloating the terminal emulator's memory footprint unless those + * features are in constant use.) + */ +struct buf { + unsigned char *data; + int len, size; +}; +static void add(struct buf *b, unsigned char c) +{ + if (b->len >= b->size) { + b->size = (b->len * 3 / 2) + 512; + b->data = sresize(b->data, b->size, unsigned char); + } + b->data[b->len++] = c; +} +static int get(struct buf *b) +{ + return b->data[b->len++]; +} +static void makerle(struct buf *b, termline *ldata, + void (*makeliteral)(struct buf *b, termchar *c, + unsigned long *state)) +{ + int hdrpos, hdrsize, n, prevlen, prevpos, thislen, thispos, prev2; + termchar *c = ldata->chars; + unsigned long state = 0, oldstate; + + n = ldata->cols; + + hdrpos = b->len; + hdrsize = 0; + add(b, 0); + prevlen = prevpos = 0; + prev2 = FALSE; + + while (n-- > 0) { + thispos = b->len; + makeliteral(b, c++, &state); + thislen = b->len - thispos; + if (thislen == prevlen && + !memcmp(b->data + prevpos, b->data + thispos, thislen)) { + /* + * This literal precisely matches the previous one. + * Turn it into a run if it's worthwhile. + * + * With one-byte literals, it costs us two bytes to + * encode a run, plus another byte to write the header + * to resume normal output; so a three-element run is + * neutral, and anything beyond that is unconditionally + * worthwhile. With two-byte literals or more, even a + * 2-run is a win. + */ + if (thislen > 1 || prev2) { + int runpos, runlen; + + /* + * It's worth encoding a run. Start at prevpos, + * unless hdrsize==0 in which case we can back up + * another one and start by overwriting hdrpos. + */ + + hdrsize--; /* remove the literal at prevpos */ + if (prev2) { + assert(hdrsize > 0); + hdrsize--; + prevpos -= prevlen;/* and possibly another one */ + } + + if (hdrsize == 0) { + assert(prevpos == hdrpos + 1); + runpos = hdrpos; + b->len = prevpos+prevlen; + } else { + memmove(b->data + prevpos+1, b->data + prevpos, prevlen); + runpos = prevpos; + b->len = prevpos+prevlen+1; + /* + * Terminate the previous run of ordinary + * literals. + */ + assert(hdrsize >= 1 && hdrsize <= 128); + b->data[hdrpos] = hdrsize - 1; + } + + runlen = prev2 ? 3 : 2; + + while (n > 0 && runlen < 129) { + int tmppos, tmplen; + tmppos = b->len; + oldstate = state; + makeliteral(b, c, &state); + tmplen = b->len - tmppos; + b->len = tmppos; + if (tmplen != thislen || + memcmp(b->data + runpos+1, b->data + tmppos, tmplen)) { + state = oldstate; + break; /* run over */ + } + n--, c++, runlen++; + } + + assert(runlen >= 2 && runlen <= 129); + b->data[runpos] = runlen + 0x80 - 2; + + hdrpos = b->len; + hdrsize = 0; + add(b, 0); + /* And ensure this run doesn't interfere with the next. */ + prevlen = prevpos = 0; + prev2 = FALSE; + + continue; + } else { + /* + * Just flag that the previous two literals were + * identical, in case we find a third identical one + * we want to turn into a run. + */ + prev2 = TRUE; + prevlen = thislen; + prevpos = thispos; + } + } else { + prev2 = FALSE; + prevlen = thislen; + prevpos = thispos; + } + + /* + * This character isn't (yet) part of a run. Add it to + * hdrsize. + */ + hdrsize++; + if (hdrsize == 128) { + b->data[hdrpos] = hdrsize - 1; + hdrpos = b->len; + hdrsize = 0; + add(b, 0); + prevlen = prevpos = 0; + prev2 = FALSE; + } + } + + /* + * Clean up. + */ + if (hdrsize > 0) { + assert(hdrsize <= 128); + b->data[hdrpos] = hdrsize - 1; + } else { + b->len = hdrpos; + } +} +static void makeliteral_chr(struct buf *b, termchar *c, unsigned long *state) +{ + /* + * My encoding for characters is UTF-8-like, in that it stores + * 7-bit ASCII in one byte and uses high-bit-set bytes as + * introducers to indicate a longer sequence. However, it's + * unlike UTF-8 in that it doesn't need to be able to + * resynchronise, and therefore I don't want to waste two bits + * per byte on having recognisable continuation characters. + * Also I don't want to rule out the possibility that I may one + * day use values 0x80000000-0xFFFFFFFF for interesting + * purposes, so unlike UTF-8 I need a full 32-bit range. + * Accordingly, here is my encoding: + * + * 00000000-0000007F: 0xxxxxxx (but see below) + * 00000080-00003FFF: 10xxxxxx xxxxxxxx + * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx + * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx + * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx + * + * (`Z' is like `x' but is always going to be zero since the + * values I'm encoding don't go above 2^32. In principle the + * five-byte form of the encoding could extend to 2^35, and + * there could be six-, seven-, eight- and nine-byte forms as + * well to allow up to 64-bit values to be encoded. But that's + * completely unnecessary for these purposes!) + * + * The encoding as written above would be very simple, except + * that 7-bit ASCII can occur in several different ways in the + * terminal data; sometimes it crops up in the D800 page + * (CSET_ASCII) but at other times it's in the 0000 page (real + * Unicode). Therefore, this encoding is actually _stateful_: + * the one-byte encoding of 00-7F actually indicates `reuse the + * upper three bytes of the last character', and to encode an + * absolute value of 00-7F you need to use the two-byte form + * instead. + */ + if ((c->chr & ~0x7F) == *state) { + add(b, (unsigned char)(c->chr & 0x7F)); + } else if (c->chr < 0x4000) { + add(b, (unsigned char)(((c->chr >> 8) & 0x3F) | 0x80)); + add(b, (unsigned char)(c->chr & 0xFF)); + } else if (c->chr < 0x200000) { + add(b, (unsigned char)(((c->chr >> 16) & 0x1F) | 0xC0)); + add(b, (unsigned char)((c->chr >> 8) & 0xFF)); + add(b, (unsigned char)(c->chr & 0xFF)); + } else if (c->chr < 0x10000000) { + add(b, (unsigned char)(((c->chr >> 24) & 0x0F) | 0xE0)); + add(b, (unsigned char)((c->chr >> 16) & 0xFF)); + add(b, (unsigned char)((c->chr >> 8) & 0xFF)); + add(b, (unsigned char)(c->chr & 0xFF)); + } else { + add(b, 0xF0); + add(b, (unsigned char)((c->chr >> 24) & 0xFF)); + add(b, (unsigned char)((c->chr >> 16) & 0xFF)); + add(b, (unsigned char)((c->chr >> 8) & 0xFF)); + add(b, (unsigned char)(c->chr & 0xFF)); + } + *state = c->chr & ~0xFF; +} +static void makeliteral_attr(struct buf *b, termchar *c, unsigned long *state) +{ + /* + * My encoding for attributes is 16-bit-granular and assumes + * that the top bit of the word is never required. I either + * store a two-byte value with the top bit clear (indicating + * just that value), or a four-byte value with the top bit set + * (indicating the same value with its top bit clear). + * + * However, first I permute the bits of the attribute value, so + * that the eight bits of colour (four in each of fg and bg) + * which are never non-zero unless xterm 256-colour mode is in + * use are placed higher up the word than everything else. This + * ensures that attribute values remain 16-bit _unless_ the + * user uses extended colour. + */ + unsigned attr, colourbits; + + attr = c->attr; + + assert(ATTR_BGSHIFT > ATTR_FGSHIFT); + + colourbits = (attr >> (ATTR_BGSHIFT + 4)) & 0xF; + colourbits <<= 4; + colourbits |= (attr >> (ATTR_FGSHIFT + 4)) & 0xF; + + attr = (((attr >> (ATTR_BGSHIFT + 8)) << (ATTR_BGSHIFT + 4)) | + (attr & ((1 << (ATTR_BGSHIFT + 4))-1))); + attr = (((attr >> (ATTR_FGSHIFT + 8)) << (ATTR_FGSHIFT + 4)) | + (attr & ((1 << (ATTR_FGSHIFT + 4))-1))); + + attr |= (colourbits << (32-9)); + + if (attr < 0x8000) { + add(b, (unsigned char)((attr >> 8) & 0xFF)); + add(b, (unsigned char)(attr & 0xFF)); + } else { + add(b, (unsigned char)(((attr >> 24) & 0x7F) | 0x80)); + add(b, (unsigned char)((attr >> 16) & 0xFF)); + add(b, (unsigned char)((attr >> 8) & 0xFF)); + add(b, (unsigned char)(attr & 0xFF)); + } +} +static void makeliteral_cc(struct buf *b, termchar *c, unsigned long *state) +{ + /* + * For combining characters, I just encode a bunch of ordinary + * chars using makeliteral_chr, and terminate with a \0 + * character (which I know won't come up as a combining char + * itself). + * + * I don't use the stateful encoding in makeliteral_chr. + */ + unsigned long zstate; + termchar z; + + while (c->cc_next) { + c += c->cc_next; + + assert(c->chr != 0); + + zstate = 0; + makeliteral_chr(b, c, &zstate); + } + + z.chr = 0; + zstate = 0; + makeliteral_chr(b, &z, &zstate); +} + +static termline *decompressline(unsigned char *data, int *bytes_used); + +static unsigned char *compressline(termline *ldata) +{ + struct buf buffer = { NULL, 0, 0 }, *b = &buffer; + + /* + * First, store the column count, 7 bits at a time, least + * significant `digit' first, with the high bit set on all but + * the last. + */ + { + int n = ldata->cols; + while (n >= 128) { + add(b, (unsigned char)((n & 0x7F) | 0x80)); + n >>= 7; + } + add(b, (unsigned char)(n)); + } + + /* + * Next store the lattrs; same principle. + */ + { + int n = ldata->lattr; + while (n >= 128) { + add(b, (unsigned char)((n & 0x7F) | 0x80)); + n >>= 7; + } + add(b, (unsigned char)(n)); + } + + /* + * Now we store a sequence of separate run-length encoded + * fragments, each containing exactly as many symbols as there + * are columns in the ldata. + * + * All of these have a common basic format: + * + * - a byte 00-7F indicates that X+1 literals follow it + * - a byte 80-FF indicates that a single literal follows it + * and expects to be repeated (X-0x80)+2 times. + * + * The format of the `literals' varies between the fragments. + */ + makerle(b, ldata, makeliteral_chr); + makerle(b, ldata, makeliteral_attr); + makerle(b, ldata, makeliteral_cc); + + /* + * Diagnostics: ensure that the compressed data really does + * decompress to the right thing. + * + * This is a bit performance-heavy for production code. + */ +#ifdef TERM_CC_DIAGS +#ifndef CHECK_SB_COMPRESSION + { + int dused; + termline *dcl; + int i; + +#ifdef DIAGNOSTIC_SB_COMPRESSION + for (i = 0; i < b->len; i++) { + printf(" %02x ", b->data[i]); + } + printf("\n"); +#endif + + dcl = decompressline(b->data, &dused); + assert(b->len == dused); + assert(ldata->cols == dcl->cols); + assert(ldata->lattr == dcl->lattr); + for (i = 0; i < ldata->cols; i++) + assert(termchars_equal(&ldata->chars[i], &dcl->chars[i])); + +#ifdef DIAGNOSTIC_SB_COMPRESSION + printf("%d cols (%d bytes) -> %d bytes (factor of %g)\n", + ldata->cols, 4 * ldata->cols, dused, + (double)dused / (4 * ldata->cols)); +#endif + + freeline(dcl); + } +#endif +#endif /* TERM_CC_DIAGS */ + + /* + * Trim the allocated memory so we don't waste any, and return. + */ + return sresize(b->data, b->len, unsigned char); +} + +static void readrle(struct buf *b, termline *ldata, + void (*readliteral)(struct buf *b, termchar *c, + termline *ldata, unsigned long *state)) +{ + int n = 0; + unsigned long state = 0; + + while (n < ldata->cols) { + int hdr = get(b); + + if (hdr >= 0x80) { + /* A run. */ + + int pos = b->len, count = hdr + 2 - 0x80; + while (count--) { + assert(n < ldata->cols); + b->len = pos; + readliteral(b, ldata->chars + n, ldata, &state); + n++; + } + } else { + /* Just a sequence of consecutive literals. */ + + int count = hdr + 1; + while (count--) { + assert(n < ldata->cols); + readliteral(b, ldata->chars + n, ldata, &state); + n++; + } + } + } + + assert(n == ldata->cols); +} +static void readliteral_chr(struct buf *b, termchar *c, termline *ldata, + unsigned long *state) +{ + int byte; + + /* + * 00000000-0000007F: 0xxxxxxx + * 00000080-00003FFF: 10xxxxxx xxxxxxxx + * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx + * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx + * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx + */ + + byte = get(b); + if (byte < 0x80) { + c->chr = byte | *state; + } else if (byte < 0xC0) { + c->chr = (byte &~ 0xC0) << 8; + c->chr |= get(b); + } else if (byte < 0xE0) { + c->chr = (byte &~ 0xE0) << 16; + c->chr |= get(b) << 8; + c->chr |= get(b); + } else if (byte < 0xF0) { + c->chr = (byte &~ 0xF0) << 24; + c->chr |= get(b) << 16; + c->chr |= get(b) << 8; + c->chr |= get(b); + } else { + assert(byte == 0xF0); + c->chr = get(b) << 24; + c->chr |= get(b) << 16; + c->chr |= get(b) << 8; + c->chr |= get(b); + } + *state = c->chr & ~0xFF; +} +static void readliteral_attr(struct buf *b, termchar *c, termline *ldata, + unsigned long *state) +{ + unsigned val, attr, colourbits; + + val = get(b) << 8; + val |= get(b); + + if (val >= 0x8000) { + val &= ~0x8000; + val <<= 16; + val |= get(b) << 8; + val |= get(b); + } + + colourbits = (val >> (32-9)) & 0xFF; + attr = (val & ((1<<(32-9))-1)); + + attr = (((attr >> (ATTR_FGSHIFT + 4)) << (ATTR_FGSHIFT + 8)) | + (attr & ((1 << (ATTR_FGSHIFT + 4))-1))); + attr = (((attr >> (ATTR_BGSHIFT + 4)) << (ATTR_BGSHIFT + 8)) | + (attr & ((1 << (ATTR_BGSHIFT + 4))-1))); + + attr |= (colourbits >> 4) << (ATTR_BGSHIFT + 4); + attr |= (colourbits & 0xF) << (ATTR_FGSHIFT + 4); + + c->attr = attr; +} +static void readliteral_cc(struct buf *b, termchar *c, termline *ldata, + unsigned long *state) +{ + termchar n; + unsigned long zstate; + int x = c - ldata->chars; + + c->cc_next = 0; + + while (1) { + zstate = 0; + readliteral_chr(b, &n, ldata, &zstate); + if (!n.chr) + break; + add_cc(ldata, x, n.chr); + } +} + +static termline *decompressline(unsigned char *data, int *bytes_used) +{ + int ncols, byte, shift; + struct buf buffer, *b = &buffer; + termline *ldata; + + b->data = data; + b->len = 0; + + /* + * First read in the column count. + */ + ncols = shift = 0; + do { + byte = get(b); + ncols |= (byte & 0x7F) << shift; + shift += 7; + } while (byte & 0x80); + + /* + * Now create the output termline. + */ + ldata = snew(termline); + ldata->chars = snewn(ncols, termchar); + ldata->cols = ldata->size = ncols; + ldata->temporary = TRUE; + ldata->cc_free = 0; + + /* + * We must set all the cc pointers in ldata->chars to 0 right + * now, so that cc diagnostics that verify the integrity of the + * whole line will make sense while we're in the middle of + * building it up. + */ + { + int i; + for (i = 0; i < ldata->cols; i++) + ldata->chars[i].cc_next = 0; + } + + /* + * Now read in the lattr. + */ + ldata->lattr = shift = 0; + do { + byte = get(b); + ldata->lattr |= (byte & 0x7F) << shift; + shift += 7; + } while (byte & 0x80); + + /* + * Now we read in each of the RLE streams in turn. + */ + readrle(b, ldata, readliteral_chr); + readrle(b, ldata, readliteral_attr); + readrle(b, ldata, readliteral_cc); + + /* Return the number of bytes read, for diagnostic purposes. */ + if (bytes_used) + *bytes_used = b->len; + + return ldata; +} + +/* + * Resize a line to make it `cols' columns wide. + */ +static void resizeline(Terminal *term, termline *line, int cols) +{ + int i, oldcols; + + if (line->cols != cols) { + + oldcols = line->cols; + + /* + * This line is the wrong length, which probably means it + * hasn't been accessed since a resize. Resize it now. + * + * First, go through all the characters that will be thrown + * out in the resize (if we're shrinking the line) and + * return their cc lists to the cc free list. + */ + for (i = cols; i < oldcols; i++) + clear_cc(line, i); + + /* + * If we're shrinking the line, we now bodily move the + * entire cc section from where it started to where it now + * needs to be. (We have to do this before the resize, so + * that the data we're copying is still there. However, if + * we're expanding, we have to wait until _after_ the + * resize so that the space we're copying into is there.) + */ + if (cols < oldcols) + memmove(line->chars + cols, line->chars + oldcols, + (line->size - line->cols) * TSIZE); + + /* + * Now do the actual resize, leaving the _same_ amount of + * cc space as there was to begin with. + */ + line->size += cols - oldcols; + line->chars = sresize(line->chars, line->size, TTYPE); + line->cols = cols; + + /* + * If we're expanding the line, _now_ we move the cc + * section. + */ + if (cols > oldcols) + memmove(line->chars + cols, line->chars + oldcols, + (line->size - line->cols) * TSIZE); + + /* + * Go through what's left of the original line, and adjust + * the first cc_next pointer in each list. (All the + * subsequent ones are still valid because they are + * relative offsets within the cc block.) Also do the same + * to the head of the cc_free list. + */ + for (i = 0; i < oldcols && i < cols; i++) + if (line->chars[i].cc_next) + line->chars[i].cc_next += cols - oldcols; + if (line->cc_free) + line->cc_free += cols - oldcols; + + /* + * And finally fill in the new space with erase chars. (We + * don't have to worry about cc lists here, because we + * _know_ the erase char doesn't have one.) + */ + for (i = oldcols; i < cols; i++) + line->chars[i] = term->basic_erase_char; + +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif + } +} + +/* + * Get the number of lines in the scrollback. + */ +static int sblines(Terminal *term) +{ + int sblines = count234(term->scrollback); + if (term->cfg.erase_to_scrollback && + term->alt_which && term->alt_screen) { + sblines += term->alt_sblines; + } + return sblines; +} + +/* + * Retrieve a line of the screen or of the scrollback, according to + * whether the y coordinate is non-negative or negative + * (respectively). + */ +static termline *lineptr(Terminal *term, int y, int lineno, int screen) +{ + termline *line; + tree234 *whichtree; + int treeindex; + + if (y >= 0) { + whichtree = term->screen; + treeindex = y; + } else { + int altlines = 0; + + assert(!screen); + + if (term->cfg.erase_to_scrollback && + term->alt_which && term->alt_screen) { + altlines = term->alt_sblines; + } + if (y < -altlines) { + whichtree = term->scrollback; + treeindex = y + altlines + count234(term->scrollback); + } else { + whichtree = term->alt_screen; + treeindex = y + term->alt_sblines; + /* treeindex = y + count234(term->alt_screen); */ + } + } + if (whichtree == term->scrollback) { + unsigned char *cline = index234(whichtree, treeindex); + line = decompressline(cline, NULL); + } else { + line = index234(whichtree, treeindex); + } + + /* We assume that we don't screw up and retrieve something out of range. */ + if (line == NULL) { + fatalbox("line==NULL in terminal.c\n" + "lineno=%d y=%d w=%d h=%d\n" + "count(scrollback=%p)=%d\n" + "count(screen=%p)=%d\n" + "count(alt=%p)=%d alt_sblines=%d\n" + "whichtree=%p treeindex=%d\n\n" + "Please contact " + "and pass on the above information.", + lineno, y, term->cols, term->rows, + term->scrollback, count234(term->scrollback), + term->screen, count234(term->screen), + term->alt_screen, count234(term->alt_screen), term->alt_sblines, + whichtree, treeindex); + } + assert(line != NULL); + + resizeline(term, line, term->cols); + /* FIXME: should we sort the compressed scrollback out here? */ + + return line; +} + +#define lineptr(x) (lineptr)(term,x,__LINE__,FALSE) +#define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE) + +static void term_schedule_tblink(Terminal *term); +static void term_schedule_cblink(Terminal *term); + +static void term_timer(void *ctx, long now) +{ + Terminal *term = (Terminal *)ctx; + int update = FALSE; + + if (term->tblink_pending && now - term->next_tblink >= 0) { + term->tblinker = !term->tblinker; + term->tblink_pending = FALSE; + term_schedule_tblink(term); + update = TRUE; + } + + if (term->cblink_pending && now - term->next_cblink >= 0) { + term->cblinker = !term->cblinker; + term->cblink_pending = FALSE; + term_schedule_cblink(term); + update = TRUE; + } + + if (term->in_vbell && now - term->vbell_end >= 0) { + term->in_vbell = FALSE; + update = TRUE; + } + + if (update || + (term->window_update_pending && now - term->next_update >= 0)) + term_update(term); +} + +static void term_schedule_update(Terminal *term) +{ + if (!term->window_update_pending) { + term->window_update_pending = TRUE; + term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term); + } +} + +/* + * Call this whenever the terminal window state changes, to queue + * an update. + */ +static void seen_disp_event(Terminal *term) +{ + term->seen_disp_event = TRUE; /* for scrollback-reset-on-activity */ + term_schedule_update(term); +} + +/* + * Call when the terminal's blinking-text settings change, or when + * a text blink has just occurred. + */ +static void term_schedule_tblink(Terminal *term) +{ + if (term->blink_is_real) { + if (!term->tblink_pending) + term->next_tblink = schedule_timer(TBLINK_DELAY, term_timer, term); + term->tblink_pending = TRUE; + } else { + term->tblinker = 1; /* reset when not in use */ + term->tblink_pending = FALSE; + } +} + +/* + * Likewise with cursor blinks. + */ +static void term_schedule_cblink(Terminal *term) +{ + if (term->cfg.blink_cur && term->has_focus) { + if (!term->cblink_pending) + term->next_cblink = schedule_timer(CBLINK_DELAY, term_timer, term); + term->cblink_pending = TRUE; + } else { + term->cblinker = 1; /* reset when not in use */ + term->cblink_pending = FALSE; + } +} + +/* + * Call to reset cursor blinking on new output. + */ +static void term_reset_cblink(Terminal *term) +{ + seen_disp_event(term); + term->cblinker = 1; + term->cblink_pending = FALSE; + term_schedule_cblink(term); +} + +/* + * Call to begin a visual bell. + */ +static void term_schedule_vbell(Terminal *term, int already_started, + long startpoint) +{ + long ticks_already_gone; + + if (already_started) + ticks_already_gone = GETTICKCOUNT() - startpoint; + else + ticks_already_gone = 0; + + if (ticks_already_gone < VBELL_DELAY) { + term->in_vbell = TRUE; + term->vbell_end = schedule_timer(VBELL_DELAY - ticks_already_gone, + term_timer, term); + } else { + term->in_vbell = FALSE; + } +} + +/* + * Set up power-on settings for the terminal. + * If 'clear' is false, don't actually clear the primary screen, and + * position the cursor below the last non-blank line (scrolling if + * necessary). + */ +static void power_on(Terminal *term, int clear) +{ + term->alt_x = term->alt_y = 0; + term->savecurs.x = term->savecurs.y = 0; + term->alt_savecurs.x = term->alt_savecurs.y = 0; + term->alt_t = term->marg_t = 0; + if (term->rows != -1) + term->alt_b = term->marg_b = term->rows - 1; + else + term->alt_b = term->marg_b = 0; + if (term->cols != -1) { + int i; + for (i = 0; i < term->cols; i++) + term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE); + } + term->alt_om = term->dec_om = term->cfg.dec_om; + term->alt_ins = term->insert = FALSE; + term->alt_wnext = term->wrapnext = + term->save_wnext = term->alt_save_wnext = FALSE; + term->alt_wrap = term->wrap = term->cfg.wrap_mode; + term->alt_cset = term->cset = term->save_cset = term->alt_save_cset = 0; + term->alt_utf = term->utf = term->save_utf = term->alt_save_utf = 0; + term->utf_state = 0; + term->alt_sco_acs = term->sco_acs = + term->save_sco_acs = term->alt_save_sco_acs = 0; + term->cset_attr[0] = term->cset_attr[1] = + term->save_csattr = term->alt_save_csattr = CSET_ASCII; + term->rvideo = 0; + term->in_vbell = FALSE; + term->cursor_on = 1; + term->big_cursor = 0; + term->default_attr = term->save_attr = + term->alt_save_attr = term->curr_attr = ATTR_DEFAULT; + term->term_editing = term->term_echoing = FALSE; + term->app_cursor_keys = term->cfg.app_cursor; + term->app_keypad_keys = term->cfg.app_keypad; + term->use_bce = term->cfg.bce; + term->blink_is_real = term->cfg.blinktext; + term->erase_char = term->basic_erase_char; + term->alt_which = 0; + term_print_finish(term); + term->xterm_mouse = 0; + set_raw_mouse_mode(term->frontend, FALSE); + { + int i; + for (i = 0; i < 256; i++) + term->wordness[i] = term->cfg.wordness[i]; + } + if (term->screen) { + swap_screen(term, 1, FALSE, FALSE); + erase_lots(term, FALSE, TRUE, TRUE); + swap_screen(term, 0, FALSE, FALSE); + if (clear) + erase_lots(term, FALSE, TRUE, TRUE); + term->curs.y = find_last_nonempty_line(term, term->screen) + 1; + if (term->curs.y == term->rows) { + term->curs.y--; + scroll(term, 0, term->rows - 1, 1, TRUE); + } + } else { + term->curs.y = 0; + } + term->curs.x = 0; + term_schedule_tblink(term); + term_schedule_cblink(term); +} + +/* + * Force a screen update. + */ +void term_update(Terminal *term) +{ + Context ctx; + + term->window_update_pending = FALSE; + + ctx = get_ctx(term->frontend); + if (ctx) { + int need_sbar_update = term->seen_disp_event; + if (term->seen_disp_event && term->cfg.scroll_on_disp) { + term->disptop = 0; /* return to main screen */ + term->seen_disp_event = 0; + need_sbar_update = TRUE; + } + + if (need_sbar_update) + update_sbar(term); + do_paint(term, ctx, TRUE); + sys_cursor(term->frontend, term->curs.x, term->curs.y - term->disptop); + free_ctx(ctx); + } +} + +/* + * Called from front end when a keypress occurs, to trigger + * anything magical that needs to happen in that situation. + */ +void term_seen_key_event(Terminal *term) +{ + /* + * On any keypress, clear the bell overload mechanism + * completely, on the grounds that large numbers of + * beeps coming from deliberate key action are likely + * to be intended (e.g. beeps from filename completion + * blocking repeatedly). + */ + term->beep_overloaded = FALSE; + while (term->beephead) { + struct beeptime *tmp = term->beephead; + term->beephead = tmp->next; + sfree(tmp); + } + term->beeptail = NULL; + term->nbeeps = 0; + + /* + * Reset the scrollback on keypress, if we're doing that. + */ + if (term->cfg.scroll_on_key) { + term->disptop = 0; /* return to main screen */ + seen_disp_event(term); + } +} + +/* + * Same as power_on(), but an external function. + */ +void term_pwron(Terminal *term, int clear) +{ + power_on(term, clear); + if (term->ldisc) /* cause ldisc to notice changes */ + ldisc_send(term->ldisc, NULL, 0, 0); + term->disptop = 0; + deselect(term); + term_update(term); +} + +static void set_erase_char(Terminal *term) +{ + term->erase_char = term->basic_erase_char; + if (term->use_bce) + term->erase_char.attr = (term->curr_attr & + (ATTR_FGMASK | ATTR_BGMASK)); +} + +/* + * 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, Config *cfg) +{ + /* + * Before adopting the new config, check all those terminal + * settings which control power-on defaults; and if they've + * changed, we will modify the current state as well as the + * default one. The full list is: Auto wrap mode, DEC Origin + * Mode, BCE, blinking text, character classes. + */ + int reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass; + int i; + + reset_wrap = (term->cfg.wrap_mode != cfg->wrap_mode); + reset_decom = (term->cfg.dec_om != cfg->dec_om); + reset_bce = (term->cfg.bce != cfg->bce); + reset_tblink = (term->cfg.blinktext != cfg->blinktext); + reset_charclass = 0; + for (i = 0; i < lenof(term->cfg.wordness); i++) + if (term->cfg.wordness[i] != cfg->wordness[i]) + reset_charclass = 1; + + /* + * If the bidi or shaping settings have changed, flush the bidi + * cache completely. + */ + if (term->cfg.arabicshaping != cfg->arabicshaping || + term->cfg.bidi != cfg->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; + } + } + + term->cfg = *cfg; /* STRUCTURE COPY */ + + if (reset_wrap) + term->alt_wrap = term->wrap = term->cfg.wrap_mode; + if (reset_decom) + term->alt_om = term->dec_om = term->cfg.dec_om; + if (reset_bce) { + term->use_bce = term->cfg.bce; + set_erase_char(term); + } + if (reset_tblink) { + term->blink_is_real = term->cfg.blinktext; + } + if (reset_charclass) + for (i = 0; i < 256; i++) + term->wordness[i] = term->cfg.wordness[i]; + + if (term->cfg.no_alt_screen) + swap_screen(term, 0, FALSE, FALSE); + if (term->cfg.no_mouse_rep) { + term->xterm_mouse = 0; + set_raw_mouse_mode(term->frontend, 0); + } + if (term->cfg.no_remote_charset) { + term->cset_attr[0] = term->cset_attr[1] = CSET_ASCII; + term->sco_acs = term->alt_sco_acs = 0; + term->utf = 0; + } + if (!*term->cfg.printer) { + term_print_finish(term); + } + term_schedule_tblink(term); + term_schedule_cblink(term); +} + +/* + * Clear the scrollback. + */ +void term_clrsb(Terminal *term) +{ + unsigned char *line; + term->disptop = 0; + while ((line = delpos234(term->scrollback, 0)) != NULL) { + sfree(line); /* this is compressed data, not a termline */ + } + term->tempsblines = 0; + term->alt_sblines = 0; + update_sbar(term); +} + +/* + * Initialise the terminal. + */ +Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata, + void *frontend) +{ + Terminal *term; + + /* + * Allocate a new Terminal structure and initialise the fields + * that need it. + */ + term = snew(Terminal); + term->frontend = frontend; + term->ucsdata = ucsdata; + term->cfg = *mycfg; /* STRUCTURE COPY */ + 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; + term->last_paste = 0; + bufchain_init(&term->inbuf); + bufchain_init(&term->printer_buf); + term->printing = term->only_printing = FALSE; + term->print_job = NULL; + term->vt52_mode = FALSE; + term->cr_lf_return = FALSE; + term->seen_disp_event = FALSE; + term->mouse_is_down = FALSE; + term->reset_132 = FALSE; + term->cblinker = term->tblinker = 0; + term->has_focus = 1; + term->repeat_off = FALSE; + term->termstate = TOPLEVEL; + term->selstate = NO_SELECTION; + term->curstype = 0; + + term->screen = term->alt_screen = term->scrollback = NULL; + term->tempsblines = 0; + term->alt_sblines = 0; + term->disptop = 0; + term->disptext = NULL; + term->dispcursx = term->dispcursy = -1; + term->tabs = NULL; + deselect(term); + term->rows = term->cols = -1; + power_on(term, TRUE); + term->beephead = term->beeptail = NULL; +#ifdef OPTIMISE_SCROLL + term->scrollhead = term->scrolltail = NULL; +#endif /* OPTIMISE_SCROLL */ + term->nbeeps = 0; + term->lastbeep = FALSE; + term->beep_overloaded = FALSE; + term->attr_mask = 0xffffffff; + term->resize_fn = NULL; + term->resize_ctx = NULL; + term->in_term_out = FALSE; + term->ltemp = NULL; + term->ltemp_size = 0; + term->wcFrom = NULL; + term->wcTo = NULL; + term->wcFromTo_size = 0; + + term->window_update_pending = FALSE; + + term->bidi_cache_size = 0; + term->pre_bidi_cache = term->post_bidi_cache = NULL; + + /* FULL-TERMCHAR */ + term->basic_erase_char.chr = CSET_ASCII | ' '; + term->basic_erase_char.attr = ATTR_DEFAULT; + term->basic_erase_char.cc_next = 0; + term->erase_char = term->basic_erase_char; + + return term; +} + +void term_free(Terminal *term) +{ + termline *line; + struct beeptime *beep; + int i; + + while ((line = delpos234(term->scrollback, 0)) != NULL) + sfree(line); /* compressed data, not a termline */ + freetree234(term->scrollback); + while ((line = delpos234(term->screen, 0)) != NULL) + freeline(line); + freetree234(term->screen); + while ((line = delpos234(term->alt_screen, 0)) != NULL) + freeline(line); + freetree234(term->alt_screen); + if (term->disptext) { + for (i = 0; i < term->rows; i++) + freeline(term->disptext[i]); + } + sfree(term->disptext); + while (term->beephead) { + beep = term->beephead; + term->beephead = beep->next; + sfree(beep); + } + bufchain_clear(&term->inbuf); + if(term->print_job) + printer_finish_job(term->print_job); + bufchain_clear(&term->printer_buf); + sfree(term->paste_buffer); + sfree(term->ltemp); + sfree(term->wcFrom); + sfree(term->wcTo); + + for (i = 0; i < term->bidi_cache_size; i++) { + sfree(term->pre_bidi_cache[i].chars); + sfree(term->post_bidi_cache[i].chars); + } + sfree(term->pre_bidi_cache); + sfree(term->post_bidi_cache); + + expire_timer_context(term); + + sfree(term); +} + +/* + * Set up the terminal for a given size. + */ +void term_size(Terminal *term, int newrows, int newcols, int newsavelines) +{ + tree234 *newalt; + termline **newdisp, *line; + int i, j, oldrows = term->rows; + int sblen; + int save_alt_which = term->alt_which; + + if (newrows == term->rows && newcols == term->cols && + newsavelines == term->savelines) + return; /* nothing to do */ + + /* Behave sensibly if we're given zero (or negative) rows/cols */ + + if (newrows < 1) newrows = 1; + if (newcols < 1) newcols = 1; + + deselect(term); + swap_screen(term, 0, FALSE, FALSE); + + term->alt_t = term->marg_t = 0; + term->alt_b = term->marg_b = newrows - 1; + + if (term->rows == -1) { + term->scrollback = newtree234(NULL); + term->screen = newtree234(NULL); + term->tempsblines = 0; + term->rows = 0; + } + + /* + * Resize the screen and scrollback. We only need to shift + * lines around within our data structures, because lineptr() + * will take care of resizing each individual line if + * necessary. So: + * + * - If the new screen is longer, we shunt lines in from temporary + * scrollback if possible, otherwise we add new blank lines at + * the bottom. + * + * - If the new screen is shorter, we remove any blank lines at + * the bottom if possible, otherwise shunt lines above the cursor + * to scrollback if possible, otherwise delete lines below the + * cursor. + * + * - Then, if the new scrollback length is less than the + * amount of scrollback we actually have, we must throw some + * away. + */ + sblen = count234(term->scrollback); + /* Do this loop to expand the screen if newrows > rows */ + assert(term->rows == count234(term->screen)); + while (term->rows < newrows) { + if (term->tempsblines > 0) { + unsigned char *cline; + /* Insert a line from the scrollback at the top of the screen. */ + assert(sblen >= term->tempsblines); + cline = delpos234(term->scrollback, --sblen); + line = decompressline(cline, NULL); + sfree(cline); + line->temporary = FALSE; /* reconstituted line is now real */ + term->tempsblines -= 1; + addpos234(term->screen, line, 0); + term->curs.y += 1; + term->savecurs.y += 1; + term->alt_y += 1; + term->alt_savecurs.y += 1; + } else { + /* Add a new blank line at the bottom of the screen. */ + line = newline(term, newcols, FALSE); + addpos234(term->screen, line, count234(term->screen)); + } + term->rows += 1; + } + /* Do this loop to shrink the screen if newrows < rows */ + while (term->rows > newrows) { + if (term->curs.y < term->rows - 1) { + /* delete bottom row, unless it contains the cursor */ + sfree(delpos234(term->screen, term->rows - 1)); + } else { + /* push top row to scrollback */ + line = delpos234(term->screen, 0); + addpos234(term->scrollback, compressline(line), sblen++); + freeline(line); + term->tempsblines += 1; + term->curs.y -= 1; + term->savecurs.y -= 1; + term->alt_y -= 1; + term->alt_savecurs.y -= 1; + } + term->rows -= 1; + } + assert(term->rows == newrows); + assert(count234(term->screen) == newrows); + + /* Delete any excess lines from the scrollback. */ + while (sblen > newsavelines) { + line = delpos234(term->scrollback, 0); + sfree(line); + sblen--; + } + if (sblen < term->tempsblines) + term->tempsblines = sblen; + assert(count234(term->scrollback) <= newsavelines); + assert(count234(term->scrollback) >= term->tempsblines); + term->disptop = 0; + + /* Make a new displayed text buffer. */ + newdisp = snewn(newrows, termline *); + for (i = 0; i < newrows; i++) { + newdisp[i] = newline(term, newcols, FALSE); + for (j = 0; j < newcols; j++) + newdisp[i]->chars[j].attr = ATTR_INVALID; + } + if (term->disptext) { + for (i = 0; i < oldrows; i++) + freeline(term->disptext[i]); + } + sfree(term->disptext); + term->disptext = newdisp; + term->dispcursx = term->dispcursy = -1; + + /* Make a new alternate screen. */ + newalt = newtree234(NULL); + for (i = 0; i < newrows; i++) { + line = newline(term, newcols, TRUE); + addpos234(newalt, line, i); + } + if (term->alt_screen) { + while (NULL != (line = delpos234(term->alt_screen, 0))) + freeline(line); + freetree234(term->alt_screen); + } + term->alt_screen = newalt; + term->alt_sblines = 0; + + term->tabs = sresize(term->tabs, newcols, unsigned char); + { + int i; + for (i = (term->cols > 0 ? term->cols : 0); i < newcols; i++) + term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE); + } + + /* Check that the cursor positions are still valid. */ + if (term->savecurs.y < 0) + term->savecurs.y = 0; + if (term->savecurs.y >= newrows) + term->savecurs.y = newrows - 1; + if (term->savecurs.x >= newcols) + term->savecurs.x = newcols - 1; + if (term->alt_savecurs.y < 0) + term->alt_savecurs.y = 0; + if (term->alt_savecurs.y >= newrows) + term->alt_savecurs.y = newrows - 1; + if (term->alt_savecurs.x >= newcols) + term->alt_savecurs.x = newcols - 1; + if (term->curs.y < 0) + term->curs.y = 0; + if (term->curs.y >= newrows) + term->curs.y = newrows - 1; + if (term->curs.x >= newcols) + term->curs.x = newcols - 1; + if (term->alt_y < 0) + term->alt_y = 0; + if (term->alt_y >= newrows) + term->alt_y = newrows - 1; + if (term->alt_x >= newcols) + term->alt_x = newcols - 1; + term->alt_x = term->alt_y = 0; + term->wrapnext = term->alt_wnext = FALSE; + + term->rows = newrows; + term->cols = newcols; + term->savelines = newsavelines; + + swap_screen(term, save_alt_which, FALSE, FALSE); + + update_sbar(term); + term_update(term); + if (term->resize_fn) + term->resize_fn(term->resize_ctx, term->cols, term->rows); +} + +/* + * Hand a function and context pointer to the terminal which it can + * use to notify a back end of resizes. + */ +void term_provide_resize_fn(Terminal *term, + void (*resize_fn)(void *, int, int), + void *resize_ctx) +{ + term->resize_fn = resize_fn; + term->resize_ctx = resize_ctx; + if (resize_fn && term->cols > 0 && term->rows > 0) + resize_fn(resize_ctx, term->cols, term->rows); +} + +/* Find the bottom line on the screen that has any content. + * If only the top line has content, returns 0. + * If no lines have content, return -1. + */ +static int find_last_nonempty_line(Terminal * term, tree234 * screen) +{ + int i; + for (i = count234(screen) - 1; i >= 0; i--) { + termline *line = index234(screen, i); + int j; + for (j = 0; j < line->cols; j++) + if (!termchars_equal(&line->chars[j], &term->erase_char)) + break; + if (j != line->cols) break; + } + return i; +} + +/* + * Swap screens. If `reset' is TRUE and we have been asked to + * switch to the alternate screen, we must bring most of its + * configuration from the main screen and erase the contents of the + * alternate screen completely. (This is even true if we're already + * on it! Blame xterm.) + */ +static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos) +{ + int t; + pos tp; + tree234 *ttr; + + if (!which) + reset = FALSE; /* do no weird resetting if which==0 */ + + if (which != term->alt_which) { + term->alt_which = which; + + ttr = term->alt_screen; + term->alt_screen = term->screen; + term->screen = ttr; + term->alt_sblines = find_last_nonempty_line(term, term->alt_screen) + 1; + t = term->curs.x; + if (!reset && !keep_cur_pos) + term->curs.x = term->alt_x; + term->alt_x = t; + t = term->curs.y; + if (!reset && !keep_cur_pos) + term->curs.y = term->alt_y; + term->alt_y = t; + t = term->marg_t; + if (!reset) term->marg_t = term->alt_t; + term->alt_t = t; + t = term->marg_b; + if (!reset) term->marg_b = term->alt_b; + term->alt_b = t; + t = term->dec_om; + if (!reset) term->dec_om = term->alt_om; + term->alt_om = t; + t = term->wrap; + if (!reset) term->wrap = term->alt_wrap; + term->alt_wrap = t; + t = term->wrapnext; + if (!reset) term->wrapnext = term->alt_wnext; + term->alt_wnext = t; + t = term->insert; + if (!reset) term->insert = term->alt_ins; + term->alt_ins = t; + t = term->cset; + if (!reset) term->cset = term->alt_cset; + term->alt_cset = t; + t = term->utf; + if (!reset) term->utf = term->alt_utf; + term->alt_utf = t; + t = term->sco_acs; + if (!reset) term->sco_acs = term->alt_sco_acs; + term->alt_sco_acs = t; + + tp = term->savecurs; + if (!reset && !keep_cur_pos) + term->savecurs = term->alt_savecurs; + term->alt_savecurs = tp; + t = term->save_cset; + if (!reset && !keep_cur_pos) + term->save_cset = term->alt_save_cset; + term->alt_save_cset = t; + t = term->save_csattr; + if (!reset && !keep_cur_pos) + term->save_csattr = term->alt_save_csattr; + term->alt_save_csattr = t; + t = term->save_attr; + if (!reset && !keep_cur_pos) + term->save_attr = term->alt_save_attr; + term->alt_save_attr = t; + t = term->save_utf; + if (!reset && !keep_cur_pos) + term->save_utf = term->alt_save_utf; + term->alt_save_utf = t; + t = term->save_wnext; + if (!reset && !keep_cur_pos) + term->save_wnext = term->alt_save_wnext; + term->alt_save_wnext = t; + t = term->save_sco_acs; + if (!reset && !keep_cur_pos) + term->save_sco_acs = term->alt_save_sco_acs; + term->alt_save_sco_acs = t; + } + + if (reset && term->screen) { + /* + * Yes, this _is_ supposed to honour background-colour-erase. + */ + erase_lots(term, FALSE, TRUE, TRUE); + } +} + +/* + * Update the scroll bar. + */ +static void update_sbar(Terminal *term) +{ + int nscroll = sblines(term); + set_sbar(term->frontend, nscroll + term->rows, + nscroll + term->disptop, term->rows); +} + +/* + * Check whether the region bounded by the two pointers intersects + * the scroll region, and de-select the on-screen selection if so. + */ +static void check_selection(Terminal *term, pos from, pos to) +{ + if (poslt(from, term->selend) && poslt(term->selstart, to)) + deselect(term); +} + +/* + * Scroll the screen. (`lines' is +ve for scrolling forward, -ve + * for backward.) `sb' is TRUE if the scrolling is permitted to + * affect the scrollback buffer. + */ +static void scroll(Terminal *term, int topline, int botline, int lines, int sb) +{ + termline *line; + int i, seltop; +#ifdef OPTIMISE_SCROLL + int olddisptop, shift; +#endif /* OPTIMISE_SCROLL */ + + if (topline != 0 || term->alt_which != 0) + sb = FALSE; + +#ifdef OPTIMISE_SCROLL + olddisptop = term->disptop; + shift = lines; +#endif /* OPTIMISE_SCROLL */ + if (lines < 0) { + while (lines < 0) { + line = delpos234(term->screen, botline); + resizeline(term, line, term->cols); + for (i = 0; i < term->cols; i++) + copy_termchar(line, i, &term->erase_char); + line->lattr = LATTR_NORM; + addpos234(term->screen, line, topline); + + if (term->selstart.y >= topline && term->selstart.y <= botline) { + term->selstart.y++; + if (term->selstart.y > botline) { + term->selstart.y = botline + 1; + term->selstart.x = 0; + } + } + if (term->selend.y >= topline && term->selend.y <= botline) { + term->selend.y++; + if (term->selend.y > botline) { + term->selend.y = botline + 1; + term->selend.x = 0; + } + } + + lines++; + } + } else { + while (lines > 0) { + line = delpos234(term->screen, topline); +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif + if (sb && term->savelines > 0) { + int sblen = count234(term->scrollback); + /* + * We must add this line to the scrollback. We'll + * remove a line from the top of the scrollback if + * the scrollback is full. + */ + if (sblen == term->savelines) { + unsigned char *cline; + + sblen--; + cline = delpos234(term->scrollback, 0); + sfree(cline); + } else + term->tempsblines += 1; + + addpos234(term->scrollback, compressline(line), sblen); + + /* now `line' itself can be reused as the bottom line */ + + /* + * If the user is currently looking at part of the + * scrollback, and they haven't enabled any options + * that are going to reset the scrollback as a + * result of this movement, then the chances are + * they'd like to keep looking at the same line. So + * we move their viewpoint at the same rate as the + * scroll, at least until their viewpoint hits the + * top end of the scrollback buffer, at which point + * we don't have the choice any more. + * + * Thanks to Jan Holmen Holsten for the idea and + * initial implementation. + */ + if (term->disptop > -term->savelines && term->disptop < 0) + term->disptop--; + } + resizeline(term, line, term->cols); + for (i = 0; i < term->cols; i++) + copy_termchar(line, i, &term->erase_char); + line->lattr = LATTR_NORM; + addpos234(term->screen, line, botline); + + /* + * If the selection endpoints move into the scrollback, + * we keep them moving until they hit the top. However, + * of course, if the line _hasn't_ moved into the + * scrollback then we don't do this, and cut them off + * at the top of the scroll region. + * + * This applies to selstart and selend (for an existing + * selection), and also selanchor (for one being + * selected as we speak). + */ + seltop = sb ? -term->savelines : topline; + + if (term->selstate != NO_SELECTION) { + if (term->selstart.y >= seltop && + term->selstart.y <= botline) { + term->selstart.y--; + if (term->selstart.y < seltop) { + term->selstart.y = seltop; + term->selstart.x = 0; + } + } + if (term->selend.y >= seltop && term->selend.y <= botline) { + term->selend.y--; + if (term->selend.y < seltop) { + term->selend.y = seltop; + term->selend.x = 0; + } + } + if (term->selanchor.y >= seltop && + term->selanchor.y <= botline) { + term->selanchor.y--; + if (term->selanchor.y < seltop) { + term->selanchor.y = seltop; + term->selanchor.x = 0; + } + } + } + + lines--; + } + } +#ifdef OPTIMISE_SCROLL + shift += term->disptop - olddisptop; + if (shift < term->rows && shift > -term->rows && shift != 0) + scroll_display(term, topline, botline, shift); +#endif /* OPTIMISE_SCROLL */ +} + +#ifdef OPTIMISE_SCROLL +/* + * Add a scroll of a region on the screen into the pending scroll list. + * `lines' is +ve for scrolling forward, -ve for backward. + * + * If the scroll is on the same area as the last scroll in the list, + * merge them. + */ +static void save_scroll(Terminal *term, int topline, int botline, int lines) +{ + struct scrollregion *newscroll; + if (term->scrolltail && + term->scrolltail->topline == topline && + term->scrolltail->botline == botline) { + term->scrolltail->lines += lines; + } else { + newscroll = snew(struct scrollregion); + newscroll->topline = topline; + newscroll->botline = botline; + newscroll->lines = lines; + newscroll->next = NULL; + + if (!term->scrollhead) + term->scrollhead = newscroll; + else + term->scrolltail->next = newscroll; + term->scrolltail = newscroll; + } +} + +/* + * Scroll the physical display, and our conception of it in disptext. + */ +static void scroll_display(Terminal *term, int topline, int botline, int lines) +{ + int distance, nlines, i, j; + + distance = lines > 0 ? lines : -lines; + nlines = botline - topline + 1 - distance; + if (lines > 0) { + for (i = 0; i < nlines; i++) + for (j = 0; j < term->cols; j++) + copy_termchar(term->disptext[i], j, + term->disptext[i+distance]->chars+j); + if (term->dispcursy >= 0 && + term->dispcursy >= topline + distance && + term->dispcursy < topline + distance + nlines) + term->dispcursy -= distance; + for (i = 0; i < distance; i++) + for (j = 0; j < term->cols; j++) + term->disptext[nlines+i]->chars[j].attr |= ATTR_INVALID; + } else { + for (i = nlines; i-- ;) + for (j = 0; j < term->cols; j++) + copy_termchar(term->disptext[i+distance], j, + term->disptext[i]->chars+j); + if (term->dispcursy >= 0 && + term->dispcursy >= topline && + term->dispcursy < topline + nlines) + term->dispcursy += distance; + for (i = 0; i < distance; i++) + for (j = 0; j < term->cols; j++) + term->disptext[i]->chars[j].attr |= ATTR_INVALID; + } + save_scroll(term, topline, botline, lines); +} +#endif /* OPTIMISE_SCROLL */ + +/* + * Move the cursor to a given position, clipping at boundaries. We + * may or may not want to clip at the scroll margin: marg_clip is 0 + * not to, 1 to disallow _passing_ the margins, and 2 to disallow + * even _being_ outside the margins. + */ +static void move(Terminal *term, int x, int y, int marg_clip) +{ + if (x < 0) + x = 0; + if (x >= term->cols) + x = term->cols - 1; + if (marg_clip) { + if ((term->curs.y >= term->marg_t || marg_clip == 2) && + y < term->marg_t) + y = term->marg_t; + if ((term->curs.y <= term->marg_b || marg_clip == 2) && + y > term->marg_b) + y = term->marg_b; + } + if (y < 0) + y = 0; + if (y >= term->rows) + y = term->rows - 1; + term->curs.x = x; + term->curs.y = y; + term->wrapnext = FALSE; +} + +/* + * Save or restore the cursor and SGR mode. + */ +static void save_cursor(Terminal *term, int save) +{ + if (save) { + term->savecurs = term->curs; + term->save_attr = term->curr_attr; + term->save_cset = term->cset; + term->save_utf = term->utf; + term->save_wnext = term->wrapnext; + term->save_csattr = term->cset_attr[term->cset]; + term->save_sco_acs = term->sco_acs; + } else { + term->curs = term->savecurs; + /* Make sure the window hasn't shrunk since the save */ + if (term->curs.x >= term->cols) + term->curs.x = term->cols - 1; + if (term->curs.y >= term->rows) + term->curs.y = term->rows - 1; + + term->curr_attr = term->save_attr; + term->cset = term->save_cset; + term->utf = term->save_utf; + term->wrapnext = term->save_wnext; + /* + * wrapnext might reset to False if the x position is no + * longer at the rightmost edge. + */ + if (term->wrapnext && term->curs.x < term->cols-1) + term->wrapnext = FALSE; + term->cset_attr[term->cset] = term->save_csattr; + term->sco_acs = term->save_sco_acs; + set_erase_char(term); + } +} + +/* + * This function is called before doing _anything_ which affects + * only part of a line of text. It is used to mark the boundary + * between two character positions, and it indicates that some sort + * of effect is going to happen on only one side of that boundary. + * + * The effect of this function is to check whether a CJK + * double-width character is straddling the boundary, and to remove + * it and replace it with two spaces if so. (Of course, one or + * other of those spaces is then likely to be replaced with + * something else again, as a result of whatever happens next.) + * + * Also, if the boundary is at the right-hand _edge_ of the screen, + * it implies something deliberate is being done to the rightmost + * column position; hence we must clear LATTR_WRAPPED2. + * + * The input to the function is the coordinates of the _second_ + * character of the pair. + */ +static void check_boundary(Terminal *term, int x, int y) +{ + termline *ldata; + + /* Validate input coordinates, just in case. */ + if (x == 0 || x > term->cols) + return; + + ldata = scrlineptr(y); + if (x == term->cols) { + ldata->lattr &= ~LATTR_WRAPPED2; + } else { + if (ldata->chars[x].chr == UCSWIDE) { + clear_cc(ldata, x-1); + clear_cc(ldata, x); + ldata->chars[x-1].chr = ' ' | CSET_ASCII; + ldata->chars[x] = ldata->chars[x-1]; + } + } +} + +/* + * Erase a large portion of the screen: the whole screen, or the + * whole line, or parts thereof. + */ +static void erase_lots(Terminal *term, + int line_only, int from_begin, int to_end) +{ + pos start, end; + int erase_lattr; + int erasing_lines_from_top = 0; + + if (line_only) { + start.y = term->curs.y; + start.x = 0; + end.y = term->curs.y + 1; + end.x = 0; + erase_lattr = FALSE; + } else { + start.y = 0; + start.x = 0; + end.y = term->rows; + end.x = 0; + erase_lattr = TRUE; + } + if (!from_begin) { + start = term->curs; + } + if (!to_end) { + end = term->curs; + incpos(end); + } + if (!from_begin || !to_end) + check_boundary(term, term->curs.x, term->curs.y); + check_selection(term, start, end); + + /* Clear screen also forces a full window redraw, just in case. */ + if (start.y == 0 && start.x == 0 && end.y == term->rows) + term_invalidate(term); + + /* Lines scrolled away shouldn't be brought back on if the terminal + * resizes. */ + if (start.y == 0 && start.x == 0 && end.x == 0 && erase_lattr) + erasing_lines_from_top = 1; + + if (term->cfg.erase_to_scrollback && erasing_lines_from_top) { + /* If it's a whole number of lines, starting at the top, and + * we're fully erasing them, erase by scrolling and keep the + * lines in the scrollback. */ + int scrolllines = end.y; + if (end.y == term->rows) { + /* Shrink until we find a non-empty row.*/ + scrolllines = find_last_nonempty_line(term, term->screen) + 1; + } + if (scrolllines > 0) + scroll(term, 0, scrolllines - 1, scrolllines, TRUE); + } else { + termline *ldata = scrlineptr(start.y); + while (poslt(start, end)) { + if (start.x == term->cols) { + if (!erase_lattr) + ldata->lattr &= ~(LATTR_WRAPPED | LATTR_WRAPPED2); + else + ldata->lattr = LATTR_NORM; + } else { + copy_termchar(ldata, start.x, &term->erase_char); + } + if (incpos(start) && start.y < term->rows) { + ldata = scrlineptr(start.y); + } + } + } + + /* After an erase of lines from the top of the screen, we shouldn't + * bring the lines back again if the terminal enlarges (since the user or + * application has explictly thrown them away). */ + if (erasing_lines_from_top && !(term->alt_which)) + term->tempsblines = 0; +} + +/* + * Insert or delete characters within the current line. n is +ve if + * insertion is desired, and -ve for deletion. + */ +static void insch(Terminal *term, int n) +{ + int dir = (n < 0 ? -1 : +1); + int m, j; + pos cursplus; + 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; + cursplus.y = term->curs.y; + cursplus.x = term->curs.x + n; + check_selection(term, term->curs, cursplus); + check_boundary(term, term->curs.x, term->curs.y); + if (dir < 0) + check_boundary(term, term->curs.x + n, term->curs.y); + ldata = scrlineptr(term->curs.y); + if (dir < 0) { + for (j = 0; j < m; j++) + move_termchar(ldata, + ldata->chars + term->curs.x + j, + ldata->chars + term->curs.x + j + n); + while (n--) + copy_termchar(ldata, term->curs.x + m++, &term->erase_char); + } else { + for (j = m; j-- ;) + move_termchar(ldata, + ldata->chars + term->curs.x + j + n, + ldata->chars + term->curs.x + j); + while (n--) + copy_termchar(ldata, term->curs.x + n, &term->erase_char); + } +} + +/* + * Toggle terminal mode `mode' to state `state'. (`query' indicates + * whether the mode is a DEC private one or a normal one.) + */ +static void toggle_mode(Terminal *term, int mode, int query, int state) +{ + if (query) + switch (mode) { + case 1: /* DECCKM: application cursor keys */ + term->app_cursor_keys = state; + break; + case 2: /* DECANM: VT52 mode */ + term->vt52_mode = !state; + if (term->vt52_mode) { + term->blink_is_real = FALSE; + term->vt52_bold = FALSE; + } else { + term->blink_is_real = term->cfg.blinktext; + } + term_schedule_tblink(term); + break; + case 3: /* DECCOLM: 80/132 columns */ + deselect(term); + if (!term->cfg.no_remote_resize) + request_resize(term->frontend, state ? 132 : 80, term->rows); + term->reset_132 = state; + term->alt_t = term->marg_t = 0; + term->alt_b = term->marg_b = term->rows - 1; + move(term, 0, 0, 0); + erase_lots(term, FALSE, TRUE, TRUE); + break; + case 5: /* DECSCNM: reverse video */ + /* + * Toggle reverse video. If we receive an OFF within the + * visual bell timeout period after an ON, we trigger an + * effective visual bell, so that ESC[?5hESC[?5l will + * always be an actually _visible_ visual bell. + */ + if (term->rvideo && !state) { + /* This is an OFF, so set up a vbell */ + term_schedule_vbell(term, TRUE, term->rvbell_startpoint); + } else if (!term->rvideo && state) { + /* This is an ON, so we notice the time and save it. */ + term->rvbell_startpoint = GETTICKCOUNT(); + } + term->rvideo = state; + seen_disp_event(term); + break; + case 6: /* DECOM: DEC origin mode */ + term->dec_om = state; + break; + case 7: /* DECAWM: auto wrap */ + term->wrap = state; + break; + case 8: /* DECARM: auto key repeat */ + term->repeat_off = !state; + break; + case 10: /* DECEDM: set local edit mode */ + term->term_editing = state; + if (term->ldisc) /* cause ldisc to notice changes */ + ldisc_send(term->ldisc, NULL, 0, 0); + break; + case 25: /* DECTCEM: enable/disable cursor */ + compatibility2(OTHER, VT220); + term->cursor_on = state; + seen_disp_event(term); + break; + case 47: /* alternate screen */ + compatibility(OTHER); + deselect(term); + swap_screen(term, term->cfg.no_alt_screen ? 0 : state, FALSE, FALSE); + term->disptop = 0; + break; + case 1000: /* xterm mouse 1 (normal) */ + term->xterm_mouse = state ? 1 : 0; + set_raw_mouse_mode(term->frontend, state); + break; + case 1002: /* xterm mouse 2 (inc. button drags) */ + term->xterm_mouse = state ? 2 : 0; + set_raw_mouse_mode(term->frontend, state); + break; + case 1047: /* alternate screen */ + compatibility(OTHER); + deselect(term); + swap_screen(term, term->cfg.no_alt_screen ? 0 : state, TRUE, TRUE); + term->disptop = 0; + break; + case 1048: /* save/restore cursor */ + if (!term->cfg.no_alt_screen) + save_cursor(term, state); + if (!state) seen_disp_event(term); + break; + case 1049: /* cursor & alternate screen */ + if (state && !term->cfg.no_alt_screen) + save_cursor(term, state); + if (!state) seen_disp_event(term); + compatibility(OTHER); + deselect(term); + swap_screen(term, term->cfg.no_alt_screen ? 0 : state, TRUE, FALSE); + if (!state && !term->cfg.no_alt_screen) + save_cursor(term, state); + term->disptop = 0; + break; + } else + switch (mode) { + case 4: /* IRM: set insert mode */ + compatibility(VT102); + term->insert = state; + break; + case 12: /* SRM: set echo mode */ + term->term_echoing = !state; + if (term->ldisc) /* cause ldisc to notice changes */ + ldisc_send(term->ldisc, NULL, 0, 0); + break; + case 20: /* LNM: Return sends ... */ + term->cr_lf_return = state; + break; + case 34: /* WYULCURM: Make cursor BIG */ + compatibility2(OTHER, VT220); + term->big_cursor = !state; + } +} + +/* + * Process an OSC sequence: set window title or icon name. + */ +static void do_osc(Terminal *term) +{ + if (term->osc_w) { + while (term->osc_strlen--) + term->wordness[(unsigned char) + term->osc_string[term->osc_strlen]] = term->esc_args[0]; + } else { + term->osc_string[term->osc_strlen] = '\0'; + switch (term->esc_args[0]) { + case 0: + case 1: + if (!term->cfg.no_remote_wintitle) + set_icon(term->frontend, term->osc_string); + if (term->esc_args[0] == 1) + break; + /* fall through: parameter 0 means set both */ + case 2: + case 21: + if (!term->cfg.no_remote_wintitle) + set_title(term->frontend, term->osc_string); + break; + } + } +} + +/* + * ANSI printing routines. + */ +static void term_print_setup(Terminal *term) +{ + bufchain_clear(&term->printer_buf); + term->print_job = printer_start_job(term->cfg.printer); +} +static void term_print_flush(Terminal *term) +{ + void *data; + int len; + int size; + while ((size = bufchain_size(&term->printer_buf)) > 5) { + bufchain_prefix(&term->printer_buf, &data, &len); + if (len > size-5) + len = size-5; + printer_job_data(term->print_job, data, len); + bufchain_consume(&term->printer_buf, len); + } +} +static void term_print_finish(Terminal *term) +{ + void *data; + int len, size; + char c; + + if (!term->printing && !term->only_printing) + return; /* we need do nothing */ + + term_print_flush(term); + while ((size = bufchain_size(&term->printer_buf)) > 0) { + bufchain_prefix(&term->printer_buf, &data, &len); + c = *(char *)data; + if (c == '\033' || c == '\233') { + bufchain_consume(&term->printer_buf, size); + break; + } else { + printer_job_data(term->print_job, &c, 1); + bufchain_consume(&term->printer_buf, 1); + } + } + printer_finish_job(term->print_job); + term->print_job = NULL; + term->printing = term->only_printing = FALSE; +} + +/* + * Remove everything currently in `inbuf' and stick it up on the + * in-memory display. There's a big state machine in here to + * process escape sequences... + */ +static void term_out(Terminal *term) +{ + unsigned long c; + int unget; + unsigned char localbuf[256], *chars; + int nchars = 0; + + unget = -1; + + chars = NULL; /* placate compiler warnings */ + while (nchars > 0 || unget != -1 || bufchain_size(&term->inbuf) > 0) { + if (unget == -1) { + if (nchars == 0) { + void *ret; + bufchain_prefix(&term->inbuf, &ret, &nchars); + if (nchars > sizeof(localbuf)) + nchars = sizeof(localbuf); + memcpy(localbuf, ret, nchars); + bufchain_consume(&term->inbuf, nchars); + chars = localbuf; + assert(chars != NULL); + } + c = *chars++; + nchars--; + + /* + * Optionally log the session traffic to a file. Useful for + * debugging and possibly also useful for actual logging. + */ + if (term->cfg.logtype == LGTYP_DEBUG && term->logctx) + logtraffic(term->logctx, (unsigned char) c, LGTYP_DEBUG); + } else { + c = unget; + unget = -1; + } + + /* Note only VT220+ are 8-bit VT102 is seven bit, it shouldn't even + * be able to display 8-bit characters, but I'll let that go 'cause + * of i18n. + */ + + /* + * If we're printing, add the character to the printer + * buffer. + */ + if (term->printing) { + bufchain_add(&term->printer_buf, &c, 1); + + /* + * If we're in print-only mode, we use a much simpler + * state machine designed only to recognise the ESC[4i + * termination sequence. + */ + if (term->only_printing) { + if (c == '\033') + term->print_state = 1; + else if (c == (unsigned char)'\233') + term->print_state = 2; + else if (c == '[' && term->print_state == 1) + term->print_state = 2; + else if (c == '4' && term->print_state == 2) + term->print_state = 3; + else if (c == 'i' && term->print_state == 3) + term->print_state = 4; + else + term->print_state = 0; + if (term->print_state == 4) { + term_print_finish(term); + } + continue; + } + } + + /* First see about all those translations. */ + if (term->termstate == TOPLEVEL) { + if (in_utf(term)) + switch (term->utf_state) { + case 0: + if (c < 0x80) { + /* UTF-8 must be stateless so we ignore iso2022. */ + if (term->ucsdata->unitab_ctrl[c] != 0xFF) + c = term->ucsdata->unitab_ctrl[c]; + else c = ((unsigned char)c) | CSET_ASCII; + break; + } else if ((c & 0xe0) == 0xc0) { + term->utf_size = term->utf_state = 1; + term->utf_char = (c & 0x1f); + } else if ((c & 0xf0) == 0xe0) { + term->utf_size = term->utf_state = 2; + term->utf_char = (c & 0x0f); + } else if ((c & 0xf8) == 0xf0) { + term->utf_size = term->utf_state = 3; + term->utf_char = (c & 0x07); + } else if ((c & 0xfc) == 0xf8) { + term->utf_size = term->utf_state = 4; + term->utf_char = (c & 0x03); + } else if ((c & 0xfe) == 0xfc) { + term->utf_size = term->utf_state = 5; + term->utf_char = (c & 0x01); + } else { + c = UCSERR; + break; + } + continue; + case 1: + case 2: + case 3: + case 4: + case 5: + if ((c & 0xC0) != 0x80) { + unget = c; + c = UCSERR; + term->utf_state = 0; + break; + } + term->utf_char = (term->utf_char << 6) | (c & 0x3f); + if (--term->utf_state) + continue; + + c = term->utf_char; + + /* Is somebody trying to be evil! */ + if (c < 0x80 || + (c < 0x800 && term->utf_size >= 2) || + (c < 0x10000 && term->utf_size >= 3) || + (c < 0x200000 && term->utf_size >= 4) || + (c < 0x4000000 && term->utf_size >= 5)) + c = UCSERR; + + /* Unicode line separator and paragraph separator are CR-LF */ + if (c == 0x2028 || c == 0x2029) + c = 0x85; + + /* High controls are probably a Baaad idea too. */ + if (c < 0xA0) + c = 0xFFFD; + + /* The UTF-16 surrogates are not nice either. */ + /* The standard give the option of decoding these: + * I don't want to! */ + if (c >= 0xD800 && c < 0xE000) + c = UCSERR; + + /* ISO 10646 characters now limited to UTF-16 range. */ + if (c > 0x10FFFF) + c = UCSERR; + + /* This is currently a TagPhobic application.. */ + if (c >= 0xE0000 && c <= 0xE007F) + continue; + + /* U+FEFF is best seen as a null. */ + if (c == 0xFEFF) + continue; + /* But U+FFFE is an error. */ + if (c == 0xFFFE || c == 0xFFFF) + c = UCSERR; + + break; + } + /* Are we in the nasty ACS mode? Note: no sco in utf mode. */ + else if(term->sco_acs && + (c!='\033' && c!='\012' && c!='\015' && c!='\b')) + { + if (term->sco_acs == 2) c |= 0x80; + c |= CSET_SCOACS; + } else { + switch (term->cset_attr[term->cset]) { + /* + * Linedraw characters are different from 'ESC ( B' + * only for a small range. For ones outside that + * range, make sure we use the same font as well as + * the same encoding. + */ + case CSET_LINEDRW: + if (term->ucsdata->unitab_ctrl[c] != 0xFF) + c = term->ucsdata->unitab_ctrl[c]; + else + c = ((unsigned char) c) | CSET_LINEDRW; + break; + + case CSET_GBCHR: + /* If UK-ASCII, make the '#' a LineDraw Pound */ + if (c == '#') { + c = '}' | CSET_LINEDRW; + break; + } + /*FALLTHROUGH*/ case CSET_ASCII: + if (term->ucsdata->unitab_ctrl[c] != 0xFF) + c = term->ucsdata->unitab_ctrl[c]; + else + c = ((unsigned char) c) | CSET_ASCII; + break; + case CSET_SCOACS: + if (c>=' ') c = ((unsigned char)c) | CSET_SCOACS; + break; + } + } + } + + /* + * How about C1 controls? + * Explicitly ignore SCI (0x9a), which we don't translate to DECID. + */ + if ((c & -32) == 0x80 && term->termstate < DO_CTRLS && + !term->vt52_mode && has_compat(VT220)) { + if (c == 0x9a) + c = 0; + else { + term->termstate = SEEN_ESC; + term->esc_query = FALSE; + c = '@' + (c & 0x1F); + } + } + + /* Or the GL control. */ + if (c == '\177' && term->termstate < DO_CTRLS && has_compat(OTHER)) { + if (term->curs.x && !term->wrapnext) + term->curs.x--; + term->wrapnext = FALSE; + /* destructive backspace might be disabled */ + if (!term->cfg.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) { + char abuf[lenof(term->cfg.answerback)], *s, *d; + for (s = term->cfg.answerback, d = abuf; *s;) { + char *n; + char c = ctrlparse(s, &n); + if (n) { + *d++ = c; + s = n; + } else { + *d++ = *s++; + } + } + lpage_send(term->ldisc, DEFAULT_CODEPAGE, + abuf, d - abuf, 0); + } + break; + case '\007': /* BEL: Bell */ + { + struct beeptime *newbeep; + unsigned long ticks; + + ticks = GETTICKCOUNT(); + + if (!term->beep_overloaded) { + newbeep = snew(struct beeptime); + newbeep->ticks = ticks; + newbeep->next = NULL; + if (!term->beephead) + term->beephead = newbeep; + else + term->beeptail->next = newbeep; + term->beeptail = newbeep; + term->nbeeps++; + } + + /* + * Throw out any beeps that happened more than + * t seconds ago. + */ + while (term->beephead && + term->beephead->ticks < ticks - term->cfg.bellovl_t) { + struct beeptime *tmp = term->beephead; + term->beephead = tmp->next; + sfree(tmp); + if (!term->beephead) + term->beeptail = NULL; + term->nbeeps--; + } + + if (term->cfg.bellovl && term->beep_overloaded && + ticks - term->lastbeep >= (unsigned)term->cfg.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->cfg.bellovl && !term->beep_overloaded && + term->nbeeps >= term->cfg.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->cfg.bellovl || !term->beep_overloaded) { + do_beep(term->frontend, term->cfg.beep); + + if (term->cfg.beep == BELL_VISUAL) { + term_schedule_vbell(term, FALSE, 0); + } + } + seen_disp_event(term); + } + break; + case '\b': /* BS: Back space */ + if (term->curs.x == 0 && + (term->curs.y == 0 || term->wrap == 0)) + /* do nothing */ ; + else if (term->curs.x == 0 && term->curs.y > 0) + term->curs.x = term->cols - 1, term->curs.y--; + else if (term->wrapnext) + term->wrapnext = FALSE; + else + term->curs.x--; + seen_disp_event(term); + break; + case '\016': /* LS1: Locking-shift one */ + compatibility(VT100); + term->cset = 1; + break; + case '\017': /* LS0: Locking-shift zero */ + compatibility(VT100); + term->cset = 0; + break; + case '\033': /* ESC: Escape */ + if (term->vt52_mode) + term->termstate = VT52_ESC; + else { + compatibility(ANSIMIN); + term->termstate = SEEN_ESC; + term->esc_query = FALSE; + } + break; + case '\015': /* CR: Carriage return */ + term->curs.x = 0; + term->wrapnext = FALSE; + seen_disp_event(term); + term->paste_hold = 0; + + if (term->cfg.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); + 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->cfg.lfhascr) + term->curs.x = 0; + term->wrapnext = FALSE; + seen_disp_event(term); + term->paste_hold = 0; + if (term->logctx) + logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); + break; + case '\t': /* HT: Character tabulation */ + { + pos old_curs = term->curs; + termline *ldata = scrlineptr(term->curs.y); + + do { + term->curs.x++; + } while (term->curs.x < term->cols - 1 && + !term->tabs[term->curs.x]); + + if ((ldata->lattr & LATTR_MODE) != LATTR_NORM) { + if (term->curs.x >= term->cols / 2) + term->curs.x = term->cols / 2 - 1; + } else { + if (term->curs.x >= term->cols) + term->curs.x = term->cols - 1; + } + + check_selection(term, old_curs, term->curs); + } + seen_disp_event(term); + break; + } + } else + switch (term->termstate) { + case TOPLEVEL: + /* Only graphic characters get this far; + * ctrls are stripped above */ + { + termline *cline = scrlineptr(term->curs.y); + int width = 0; + if (DIRECT_CHAR(c)) + width = 1; + if (!width) + width = (term->cfg.cjk_ambig_wide ? + mk_wcwidth_cjk((wchar_t) c) : + mk_wcwidth((wchar_t) c)); + + if (term->wrapnext && term->wrap && width > 0) { + cline->lattr |= LATTR_WRAPPED; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->curs.x = 0; + term->wrapnext = FALSE; + cline = scrlineptr(term->curs.y); + } + if (term->insert && width > 0) + insch(term, width); + if (term->selstate != NO_SELECTION) { + pos cursplus = term->curs; + incpos(cursplus); + check_selection(term, term->curs, cursplus); + } + if (((c & CSET_MASK) == CSET_ASCII || + (c & CSET_MASK) == 0) && + term->logctx) + logtraffic(term->logctx, (unsigned char) c, + LGTYP_ASCII); + + switch (width) { + case 2: + /* + * If we're about to display a double-width + * character starting in the rightmost + * column, then we do something special + * instead. We must print a space in the + * last column of the screen, then wrap; + * and we also set LATTR_WRAPPED2 which + * instructs subsequent cut-and-pasting not + * only to splice this line to the one + * after it, but to ignore the space in the + * last character position as well. + * (Because what was actually output to the + * terminal was presumably just a sequence + * of CJK characters, and we don't want a + * space to be pasted in the middle of + * those just because they had the + * misfortune to start in the wrong parity + * column. xterm concurs.) + */ + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+2, term->curs.y); + if (term->curs.x == term->cols-1) { + copy_termchar(cline, term->curs.x, + &term->erase_char); + cline->lattr |= LATTR_WRAPPED | LATTR_WRAPPED2; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, + 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->curs.x = 0; + cline = scrlineptr(term->curs.y); + /* Now we must check_boundary again, of course. */ + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+2, term->curs.y); + } + + /* FULL-TERMCHAR */ + clear_cc(cline, term->curs.x); + cline->chars[term->curs.x].chr = c; + cline->chars[term->curs.x].attr = term->curr_attr; + + term->curs.x++; + + /* FULL-TERMCHAR */ + clear_cc(cline, term->curs.x); + cline->chars[term->curs.x].chr = UCSWIDE; + cline->chars[term->curs.x].attr = term->curr_attr; + + break; + case 1: + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+1, term->curs.y); + + /* FULL-TERMCHAR */ + clear_cc(cline, term->curs.x); + cline->chars[term->curs.x].chr = c; + cline->chars[term->curs.x].attr = term->curr_attr; + + break; + case 0: + if (term->curs.x > 0) { + int x = term->curs.x - 1; + + /* If we're in wrapnext state, the character + * to combine with is _here_, not to our left. */ + if (term->wrapnext) + x++; + + /* + * If the previous character is + * UCSWIDE, back up another one. + */ + if (cline->chars[x].chr == UCSWIDE) { + assert(x > 0); + x--; + } + + add_cc(cline, x, c); + seen_disp_event(term); + } + continue; + default: + continue; + } + term->curs.x++; + if (term->curs.x == term->cols) { + term->curs.x--; + term->wrapnext = TRUE; + if (term->wrap && term->vt52_mode) { + cline->lattr |= LATTR_WRAPPED; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->curs.x = 0; + term->wrapnext = FALSE; + } + } + seen_disp_event(term); + } + break; + + case OSC_MAYBE_ST: + /* + * This state is virtually identical to SEEN_ESC, with the + * exception that we have an OSC sequence in the pipeline, + * and _if_ we see a backslash, we process it. + */ + if (c == '\\') { + do_osc(term); + term->termstate = TOPLEVEL; + break; + } + /* else fall through */ + case SEEN_ESC: + if (c >= ' ' && c <= '/') { + if (term->esc_query) + term->esc_query = -1; + else + term->esc_query = c; + break; + } + term->termstate = TOPLEVEL; + switch (ANSI(c, term->esc_query)) { + case '[': /* enter CSI mode */ + term->termstate = SEEN_CSI; + term->esc_nargs = 1; + term->esc_args[0] = ARG_DEFAULT; + term->esc_query = FALSE; + break; + case ']': /* OSC: xterm escape sequences */ + /* Compatibility is nasty here, xterm, linux, decterm yuk! */ + compatibility(OTHER); + term->termstate = SEEN_OSC; + term->esc_args[0] = 0; + break; + case '7': /* DECSC: save cursor */ + compatibility(VT100); + save_cursor(term, TRUE); + break; + case '8': /* DECRC: restore cursor */ + compatibility(VT100); + save_cursor(term, FALSE); + seen_disp_event(term); + break; + case '=': /* DECKPAM: Keypad application mode */ + compatibility(VT100); + term->app_keypad_keys = TRUE; + break; + case '>': /* DECKPNM: Keypad numeric mode */ + compatibility(VT100); + term->app_keypad_keys = FALSE; + break; + case 'D': /* IND: exactly equivalent to LF */ + compatibility(VT100); + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->wrapnext = FALSE; + seen_disp_event(term); + break; + case 'E': /* NEL: exactly equivalent to CR-LF */ + compatibility(VT100); + term->curs.x = 0; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, TRUE); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->wrapnext = FALSE; + seen_disp_event(term); + break; + case 'M': /* RI: reverse index - backwards LF */ + compatibility(VT100); + if (term->curs.y == term->marg_t) + scroll(term, term->marg_t, term->marg_b, -1, TRUE); + else if (term->curs.y > 0) + term->curs.y--; + term->wrapnext = FALSE; + seen_disp_event(term); + break; + case 'Z': /* DECID: terminal type query */ + compatibility(VT100); + if (term->ldisc) + ldisc_send(term->ldisc, term->id_string, + strlen(term->id_string), 0); + break; + case 'c': /* RIS: restore power-on settings */ + compatibility(VT100); + power_on(term, TRUE); + if (term->ldisc) /* cause ldisc to notice changes */ + ldisc_send(term->ldisc, NULL, 0, 0); + if (term->reset_132) { + if (!term->cfg.no_remote_resize) + request_resize(term->frontend, 80, term->rows); + term->reset_132 = 0; + } + 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); + for (j = 0; j < term->cols; j++) { + copy_termchar(ldata, j, + &term->basic_erase_char); + ldata->chars[j].chr = 'E'; + } + ldata->lattr = LATTR_NORM; + } + 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; + + 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; + } + scrlineptr(term->curs.y)->lattr = nlattr; + } + break; + /* GZD4: G0 designate 94-set */ + case ANSI('A', '('): + compatibility(VT100); + if (!term->cfg.no_remote_charset) + term->cset_attr[0] = CSET_GBCHR; + break; + case ANSI('B', '('): + compatibility(VT100); + if (!term->cfg.no_remote_charset) + term->cset_attr[0] = CSET_ASCII; + break; + case ANSI('0', '('): + compatibility(VT100); + if (!term->cfg.no_remote_charset) + term->cset_attr[0] = CSET_LINEDRW; + break; + case ANSI('U', '('): + compatibility(OTHER); + if (!term->cfg.no_remote_charset) + term->cset_attr[0] = CSET_SCOACS; + break; + /* G1D4: G1-designate 94-set */ + case ANSI('A', ')'): + compatibility(VT100); + if (!term->cfg.no_remote_charset) + term->cset_attr[1] = CSET_GBCHR; + break; + case ANSI('B', ')'): + compatibility(VT100); + if (!term->cfg.no_remote_charset) + term->cset_attr[1] = CSET_ASCII; + break; + case ANSI('0', ')'): + compatibility(VT100); + if (!term->cfg.no_remote_charset) + term->cset_attr[1] = CSET_LINEDRW; + break; + case ANSI('U', ')'): + compatibility(OTHER); + if (!term->cfg.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->cfg.no_remote_charset) + term->utf = 1; + break; + case ANSI('@', '%'): + compatibility(OTHER); + if (!term->cfg.no_remote_charset) + term->utf = 0; + break; + } + break; + case SEEN_CSI: + term->termstate = TOPLEVEL; /* default */ + if (isdigit(c)) { + if (term->esc_nargs <= ARGS_MAX) { + if (term->esc_args[term->esc_nargs - 1] == ARG_DEFAULT) + term->esc_args[term->esc_nargs - 1] = 0; + term->esc_args[term->esc_nargs - 1] = + 10 * term->esc_args[term->esc_nargs - 1] + c - '0'; + } + term->termstate = SEEN_CSI; + } else if (c == ';') { + if (term->esc_nargs < ARGS_MAX) + term->esc_args[term->esc_nargs++] = ARG_DEFAULT; + term->termstate = SEEN_CSI; + } else if (c < '@') { + if (term->esc_query) + term->esc_query = -1; + else if (c == '?') + term->esc_query = TRUE; + else + term->esc_query = c; + term->termstate = SEEN_CSI; + } else + switch (ANSI(c, term->esc_query)) { + case 'A': /* CUU: move up N lines */ + 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 */ + move(term, term->curs.x, + term->curs.y + def(term->esc_args[0], 1), 1); + seen_disp_event(term); + break; + case ANSI('c', '>'): /* DA: report xterm version */ + compatibility(OTHER); + /* this reports xterm version 136 so that VIM can + use the drag messages from the mouse reporting */ + if (term->ldisc) + ldisc_send(term->ldisc, "\033[>0;136;0c", 11, 0); + break; + case 'a': /* HPR: move right N cols */ + compatibility(ANSI); + /* FALLTHROUGH */ + case 'C': /* CUF: Cursor right */ + 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 */ + 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); + 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); + 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); + 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); + 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; + move(term, def(term->esc_args[1], 1) - 1, + ((term->dec_om ? term->marg_t : 0) + + def(term->esc_args[0], 1) - 1), + (term->dec_om ? 2 : 0)); + seen_disp_event(term); + break; + case 'J': /* ED: erase screen or parts of it */ + { + unsigned int i = def(term->esc_args[0], 0); + if (i == 3) { + /* Erase Saved Lines (xterm) + * This follows Thomas Dickey's xterm. */ + term_clrsb(term); + } else { + i++; + if (i > 3) + i = 0; + erase_lots(term, FALSE, !!(i & 2), !!(i & 1)); + } + } + 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); + 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); + 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); + insch(term, def(term->esc_args[0], 1)); + seen_disp_event(term); + break; + case 'P': /* DCH: delete chars */ + compatibility(VT102); + insch(term, -def(term->esc_args[0], 1)); + seen_disp_event(term); + break; + case 'c': /* DA: terminal type query */ + compatibility(VT100); + /* This is the response for a VT102 */ + if (term->ldisc) + ldisc_send(term->ldisc, term->id_string, + strlen(term->id_string), 0); + break; + case 'n': /* DSR: cursor position query */ + if (term->ldisc) { + if (term->esc_args[0] == 6) { + char buf[32]; + sprintf(buf, "\033[%d;%dR", term->curs.y + 1, + term->curs.x + 1); + ldisc_send(term->ldisc, buf, strlen(buf), 0); + } else if (term->esc_args[0] == 5) { + ldisc_send(term->ldisc, "\033[0n", 4, 0); + } + } + break; + case 'h': /* SM: toggle modes to high */ + case ANSI_QUE('h'): + compatibility(VT100); + { + int i; + for (i = 0; i < term->esc_nargs; i++) + toggle_mode(term, term->esc_args[i], + term->esc_query, TRUE); + } + break; + case 'i': /* MC: Media copy */ + case ANSI_QUE('i'): + compatibility(VT100); + { + if (term->esc_nargs != 1) break; + if (term->esc_args[0] == 5 && *term->cfg.printer) { + term->printing = TRUE; + term->only_printing = !term->esc_query; + term->print_state = 0; + term_print_setup(term); + } else if (term->esc_args[0] == 4 && + term->printing) { + term_print_finish(term); + } + } + break; + case 'l': /* RM: toggle modes to low */ + case ANSI_QUE('l'): + compatibility(VT100); + { + int i; + for (i = 0; i < term->esc_nargs; i++) + toggle_mode(term, term->esc_args[i], + term->esc_query, FALSE); + } + break; + case 'g': /* TBC: clear tabs */ + compatibility(VT100); + if (term->esc_nargs == 1) { + if (term->esc_args[0] == 0) { + term->tabs[term->curs.x] = FALSE; + } else if (term->esc_args[0] == 3) { + int i; + for (i = 0; i < term->cols; i++) + term->tabs[i] = FALSE; + } + } + break; + case 'r': /* DECSTBM: set scroll margins */ + compatibility(VT100); + if (term->esc_nargs <= 2) { + int top, bot; + top = def(term->esc_args[0], 1) - 1; + bot = (term->esc_nargs <= 1 + || term->esc_args[1] == 0 ? + term->rows : + def(term->esc_args[1], term->rows)) - 1; + if (bot >= term->rows) + bot = term->rows - 1; + /* VTTEST Bug 9 - if region is less than 2 lines + * don't change region. + */ + if (bot - top > 0) { + term->marg_t = top; + term->marg_b = bot; + term->curs.x = 0; + /* + * I used to think the cursor should be + * placed at the top of the newly marginned + * area. Apparently not: VMS TPU falls over + * if so. + * + * Well actually it should for + * Origin mode - RDB + */ + term->curs.y = (term->dec_om ? + term->marg_t : 0); + seen_disp_event(term); + } + } + break; + case 'm': /* SGR: set graphics rendition */ + { + /* + * A VT100 without the AVO only had one + * attribute, either underline or + * reverse video depending on the + * cursor type, this was selected by + * CSI 7m. + * + * case 2: + * This is sometimes DIM, eg on the + * GIGI and Linux + * case 8: + * This is sometimes INVIS various ANSI. + * case 21: + * This like 22 disables BOLD, DIM and INVIS + * + * The ANSI colours appear on any + * terminal that has colour (obviously) + * but the interaction between sgr0 and + * the colours varies but is usually + * related to the background colour + * erase item. The interaction between + * colour attributes and the mono ones + * is also very implementation + * dependent. + * + * The 39 and 49 attributes are likely + * to be unimplemented. + */ + int i; + for (i = 0; i < term->esc_nargs; i++) { + switch (def(term->esc_args[i], 0)) { + case 0: /* restore defaults */ + term->curr_attr = term->default_attr; + break; + case 1: /* enable bold */ + compatibility(VT100AVO); + term->curr_attr |= ATTR_BOLD; + break; + case 21: /* (enable double underline) */ + compatibility(OTHER); + case 4: /* enable underline */ + compatibility(VT100AVO); + term->curr_attr |= ATTR_UNDER; + break; + case 5: /* enable blink */ + compatibility(VT100AVO); + term->curr_attr |= ATTR_BLINK; + break; + case 6: /* SCO light bkgrd */ + compatibility(SCOANSI); + term->blink_is_real = FALSE; + term->curr_attr |= ATTR_BLINK; + term_schedule_tblink(term); + break; + case 7: /* enable reverse video */ + term->curr_attr |= ATTR_REVERSE; + break; + case 10: /* SCO acs off */ + compatibility(SCOANSI); + if (term->cfg.no_remote_charset) break; + term->sco_acs = 0; break; + case 11: /* SCO acs on */ + compatibility(SCOANSI); + if (term->cfg.no_remote_charset) break; + term->sco_acs = 1; break; + case 12: /* SCO acs on, |0x80 */ + compatibility(SCOANSI); + if (term->cfg.no_remote_charset) break; + term->sco_acs = 2; break; + case 22: /* disable bold */ + compatibility2(OTHER, VT220); + term->curr_attr &= ~ATTR_BOLD; + break; + case 24: /* disable underline */ + compatibility2(OTHER, VT220); + term->curr_attr &= ~ATTR_UNDER; + break; + case 25: /* disable blink */ + compatibility2(OTHER, VT220); + term->curr_attr &= ~ATTR_BLINK; + break; + case 27: /* disable reverse video */ + compatibility2(OTHER, VT220); + term->curr_attr &= ~ATTR_REVERSE; + break; + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + /* foreground */ + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= + (term->esc_args[i] - 30)<curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= + ((term->esc_args[i] - 90 + 8) + << ATTR_FGSHIFT); + break; + case 39: /* default-foreground */ + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= ATTR_DEFFG; + break; + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: + /* background */ + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= + (term->esc_args[i] - 40)<curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= + ((term->esc_args[i] - 100 + 8) + << ATTR_BGSHIFT); + break; + case 49: /* default-background */ + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= ATTR_DEFBG; + break; + case 38: /* xterm 256-colour mode */ + if (i+2 < term->esc_nargs && + term->esc_args[i+1] == 5) { + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= + ((term->esc_args[i+2] & 0xFF) + << ATTR_FGSHIFT); + i += 2; + } + break; + case 48: /* xterm 256-colour mode */ + if (i+2 < term->esc_nargs && + term->esc_args[i+1] == 5) { + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= + ((term->esc_args[i+2] & 0xFF) + << ATTR_BGSHIFT); + i += 2; + } + break; + } + } + set_erase_char(term); + } + break; + case 's': /* save cursor */ + save_cursor(term, TRUE); + break; + case 'u': /* restore cursor */ + save_cursor(term, FALSE); + seen_disp_event(term); + break; + case 't': /* DECSLPP: set page size - ie window height */ + /* + * VT340/VT420 sequence DECSLPP, DEC only allows values + * 24/25/36/48/72/144 other emulators (eg dtterm) use + * illegal values (eg first arg 1..9) for window changing + * and reports. + */ + if (term->esc_nargs <= 1 + && (term->esc_args[0] < 1 || + term->esc_args[0] >= 24)) { + compatibility(VT340TEXT); + if (!term->cfg.no_remote_resize) + request_resize(term->frontend, term->cols, + def(term->esc_args[0], 24)); + deselect(term); + } else if (term->esc_nargs >= 1 && + term->esc_args[0] >= 1 && + term->esc_args[0] < 24) { + compatibility(OTHER); + + switch (term->esc_args[0]) { + int x, y, len; + char buf[80], *p; + case 1: + set_iconic(term->frontend, FALSE); + break; + case 2: + set_iconic(term->frontend, TRUE); + break; + case 3: + if (term->esc_nargs >= 3) { + if (!term->cfg.no_remote_resize) + move_window(term->frontend, + def(term->esc_args[1], 0), + def(term->esc_args[2], 0)); + } + break; + case 4: + /* We should resize the window to a given + * size in pixels here, but currently our + * resizing code isn't healthy enough to + * manage it. */ + break; + case 5: + /* move to top */ + set_zorder(term->frontend, TRUE); + break; + case 6: + /* move to bottom */ + set_zorder(term->frontend, FALSE); + break; + case 7: + refresh_window(term->frontend); + break; + case 8: + if (term->esc_nargs >= 3) { + if (!term->cfg.no_remote_resize) + request_resize(term->frontend, + def(term->esc_args[2], term->cfg.width), + def(term->esc_args[1], term->cfg.height)); + } + break; + case 9: + if (term->esc_nargs >= 2) + set_zoomed(term->frontend, + term->esc_args[1] ? + TRUE : FALSE); + break; + case 11: + if (term->ldisc) + ldisc_send(term->ldisc, + is_iconic(term->frontend) ? + "\033[2t" : "\033[1t", 4, 0); + break; + case 13: + if (term->ldisc) { + get_window_pos(term->frontend, &x, &y); + len = sprintf(buf, "\033[3;%d;%dt", x, y); + ldisc_send(term->ldisc, buf, len, 0); + } + break; + case 14: + if (term->ldisc) { + get_window_pixels(term->frontend, &x, &y); + len = sprintf(buf, "\033[4;%d;%dt", y, x); + ldisc_send(term->ldisc, buf, len, 0); + } + break; + case 18: + if (term->ldisc) { + len = sprintf(buf, "\033[8;%d;%dt", + term->rows, term->cols); + ldisc_send(term->ldisc, buf, len, 0); + } + break; + case 19: + /* + * Hmmm. Strictly speaking we + * should return `the size of the + * screen in characters', but + * that's not easy: (a) window + * furniture being what it is it's + * hard to compute, and (b) in + * resize-font mode maximising the + * window wouldn't change the + * number of characters. *shrug*. I + * think we'll ignore it for the + * moment and see if anyone + * complains, and then ask them + * what they would like it to do. + */ + break; + case 20: + if (term->ldisc && + term->cfg.remote_qtitle_action != TITLE_NONE) { + if(term->cfg.remote_qtitle_action == TITLE_REAL) + p = get_window_title(term->frontend, TRUE); + else + p = EMPTY_WINDOW_TITLE; + len = strlen(p); + ldisc_send(term->ldisc, "\033]L", 3, 0); + ldisc_send(term->ldisc, p, len, 0); + ldisc_send(term->ldisc, "\033\\", 2, 0); + } + break; + case 21: + if (term->ldisc && + term->cfg.remote_qtitle_action != TITLE_NONE) { + if(term->cfg.remote_qtitle_action == TITLE_REAL) + p = get_window_title(term->frontend, FALSE); + else + p = EMPTY_WINDOW_TITLE; + len = strlen(p); + ldisc_send(term->ldisc, "\033]l", 3, 0); + ldisc_send(term->ldisc, p, len, 0); + ldisc_send(term->ldisc, "\033\\", 2, 0); + } + break; + } + } + break; + case 'S': /* SU: Scroll up */ + 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 */ + 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->cfg.no_remote_resize) + request_resize(term->frontend, term->cols, + def(term->esc_args[0], + term->cfg.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->cfg.no_remote_resize) + request_resize(term->frontend, + def(term->esc_args[0], + term->cfg.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); + { + int n = def(term->esc_args[0], 1); + pos cursplus; + int p = term->curs.x; + termline *cline = scrlineptr(term->curs.y); + + if (n > term->cols - term->curs.x) + n = term->cols - term->curs.x; + cursplus = term->curs; + cursplus.x += n; + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+n, term->curs.y); + check_selection(term, term->curs, cursplus); + while (n--) + copy_termchar(cline, p++, + &term->erase_char); + seen_disp_event(term); + } + break; + case 'x': /* DECREQTPARM: report terminal characteristics */ + compatibility(VT100); + if (term->ldisc) { + char buf[32]; + int i = def(term->esc_args[0], 0); + if (i == 0 || i == 1) { + strcpy(buf, "\033[2;1;1;112;112;1;0x"); + buf[2] += i; + ldisc_send(term->ldisc, buf, 20, 0); + } + } + break; + case 'Z': /* CBT */ + compatibility(OTHER); + { + 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] >= 0 && term->esc_args[0] < 16) { + long colour = + (sco2ansicolour[term->esc_args[0] & 0x7] | + (term->esc_args[0] & 0x8)) << + ATTR_FGSHIFT; + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= colour; + term->default_attr &= ~ATTR_FGMASK; + term->default_attr |= colour; + set_erase_char(term); + } + break; + case ANSI('G', '='): /* set normal background */ + compatibility(SCOANSI); + if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) { + long colour = + (sco2ansicolour[term->esc_args[0] & 0x7] | + (term->esc_args[0] & 0x8)) << + ATTR_BGSHIFT; + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= colour; + term->default_attr &= ~ATTR_BGMASK; + term->default_attr |= colour; + set_erase_char(term); + } + break; + case ANSI('L', '='): + compatibility(SCOANSI); + term->use_bce = (term->esc_args[0] <= 0); + set_erase_char(term); + break; + case ANSI('p', '"'): /* DECSCL: set compat level */ + /* + * Allow the host to make this emulator a + * 'perfect' VT102. This first appeared in + * the VT220, but we do need to get back to + * PuTTY mode so I won't check it. + * + * The arg in 40..42,50 are a PuTTY extension. + * The 2nd arg, 8bit vs 7bit is not checked. + * + * Setting VT102 mode should also change + * the Fkeys to generate PF* codes as a + * real VT102 has no Fkeys. The VT220 does + * this, F11..F13 become ESC,BS,LF other + * Fkeys send nothing. + * + * Note ESC c will NOT change this! + */ + + switch (term->esc_args[0]) { + case 61: + term->compatibility_level &= ~TM_VTXXX; + term->compatibility_level |= TM_VT102; + break; + case 62: + term->compatibility_level &= ~TM_VTXXX; + term->compatibility_level |= TM_VT220; + break; + + default: + if (term->esc_args[0] > 60 && + term->esc_args[0] < 70) + term->compatibility_level |= TM_VTXXX; + break; + + case 40: + term->compatibility_level &= TM_VTXXX; + break; + case 41: + term->compatibility_level = TM_PUTTY; + break; + case 42: + term->compatibility_level = TM_SCOANSI; + break; + + case ARG_DEFAULT: + term->compatibility_level = TM_PUTTY; + break; + case 50: + break; + } + + /* Change the response to CSI c */ + if (term->esc_args[0] == 50) { + int i; + char lbuf[64]; + strcpy(term->id_string, "\033[?"); + for (i = 1; i < term->esc_nargs; i++) { + if (i != 1) + strcat(term->id_string, ";"); + sprintf(lbuf, "%d", term->esc_args[i]); + strcat(term->id_string, lbuf); + } + strcat(term->id_string, "c"); + } +#if 0 + /* Is this a good idea ? + * Well we should do a soft reset at this point ... + */ + if (!has_compat(VT420) && has_compat(VT100)) { + if (!term->cfg.no_remote_resize) { + if (term->reset_132) + request_resize(132, 24); + else + request_resize(80, 24); + } + } +#endif + break; + } + break; + case SEEN_OSC: + term->osc_w = FALSE; + switch (c) { + case 'P': /* Linux palette sequence */ + term->termstate = SEEN_OSC_P; + term->osc_strlen = 0; + break; + case 'R': /* Linux palette reset */ + palette_reset(term->frontend); + term_invalidate(term); + term->termstate = TOPLEVEL; + break; + case 'W': /* word-set */ + term->termstate = SEEN_OSC_W; + term->osc_w = TRUE; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + term->esc_args[0] = 10 * term->esc_args[0] + c - '0'; + break; + case 'L': + /* + * Grotty hack to support xterm and DECterm title + * sequences concurrently. + */ + if (term->esc_args[0] == 2) { + term->esc_args[0] = 1; + break; + } + /* else fall through */ + default: + term->termstate = OSC_STRING; + term->osc_strlen = 0; + } + break; + case OSC_STRING: + /* + * This OSC stuff is EVIL. It takes just one character to get into + * sysline mode and it's not initially obvious how to get out. + * So I've added CR and LF as string aborts. + * This shouldn't effect compatibility as I believe embedded + * control characters are supposed to be interpreted (maybe?) + * and they don't display anything useful anyway. + * + * -- RDB + */ + if (c == '\012' || c == '\015') { + term->termstate = TOPLEVEL; + } else if (c == 0234 || c == '\007') { + /* + * These characters terminate the string; ST and BEL + * terminate the sequence and trigger instant + * processing of it, whereas ESC goes back to SEEN_ESC + * mode unless it is followed by \, in which case it is + * synonymous with ST in the first place. + */ + do_osc(term); + term->termstate = TOPLEVEL; + } else if (c == '\033') + term->termstate = OSC_MAYBE_ST; + else if (term->osc_strlen < OSC_STR_MAX) + term->osc_string[term->osc_strlen++] = (char)c; + break; + case SEEN_OSC_P: + { + int max = (term->osc_strlen == 0 ? 21 : 15); + int val; + if ((int)c >= '0' && (int)c <= '9') + val = c - '0'; + else if ((int)c >= 'A' && (int)c <= 'A' + max - 10) + val = c - 'A' + 10; + else if ((int)c >= 'a' && (int)c <= 'a' + max - 10) + val = c - 'a' + 10; + else { + term->termstate = TOPLEVEL; + break; + } + term->osc_string[term->osc_strlen++] = val; + if (term->osc_strlen >= 7) { + palette_set(term->frontend, term->osc_string[0], + term->osc_string[1] * 16 + term->osc_string[2], + term->osc_string[3] * 16 + term->osc_string[4], + term->osc_string[5] * 16 + term->osc_string[6]); + term_invalidate(term); + term->termstate = TOPLEVEL; + } + } + break; + case SEEN_OSC_W: + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + term->esc_args[0] = 10 * term->esc_args[0] + c - '0'; + 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); + term->disptop = 0; + break; + case 'K': + erase_lots(term, TRUE, FALSE, TRUE); + break; +#if 0 + case 'V': + /* XXX Print cursor line */ + break; + case 'W': + /* XXX Start controller mode */ + break; + case 'X': + /* XXX Stop controller mode */ + break; +#endif + case 'Y': + term->termstate = VT52_Y1; + break; + case 'Z': + if (term->ldisc) + ldisc_send(term->ldisc, "\033/Z", 3, 0); + break; + case '=': + term->app_keypad_keys = TRUE; + break; + case '>': + term->app_keypad_keys = FALSE; + break; + case '<': + /* XXX This should switch to VT100 mode not current or default + * VT mode. But this will only have effect in a VT220+ + * emulation. + */ + term->vt52_mode = FALSE; + term->blink_is_real = term->cfg.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); + 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); + term->disptop = 0; + break; + case 'e': + /* compatibility(ATARI) */ + term->cursor_on = TRUE; + break; + case 'f': + /* compatibility(ATARI) */ + term->cursor_on = FALSE; + break; + /* case 'j': Save cursor position - broken on ST */ + /* case 'k': Restore cursor position */ + case 'l': + /* compatibility(ATARI) */ + erase_lots(term, TRUE, TRUE, TRUE); + term->curs.x = 0; + term->wrapnext = FALSE; + break; + case 'o': + /* compatibility(ATARI) */ + erase_lots(term, TRUE, TRUE, FALSE); + break; + case 'p': + /* compatibility(ATARI) */ + term->curr_attr |= ATTR_REVERSE; + break; + case 'q': + /* compatibility(ATARI) */ + term->curr_attr &= ~ATTR_REVERSE; + break; + case 'v': /* wrap Autowrap on - Wyse style */ + /* compatibility(ATARI) */ + term->wrap = 1; + break; + case 'w': /* Autowrap off */ + /* compatibility(ATARI) */ + term->wrap = 0; + break; + + case 'R': + /* compatibility(OTHER) */ + term->vt52_bold = FALSE; + term->curr_attr = ATTR_DEFAULT; + set_erase_char(term); + break; + case 'S': + /* compatibility(VI50) */ + term->curr_attr |= ATTR_UNDER; + break; + case 'W': + /* compatibility(VI50) */ + term->curr_attr &= ~ATTR_UNDER; + break; + case 'U': + /* compatibility(VI50) */ + term->vt52_bold = TRUE; + term->curr_attr |= ATTR_BOLD; + break; + case 'T': + /* compatibility(VI50) */ + term->vt52_bold = FALSE; + term->curr_attr &= ~ATTR_BOLD; + break; +#endif + } + break; + case VT52_Y1: + term->termstate = VT52_Y2; + move(term, term->curs.x, c - ' ', 0); + break; + case VT52_Y2: + term->termstate = TOPLEVEL; + move(term, c - ' ', term->curs.y, 0); + break; + +#ifdef VT52_PLUS + case VT52_FG: + term->termstate = TOPLEVEL; + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr &= ~ATTR_BOLD; + term->curr_attr |= (c & 0xF) << ATTR_FGSHIFT; + set_erase_char(term); + break; + case VT52_BG: + term->termstate = TOPLEVEL; + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr &= ~ATTR_BLINK; + term->curr_attr |= (c & 0xF) << ATTR_BGSHIFT; + set_erase_char(term); + break; +#endif + default: break; /* placate gcc warning about enum use */ + } + if (term->selstate != NO_SELECTION) { + pos cursplus = term->curs; + incpos(cursplus); + check_selection(term, term->curs, cursplus); + } + } + + term_print_flush(term); + if (term->cfg.logflush) + logflush(term->logctx); +} + +/* + * To prevent having to run the reasonably tricky bidi algorithm + * too many times, we maintain a cache of the last lineful of data + * fed to the algorithm on each line of the display. + */ +static int term_bidi_cache_hit(Terminal *term, int line, + termchar *lbefore, int width) +{ + int i; + + if (!term->pre_bidi_cache) + return FALSE; /* cache doesn't even exist yet! */ + + if (line >= term->bidi_cache_size) + return FALSE; /* cache doesn't have this many lines */ + + if (!term->pre_bidi_cache[line].chars) + return FALSE; /* cache doesn't contain _this_ line */ + + if (term->pre_bidi_cache[line].width != width) + return FALSE; /* line is wrong width */ + + for (i = 0; i < width; i++) + if (!termchars_equal(term->pre_bidi_cache[line].chars+i, lbefore+i)) + return FALSE; /* line doesn't match cache */ + + return TRUE; /* it didn't match. */ +} + +static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore, + termchar *lafter, bidi_char *wcTo, + int width, int size) +{ + int i; + + if (!term->pre_bidi_cache || term->bidi_cache_size <= line) { + int j = term->bidi_cache_size; + term->bidi_cache_size = line+1; + term->pre_bidi_cache = sresize(term->pre_bidi_cache, + term->bidi_cache_size, + struct bidi_cache_entry); + term->post_bidi_cache = sresize(term->post_bidi_cache, + term->bidi_cache_size, + struct bidi_cache_entry); + while (j < term->bidi_cache_size) { + term->pre_bidi_cache[j].chars = + term->post_bidi_cache[j].chars = NULL; + term->pre_bidi_cache[j].width = + term->post_bidi_cache[j].width = -1; + term->pre_bidi_cache[j].forward = + term->post_bidi_cache[j].forward = NULL; + term->pre_bidi_cache[j].backward = + term->post_bidi_cache[j].backward = NULL; + j++; + } + } + + sfree(term->pre_bidi_cache[line].chars); + sfree(term->post_bidi_cache[line].chars); + sfree(term->post_bidi_cache[line].forward); + sfree(term->post_bidi_cache[line].backward); + + term->pre_bidi_cache[line].width = width; + term->pre_bidi_cache[line].chars = snewn(size, termchar); + term->post_bidi_cache[line].width = width; + term->post_bidi_cache[line].chars = snewn(size, termchar); + term->post_bidi_cache[line].forward = snewn(width, int); + term->post_bidi_cache[line].backward = snewn(width, int); + + memcpy(term->pre_bidi_cache[line].chars, lbefore, size * TSIZE); + memcpy(term->post_bidi_cache[line].chars, lafter, size * TSIZE); + memset(term->post_bidi_cache[line].forward, 0, width * sizeof(int)); + memset(term->post_bidi_cache[line].backward, 0, width * sizeof(int)); + + for (i = 0; i < width; i++) { + int p = wcTo[i].index; + + assert(0 <= p && p < width); + + term->post_bidi_cache[line].backward[i] = p; + term->post_bidi_cache[line].forward[p] = i; + } +} + +/* + * Prepare the bidi information for a screen line. Returns the + * transformed list of termchars, or NULL if no transformation at + * all took place (because bidi is disabled). If return was + * non-NULL, auxiliary information such as the forward and reverse + * mappings of permutation position are available in + * term->post_bidi_cache[scr_y].*. + */ +static termchar *term_bidi_line(Terminal *term, struct termline *ldata, + int scr_y) +{ + termchar *lchars; + int it; + + /* Do Arabic shaping and bidi. */ + if(!term->cfg.bidi || !term->cfg.arabicshaping) { + + if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols)) { + + if (term->wcFromTo_size < term->cols) { + term->wcFromTo_size = term->cols; + term->wcFrom = sresize(term->wcFrom, term->wcFromTo_size, + bidi_char); + term->wcTo = sresize(term->wcTo, term->wcFromTo_size, + bidi_char); + } + + for(it=0; itcols ; it++) + { + unsigned long uc = (ldata->chars[it].chr); + + switch (uc & CSET_MASK) { + case CSET_LINEDRW: + if (!term->cfg.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 = + (wchar_t)uc; + term->wcFrom[it].index = it; + } + + if(!term->cfg.bidi) + do_bidi(term->wcFrom, term->cols); + + /* this is saved iff done from inside the shaping */ + if(!term->cfg.bidi && term->cfg.arabicshaping) + for(it=0; itcols; it++) + term->wcTo[it] = term->wcFrom[it]; + + if(!term->cfg.arabicshaping) + do_shape(term->wcFrom, term->wcTo, term->cols); + + if (term->ltemp_size < ldata->size) { + term->ltemp_size = ldata->size; + term->ltemp = sresize(term->ltemp, term->ltemp_size, + termchar); + } + + memcpy(term->ltemp, ldata->chars, ldata->size * TSIZE); + + for(it=0; itcols ; it++) + { + term->ltemp[it] = ldata->chars[term->wcTo[it].index]; + if (term->ltemp[it].cc_next) + term->ltemp[it].cc_next -= + it - term->wcTo[it].index; + + if (term->wcTo[it].origwc != term->wcTo[it].wc) + term->ltemp[it].chr = term->wcTo[it].wc; + } + term_bidi_cache_store(term, scr_y, ldata->chars, + term->ltemp, term->wcTo, + term->cols, ldata->size); + + lchars = term->ltemp; + } else { + lchars = term->post_bidi_cache[scr_y].chars; + } + } else { + lchars = NULL; + } + + return lchars; +} + +/* + * Given a context, update the window. Out of paranoia, we don't + * allow WM_PAINT responses to do scrolling optimisations. + */ +static void do_paint(Terminal *term, Context ctx, int may_optimise) +{ + int i, j, our_curs_y, our_curs_x; + int rv, cursor; + pos scrpos; + wchar_t *ch; + int chlen; +#ifdef OPTIMISE_SCROLL + struct scrollregion *sr; +#endif /* OPTIMISE_SCROLL */ + termchar *newline; + + chlen = 1024; + ch = snewn(chlen, wchar_t); + + newline = snewn(term->cols, termchar); + + rv = (!term->rvideo ^ !term->in_vbell ? ATTR_REVERSE : 0); + + /* Depends on: + * screen array, disptop, scrtop, + * selection, rv, + * cfg.blinkpc, blink_is_real, tblinker, + * curs.y, curs.x, cblinker, cfg.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->cfg.blink_cur) + cursor = TATTR_ACTCURS; + else + cursor = 0; + } else + cursor = TATTR_PASCURS; + if (term->wrapnext) + cursor |= TATTR_RIGHTCURS; + } else + cursor = 0; + our_curs_y = term->curs.y - term->disptop; + { + /* + * Adjust the cursor position: + * - for bidi + * - in the case where it's resting on the right-hand half + * of a CJK wide character. xterm's behaviour here, + * which seems adequate to me, is to display the cursor + * covering the _whole_ character, exactly as if it were + * one space to the left. + */ + termline *ldata = lineptr(term->curs.y); + termchar *lchars; + + our_curs_x = term->curs.x; + + if ( (lchars = term_bidi_line(term, ldata, our_curs_y)) != NULL) { + our_curs_x = term->post_bidi_cache[our_curs_y].forward[our_curs_x]; + } else + lchars = ldata->chars; + + if (our_curs_x > 0 && + lchars[our_curs_x].chr == UCSWIDE) + our_curs_x--; + + unlineptr(ldata); + } + + /* + * If the cursor is not where it was last time we painted, and + * its previous position is visible on screen, invalidate its + * previous position. + */ + if (term->dispcursy >= 0 && + (term->curstype != cursor || + term->dispcursy != our_curs_y || + term->dispcursx != our_curs_x)) { + termchar *dispcurs = term->disptext[term->dispcursy]->chars + + term->dispcursx; + + if (term->dispcursx > 0 && dispcurs->chr == UCSWIDE) + dispcurs[-1].attr |= ATTR_INVALID; + if (term->dispcursx < term->cols-1 && dispcurs[1].chr == UCSWIDE) + dispcurs[1].attr |= ATTR_INVALID; + dispcurs->attr |= ATTR_INVALID; + + term->curstype = 0; + } + term->dispcursx = term->dispcursy = -1; + +#ifdef OPTIMISE_SCROLL + /* Do scrolls */ + sr = term->scrollhead; + while (sr) { + struct scrollregion *next = sr->next; + do_scroll(ctx, sr->topline, sr->botline, sr->lines); + sfree(sr); + sr = next; + } + term->scrollhead = term->scrolltail = NULL; +#endif /* OPTIMISE_SCROLL */ + + /* The normal screen data */ + for (i = 0; i < term->rows; i++) { + termline *ldata; + termchar *lchars; + int dirty_line, dirty_run, selected; + unsigned long attr = 0, cset = 0; + int start = 0; + int ccount = 0; + int last_run_dirty = 0; + int laststart, dirtyrect; + int *backward; + + scrpos.y = i + term->disptop; + ldata = lineptr(scrpos.y); + + /* Do Arabic shaping and bidi. */ + lchars = term_bidi_line(term, ldata, i); + if (lchars) { + backward = term->post_bidi_cache[i].backward; + } else { + lchars = ldata->chars; + backward = NULL; + } + + /* + * First loop: work along the line deciding what we want + * each character cell to look like. + */ + for (j = 0; j < term->cols; j++) { + unsigned long tattr, tchar; + termchar *d = lchars + j; + scrpos.x = backward ? backward[j] : j; + + tchar = d->chr; + tattr = d->attr; + + if (!term->cfg.ansi_colour) + tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) | + ATTR_DEFFG | ATTR_DEFBG; + + if (!term->cfg.xterm_256_colour) { + int colour; + colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT; + if (colour >= 16 && colour < 256) + tattr = (tattr &~ ATTR_FGMASK) | ATTR_DEFFG; + colour = (tattr & ATTR_BGMASK) >> ATTR_BGSHIFT; + if (colour >= 16 && colour < 256) + tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG; + } + + switch (tchar & CSET_MASK) { + case CSET_ASCII: + tchar = term->ucsdata->unitab_line[tchar & 0xFF]; + break; + case CSET_LINEDRW: + tchar = term->ucsdata->unitab_xterm[tchar & 0xFF]; + break; + case CSET_SCOACS: + tchar = term->ucsdata->unitab_scoacs[tchar&0xFF]; + break; + } + if (j < term->cols-1 && d[1].chr == UCSWIDE) + tattr |= ATTR_WIDE; + + /* Video reversing things */ + if (term->selstate == DRAGGING || term->selstate == SELECTED) { + if (term->seltype == LEXICOGRAPHIC) + selected = (posle(term->selstart, scrpos) && + poslt(scrpos, term->selend)); + else + selected = (posPle(term->selstart, scrpos) && + posPlt(scrpos, term->selend)); + } else + selected = FALSE; + tattr = (tattr ^ rv + ^ (selected ? ATTR_REVERSE : 0)); + + /* 'Real' blinking ? */ + if (term->blink_is_real && (tattr & ATTR_BLINK)) { + if (term->has_focus && term->tblinker) { + tchar = term->ucsdata->unitab_line[(unsigned char)' ']; + } + tattr &= ~ATTR_BLINK; + } + + /* + * Check the font we'll _probably_ be using to see if + * the character is wide when we don't want it to be. + */ + if (tchar != term->disptext[i]->chars[j].chr || + tattr != (term->disptext[i]->chars[j].attr &~ + (ATTR_NARROW | DATTR_MASK))) { + if ((tattr & ATTR_WIDE) == 0 && char_width(ctx, tchar) == 2) + tattr |= ATTR_NARROW; + } else if (term->disptext[i]->chars[j].attr & ATTR_NARROW) + tattr |= ATTR_NARROW; + + if (i == our_curs_y && j == our_curs_x) { + tattr |= cursor; + term->curstype = cursor; + term->dispcursx = j; + term->dispcursy = i; + } + + /* FULL-TERMCHAR */ + newline[j].attr = tattr; + newline[j].chr = tchar; + /* Combining characters are still read from lchars */ + newline[j].cc_next = 0; + } + + /* + * Now loop over the line again, noting where things have + * changed. + * + * During this loop, we keep track of where we last saw + * DATTR_STARTRUN. Any mismatch automatically invalidates + * _all_ of the containing run that was last printed: that + * is, any rectangle that was drawn in one go in the + * previous update should be either left completely alone + * or overwritten in its entirety. This, along with the + * expectation that front ends clip all text runs to their + * bounding rectangle, should solve any possible problems + * with fonts that overflow their character cells. + */ + laststart = 0; + dirtyrect = FALSE; + for (j = 0; j < term->cols; j++) { + if (term->disptext[i]->chars[j].attr & DATTR_STARTRUN) { + laststart = j; + dirtyrect = FALSE; + } + + if (term->disptext[i]->chars[j].chr != newline[j].chr || + (term->disptext[i]->chars[j].attr &~ DATTR_MASK) + != newline[j].attr) { + int k; + + if (!dirtyrect) { + for (k = laststart; k < j; k++) + term->disptext[i]->chars[k].attr |= ATTR_INVALID; + + dirtyrect = TRUE; + } + } + + if (dirtyrect) + term->disptext[i]->chars[j].attr |= ATTR_INVALID; + } + + /* + * Finally, loop once more and actually do the drawing. + */ + dirty_run = dirty_line = (ldata->lattr != + term->disptext[i]->lattr); + term->disptext[i]->lattr = ldata->lattr; + + for (j = 0; j < term->cols; j++) { + unsigned long tattr, tchar; + int break_run, do_copy; + termchar *d = lchars + j; + + tattr = newline[j].attr; + tchar = newline[j].chr; + + if ((term->disptext[i]->chars[j].attr ^ tattr) & ATTR_WIDE) + dirty_line = TRUE; + + break_run = ((tattr ^ attr) & term->attr_mask) != 0; + + /* Special hack for VT100 Linedraw glyphs */ + if (tchar >= 0x23BA && tchar <= 0x23BD) + break_run = TRUE; + + /* + * Separate out sequences of characters that have the + * same CSET, if that CSET is a magic one. + */ + if (CSET_OF(tchar) != cset) + break_run = TRUE; + + /* + * Break on both sides of any combined-character cell. + */ + if (d->cc_next != 0 || + (j > 0 && d[-1].cc_next != 0)) + break_run = TRUE; + + if (!term->ucsdata->dbcs_screenfont && !dirty_line) { + if (term->disptext[i]->chars[j].chr == tchar && + (term->disptext[i]->chars[j].attr &~ DATTR_MASK) == tattr) + break_run = TRUE; + else if (!dirty_run && ccount == 1) + break_run = TRUE; + } + + if (break_run) { + if ((dirty_run || last_run_dirty) && ccount > 0) { + do_text(ctx, start, i, ch, ccount, attr, + ldata->lattr); + if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) + do_cursor(ctx, start, i, ch, ccount, attr, + ldata->lattr); + } + start = j; + ccount = 0; + attr = tattr; + cset = CSET_OF(tchar); + if (term->ucsdata->dbcs_screenfont) + last_run_dirty = dirty_run; + dirty_run = dirty_line; + } + + do_copy = FALSE; + if (!termchars_equal_override(&term->disptext[i]->chars[j], + d, tchar, tattr)) { + do_copy = TRUE; + dirty_run = TRUE; + } + + if (ccount >= chlen) { + chlen = ccount + 256; + ch = sresize(ch, chlen, wchar_t); + } + ch[ccount++] = (wchar_t) tchar; + + if (d->cc_next) { + termchar *dd = d; + + while (dd->cc_next) { + unsigned long schar; + + dd += dd->cc_next; + + schar = dd->chr; + switch (schar & CSET_MASK) { + case CSET_ASCII: + schar = term->ucsdata->unitab_line[schar & 0xFF]; + break; + case CSET_LINEDRW: + schar = term->ucsdata->unitab_xterm[schar & 0xFF]; + break; + case CSET_SCOACS: + schar = term->ucsdata->unitab_scoacs[schar&0xFF]; + break; + } + + if (ccount >= chlen) { + chlen = ccount + 256; + ch = sresize(ch, chlen, wchar_t); + } + ch[ccount++] = (wchar_t) schar; + } + + attr |= TATTR_COMBINING; + } + + if (do_copy) { + copy_termchar(term->disptext[i], j, d); + term->disptext[i]->chars[j].chr = tchar; + term->disptext[i]->chars[j].attr = tattr; + if (start == j) + term->disptext[i]->chars[j].attr |= DATTR_STARTRUN; + } + + /* If it's a wide char step along to the next one. */ + if (tattr & ATTR_WIDE) { + if (++j < term->cols) { + d++; + /* + * By construction above, the cursor should not + * be on the right-hand half of this character. + * Ever. + */ + assert(!(i == our_curs_y && j == our_curs_x)); + if (!termchars_equal(&term->disptext[i]->chars[j], d)) + dirty_run = TRUE; + copy_termchar(term->disptext[i], j, d); + } + } + } + if (dirty_run && ccount > 0) { + do_text(ctx, start, i, ch, ccount, attr, + ldata->lattr); + if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) + do_cursor(ctx, start, i, ch, ccount, attr, + ldata->lattr); + } + + unlineptr(ldata); + } + + sfree(newline); + sfree(ch); +} + +/* + * Invalidate the whole screen so it will be repainted in full. + */ +void term_invalidate(Terminal *term) +{ + int i, j; + + for (i = 0; i < term->rows; i++) + for (j = 0; j < term->cols; j++) + term->disptext[i]->chars[j].attr |= ATTR_INVALID; + + term_schedule_update(term); +} + +/* + * Paint the window in response to a WM_PAINT message. + */ +void term_paint(Terminal *term, Context ctx, + int left, int top, int right, int bottom, int immediately) +{ + int i, j; + if (left < 0) left = 0; + if (top < 0) top = 0; + if (right >= term->cols) right = term->cols-1; + if (bottom >= term->rows) bottom = term->rows-1; + + for (i = top; i <= bottom && i < term->rows; i++) { + if ((term->disptext[i]->lattr & LATTR_MODE) == LATTR_NORM) + for (j = left; j <= right && j < term->cols; j++) + term->disptext[i]->chars[j].attr |= ATTR_INVALID; + else + for (j = left / 2; j <= right / 2 + 1 && j < term->cols; j++) + term->disptext[i]->chars[j].attr |= ATTR_INVALID; + } + + if (immediately) { + do_paint (term, ctx, FALSE); + } else { + term_schedule_update(term); + } +} + +/* + * Attempt to scroll the scrollback. The second parameter gives the + * position we want to scroll to; the first is +1 to denote that + * this position is relative to the beginning of the scrollback, -1 + * to denote it is relative to the end, and 0 to denote that it is + * relative to the current position. + */ +void term_scroll(Terminal *term, int rel, int where) +{ + int sbtop = -sblines(term); +#ifdef OPTIMISE_SCROLL + int olddisptop = term->disptop; + int shift; +#endif /* OPTIMISE_SCROLL */ + + term->disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : term->disptop) + where; + if (term->disptop < sbtop) + term->disptop = sbtop; + if (term->disptop > 0) + term->disptop = 0; + update_sbar(term); +#ifdef OPTIMISE_SCROLL + shift = (term->disptop - olddisptop); + if (shift < term->rows && shift > -term->rows) + scroll_display(term, 0, term->rows - 1, shift); +#endif /* OPTIMISE_SCROLL */ + term_update(term); +} + +/* + * Scroll the scrollback to centre it on the beginning or end of the + * current selection, if any. + */ +void term_scroll_to_selection(Terminal *term, int which_end) +{ + pos target; + int y; + int sbtop = -sblines(term); + + if (term->selstate != SELECTED) + return; + if (which_end) + target = term->selend; + else + target = term->selstart; + + y = target.y - term->rows/2; + if (y < sbtop) + y = sbtop; + else if (y > 0) + y = 0; + term_scroll(term, -1, y); +} + +/* + * Helper routine for clipme(): growing buffer. + */ +typedef struct { + int buflen; /* amount of allocated space in textbuf/attrbuf */ + int bufpos; /* amount of actual data */ + wchar_t *textbuf; /* buffer for copied text */ + wchar_t *textptr; /* = textbuf + bufpos (current insertion point) */ + int *attrbuf; /* buffer for copied attributes */ + int *attrptr; /* = attrbuf + bufpos */ +} clip_workbuf; + +static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr) +{ + if (b->bufpos >= b->buflen) { + b->buflen += 128; + b->textbuf = sresize(b->textbuf, b->buflen, wchar_t); + b->textptr = b->textbuf + b->bufpos; + b->attrbuf = sresize(b->attrbuf, b->buflen, int); + b->attrptr = b->attrbuf + b->bufpos; + } + *b->textptr++ = chr; + *b->attrptr++ = attr; + b->bufpos++; +} + +static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) +{ + clip_workbuf buf; + int old_top_x; + int attr; + + buf.buflen = 5120; + buf.bufpos = 0; + buf.textptr = buf.textbuf = snewn(buf.buflen, wchar_t); + buf.attrptr = buf.attrbuf = snewn(buf.buflen, int); + + old_top_x = top.x; /* needed for rect==1 */ + + while (poslt(top, bottom)) { + int nl = FALSE; + termline *ldata = lineptr(top.y); + pos nlpos; + + /* + * nlpos will point at the maximum position on this line we + * should copy up to. So we start it at the end of the + * line... + */ + nlpos.y = top.y; + nlpos.x = term->cols; + + /* + * ... move it backwards if there's unused space at the end + * of the line (and also set `nl' if this is the case, + * because in normal selection mode this means we need a + * newline at the end)... + */ + if (!(ldata->lattr & LATTR_WRAPPED)) { + while (nlpos.x && + IS_SPACE_CHR(ldata->chars[nlpos.x - 1].chr) && + !ldata->chars[nlpos.x - 1].cc_next && + poslt(top, nlpos)) + decpos(nlpos); + if (poslt(nlpos, bottom)) + nl = TRUE; + } else if (ldata->lattr & LATTR_WRAPPED2) { + /* Ignore the last char on the line in a WRAPPED2 line. */ + decpos(nlpos); + } + + /* + * ... and then clip it to the terminal x coordinate if + * we're doing rectangular selection. (In this case we + * still did the above, so that copying e.g. the right-hand + * column from a table doesn't fill with spaces on the + * right.) + */ + if (rect) { + if (nlpos.x > bottom.x) + nlpos.x = bottom.x; + nl = (top.y < bottom.y); + } + + while (poslt(top, bottom) && poslt(top, nlpos)) { +#if 0 + char cbuf[16], *p; + sprintf(cbuf, "", (ldata[top.x] & 0xFFFF)); +#else + wchar_t cbuf[16], *p; + int c; + int x = top.x; + + if (ldata->chars[x].chr == UCSWIDE) { + top.x++; + continue; + } + + while (1) { + int uc = ldata->chars[x].chr; + attr = ldata->chars[x].attr; + + switch (uc & CSET_MASK) { + case CSET_LINEDRW: + if (!term->cfg.rawcnp) { + uc = term->ucsdata->unitab_xterm[uc & 0xFF]; + break; + } + case CSET_ASCII: + uc = term->ucsdata->unitab_line[uc & 0xFF]; + break; + case CSET_SCOACS: + uc = term->ucsdata->unitab_scoacs[uc&0xFF]; + break; + } + switch (uc & CSET_MASK) { + case CSET_ACP: + uc = term->ucsdata->unitab_font[uc & 0xFF]; + break; + case CSET_OEMCP: + uc = term->ucsdata->unitab_oemcp[uc & 0xFF]; + break; + } + + c = (uc & ~CSET_MASK); +#ifdef PLATFORM_IS_UTF16 + if (uc > 0x10000 && uc < 0x110000) { + cbuf[0] = 0xD800 | ((uc - 0x10000) >> 10); + cbuf[1] = 0xDC00 | ((uc - 0x10000) & 0x3FF); + cbuf[2] = 0; + } else +#endif + { + cbuf[0] = uc; + cbuf[1] = 0; + } + + if (DIRECT_FONT(uc)) { + if (c >= ' ' && c != 0x7F) { + char buf[4]; + WCHAR wbuf[4]; + int rv; + if (is_dbcs_leadbyte(term->ucsdata->font_codepage, (BYTE) c)) { + buf[0] = c; + buf[1] = (char) (0xFF & ldata->chars[top.x + 1].chr); + rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 2, wbuf, 4); + top.x++; + } else { + buf[0] = c; + rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 1, wbuf, 4); + } + + if (rv > 0) { + memcpy(cbuf, wbuf, rv * sizeof(wchar_t)); + cbuf[rv] = 0; + } + } + } +#endif + + for (p = cbuf; *p; p++) + clip_addchar(&buf, *p, attr); + + if (ldata->chars[x].cc_next) + x += ldata->chars[x].cc_next; + else + break; + } + top.x++; + } + if (nl) { + int i; + for (i = 0; i < sel_nl_sz; i++) + clip_addchar(&buf, sel_nl[i], 0); + } + top.y++; + top.x = rect ? old_top_x : 0; + + unlineptr(ldata); + } +#if SELECTION_NUL_TERMINATED + clip_addchar(&buf, 0, 0); +#endif + /* Finally, transfer all that to the clipboard. */ + write_clip(term->frontend, buf.textbuf, buf.attrbuf, buf.bufpos, desel); + sfree(buf.textbuf); + sfree(buf.attrbuf); +} + +void term_copyall(Terminal *term) +{ + pos top; + pos bottom; + tree234 *screen = term->screen; + top.y = -sblines(term); + top.x = 0; + bottom.y = find_last_nonempty_line(term, screen); + bottom.x = term->cols; + clipme(term, top, bottom, 0, TRUE); +} + +/* + * The wordness array is mainly for deciding the disposition of the + * US-ASCII characters. + */ +static int wordtype(Terminal *term, int uc) +{ + struct ucsword { + int start, end, ctype; + }; + static const struct ucsword ucs_words[] = { + { + 128, 160, 0}, { + 161, 191, 1}, { + 215, 215, 1}, { + 247, 247, 1}, { + 0x037e, 0x037e, 1}, /* Greek question mark */ + { + 0x0387, 0x0387, 1}, /* Greek ano teleia */ + { + 0x055a, 0x055f, 1}, /* Armenian punctuation */ + { + 0x0589, 0x0589, 1}, /* Armenian full stop */ + { + 0x0700, 0x070d, 1}, /* Syriac punctuation */ + { + 0x104a, 0x104f, 1}, /* Myanmar punctuation */ + { + 0x10fb, 0x10fb, 1}, /* Georgian punctuation */ + { + 0x1361, 0x1368, 1}, /* Ethiopic punctuation */ + { + 0x166d, 0x166e, 1}, /* Canadian Syl. punctuation */ + { + 0x17d4, 0x17dc, 1}, /* Khmer punctuation */ + { + 0x1800, 0x180a, 1}, /* Mongolian punctuation */ + { + 0x2000, 0x200a, 0}, /* Various spaces */ + { + 0x2070, 0x207f, 2}, /* superscript */ + { + 0x2080, 0x208f, 2}, /* subscript */ + { + 0x200b, 0x27ff, 1}, /* punctuation and symbols */ + { + 0x3000, 0x3000, 0}, /* ideographic space */ + { + 0x3001, 0x3020, 1}, /* ideographic punctuation */ + { + 0x303f, 0x309f, 3}, /* Hiragana */ + { + 0x30a0, 0x30ff, 3}, /* Katakana */ + { + 0x3300, 0x9fff, 3}, /* CJK Ideographs */ + { + 0xac00, 0xd7a3, 3}, /* Hangul Syllables */ + { + 0xf900, 0xfaff, 3}, /* CJK Ideographs */ + { + 0xfe30, 0xfe6b, 1}, /* punctuation forms */ + { + 0xff00, 0xff0f, 1}, /* half/fullwidth ASCII */ + { + 0xff1a, 0xff20, 1}, /* half/fullwidth ASCII */ + { + 0xff3b, 0xff40, 1}, /* half/fullwidth ASCII */ + { + 0xff5b, 0xff64, 1}, /* half/fullwidth ASCII */ + { + 0xfff0, 0xffff, 0}, /* half/fullwidth ASCII */ + { + 0, 0, 0} + }; + const struct ucsword *wptr; + + switch (uc & CSET_MASK) { + case CSET_LINEDRW: + uc = term->ucsdata->unitab_xterm[uc & 0xFF]; + break; + case CSET_ASCII: + uc = term->ucsdata->unitab_line[uc & 0xFF]; + break; + case CSET_SCOACS: + uc = term->ucsdata->unitab_scoacs[uc&0xFF]; + break; + } + switch (uc & CSET_MASK) { + case CSET_ACP: + uc = term->ucsdata->unitab_font[uc & 0xFF]; + break; + case CSET_OEMCP: + uc = term->ucsdata->unitab_oemcp[uc & 0xFF]; + break; + } + + /* For DBCS fonts I can't do anything useful. Even this will sometimes + * fail as there's such a thing as a double width space. :-( + */ + if (term->ucsdata->dbcs_screenfont && + term->ucsdata->font_codepage == term->ucsdata->line_codepage) + return (uc != ' '); + + if (uc < 0x80) + return term->wordness[uc]; + + for (wptr = ucs_words; wptr->start; wptr++) { + if (uc >= wptr->start && uc <= wptr->end) + return wptr->ctype; + } + + return 2; +} + +/* + * Spread the selection outwards according to the selection mode. + */ +static pos sel_spread_half(Terminal *term, pos p, int dir) +{ + termline *ldata; + short wvalue; + int topy = -sblines(term); + + ldata = lineptr(p.y); + + switch (term->selmode) { + case SM_CHAR: + /* + * In this mode, every character is a separate unit, except + * for runs of spaces at the end of a non-wrapping line. + */ + if (!(ldata->lattr & LATTR_WRAPPED)) { + termchar *q = ldata->chars + term->cols; + while (q > ldata->chars && + IS_SPACE_CHR(q[-1].chr) && !q[-1].cc_next) + q--; + if (q == ldata->chars + term->cols) + q--; + if (p.x >= q - ldata->chars) + p.x = (dir == -1 ? q - ldata->chars : term->cols - 1); + } + break; + case SM_WORD: + /* + * In this mode, the units are maximal runs of characters + * whose `wordness' has the same value. + */ + wvalue = wordtype(term, UCSGET(ldata->chars, p.x)); + if (dir == +1) { + while (1) { + int maxcols = (ldata->lattr & LATTR_WRAPPED2 ? + term->cols-1 : term->cols); + if (p.x < maxcols-1) { + if (wordtype(term, UCSGET(ldata->chars, p.x+1)) == wvalue) + p.x++; + else + break; + } else { + if (ldata->lattr & LATTR_WRAPPED) { + termline *ldata2; + ldata2 = lineptr(p.y+1); + if (wordtype(term, UCSGET(ldata2->chars, 0)) + == wvalue) { + p.x = 0; + p.y++; + unlineptr(ldata); + ldata = ldata2; + } else { + unlineptr(ldata2); + break; + } + } else + break; + } + } + } else { + while (1) { + if (p.x > 0) { + if (wordtype(term, UCSGET(ldata->chars, p.x-1)) == wvalue) + p.x--; + else + break; + } else { + termline *ldata2; + int maxcols; + if (p.y <= topy) + break; + ldata2 = lineptr(p.y-1); + maxcols = (ldata2->lattr & LATTR_WRAPPED2 ? + term->cols-1 : term->cols); + if (ldata2->lattr & LATTR_WRAPPED) { + if (wordtype(term, UCSGET(ldata2->chars, maxcols-1)) + == wvalue) { + p.x = maxcols-1; + p.y--; + unlineptr(ldata); + ldata = ldata2; + } else { + unlineptr(ldata2); + break; + } + } else + break; + } + } + } + break; + case SM_LINE: + /* + * In this mode, every line is a unit. + */ + p.x = (dir == -1 ? 0 : term->cols - 1); + break; + } + + unlineptr(ldata); + return p; +} + +static void sel_spread(Terminal *term) +{ + if (term->seltype == LEXICOGRAPHIC) { + term->selstart = sel_spread_half(term, term->selstart, -1); + decpos(term->selend); + term->selend = sel_spread_half(term, term->selend, +1); + incpos(term->selend); + } +} + +void term_do_paste(Terminal *term) +{ + wchar_t *data; + int len; + + get_clip(term->frontend, &data, &len); + if (data && len > 0) { + wchar_t *p, *q; + + term_seen_key_event(term); /* pasted data counts */ + + if (term->paste_buffer) + sfree(term->paste_buffer); + term->paste_pos = term->paste_hold = term->paste_len = 0; + term->paste_buffer = snewn(len, wchar_t); + + p = q = data; + while (p < data + len) { + while (p < data + len && + !(p <= data + len - sel_nl_sz && + !memcmp(p, sel_nl, sizeof(sel_nl)))) + p++; + + { + int i; + for (i = 0; i < p - q; i++) { + term->paste_buffer[term->paste_len++] = q[i]; + } + } + + if (p <= data + len - sel_nl_sz && + !memcmp(p, sel_nl, sizeof(sel_nl))) { + term->paste_buffer[term->paste_len++] = '\015'; + p += sel_nl_sz; + } + q = p; + } + + /* Assume a small paste will be OK in one go. */ + if (term->paste_len < 256) { + if (term->ldisc) + luni_send(term->ldisc, term->paste_buffer, term->paste_len, 0); + if (term->paste_buffer) + sfree(term->paste_buffer); + term->paste_buffer = 0; + term->paste_pos = term->paste_hold = term->paste_len = 0; + } + } + get_clip(term->frontend, NULL, NULL); +} + +void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, + Mouse_Action a, int x, int y, int shift, int ctrl, int alt) +{ + pos selpoint; + termline *ldata; + int raw_mouse = (term->xterm_mouse && + !term->cfg.no_mouse_rep && + !(term->cfg.mouse_override && shift)); + int default_seltype; + + if (y < 0) { + y = 0; + if (a == MA_DRAG && !raw_mouse) + term_scroll(term, 0, -1); + } + if (y >= term->rows) { + y = term->rows - 1; + if (a == MA_DRAG && !raw_mouse) + term_scroll(term, 0, +1); + } + if (x < 0) { + if (y > 0) { + x = term->cols - 1; + y--; + } else + x = 0; + } + if (x >= term->cols) + x = term->cols - 1; + + selpoint.y = y + term->disptop; + ldata = lineptr(selpoint.y); + + if ((ldata->lattr & LATTR_MODE) != LATTR_NORM) + x /= 2; + + /* + * Transform x through the bidi algorithm to find the _logical_ + * click point from the physical one. + */ + if (term_bidi_line(term, ldata, y) != NULL) { + x = term->post_bidi_cache[y].backward[x]; + } + + selpoint.x = x; + unlineptr(ldata); + + /* + * If we're in the middle of a selection operation, we ignore raw + * mouse mode until it's done (we must have been not in raw mouse + * mode when it started). + * This makes use of Shift for selection reliable, and avoids the + * host seeing mouse releases for which they never saw corresponding + * presses. + */ + if (raw_mouse && + (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) { + int encstate = 0, r, c; + char abuf[16]; + + if (term->ldisc) { + + switch (braw) { + case MBT_LEFT: + encstate = 0x20; /* left button down */ + break; + case MBT_MIDDLE: + encstate = 0x21; + break; + case MBT_RIGHT: + encstate = 0x22; + break; + case MBT_WHEEL_UP: + encstate = 0x60; + break; + case MBT_WHEEL_DOWN: + encstate = 0x61; + break; + default: break; /* placate gcc warning about enum use */ + } + switch (a) { + case MA_DRAG: + if (term->xterm_mouse == 1) + return; + encstate += 0x20; + break; + case MA_RELEASE: + encstate = 0x23; + term->mouse_is_down = 0; + break; + case MA_CLICK: + if (term->mouse_is_down == braw) + return; + term->mouse_is_down = braw; + break; + default: break; /* placate gcc warning about enum use */ + } + if (shift) + encstate += 0x04; + if (ctrl) + encstate += 0x10; + r = y + 33; + c = x + 33; + + sprintf(abuf, "\033[M%c%c%c", encstate, c, r); + ldisc_send(term->ldisc, abuf, 6, 0); + } + return; + } + + /* + * Set the selection type (rectangular or normal) at the start + * of a selection attempt, from the state of Alt. + */ + if (!alt ^ !term->cfg.rect_select) + default_seltype = RECTANGULAR; + else + default_seltype = LEXICOGRAPHIC; + + if (term->selstate == NO_SELECTION) { + term->seltype = default_seltype; + } + + if (bcooked == MBT_SELECT && a == MA_CLICK) { + deselect(term); + term->selstate = ABOUT_TO; + term->seltype = default_seltype; + term->selanchor = selpoint; + term->selmode = SM_CHAR; + } else if (bcooked == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) { + deselect(term); + term->selmode = (a == MA_2CLK ? SM_WORD : SM_LINE); + term->selstate = DRAGGING; + term->selstart = term->selanchor = selpoint; + term->selend = term->selstart; + incpos(term->selend); + sel_spread(term); + } else if ((bcooked == MBT_SELECT && a == MA_DRAG) || + (bcooked == MBT_EXTEND && a != MA_RELEASE)) { + if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint)) + return; + if (bcooked == MBT_EXTEND && a != MA_DRAG && + term->selstate == SELECTED) { + if (term->seltype == LEXICOGRAPHIC) { + /* + * For normal selection, we extend by moving + * whichever end of the current selection is closer + * to the mouse. + */ + if (posdiff(selpoint, term->selstart) < + posdiff(term->selend, term->selstart) / 2) { + term->selanchor = term->selend; + decpos(term->selanchor); + } else { + term->selanchor = term->selstart; + } + } else { + /* + * For rectangular selection, we have a choice of + * _four_ places to put selanchor and selpoint: the + * four corners of the selection. + */ + if (2*selpoint.x < term->selstart.x + term->selend.x) + term->selanchor.x = term->selend.x-1; + else + term->selanchor.x = term->selstart.x; + + if (2*selpoint.y < term->selstart.y + term->selend.y) + term->selanchor.y = term->selend.y; + else + term->selanchor.y = term->selstart.y; + } + term->selstate = DRAGGING; + } + if (term->selstate != ABOUT_TO && term->selstate != DRAGGING) + term->selanchor = selpoint; + term->selstate = DRAGGING; + if (term->seltype == LEXICOGRAPHIC) { + /* + * For normal selection, we set (selstart,selend) to + * (selpoint,selanchor) in some order. + */ + if (poslt(selpoint, term->selanchor)) { + term->selstart = selpoint; + term->selend = term->selanchor; + incpos(term->selend); + } else { + term->selstart = term->selanchor; + term->selend = selpoint; + incpos(term->selend); + } + } else { + /* + * For rectangular selection, we may need to + * interchange x and y coordinates (if the user has + * dragged in the -x and +y directions, or vice versa). + */ + term->selstart.x = min(term->selanchor.x, selpoint.x); + term->selend.x = 1+max(term->selanchor.x, selpoint.x); + term->selstart.y = min(term->selanchor.y, selpoint.y); + term->selend.y = max(term->selanchor.y, selpoint.y); + } + sel_spread(term); + } else if ((bcooked == MBT_SELECT || bcooked == MBT_EXTEND) && + a == MA_RELEASE) { + if (term->selstate == DRAGGING) { + /* + * We've completed a selection. We now transfer the + * data to the clipboard. + */ + clipme(term, term->selstart, term->selend, + (term->seltype == RECTANGULAR), FALSE); + term->selstate = SELECTED; + } else + term->selstate = NO_SELECTION; + } else if (bcooked == MBT_PASTE + && (a == MA_CLICK +#if MULTICLICK_ONLY_EVENT + || a == MA_2CLK || a == MA_3CLK +#endif + )) { + request_paste(term->frontend); + } + + term_update(term); +} + +int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl) +{ + char *p = buf; + + if (term->vt52_mode) + p += sprintf((char *) p, "\x1B%c", xkey); + else { + int app_flg = (term->app_cursor_keys && !term->cfg.no_applic_c); +#if 0 + /* + * RDB: VT100 & VT102 manuals both state the app cursor + * keys only work if the app keypad is on. + * + * SGT: That may well be true, but xterm disagrees and so + * does at least one application, so I've #if'ed this out + * and the behaviour is back to PuTTY's original: app + * cursor and app keypad are independently switchable + * modes. If anyone complains about _this_ I'll have to + * put in a configurable option. + */ + if (!term->app_keypad_keys) + app_flg = 0; +#endif + /* Useful mapping of Ctrl-arrows */ + if (ctrl) + app_flg = !app_flg; + + if (app_flg) + p += sprintf((char *) p, "\x1BO%c", xkey); + else + p += sprintf((char *) p, "\x1B[%c", xkey); + } + + return p - buf; +} + +void term_key(Terminal *term, Key_Sym keysym, wchar_t *text, size_t tlen, + unsigned int modifiers, unsigned int flags) +{ + char output[10]; + char *p = output; + int prependesc = FALSE; +#if 0 + int i; + + fprintf(stderr, "keysym = %d, %d chars:", keysym, tlen); + for (i = 0; i < tlen; i++) + fprintf(stderr, " %04x", (unsigned)text[i]); + fprintf(stderr, "\n"); +#endif + + /* XXX Num Lock */ + if ((flags & PKF_REPEAT) && term->repeat_off) + return; + + /* Currently, Meta always just prefixes everything with ESC. */ + if (modifiers & PKM_META) + prependesc = TRUE; + modifiers &= ~PKM_META; + + /* + * Alt is only used for Alt+keypad, which isn't supported yet, so + * ignore it. + */ + modifiers &= ~PKM_ALT; + + /* Standard local function keys */ + switch (modifiers & (PKM_SHIFT | PKM_CONTROL)) { + case PKM_SHIFT: + if (keysym == PK_PAGEUP) + /* scroll up one page */; + if (keysym == PK_PAGEDOWN) + /* scroll down on page */; + if (keysym == PK_INSERT) + term_do_paste(term); + break; + case PKM_CONTROL: + if (keysym == PK_PAGEUP) + /* scroll up one line */; + if (keysym == PK_PAGEDOWN) + /* scroll down one line */; + /* Control-Numlock for app-keypad mode switch */ + if (keysym == PK_PF1) + term->app_keypad_keys ^= 1; + break; + } + + if (modifiers & PKM_ALT) { + /* Alt+F4 (close) */ + /* Alt+Return (full screen) */ + /* Alt+Space (system menu) */ + } + + if (keysym == PK_NULL && (modifiers & PKM_CONTROL) && tlen == 1 && + text[0] >= 0x20 && text[0] <= 0x7e) { + /* ASCII chars + Control */ + if ((text[0] >= 0x40 && text[0] <= 0x5f) || + (text[0] >= 0x61 && text[0] <= 0x7a)) + text[0] &= 0x1f; + else { + /* + * Control-2 should return ^@ (0x00), Control-6 should return + * ^^ (0x1E), and Control-Minus should return ^_ (0x1F). Since + * the DOS keyboard handling did it, and we have nothing better + * to do with the key combo in question, we'll also map + * Control-Backquote to ^\ (0x1C). + */ + switch (text[0]) { + case ' ': text[0] = 0x00; break; + case '-': text[0] = 0x1f; break; + case '/': text[0] = 0x1f; break; + case '2': text[0] = 0x00; break; + case '3': text[0] = 0x1b; break; + case '4': text[0] = 0x1c; break; + case '5': text[0] = 0x1d; break; + case '6': text[0] = 0x1e; break; + case '7': text[0] = 0x1f; break; + case '8': text[0] = 0x7f; break; + case '`': text[0] = 0x1c; break; + } + } + } + + /* Nethack keypad */ + if (term->cfg.nethack_keypad) { + char c = 0; + switch (keysym) { + case PK_KP1: c = 'b'; break; + case PK_KP2: c = 'j'; break; + case PK_KP3: c = 'n'; break; + case PK_KP4: c = 'h'; break; + case PK_KP5: c = '.'; break; + case PK_KP6: c = 'l'; break; + case PK_KP7: c = 'y'; break; + case PK_KP8: c = 'k'; break; + case PK_KP9: c = 'u'; break; + default: break; /* else gcc warns `enum value not used' */ + } + if (c != 0) { + if (c != '.') { + if (modifiers & PKM_CONTROL) + c &= 0x1f; + else if (modifiers & PKM_SHIFT) + c = toupper((unsigned char)c); + } + *p++ = c; + goto done; + } + } + + /* Numeric Keypad */ + if (PK_ISKEYPAD(keysym)) { + int xkey = 0; + + /* + * In VT400 mode, PFn always emits an escape sequence. In + * Linux and tilde modes, this only happens in app keypad mode. + */ + if (term->cfg.funky_type == FUNKY_VT400 || + ((term->cfg.funky_type == FUNKY_LINUX || + term->cfg.funky_type == FUNKY_TILDE) && + term->app_keypad_keys && !term->cfg.no_applic_k)) { + switch (keysym) { + case PK_PF1: xkey = 'P'; break; + case PK_PF2: xkey = 'Q'; break; + case PK_PF3: xkey = 'R'; break; + case PK_PF4: xkey = 'S'; break; + default: break; /* else gcc warns `enum value not used' */ + } + } + if (term->app_keypad_keys && !term->cfg.no_applic_k) { + switch (keysym) { + case PK_KP0: xkey = 'p'; break; + case PK_KP1: xkey = 'q'; break; + case PK_KP2: xkey = 'r'; break; + case PK_KP3: xkey = 's'; break; + case PK_KP4: xkey = 't'; break; + case PK_KP5: xkey = 'u'; break; + case PK_KP6: xkey = 'v'; break; + case PK_KP7: xkey = 'w'; break; + case PK_KP8: xkey = 'x'; break; + case PK_KP9: xkey = 'y'; break; + case PK_KPDECIMAL: xkey = 'n'; break; + case PK_KPENTER: xkey = 'M'; break; + default: break; /* else gcc warns `enum value not used' */ + } + if (term->cfg.funky_type == FUNKY_XTERM && tlen > 0) { + /* + * xterm can't see the layout of the keypad, so it has + * to rely on the X keysyms returned by the keys. + * Hence, we look at the strings here, not the PuTTY + * keysyms (which describe the layout). + */ + switch (text[0]) { + case '+': + if (modifiers & PKM_SHIFT) + xkey = 'l'; + else + xkey = 'k'; + break; + case '/': xkey = 'o'; break; + case '*': xkey = 'j'; break; + case '-': xkey = 'm'; break; + } + } else { + /* + * In all other modes, we try to retain the layout of + * the DEC keypad in application mode. + */ + switch (keysym) { + case PK_KPBIGPLUS: + /* This key covers the '-' and ',' keys on a VT220 */ + if (modifiers & PKM_SHIFT) + xkey = 'm'; /* VT220 '-' */ + else + xkey = 'l'; /* VT220 ',' */ + break; + case PK_KPMINUS: xkey = 'm'; break; + case PK_KPCOMMA: xkey = 'l'; break; + default: break; /* else gcc warns `enum value not used' */ + } + } + } + if (xkey) { + if (term->vt52_mode) { + if (xkey >= 'P' && xkey <= 'S') + p += sprintf((char *) p, "\x1B%c", xkey); + else + p += sprintf((char *) p, "\x1B?%c", xkey); + } else + p += sprintf((char *) p, "\x1BO%c", xkey); + goto done; + } + /* Not in application mode -- treat the number pad as arrow keys? */ + if ((flags & PKF_NUMLOCK) == 0) { + switch (keysym) { + case PK_KP0: keysym = PK_INSERT; break; + case PK_KP1: keysym = PK_END; break; + case PK_KP2: keysym = PK_DOWN; break; + case PK_KP3: keysym = PK_PAGEDOWN; break; + case PK_KP4: keysym = PK_LEFT; break; + case PK_KP5: keysym = PK_REST; break; + case PK_KP6: keysym = PK_RIGHT; break; + case PK_KP7: keysym = PK_HOME; break; + case PK_KP8: keysym = PK_UP; break; + case PK_KP9: keysym = PK_PAGEUP; break; + default: break; /* else gcc warns `enum value not used' */ + } + } + } + + /* Miscellaneous keys */ + switch (keysym) { + case PK_ESCAPE: + *p++ = 0x1b; + goto done; + case PK_BACKSPACE: + if (modifiers == 0) + *p++ = (term->cfg.bksp_is_delete ? 0x7F : 0x08); + else if (modifiers == PKM_SHIFT) + /* We do the opposite of what is configured */ + *p++ = (term->cfg.bksp_is_delete ? 0x08 : 0x7F); + else break; + goto done; + case PK_TAB: + if (modifiers == 0) + *p++ = 0x09; + else if (modifiers == PKM_SHIFT) + *p++ = 0x1B, *p++ = '[', *p++ = 'Z'; + else break; + goto done; + /* XXX window.c has ctrl+shift+space sending 0xa0 */ + case PK_PAUSE: + if (modifiers == PKM_CONTROL) + *p++ = 26; + else break; + goto done; + case PK_RETURN: + case PK_KPENTER: /* Odd keypad modes handled above */ + if (modifiers == 0) { + *p++ = 0x0d; + if (term->cr_lf_return) + *p++ = 0x0a; + goto done; + } + default: break; /* else gcc warns `enum value not used' */ + } + + /* SCO function keys and editing keys */ + if (term->cfg.funky_type == FUNKY_SCO) { + if (PK_ISFKEY(keysym) && keysym <= PK_F12) { + static char const codes[] = + "MNOPQRSTUVWX" "YZabcdefghij" "klmnopqrstuv" "wxyz@[\\]^_`{"; + int index = keysym - PK_F1; + + if (modifiers & PKM_SHIFT) index += 12; + if (modifiers & PKM_CONTROL) index += 24; + p += sprintf((char *) p, "\x1B[%c", codes[index]); + goto done; + } + if (PK_ISEDITING(keysym)) { + int xkey = 0; + + switch (keysym) { + case PK_DELETE: *p++ = 0x7f; goto done; + case PK_HOME: xkey = 'H'; break; + case PK_INSERT: xkey = 'L'; break; + case PK_END: xkey = 'F'; break; + case PK_PAGEUP: xkey = 'I'; break; + case PK_PAGEDOWN: xkey = 'G'; break; + default: break; /* else gcc warns `enum value not used' */ + } + p += sprintf((char *) p, "\x1B[%c", xkey); + } + } + + if (PK_ISEDITING(keysym) && (modifiers & PKM_SHIFT) == 0) { + int code; + + if (term->cfg.funky_type == FUNKY_XTERM) { + /* Xterm shuffles these keys, apparently. */ + switch (keysym) { + case PK_HOME: keysym = PK_INSERT; break; + case PK_INSERT: keysym = PK_HOME; break; + case PK_DELETE: keysym = PK_END; break; + case PK_END: keysym = PK_PAGEUP; break; + case PK_PAGEUP: keysym = PK_DELETE; break; + case PK_PAGEDOWN: keysym = PK_PAGEDOWN; break; + default: break; /* else gcc warns `enum value not used' */ + } + } + + /* RXVT Home/End */ + if (term->cfg.rxvt_homeend && + (keysym == PK_HOME || keysym == PK_END)) { + p += sprintf((char *) p, keysym == PK_HOME ? "\x1B[H" : "\x1BOw"); + goto done; + } + + if (term->vt52_mode) { + int xkey; + + /* + * A real VT52 doesn't have these, and a VT220 doesn't + * send anything for them in VT52 mode. + */ + switch (keysym) { + case PK_HOME: xkey = 'H'; break; + case PK_INSERT: xkey = 'L'; break; + case PK_DELETE: xkey = 'M'; break; + case PK_END: xkey = 'E'; break; + case PK_PAGEUP: xkey = 'I'; break; + case PK_PAGEDOWN: xkey = 'G'; break; + default: xkey=0; break; /* else gcc warns `enum value not used'*/ + } + p += sprintf((char *) p, "\x1B%c", xkey); + goto done; + } + + switch (keysym) { + case PK_HOME: code = 1; break; + case PK_INSERT: code = 2; break; + case PK_DELETE: code = 3; break; + case PK_END: code = 4; break; + case PK_PAGEUP: code = 5; break; + case PK_PAGEDOWN: code = 6; break; + default: code = 0; break; /* else gcc warns `enum value not used' */ + } + p += sprintf((char *) p, "\x1B[%d~", code); + goto done; + } + + if (PK_ISFKEY(keysym)) { + /* Map Shift+F1-F10 to F11-F20 */ + if (keysym >= PK_F1 && keysym <= PK_F10 && (modifiers & PKM_SHIFT)) + keysym += 10; + if ((term->vt52_mode || term->cfg.funky_type == FUNKY_VT100P) && + keysym <= PK_F14) { + /* XXX This overrides the XTERM/VT52 mode below */ + int offt = 0; + if (keysym >= PK_F6) offt++; + if (keysym >= PK_F12) offt++; + p += sprintf((char *) p, term->vt52_mode ? "\x1B%c" : "\x1BO%c", + 'P' + keysym - PK_F1 - offt); + goto done; + } + if (term->cfg.funky_type == FUNKY_LINUX && keysym <= PK_F5) { + p += sprintf((char *) p, "\x1B[[%c", 'A' + keysym - PK_F1); + goto done; + } + if (term->cfg.funky_type == FUNKY_XTERM && keysym <= PK_F4) { + if (term->vt52_mode) + p += sprintf((char *) p, "\x1B%c", 'P' + keysym - PK_F1); + else + p += sprintf((char *) p, "\x1BO%c", 'P' + keysym - PK_F1); + goto done; + } + p += sprintf((char *) p, "\x1B[%d~", 11 + keysym - PK_F1); + goto done; + } + + if (PK_ISCURSOR(keysym)) { + int xkey; + + switch (keysym) { + case PK_UP: xkey = 'A'; break; + case PK_DOWN: xkey = 'B'; break; + case PK_RIGHT: xkey = 'C'; break; + case PK_LEFT: xkey = 'D'; break; + case PK_REST: xkey = 'G'; break; /* centre key on number pad */ + default: xkey = 0; break; /* else gcc warns `enum value not used' */ + } + p += format_arrow_key(p, term, xkey, modifiers == PKM_CONTROL); + goto done; + } + + done: + if (p > output || tlen > 0) { + /* + * Interrupt an ongoing paste. I'm not sure + * this is sensible, but for the moment it's + * preferable to having to faff about buffering + * things. + */ + term_nopaste(term); + + /* + * We need not bother about stdin backlogs + * here, because in GUI PuTTY we can't do + * anything about it anyway; there's no means + * of asking Windows to hold off on KEYDOWN + * messages. We _have_ to buffer everything + * we're sent. + */ + term_seen_key_event(term); + + if (prependesc) { +#if 0 + fprintf(stderr, "sending ESC\n"); +#endif + ldisc_send(term->ldisc, "\x1b", 1, 1); + } + + if (p > output) { +#if 0 + fprintf(stderr, "sending %d bytes:", p - output); + for (i = 0; i < p - output; i++) + fprintf(stderr, " %02x", output[i]); + fprintf(stderr, "\n"); +#endif + ldisc_send(term->ldisc, output, p - output, 1); + } else if (tlen > 0) { +#if 0 + fprintf(stderr, "sending %d unichars:", tlen); + for (i = 0; i < tlen; i++) + fprintf(stderr, " %04x", (unsigned) text[i]); + fprintf(stderr, "\n"); +#endif + luni_send(term->ldisc, text, tlen, 1); + } + } +} + +void term_nopaste(Terminal *term) +{ + if (term->paste_len == 0) + return; + sfree(term->paste_buffer); + term->paste_buffer = NULL; + term->paste_len = 0; +} + +int term_paste_pending(Terminal *term) +{ + return term->paste_len != 0; +} + +void term_paste(Terminal *term) +{ + long now, paste_diff; + + if (term->paste_len == 0) + return; + + /* Don't wait forever to paste */ + if (term->paste_hold) { + now = GETTICKCOUNT(); + paste_diff = now - term->last_paste; + if (paste_diff >= 0 && paste_diff < 450) + return; + } + term->paste_hold = 0; + + while (term->paste_pos < term->paste_len) { + int n = 0; + while (n + term->paste_pos < term->paste_len) { + if (term->paste_buffer[term->paste_pos + n++] == '\015') + break; + } + if (term->ldisc) + luni_send(term->ldisc, term->paste_buffer + term->paste_pos, n, 0); + term->paste_pos += n; + + if (term->paste_pos < term->paste_len) { + term->paste_hold = 1; + return; + } + } + sfree(term->paste_buffer); + term->paste_buffer = NULL; + term->paste_len = 0; +} + +static void deselect(Terminal *term) +{ + term->selstate = NO_SELECTION; + term->selstart.x = term->selstart.y = term->selend.x = term->selend.y = 0; +} + +void term_deselect(Terminal *term) +{ + deselect(term); + term_update(term); +} + +int term_ldisc(Terminal *term, int option) +{ + if (option == LD_ECHO) + return term->term_echoing; + if (option == LD_EDIT) + return term->term_editing; + return FALSE; +} + +int term_data(Terminal *term, int is_stderr, const char *data, int len) +{ + bufchain_add(&term->inbuf, data, len); + + if (!term->in_term_out) { + term->in_term_out = TRUE; + term_reset_cblink(term); + /* + * During drag-selects, we do not process terminal input, + * because the user will want the screen to hold still to + * be selected. + */ + if (term->selstate != DRAGGING) + term_out(term); + term->in_term_out = FALSE; + } + + /* + * term_out() always completely empties inbuf. Therefore, + * there's no reason at all to return anything other than zero + * from this function, because there _can't_ be a question of + * the remote side needing to wait until term_out() has cleared + * a backlog. + * + * This is a slightly suboptimal way to deal with SSH-2 - in + * principle, the window mechanism would allow us to continue + * to accept data on forwarded ports and X connections even + * while the terminal processing was going slowly - but we + * can't do the 100% right thing without moving the terminal + * processing into a separate thread, and that might hurt + * portability. So we manage stdout buffering the old SSH-1 way: + * if the terminal processing goes slowly, the whole SSH + * connection stops accepting data until it's ready. + * + * In practice, I can't imagine this causing serious trouble. + */ + return 0; +} + +/* + * Write untrusted data to the terminal. + * The only control character that should be honoured is \n (which + * will behave as a CRLF). + */ +int term_data_untrusted(Terminal *term, const char *data, int len) +{ + int i; + /* FIXME: more sophisticated checking? */ + for (i = 0; i < len; i++) { + if (data[i] == '\n') + term_data(term, 1, "\r\n", 2); + else if (data[i] & 0x60) + term_data(term, 1, data + i, 1); + } + return 0; /* assumes that term_data() always returns 0 */ +} + +void term_provide_logctx(Terminal *term, void *logctx) +{ + term->logctx = logctx; +} + +void term_set_focus(Terminal *term, int has_focus) +{ + term->has_focus = has_focus; + term_schedule_cblink(term); +} + +/* + * Provide "auto" settings for remote tty modes, suitable for an + * application with a terminal window. + */ +char *term_get_ttymode(Terminal *term, const char *mode) +{ + char *val = NULL; + if (strcmp(mode, "ERASE") == 0) { + val = term->cfg.bksp_is_delete ? "^?" : "^H"; + } + /* FIXME: perhaps we should set ONLCR based on cfg.lfhascr as well? */ + /* FIXME: or ECHO and friends based on local echo state? */ + return dupstr(val); +} + +struct term_userpass_state { + size_t curr_prompt; + int done_prompt; /* printed out prompt yet? */ + size_t pos; /* cursor position */ +}; + +/* + * Process some terminal data in the course of username/password + * input. + */ +int term_get_userpass_input(Terminal *term, prompts_t *p, + unsigned char *in, int inlen) +{ + struct term_userpass_state *s = (struct term_userpass_state *)p->data; + if (!s) { + /* + * First call. Set some stuff up. + */ + p->data = s = snew(struct term_userpass_state); + s->curr_prompt = 0; + s->done_prompt = 0; + /* We only print the `name' caption if we have to... */ + if (p->name_reqd && p->name) { + size_t l = strlen(p->name); + term_data_untrusted(term, p->name, l); + if (p->name[l-1] != '\n') + term_data_untrusted(term, "\n", 1); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + size_t l = strlen(p->instruction); + term_data_untrusted(term, p->instruction, l); + if (p->instruction[l-1] != '\n') + term_data_untrusted(term, "\n", 1); + } + /* + * Zero all the results, in case we abort half-way through. + */ + { + int i; + for (i = 0; i < (int)p->n_prompts; i++) + memset(p->prompts[i]->result, 0, p->prompts[i]->result_len); + } + } + + while (s->curr_prompt < p->n_prompts) { + + prompt_t *pr = p->prompts[s->curr_prompt]; + int finished_prompt = 0; + + if (!s->done_prompt) { + term_data_untrusted(term, pr->prompt, strlen(pr->prompt)); + s->done_prompt = 1; + s->pos = 0; + } + + /* Breaking out here ensures that the prompt is printed even + * if we're now waiting for user data. */ + if (!in || !inlen) break; + + /* FIXME: should we be using local-line-editing code instead? */ + while (!finished_prompt && inlen) { + char c = *in++; + inlen--; + switch (c) { + case 10: + case 13: + term_data(term, 0, "\r\n", 2); + pr->result[s->pos] = '\0'; + pr->result[pr->result_len - 1] = '\0'; + /* go to next prompt, if any */ + s->curr_prompt++; + s->done_prompt = 0; + finished_prompt = 1; /* break out */ + break; + case 8: + case 127: + if (s->pos > 0) { + if (pr->echo) + term_data(term, 0, "\b \b", 3); + s->pos--; + } + break; + case 21: + case 27: + while (s->pos > 0) { + if (pr->echo) + term_data(term, 0, "\b \b", 3); + s->pos--; + } + break; + case 3: + case 4: + /* Immediate abort. */ + term_data(term, 0, "\r\n", 2); + sfree(s); + p->data = NULL; + return 0; /* user abort */ + default: + /* + * This simplistic check for printability is disabled + * when we're doing password input, because some people + * have control characters in their passwords. + */ + if ((!pr->echo || + (c >= ' ' && c <= '~') || + ((unsigned char) c >= 160)) + && s->pos < pr->result_len - 1) { + pr->result[s->pos++] = c; + if (pr->echo) + term_data(term, 0, &c, 1); + } + break; + } + } + + } + + if (s->curr_prompt < p->n_prompts) { + return -1; /* more data required */ + } else { + sfree(s); + p->data = NULL; + return +1; /* all done */ + } +} diff --git a/putty/TERMINAL.H b/putty/TERMINAL.H new file mode 100644 index 0000000..6d3b1c5 --- /dev/null +++ b/putty/TERMINAL.H @@ -0,0 +1,280 @@ +/* + * Internals of the Terminal structure, for those other modules + * which need to look inside it. It would be nice if this could be + * folded back into terminal.c in future, with an abstraction layer + * to handle everything that other modules need to know about it; + * but for the moment, this will do. + */ + +#ifndef PUTTY_TERMINAL_H +#define PUTTY_TERMINAL_H + +#include "tree234.h" + +struct beeptime { + struct beeptime *next; + unsigned long ticks; +}; + +typedef struct { + int y, x; +} pos; + +#ifdef OPTIMISE_SCROLL +struct scrollregion { + struct scrollregion *next; + int topline; /* Top line of scroll region. */ + int botline; /* Bottom line of scroll region. */ + int lines; /* Number of lines to scroll by - +ve is forwards. */ +}; +#endif /* OPTIMISE_SCROLL */ + +typedef struct termchar termchar; +typedef struct termline termline; + +struct termchar { + /* + * Any code in terminal.c which definitely needs to be changed + * when extra fields are added here is labelled with a comment + * saying FULL-TERMCHAR. + */ + unsigned long chr; + unsigned long attr; + + /* + * The cc_next field is used to link multiple termchars + * together into a list, so as to fit more than one character + * into a character cell (Unicode combining characters). + * + * cc_next is a relative offset into the current array of + * termchars. I.e. to advance to the next character in a list, + * one does `tc += tc->next'. + * + * Zero means end of list. + */ + int cc_next; +}; + +struct termline { + unsigned short lattr; + int cols; /* number of real columns on the line */ + int size; /* number of allocated termchars + * (cc-lists may make this > cols) */ + int temporary; /* TRUE if decompressed from scrollback */ + int cc_free; /* offset to first cc in free list */ + struct termchar *chars; +}; + +struct bidi_cache_entry { + int width; + struct termchar *chars; + int *forward, *backward; /* the permutations of line positions */ +}; + +struct terminal_tag { + + int compatibility_level; + + tree234 *scrollback; /* lines scrolled off top of screen */ + tree234 *screen; /* lines on primary screen */ + tree234 *alt_screen; /* lines on alternate screen */ + int disptop; /* distance scrolled back (0 or -ve) */ + int tempsblines; /* number of lines of .scrollback that + can be retrieved onto the terminal + ("temporary scrollback") */ + + termline **disptext; /* buffer of text on real screen */ + int dispcursx, dispcursy; /* location of cursor on real screen */ + int curstype; /* type of cursor on real screen */ + +#define VBELL_TIMEOUT (TICKSPERSEC/10) /* visual bell lasts 1/10 sec */ + + struct beeptime *beephead, *beeptail; + int nbeeps; + int beep_overloaded; + long lastbeep; + +#define TTYPE termchar +#define TSIZE (sizeof(TTYPE)) + +#ifdef OPTIMISE_SCROLL + struct scrollregion *scrollhead, *scrolltail; +#endif /* OPTIMISE_SCROLL */ + + int default_attr, curr_attr, save_attr; + termchar basic_erase_char, erase_char; + + bufchain inbuf; /* terminal input buffer */ + pos curs; /* cursor */ + pos savecurs; /* saved cursor position */ + int marg_t, marg_b; /* scroll margins */ + int dec_om; /* DEC origin mode flag */ + int wrap, wrapnext; /* wrap flags */ + int insert; /* insert-mode flag */ + int cset; /* 0 or 1: which char set */ + int save_cset, save_csattr; /* saved with cursor position */ + int save_utf, save_wnext; /* saved with cursor position */ + int rvideo; /* global reverse video flag */ + unsigned long rvbell_startpoint; /* for ESC[?5hESC[?5l vbell */ + int cursor_on; /* cursor enabled flag */ + int reset_132; /* Flag ESC c resets to 80 cols */ + int use_bce; /* Use Background coloured erase */ + int cblinker; /* When blinking is the cursor on ? */ + int tblinker; /* When the blinking text is on */ + int blink_is_real; /* Actually blink blinking text */ + int term_echoing; /* Does terminal want local echo? */ + int term_editing; /* Does terminal want local edit? */ + int sco_acs, save_sco_acs; /* CSI 10,11,12m -> OEM charset */ + int vt52_bold; /* Force bold on non-bold colours */ + int utf; /* Are we in toggleable UTF-8 mode? */ + int utf_state; /* Is there a pending UTF-8 character */ + int utf_char; /* and what is it so far. */ + int utf_size; /* The size of the UTF character. */ + int printing, only_printing; /* Are we doing ANSI printing? */ + int print_state; /* state of print-end-sequence scan */ + bufchain printer_buf; /* buffered data for printer */ + printer_job *print_job; + + /* ESC 7 saved state for the alternate screen */ + pos alt_savecurs; + int alt_save_attr; + int alt_save_cset, alt_save_csattr; + int alt_save_utf, alt_save_wnext; + int alt_save_sco_acs; + + int rows, cols, savelines; + int has_focus; + int in_vbell; + long vbell_end; + int app_cursor_keys, app_keypad_keys, vt52_mode; + int repeat_off, cr_lf_return; + int seen_disp_event; + int big_cursor; + + int xterm_mouse; /* send mouse messages to host */ + int mouse_is_down; /* used while tracking mouse buttons */ + + int cset_attr[2]; + +/* + * Saved settings on the alternate screen. + */ + int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins; + int alt_cset, alt_sco_acs, alt_utf; + int alt_t, alt_b; + int alt_which; + int alt_sblines; /* # of lines on alternate screen that should be used for scrollback. */ + +#define ARGS_MAX 32 /* max # of esc sequence arguments */ +#define ARG_DEFAULT 0 /* if an arg isn't specified */ +#define def(a,d) ( (a) == ARG_DEFAULT ? (d) : (a) ) + int esc_args[ARGS_MAX]; + int esc_nargs; + int esc_query; +#define ANSI(x,y) ((x)+((y)<<8)) +#define ANSI_QUE(x) ANSI(x,TRUE) + +#define OSC_STR_MAX 2048 + int osc_strlen; + char osc_string[OSC_STR_MAX + 1]; + int osc_w; + + char id_string[1024]; + + unsigned char *tabs; + + enum { + TOPLEVEL, + SEEN_ESC, + SEEN_CSI, + SEEN_OSC, + SEEN_OSC_W, + + DO_CTRLS, + + SEEN_OSC_P, + OSC_STRING, OSC_MAYBE_ST, + VT52_ESC, + VT52_Y1, + VT52_Y2, + VT52_FG, + VT52_BG + } termstate; + + enum { + NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED + } selstate; + enum { + LEXICOGRAPHIC, RECTANGULAR + } seltype; + enum { + SM_CHAR, SM_WORD, SM_LINE + } selmode; + pos selstart, selend, selanchor; + + short wordness[256]; + + /* Mask of attributes to pay attention to when painting. */ + int attr_mask; + + wchar_t *paste_buffer; + int paste_len, paste_pos, paste_hold; + long last_paste; + + void (*resize_fn)(void *, int, int); + void *resize_ctx; + + void *ldisc; + + void *frontend; + + void *logctx; + + struct unicode_data *ucsdata; + + /* + * We maintain a full _copy_ of a Config structure 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. + */ + Config cfg; + + /* + * from_backend calls term_out, but it can also be called from + * the ldisc if the ldisc is called _within_ term_out. So we + * have to guard against re-entrancy - if from_backend is + * called recursively like this, it will simply add data to the + * end of the buffer term_out is in the process of working + * through. + */ + int in_term_out; + + /* + * We schedule a window update shortly after receiving terminal + * data. This tracks whether one is currently pending. + */ + int window_update_pending; + long next_update; + + /* + * Track pending blinks and tblinks. + */ + int tblink_pending, cblink_pending; + long next_tblink, next_cblink; + + /* + * These are buffers used by the bidi and Arabic shaping code. + */ + termchar *ltemp; + int ltemp_size; + bidi_char *wcFrom, *wcTo; + int wcFromTo_size; + struct bidi_cache_entry *pre_bidi_cache, *post_bidi_cache; + int bidi_cache_size; +}; + +#define in_utf(term) ((term)->utf || (term)->ucsdata->line_codepage==CP_UTF8) + +#endif diff --git a/putty/TESTBACK.C b/putty/TESTBACK.C new file mode 100644 index 0000000..0dd26d9 --- /dev/null +++ b/putty/TESTBACK.C @@ -0,0 +1,180 @@ +/* $Id: testback.c 7628 2007-06-30 21:56:44Z jacob $ */ +/* + * Copyright (c) 1999 Simon Tatham + * Copyright (c) 1999 Ben Harris + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* PuTTY test backends */ + +#include +#include + +#include "putty.h" + +static const char *null_init(void *, void **, Config *, char *, int, char **, + int, int); +static const char *loop_init(void *, void **, Config *, char *, int, char **, + int, int); +static void null_free(void *); +static void loop_free(void *); +static void null_reconfig(void *, Config *); +static int null_send(void *, char *, int); +static int loop_send(void *, char *, int); +static int null_sendbuffer(void *); +static void null_size(void *, int, int); +static void null_special(void *, Telnet_Special); +static const struct telnet_special *null_get_specials(void *handle); +static int null_connected(void *); +static int null_exitcode(void *); +static int null_sendok(void *); +static int null_ldisc(void *, int); +static void null_provide_ldisc(void *, void *); +static void null_provide_logctx(void *, void *); +static void null_unthrottle(void *, int); +static int null_cfg_info(void *); + +Backend null_backend = { + null_init, null_free, null_reconfig, null_send, null_sendbuffer, null_size, + null_special, null_get_specials, null_connected, null_exitcode, null_sendok, + null_ldisc, null_provide_ldisc, null_provide_logctx, null_unthrottle, + null_cfg_info, "null", -1, 0 +}; + +Backend loop_backend = { + loop_init, loop_free, null_reconfig, loop_send, null_sendbuffer, null_size, + null_special, null_get_specials, null_connected, null_exitcode, null_sendok, + null_ldisc, null_provide_ldisc, null_provide_logctx, null_unthrottle, + null_cfg_info, "loop", -1, 0 +}; + +struct loop_state { + Terminal *term; +}; + +static const char *null_init(void *frontend_handle, void **backend_handle, + Config *cfg, char *host, int port, + char **realhost, int nodelay, int keepalive) { + + return NULL; +} + +static const char *loop_init(void *frontend_handle, void **backend_handle, + Config *cfg, char *host, int port, + char **realhost, int nodelay, int keepalive) { + struct loop_state *st = snew(struct loop_state); + + st->term = frontend_handle; + *backend_handle = st; + return NULL; +} + +static void null_free(void *handle) +{ + +} + +static void loop_free(void *handle) +{ + + sfree(handle); +} + +static void null_reconfig(void *handle, Config *cfg) { + +} + +static int null_send(void *handle, char *buf, int len) { + + return 0; +} + +static int loop_send(void *handle, char *buf, int len) { + struct loop_state *st = handle; + + return from_backend(st->term, 0, buf, len); +} + +static int null_sendbuffer(void *handle) { + + return 0; +} + +static void null_size(void *handle, int width, int height) { + +} + +static void null_special(void *handle, Telnet_Special code) { + +} + +static const struct telnet_special *null_get_specials (void *handle) { + + return NULL; +} + +static int null_connected(void *handle) { + + return 0; +} + +static int null_exitcode(void *handle) { + + return 0; +} + +static int null_sendok(void *handle) { + + return 1; +} + +static void null_unthrottle(void *handle, int backlog) { + +} + +static int null_ldisc(void *handle, int option) { + + return 0; +} + +static void null_provide_ldisc (void *handle, void *ldisc) { + +} + +static void null_provide_logctx(void *handle, void *logctx) { + +} + +static int null_cfg_info(void *handle) +{ + return 0; +} + + +/* + * Emacs magic: + * Local Variables: + * c-file-style: "simon" + * End: + */ diff --git a/putty/TESTDATA/BIGNUM.PY b/putty/TESTDATA/BIGNUM.PY new file mode 100644 index 0000000..0a24780 --- /dev/null +++ b/putty/TESTDATA/BIGNUM.PY @@ -0,0 +1,115 @@ +# Generate test cases for a bignum implementation. + +import sys + +# integer square roots +def sqrt(n): + d = long(n) + a = 0L + # b must start off as a power of 4 at least as large as n + ndigits = len(hex(long(n))) + b = 1L << (ndigits*4) + while 1: + a = a >> 1 + di = 2*a + b + if di <= d: + d = d - di + a = a + b + b = b >> 2 + if b == 0: break + return a + +# continued fraction convergents of a rational +def confrac(n, d): + coeffs = [(1,0),(0,1)] + while d != 0: + i = n / d + n, d = d, n % d + coeffs.append((coeffs[-2][0]-i*coeffs[-1][0], + coeffs[-2][1]-i*coeffs[-1][1])) + return coeffs + +def findprod(target, dir = +1, ratio=(1,1)): + # Return two numbers whose product is as close as we can get to + # 'target', with any deviation having the sign of 'dir', and in + # the same approximate ratio as 'ratio'. + + r = sqrt(target * ratio[0] * ratio[1]) + a = r / ratio[1] + b = r / ratio[0] + if a*b * dir < target * dir: + a = a + 1 + b = b + 1 + assert a*b * dir >= target * dir + + best = (a,b,a*b) + + while 1: + improved = 0 + a, b = best[:2] + + coeffs = confrac(a, b) + for c in coeffs: + # a*c[0]+b*c[1] is as close as we can get it to zero. So + # if we replace a and b with a+c[1] and b+c[0], then that + # will be added to our product, along with c[0]*c[1]. + da, db = c[1], c[0] + + # Flip signs as appropriate. + if (a+da) * (b+db) * dir < target * dir: + da, db = -da, -db + + # Multiply up. We want to get as close as we can to a + # solution of the quadratic equation in n + # + # (a + n da) (b + n db) = target + # => n^2 da db + n (b da + a db) + (a b - target) = 0 + A,B,C = da*db, b*da+a*db, a*b-target + discrim = B^2-4*A*C + if discrim > 0 and A != 0: + root = sqrt(discrim) + vals = [] + vals.append((-B + root) / (2*A)) + vals.append((-B - root) / (2*A)) + if root * root != discrim: + root = root + 1 + vals.append((-B + root) / (2*A)) + vals.append((-B - root) / (2*A)) + + for n in vals: + ap = a + da*n + bp = b + db*n + pp = ap*bp + if pp * dir >= target * dir and pp * dir < best[2]*dir: + best = (ap, bp, pp) + improved = 1 + + if not improved: + break + + return best + +def hexstr(n): + s = hex(n) + if s[:2] == "0x": s = s[2:] + if s[-1:] == "L": s = s[:-1] + return s + +# Tests of multiplication which exercise the propagation of the last +# carry to the very top of the number. +for i in range(1,4200): + a, b, p = findprod((1< +#include + +struct tm ltime(void) +{ + time_t t; + time(&t); + assert (t != ((time_t)-1)); + return *localtime(&t); +} diff --git a/putty/TIMING.C b/putty/TIMING.C new file mode 100644 index 0000000..2b7b70c --- /dev/null +++ b/putty/TIMING.C @@ -0,0 +1,243 @@ +/* + * timing.c + * + * This module tracks any timers set up by schedule_timer(). It + * keeps all the currently active timers in a list; it informs the + * front end of when the next timer is due to go off if that + * changes; and, very importantly, it tracks the context pointers + * passed to schedule_timer(), so that if a context is freed all + * the timers associated with it can be immediately annulled. + */ + +#include +#include + +#include "putty.h" +#include "tree234.h" + +struct timer { + timer_fn_t fn; + void *ctx; + long now; +}; + +static tree234 *timers = NULL; +static tree234 *timer_contexts = NULL; +static long now = 0L; + +static int compare_timers(void *av, void *bv) +{ + struct timer *a = (struct timer *)av; + struct timer *b = (struct timer *)bv; + long at = a->now - now; + long bt = b->now - now; + + if (at < bt) + return -1; + else if (at > bt) + return +1; + + /* + * Failing that, compare on the other two fields, just so that + * we don't get unwanted equality. + */ +#ifdef __LCC__ + /* lcc won't let us compare function pointers. Legal, but annoying. */ + { + int c = memcmp(&a->fn, &b->fn, sizeof(a->fn)); + if (c < 0) + return -1; + else if (c > 0) + return +1; + } +#else + if (a->fn < b->fn) + return -1; + else if (a->fn > b->fn) + return +1; +#endif + + if (a->ctx < b->ctx) + return -1; + else if (a->ctx > b->ctx) + return +1; + + /* + * Failing _that_, the two entries genuinely are equal, and we + * never have a need to store them separately in the tree. + */ + return 0; +} + +static int compare_timer_contexts(void *av, void *bv) +{ + char *a = (char *)av; + char *b = (char *)bv; + if (a < b) + return -1; + else if (a > b) + return +1; + return 0; +} + +static void init_timers(void) +{ + if (!timers) { + timers = newtree234(compare_timers); + timer_contexts = newtree234(compare_timer_contexts); + now = GETTICKCOUNT(); + } +} + +long schedule_timer(int ticks, timer_fn_t fn, void *ctx) +{ + long when; + struct timer *t, *first; + + init_timers(); + + when = ticks + GETTICKCOUNT(); + + /* + * Just in case our various defences against timing skew fail + * us: if we try to schedule a timer that's already in the + * past, we instead schedule it for the immediate future. + */ + if (when - now <= 0) + when = now + 1; + + t = snew(struct timer); + t->fn = fn; + t->ctx = ctx; + t->now = when; + + if (t != add234(timers, t)) { + sfree(t); /* identical timer already exists */ + } else { + add234(timer_contexts, t->ctx);/* don't care if this fails */ + } + + first = (struct timer *)index234(timers, 0); + if (first == t) { + /* + * This timer is the very first on the list, so we must + * notify the front end. + */ + timer_change_notify(first->now); + } + + return when; +} + +/* + * Call to run any timers whose time has reached the present. + * Returns the time (in ticks) expected until the next timer after + * that triggers. + */ +int run_timers(long anow, long *next) +{ + struct timer *first; + + init_timers(); + +#ifdef TIMING_SYNC + /* + * In this ifdef I put some code which deals with the + * possibility that `anow' disagrees with GETTICKCOUNT by a + * significant margin. Our strategy for dealing with it differs + * depending on platform, because on some platforms + * GETTICKCOUNT is more likely to be right whereas on others + * `anow' is a better gold standard. + */ + { + long tnow = GETTICKCOUNT(); + + if (tnow + TICKSPERSEC/50 - anow < 0 || + anow + TICKSPERSEC/50 - tnow < 0 + ) { +#if defined TIMING_SYNC_ANOW + /* + * If anow is accurate and the tick count is wrong, + * this is likely to be because the tick count is + * derived from the system clock which has changed (as + * can occur on Unix). Therefore, we resolve this by + * inventing an offset which is used to adjust all + * future output from GETTICKCOUNT. + * + * A platform which defines TIMING_SYNC_ANOW is + * expected to have also defined this offset variable + * in (its platform-specific adjunct to) putty.h. + * Therefore we can simply reference it here and assume + * that it will exist. + */ + tickcount_offset += anow - tnow; +#elif defined TIMING_SYNC_TICKCOUNT + /* + * If the tick count is more likely to be accurate, we + * simply use that as our time value, which may mean we + * run no timers in this call (because we got called + * early), or alternatively it may mean we run lots of + * timers in a hurry because we were called late. + */ + anow = tnow; +#else +/* + * Any platform which defines TIMING_SYNC must also define one of the two + * auxiliary symbols TIMING_SYNC_ANOW and TIMING_SYNC_TICKCOUNT, to + * indicate which measurement to trust when the two disagree. + */ +#error TIMING_SYNC definition incomplete +#endif + } + } +#endif + + now = anow; + + while (1) { + first = (struct timer *)index234(timers, 0); + + if (!first) + return FALSE; /* no timers remaining */ + + if (find234(timer_contexts, first->ctx, NULL) == NULL) { + /* + * This timer belongs to a context that has been + * expired. Delete it without running. + */ + delpos234(timers, 0); + sfree(first); + } else if (first->now - now <= 0) { + /* + * This timer is active and has reached its running + * time. Run it. + */ + delpos234(timers, 0); + first->fn(first->ctx, first->now); + sfree(first); + } else { + /* + * This is the first still-active timer that is in the + * future. Return how long it has yet to go. + */ + *next = first->now; + return TRUE; + } + } +} + +/* + * Call to expire all timers associated with a given context. + */ +void expire_timer_context(void *ctx) +{ + init_timers(); + + /* + * We don't bother to check the return value; if the context + * already wasn't in the tree (presumably because no timers + * ever actually got scheduled for it) then that's fine and we + * simply don't need to do anything. + */ + del234(timer_contexts, ctx); +} diff --git a/putty/TREE234.C b/putty/TREE234.C new file mode 100644 index 0000000..4e2da9d --- /dev/null +++ b/putty/TREE234.C @@ -0,0 +1,1479 @@ +/* + * tree234.c: reasonably generic counted 2-3-4 tree routines. + * + * This file is copyright 1999-2001 Simon Tatham. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +#include "puttymem.h" +#include "tree234.h" + +#ifdef TEST +#define LOG(x) (printf x) +#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; +} + +/* + * 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, np - n->kids)); + if (n->elems[1] == NULL) { + /* + * Insert in a 2-node; simple. + */ + if (np == &n->kids[0]) { + LOG((" inserting on left of 2-node\n")); + n->kids[2] = n->kids[1]; + n->counts[2] = n->counts[1]; + n->elems[1] = n->elems[0]; + n->kids[1] = right; + n->counts[1] = rcount; + n->elems[0] = e; + n->kids[0] = left; + n->counts[0] = lcount; + } else { /* np == &n->kids[1] */ + LOG((" inserting on right of 2-node\n")); + n->kids[2] = right; + n->counts[2] = rcount; + n->elems[1] = e; + n->kids[1] = left; + n->counts[1] = lcount; + } + if (n->kids[0]) + n->kids[0]->parent = n; + if (n->kids[1]) + n->kids[1]->parent = n; + if (n->kids[2]) + n->kids[2]->parent = n; + LOG((" done\n")); + break; + } else if (n->elems[2] == NULL) { + /* + * Insert in a 3-node; simple. + */ + if (np == &n->kids[0]) { + LOG((" inserting on left of 3-node\n")); + n->kids[3] = n->kids[2]; + n->counts[3] = n->counts[2]; + n->elems[2] = n->elems[1]; + n->kids[2] = n->kids[1]; + n->counts[2] = n->counts[1]; + n->elems[1] = n->elems[0]; + n->kids[1] = right; + n->counts[1] = rcount; + n->elems[0] = e; + n->kids[0] = left; + n->counts[0] = lcount; + } else if (np == &n->kids[1]) { + LOG((" inserting in middle of 3-node\n")); + n->kids[3] = n->kids[2]; + n->counts[3] = n->counts[2]; + n->elems[2] = n->elems[1]; + n->kids[2] = right; + n->counts[2] = rcount; + n->elems[1] = e; + n->kids[1] = left; + n->counts[1] = lcount; + } else { /* np == &n->kids[2] */ + LOG((" inserting on right of 3-node\n")); + n->kids[3] = right; + n->counts[3] = rcount; + n->elems[2] = e; + n->kids[2] = left; + n->counts[2] = lcount; + } + if (n->kids[0]) + n->kids[0]->parent = n; + if (n->kids[1]) + n->kids[1]->parent = n; + if (n->kids[2]) + n->kids[2]->parent = n; + if (n->kids[3]) + n->kids[3]->parent = n; + LOG((" done\n")); + break; + } else { + node234 *m = snew(node234); + m->parent = n->parent; + LOG((" splitting a 4-node; created new node %p\n", m)); + /* + * Insert in a 4-node; split into a 2-node and a + * 3-node, and move focus up a level. + * + * I don't think it matters which way round we put the + * 2 and the 3. For simplicity, we'll put the 3 first + * always. + */ + if (np == &n->kids[0]) { + m->kids[0] = left; + m->counts[0] = lcount; + m->elems[0] = e; + m->kids[1] = right; + m->counts[1] = rcount; + m->elems[1] = n->elems[0]; + m->kids[2] = n->kids[1]; + m->counts[2] = n->counts[1]; + e = n->elems[1]; + n->kids[0] = n->kids[2]; + n->counts[0] = n->counts[2]; + n->elems[0] = n->elems[2]; + n->kids[1] = n->kids[3]; + n->counts[1] = n->counts[3]; + } else if (np == &n->kids[1]) { + m->kids[0] = n->kids[0]; + m->counts[0] = n->counts[0]; + m->elems[0] = n->elems[0]; + m->kids[1] = left; + m->counts[1] = lcount; + m->elems[1] = e; + m->kids[2] = right; + m->counts[2] = rcount; + e = n->elems[1]; + n->kids[0] = n->kids[2]; + n->counts[0] = n->counts[2]; + n->elems[0] = n->elems[2]; + n->kids[1] = n->kids[3]; + n->counts[1] = n->counts[3]; + } else if (np == &n->kids[2]) { + m->kids[0] = n->kids[0]; + m->counts[0] = n->counts[0]; + m->elems[0] = n->elems[0]; + m->kids[1] = n->kids[1]; + m->counts[1] = n->counts[1]; + m->elems[1] = n->elems[1]; + m->kids[2] = left; + m->counts[2] = lcount; + /* e = e; */ + n->kids[0] = right; + n->counts[0] = rcount; + n->elems[0] = n->elems[2]; + n->kids[1] = n->kids[3]; + n->counts[1] = n->counts[3]; + } else { /* np == &n->kids[3] */ + m->kids[0] = n->kids[0]; + m->counts[0] = n->counts[0]; + m->elems[0] = n->elems[0]; + m->kids[1] = n->kids[1]; + m->counts[1] = n->counts[1]; + m->elems[1] = n->elems[1]; + m->kids[2] = n->kids[2]; + m->counts[2] = n->counts[2]; + n->kids[0] = left; + n->counts[0] = lcount; + n->elems[0] = e; + n->kids[1] = right; + n->counts[1] = rcount; + e = n->elems[2]; + } + m->kids[3] = n->kids[3] = n->kids[2] = NULL; + m->counts[3] = n->counts[3] = n->counts[2] = 0; + m->elems[2] = n->elems[2] = n->elems[1] = NULL; + if (m->kids[0]) + m->kids[0]->parent = m; + if (m->kids[1]) + m->kids[1]->parent = m; + if (m->kids[2]) + m->kids[2]->parent = m; + if (n->kids[0]) + n->kids[0]->parent = n; + if (n->kids[1]) + n->kids[1]->parent = n; + LOG((" left (%p): %p/%d [%p] %p/%d [%p] %p/%d\n", m, + m->kids[0], m->counts[0], m->elems[0], + m->kids[1], m->counts[1], m->elems[1], + m->kids[2], m->counts[2])); + LOG((" right (%p): %p/%d [%p] %p/%d\n", n, + n->kids[0], n->counts[0], n->elems[0], + n->kids[1], n->counts[1])); + left = m; + lcount = countnode234(left); + right = n; + rcount = countnode234(right); + } + if (n->parent) + np = (n->parent->kids[0] == n ? &n->parent->kids[0] : + n->parent->kids[1] == n ? &n->parent->kids[1] : + n->parent->kids[2] == n ? &n->parent->kids[2] : + &n->parent->kids[3]); + n = n->parent; + } + + /* + * If we've come out of here by `break', n will still be + * non-NULL and all we need to do is go back up the tree + * updating counts. If we've come here because n is NULL, we + * need to create a new root for the tree because the old one + * has just split into two. */ + if (n) { + while (n->parent) { + int count = countnode234(n); + int childnum; + childnum = (n->parent->kids[0] == n ? 0 : + n->parent->kids[1] == n ? 1 : + n->parent->kids[2] == n ? 2 : 3); + n->parent->counts[childnum] = count; + n = n->parent; + } + } else { + LOG((" root is overloaded, split into two\n")); + t->root = snew(node234); + t->root->kids[0] = left; + t->root->counts[0] = lcount; + t->root->elems[0] = e; + t->root->kids[1] = right; + t->root->counts[1] = rcount; + t->root->elems[1] = NULL; + t->root->kids[2] = NULL; + t->root->counts[2] = 0; + t->root->elems[2] = NULL; + t->root->kids[3] = NULL; + t->root->counts[3] = 0; + t->root->parent = NULL; + if (t->root->kids[0]) + t->root->kids[0]->parent = t->root; + if (t->root->kids[1]) + t->root->kids[1]->parent = t->root; + LOG((" new root is %p/%d [%p] %p/%d\n", + t->root->kids[0], t->root->counts[0], + t->root->elems[0], t->root->kids[1], t->root->counts[1])); + } + + return orig_e; +} + +void *add234(tree234 * t, void *e) +{ + if (!t->cmp) /* tree is unsorted */ + return NULL; + + return add234_internal(t, e, -1); +} +void *addpos234(tree234 * t, void *e, int index) +{ + if (index < 0 || /* index out of range */ + t->cmp) /* tree is sorted */ + return NULL; /* return failure */ + + return add234_internal(t, e, index); /* this checks the upper bound */ +} + +/* + * Look up the element at a given numeric index in a 2-3-4 tree. + * Returns NULL if the index is out of range. + */ +void *index234(tree234 * t, int index) +{ + node234 *n; + + if (!t->root) + return NULL; /* tree is empty */ + + if (index < 0 || index >= countnode234(t->root)) + return NULL; /* out of range */ + + n = t->root; + + while (n) { + if (index < n->counts[0]) + n = n->kids[0]; + else if (index -= n->counts[0] + 1, index < 0) + return n->elems[0]; + else if (index < n->counts[1]) + n = n->kids[1]; + else if (index -= n->counts[1] + 1, index < 0) + return n->elems[1]; + else if (index < n->counts[2]) + n = n->kids[2]; + else if (index -= n->counts[2] + 1, index < 0) + return n->elems[2]; + else + n = n->kids[3]; + } + + /* We shouldn't ever get here. I wonder how we did. */ + return NULL; +} + +/* + * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not + * found. e is always passed as the first argument to cmp, so cmp + * can be an asymmetric function if desired. cmp can also be passed + * as NULL, in which case the compare function from the tree proper + * will be used. + */ +void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, + int relation, int *index) +{ + node234 *n; + void *ret; + int c; + int idx, ecount, kcount, cmpret; + + if (t->root == NULL) + return NULL; + + if (cmp == NULL) + cmp = t->cmp; + + n = t->root; + /* + * Attempt to find the element itself. + */ + idx = 0; + ecount = -1; + /* + * Prepare a fake `cmp' result if e is NULL. + */ + cmpret = 0; + if (e == NULL) { + assert(relation == REL234_LT || relation == REL234_GT); + if (relation == REL234_LT) + cmpret = +1; /* e is a max: always greater */ + else if (relation == REL234_GT) + cmpret = -1; /* e is a min: always smaller */ + } + while (1) { + for (kcount = 0; kcount < 4; kcount++) { + if (kcount >= 3 || n->elems[kcount] == NULL || + (c = cmpret ? cmpret : cmp(e, n->elems[kcount])) < 0) { + break; + } + if (n->kids[kcount]) + idx += n->counts[kcount]; + if (c == 0) { + ecount = kcount; + break; + } + idx++; + } + if (ecount >= 0) + break; + if (n->kids[kcount]) + n = n->kids[kcount]; + else + break; + } + + if (ecount >= 0) { + /* + * We have found the element we're looking for. It's + * n->elems[ecount], at tree index idx. If our search + * relation is EQ, LE or GE we can now go home. + */ + if (relation != REL234_LT && relation != REL234_GT) { + if (index) + *index = idx; + return n->elems[ecount]; + } + + /* + * Otherwise, we'll do an indexed lookup for the previous + * or next element. (It would be perfectly possible to + * implement these search types in a non-counted tree by + * going back up from where we are, but far more fiddly.) + */ + if (relation == REL234_LT) + idx--; + else + idx++; + } else { + /* + * We've found our way to the bottom of the tree and we + * know where we would insert this node if we wanted to: + * we'd put it in in place of the (empty) subtree + * n->kids[kcount], and it would have index idx + * + * But the actual element isn't there. So if our search + * relation is EQ, we're doomed. + */ + if (relation == REL234_EQ) + return NULL; + + /* + * Otherwise, we must do an index lookup for index idx-1 + * (if we're going left - LE or LT) or index idx (if we're + * going right - GE or GT). + */ + if (relation == REL234_LT || relation == REL234_LE) { + idx--; + } + } + + /* + * We know the index of the element we want; just call index234 + * to do the rest. This will return NULL if the index is out of + * bounds, which is exactly what we want. + */ + ret = index234(t, idx); + if (ret && index) + *index = idx; + return ret; +} +void *find234(tree234 * t, void *e, cmpfn234 cmp) +{ + return findrelpos234(t, e, cmp, REL234_EQ, NULL); +} +void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation) +{ + return findrelpos234(t, e, cmp, relation, NULL); +} +void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index) +{ + return findrelpos234(t, e, cmp, REL234_EQ, index); +} + +/* + * Delete an element e in a 2-3-4 tree. Does not free the element, + * merely removes all links to it from the tree nodes. + */ +static void *delpos234_internal(tree234 * t, int index) +{ + node234 *n; + void *retval; + int ei = -1; + + retval = 0; + + n = t->root; + LOG(("deleting item %d from tree %p\n", index, t)); + while (1) { + while (n) { + int ki; + node234 *sub; + + LOG( + (" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d index=%d\n", + n, n->kids[0], n->counts[0], n->elems[0], n->kids[1], + n->counts[1], n->elems[1], n->kids[2], n->counts[2], + n->elems[2], n->kids[3], n->counts[3], index)); + if (index < n->counts[0]) { + ki = 0; + } else if (index -= n->counts[0] + 1, index < 0) { + ei = 0; + break; + } else if (index < n->counts[1]) { + ki = 1; + } else if (index -= n->counts[1] + 1, index < 0) { + ei = 1; + break; + } else if (index < n->counts[2]) { + ki = 2; + } else if (index -= n->counts[2] + 1, index < 0) { + ei = 2; + break; + } else { + ki = 3; + } + /* + * Recurse down to subtree ki. If it has only one element, + * we have to do some transformation to start with. + */ + LOG((" moving to subtree %d\n", ki)); + sub = n->kids[ki]; + if (!sub->elems[1]) { + LOG((" subtree has only one element!\n", ki)); + if (ki > 0 && n->kids[ki - 1]->elems[1]) { + /* + * Case 3a, left-handed variant. Child ki has + * only one element, but child ki-1 has two or + * more. So we need to move a subtree from ki-1 + * to ki. + * + * . C . . B . + * / \ -> / \ + * [more] a A b B c d D e [more] a A b c C d D e + */ + node234 *sib = n->kids[ki - 1]; + int lastelem = (sib->elems[2] ? 2 : + sib->elems[1] ? 1 : 0); + sub->kids[2] = sub->kids[1]; + sub->counts[2] = sub->counts[1]; + sub->elems[1] = sub->elems[0]; + sub->kids[1] = sub->kids[0]; + sub->counts[1] = sub->counts[0]; + sub->elems[0] = n->elems[ki - 1]; + sub->kids[0] = sib->kids[lastelem + 1]; + sub->counts[0] = sib->counts[lastelem + 1]; + if (sub->kids[0]) + sub->kids[0]->parent = sub; + n->elems[ki - 1] = sib->elems[lastelem]; + sib->kids[lastelem + 1] = NULL; + sib->counts[lastelem + 1] = 0; + sib->elems[lastelem] = NULL; + n->counts[ki] = countnode234(sub); + LOG((" case 3a left\n")); + LOG( + (" index and left subtree count before adjustment: %d, %d\n", + index, n->counts[ki - 1])); + index += n->counts[ki - 1]; + n->counts[ki - 1] = countnode234(sib); + index -= n->counts[ki - 1]; + LOG( + (" index and left subtree count after adjustment: %d, %d\n", + index, n->counts[ki - 1])); + } else if (ki < 3 && n->kids[ki + 1] + && n->kids[ki + 1]->elems[1]) { + /* + * Case 3a, right-handed variant. ki has only + * one element but ki+1 has two or more. Move a + * subtree from ki+1 to ki. + * + * . B . . C . + * / \ -> / \ + * a A b c C d D e [more] a A b B c d D e [more] + */ + node234 *sib = n->kids[ki + 1]; + int j; + sub->elems[1] = n->elems[ki]; + sub->kids[2] = sib->kids[0]; + sub->counts[2] = sib->counts[0]; + if (sub->kids[2]) + sub->kids[2]->parent = sub; + n->elems[ki] = sib->elems[0]; + sib->kids[0] = sib->kids[1]; + sib->counts[0] = sib->counts[1]; + for (j = 0; j < 2 && sib->elems[j + 1]; j++) { + sib->kids[j + 1] = sib->kids[j + 2]; + sib->counts[j + 1] = sib->counts[j + 2]; + sib->elems[j] = sib->elems[j + 1]; + } + sib->kids[j + 1] = NULL; + sib->counts[j + 1] = 0; + sib->elems[j] = NULL; + n->counts[ki] = countnode234(sub); + n->counts[ki + 1] = countnode234(sib); + LOG((" case 3a right\n")); + } else { + /* + * Case 3b. ki has only one element, and has no + * neighbour with more than one. So pick a + * neighbour and merge it with ki, taking an + * element down from n to go in the middle. + * + * . B . . + * / \ -> | + * a A b c C d a A b B c C d + * + * (Since at all points we have avoided + * descending to a node with only one element, + * we can be sure that n is not reduced to + * nothingness by this move, _unless_ it was + * the very first node, ie the root of the + * tree. In that case we remove the now-empty + * root and replace it with its single large + * child as shown.) + */ + node234 *sib; + int j; + + if (ki > 0) { + ki--; + index += n->counts[ki] + 1; + } + sib = n->kids[ki]; + sub = n->kids[ki + 1]; + + sub->kids[3] = sub->kids[1]; + sub->counts[3] = sub->counts[1]; + sub->elems[2] = sub->elems[0]; + sub->kids[2] = sub->kids[0]; + sub->counts[2] = sub->counts[0]; + sub->elems[1] = n->elems[ki]; + sub->kids[1] = sib->kids[1]; + sub->counts[1] = sib->counts[1]; + if (sub->kids[1]) + sub->kids[1]->parent = sub; + sub->elems[0] = sib->elems[0]; + sub->kids[0] = sib->kids[0]; + sub->counts[0] = sib->counts[0]; + if (sub->kids[0]) + sub->kids[0]->parent = sub; + + n->counts[ki + 1] = countnode234(sub); + + sfree(sib); + + /* + * That's built the big node in sub. Now we + * need to remove the reference to sib in n. + */ + for (j = ki; j < 3 && n->kids[j + 1]; j++) { + n->kids[j] = n->kids[j + 1]; + n->counts[j] = n->counts[j + 1]; + n->elems[j] = j < 2 ? n->elems[j + 1] : NULL; + } + n->kids[j] = NULL; + n->counts[j] = 0; + if (j < 3) + n->elems[j] = NULL; + LOG((" case 3b ki=%d\n", ki)); + + if (!n->elems[0]) { + /* + * The root is empty and needs to be + * removed. + */ + LOG((" shifting root!\n")); + t->root = sub; + sub->parent = NULL; + sfree(n); + } + } + } + n = sub; + } + if (!retval) + retval = n->elems[ei]; + + if (ei == -1) + return NULL; /* although this shouldn't happen */ + + /* + * Treat special case: this is the one remaining item in + * the tree. n is the tree root (no parent), has one + * element (no elems[1]), and has no kids (no kids[0]). + */ + if (!n->parent && !n->elems[1] && !n->kids[0]) { + LOG((" removed last element in tree\n")); + sfree(n); + t->root = NULL; + return retval; + } + + /* + * Now we have the element we want, as n->elems[ei], and we + * have also arranged for that element not to be the only + * one in its node. So... + */ + + if (!n->kids[0] && n->elems[1]) { + /* + * Case 1. n is a leaf node with more than one element, + * so it's _really easy_. Just delete the thing and + * we're done. + */ + int i; + LOG((" case 1\n")); + for (i = ei; i < 2 && n->elems[i + 1]; i++) + n->elems[i] = n->elems[i + 1]; + n->elems[i] = NULL; + /* + * Having done that to the leaf node, we now go back up + * the tree fixing the counts. + */ + while (n->parent) { + int childnum; + childnum = (n->parent->kids[0] == n ? 0 : + n->parent->kids[1] == n ? 1 : + n->parent->kids[2] == n ? 2 : 3); + n->parent->counts[childnum]--; + n = n->parent; + } + return retval; /* finished! */ + } else if (n->kids[ei]->elems[1]) { + /* + * Case 2a. n is an internal node, and the root of the + * subtree to the left of e has more than one element. + * So find the predecessor p to e (ie the largest node + * in that subtree), place it where e currently is, and + * then start the deletion process over again on the + * subtree with p as target. + */ + node234 *m = n->kids[ei]; + void *target; + LOG((" case 2a\n")); + while (m->kids[0]) { + m = (m->kids[3] ? m->kids[3] : + m->kids[2] ? m->kids[2] : + m->kids[1] ? m->kids[1] : m->kids[0]); + } + target = (m->elems[2] ? m->elems[2] : + m->elems[1] ? m->elems[1] : m->elems[0]); + n->elems[ei] = target; + index = n->counts[ei] - 1; + n = n->kids[ei]; + } else if (n->kids[ei + 1]->elems[1]) { + /* + * Case 2b, symmetric to 2a but s/left/right/ and + * s/predecessor/successor/. (And s/largest/smallest/). + */ + node234 *m = n->kids[ei + 1]; + void *target; + LOG((" case 2b\n")); + while (m->kids[0]) { + m = m->kids[0]; + } + target = m->elems[0]; + n->elems[ei] = target; + n = n->kids[ei + 1]; + index = 0; + } else { + /* + * Case 2c. n is an internal node, and the subtrees to + * the left and right of e both have only one element. + * So combine the two subnodes into a single big node + * with their own elements on the left and right and e + * in the middle, then restart the deletion process on + * that subtree, with e still as target. + */ + node234 *a = n->kids[ei], *b = n->kids[ei + 1]; + int j; + + LOG((" case 2c\n")); + a->elems[1] = n->elems[ei]; + a->kids[2] = b->kids[0]; + a->counts[2] = b->counts[0]; + if (a->kids[2]) + a->kids[2]->parent = a; + a->elems[2] = b->elems[0]; + a->kids[3] = b->kids[1]; + a->counts[3] = b->counts[1]; + if (a->kids[3]) + a->kids[3]->parent = a; + sfree(b); + n->counts[ei] = countnode234(a); + /* + * That's built the big node in a, and destroyed b. Now + * remove the reference to b (and e) in n. + */ + for (j = ei; j < 2 && n->elems[j + 1]; j++) { + n->elems[j] = n->elems[j + 1]; + n->kids[j + 1] = n->kids[j + 2]; + n->counts[j + 1] = n->counts[j + 2]; + } + n->elems[j] = NULL; + n->kids[j + 1] = NULL; + n->counts[j + 1] = 0; + /* + * It's possible, in this case, that we've just removed + * the only element in the root of the tree. If so, + * shift the root. + */ + if (n->elems[0] == NULL) { + LOG((" shifting root!\n")); + t->root = a; + a->parent = NULL; + sfree(n); + } + /* + * Now go round the deletion process again, with n + * pointing at the new big node and e still the same. + */ + n = a; + index = a->counts[0] + a->counts[1] + 1; + } + } +} +void *delpos234(tree234 * t, int index) +{ + if (index < 0 || index >= countnode234(t->root)) + return NULL; + return delpos234_internal(t, index); +} +void *del234(tree234 * t, void *e) +{ + int index; + if (!findrelpos234(t, e, NULL, REL234_EQ, &index)) + return NULL; /* it wasn't in there anyway */ + return delpos234_internal(t, index); /* it's there; delete it. */ +} + +#ifdef TEST + +/* + * Test code for the 2-3-4 tree. This code maintains an alternative + * representation of the data in the tree, in an array (using the + * obvious and slow insert and delete functions). After each tree + * operation, the verify() function is called, which ensures all + * the tree properties are preserved: + * - node->child->parent always equals node + * - tree->root->parent always equals NULL + * - number of kids == 0 or number of elements + 1; + * - tree has the same depth everywhere + * - every node has at least one element + * - subtree element counts are accurate + * - any NULL kid pointer is accompanied by a zero count + * - in a sorted tree: ordering property between elements of a + * node and elements of its children is preserved + * and also ensures the list represented by the tree is the same + * list it should be. (This last check also doubly verifies the + * ordering properties, because the `same list it should be' is by + * definition correctly ordered. It also ensures all nodes are + * distinct, because the enum functions would get caught in a loop + * if not.) + */ + +#include + +/* + * Error reporting function. + */ +void error(char *fmt, ...) +{ + va_list ap; + printf("ERROR: "); + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + printf("\n"); +} + +/* The array representation of the data. */ +void **array; +int arraylen, arraysize; +cmpfn234 cmp; + +/* The tree representation of the same data. */ +tree234 *tree; + +typedef struct { + int treedepth; + int elemcount; +} chkctx; + +int chknode(chkctx * ctx, int level, node234 * node, + void *lowbound, void *highbound) +{ + int nkids, nelems; + int i; + int count; + + /* Count the non-NULL kids. */ + for (nkids = 0; nkids < 4 && node->kids[nkids]; nkids++); + /* Ensure no kids beyond the first NULL are non-NULL. */ + for (i = nkids; i < 4; i++) + if (node->kids[i]) { + error("node %p: nkids=%d but kids[%d] non-NULL", + node, nkids, i); + } else if (node->counts[i]) { + error("node %p: kids[%d] NULL but count[%d]=%d nonzero", + node, i, i, node->counts[i]); + } + + /* Count the non-NULL elements. */ + for (nelems = 0; nelems < 3 && node->elems[nelems]; nelems++); + /* Ensure no elements beyond the first NULL are non-NULL. */ + for (i = nelems; i < 3; i++) + if (node->elems[i]) { + error("node %p: nelems=%d but elems[%d] non-NULL", + node, nelems, i); + } + + if (nkids == 0) { + /* + * If nkids==0, this is a leaf node; verify that the tree + * depth is the same everywhere. + */ + if (ctx->treedepth < 0) + ctx->treedepth = level; /* we didn't know the depth yet */ + else if (ctx->treedepth != level) + error("node %p: leaf at depth %d, previously seen depth %d", + node, level, ctx->treedepth); + } else { + /* + * If nkids != 0, then it should be nelems+1, unless nelems + * is 0 in which case nkids should also be 0 (and so we + * shouldn't be in this condition at all). + */ + int shouldkids = (nelems ? nelems + 1 : 0); + if (nkids != shouldkids) { + error("node %p: %d elems should mean %d kids but has %d", + node, nelems, shouldkids, nkids); + } + } + + /* + * nelems should be at least 1. + */ + if (nelems == 0) { + error("node %p: no elems", node, nkids); + } + + /* + * Add nelems to the running element count of the whole tree. + */ + ctx->elemcount += nelems; + + /* + * Check ordering property: all elements should be strictly > + * lowbound, strictly < highbound, and strictly < each other in + * sequence. (lowbound and highbound are NULL at edges of tree + * - both NULL at root node - and NULL is considered to be < + * everything and > everything. IYSWIM.) + */ + if (cmp) { + for (i = -1; i < nelems; i++) { + void *lower = (i == -1 ? lowbound : node->elems[i]); + void *higher = + (i + 1 == nelems ? highbound : node->elems[i + 1]); + if (lower && higher && cmp(lower, higher) >= 0) { + error("node %p: kid comparison [%d=%s,%d=%s] failed", + node, i, lower, i + 1, higher); + } + } + } + + /* + * Check parent pointers: all non-NULL kids should have a + * parent pointer coming back to this node. + */ + for (i = 0; i < nkids; i++) + if (node->kids[i]->parent != node) { + error("node %p kid %d: parent ptr is %p not %p", + node, i, node->kids[i]->parent, node); + } + + + /* + * Now (finally!) recurse into subtrees. + */ + count = nelems; + + for (i = 0; i < nkids; i++) { + void *lower = (i == 0 ? lowbound : node->elems[i - 1]); + void *higher = (i >= nelems ? highbound : node->elems[i]); + int subcount = + chknode(ctx, level + 1, node->kids[i], lower, higher); + if (node->counts[i] != subcount) { + error("node %p kid %d: count says %d, subtree really has %d", + node, i, node->counts[i], subcount); + } + count += subcount; + } + + return count; +} + +void verify(void) +{ + chkctx ctx; + int i; + void *p; + + ctx.treedepth = -1; /* depth unknown yet */ + ctx.elemcount = 0; /* no elements seen yet */ + /* + * Verify validity of tree properties. + */ + if (tree->root) { + if (tree->root->parent != NULL) + error("root->parent is %p should be null", tree->root->parent); + chknode(&ctx, 0, tree->root, NULL, NULL); + } + printf("tree depth: %d\n", ctx.treedepth); + /* + * Enumerate the tree and ensure it matches up to the array. + */ + for (i = 0; NULL != (p = index234(tree, i)); i++) { + if (i >= arraylen) + error("tree contains more than %d elements", arraylen); + if (array[i] != p) + error("enum at position %d: array says %s, tree says %s", + i, array[i], p); + } + if (ctx.elemcount != i) { + error("tree really contains %d elements, enum gave %d", + ctx.elemcount, i); + } + if (i < arraylen) { + error("enum gave only %d elements, array has %d", i, arraylen); + } + i = count234(tree); + if (ctx.elemcount != i) { + error("tree really contains %d elements, count234 gave %d", + ctx.elemcount, i); + } +} + +void internal_addtest(void *elem, int index, void *realret) +{ + int i, j; + void *retval; + + if (arraysize < arraylen + 1) { + arraysize = arraylen + 1 + 256; + array = sresize(array, arraysize, void *); + } + + i = index; + /* now i points to the first element >= elem */ + retval = elem; /* expect elem returned (success) */ + for (j = arraylen; j > i; j--) + array[j] = array[j - 1]; + array[i] = elem; /* add elem to array */ + arraylen++; + + if (realret != retval) { + error("add: retval was %p expected %p", realret, retval); + } + + verify(); +} + +void addtest(void *elem) +{ + int i; + void *realret; + + realret = add234(tree, elem); + + i = 0; + while (i < arraylen && cmp(elem, array[i]) > 0) + i++; + if (i < arraylen && !cmp(elem, array[i])) { + void *retval = array[i]; /* expect that returned not elem */ + if (realret != retval) { + error("add: retval was %p expected %p", realret, retval); + } + } else + internal_addtest(elem, i, realret); +} + +void addpostest(void *elem, int i) +{ + void *realret; + + realret = addpos234(tree, elem, i); + + internal_addtest(elem, i, realret); +} + +void delpostest(int i) +{ + int index = i; + void *elem = array[i], *ret; + + /* i points to the right element */ + while (i < arraylen - 1) { + array[i] = array[i + 1]; + i++; + } + arraylen--; /* delete elem from array */ + + if (tree->cmp) + ret = del234(tree, elem); + else + ret = delpos234(tree, index); + + if (ret != elem) { + error("del returned %p, expected %p", ret, elem); + } + + verify(); +} + +void deltest(void *elem) +{ + int i; + + i = 0; + while (i < arraylen && cmp(elem, array[i]) > 0) + i++; + if (i >= arraylen || cmp(elem, array[i]) != 0) + return; /* don't do it! */ + delpostest(i); +} + +/* A sample data set and test utility. Designed for pseudo-randomness, + * and yet repeatability. */ + +/* + * This random number generator uses the `portable implementation' + * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits; + * change it if not. + */ +int randomnumber(unsigned *seed) +{ + *seed *= 1103515245; + *seed += 12345; + return ((*seed) / 65536) % 32768; +} + +int mycmp(void *av, void *bv) +{ + char const *a = (char const *) av; + char const *b = (char const *) bv; + return strcmp(a, b); +} + +#define lenof(x) ( sizeof((x)) / sizeof(*(x)) ) + +char *strings[] = { + "a", "ab", "absque", "coram", "de", + "palam", "clam", "cum", "ex", "e", + "sine", "tenus", "pro", "prae", + "banana", "carrot", "cabbage", "broccoli", "onion", "zebra", + "penguin", "blancmange", "pangolin", "whale", "hedgehog", + "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux", + "murfl", "spoo", "breen", "flarn", "octothorpe", + "snail", "tiger", "elephant", "octopus", "warthog", "armadillo", + "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin", + "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper", + "wand", "ring", "amulet" +}; + +#define NSTR lenof(strings) + +int findtest(void) +{ + const static int rels[] = { + REL234_EQ, REL234_GE, REL234_LE, REL234_LT, REL234_GT + }; + const static char *const relnames[] = { + "EQ", "GE", "LE", "LT", "GT" + }; + int i, j, rel, index; + char *p, *ret, *realret, *realret2; + int lo, hi, mid, c; + + for (i = 0; i < NSTR; i++) { + p = strings[i]; + for (j = 0; j < sizeof(rels) / sizeof(*rels); j++) { + rel = rels[j]; + + lo = 0; + hi = arraylen - 1; + while (lo <= hi) { + mid = (lo + hi) / 2; + c = strcmp(p, array[mid]); + if (c < 0) + hi = mid - 1; + else if (c > 0) + lo = mid + 1; + else + break; + } + + if (c == 0) { + if (rel == REL234_LT) + ret = (mid > 0 ? array[--mid] : NULL); + else if (rel == REL234_GT) + ret = (mid < arraylen - 1 ? array[++mid] : NULL); + else + ret = array[mid]; + } else { + assert(lo == hi + 1); + if (rel == REL234_LT || rel == REL234_LE) { + mid = hi; + ret = (hi >= 0 ? array[hi] : NULL); + } else if (rel == REL234_GT || rel == REL234_GE) { + mid = lo; + ret = (lo < arraylen ? array[lo] : NULL); + } else + ret = NULL; + } + + realret = findrelpos234(tree, p, NULL, rel, &index); + if (realret != ret) { + error("find(\"%s\",%s) gave %s should be %s", + p, relnames[j], realret, ret); + } + if (realret && index != mid) { + error("find(\"%s\",%s) gave %d should be %d", + p, relnames[j], index, mid); + } + if (realret && rel == REL234_EQ) { + realret2 = index234(tree, index); + if (realret2 != realret) { + error("find(\"%s\",%s) gave %s(%d) but %d -> %s", + p, relnames[j], realret, index, index, realret2); + } + } +#if 0 + printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j], + realret, index); +#endif + } + } + + realret = findrelpos234(tree, NULL, NULL, REL234_GT, &index); + if (arraylen && (realret != array[0] || index != 0)) { + error("find(NULL,GT) gave %s(%d) should be %s(0)", + realret, index, array[0]); + } else if (!arraylen && (realret != NULL)) { + error("find(NULL,GT) gave %s(%d) should be NULL", realret, index); + } + + realret = findrelpos234(tree, NULL, NULL, REL234_LT, &index); + if (arraylen + && (realret != array[arraylen - 1] || index != arraylen - 1)) { + error("find(NULL,LT) gave %s(%d) should be %s(0)", realret, index, + array[arraylen - 1]); + } else if (!arraylen && (realret != NULL)) { + error("find(NULL,LT) gave %s(%d) should be NULL", realret, index); + } +} + +int main(void) +{ + int in[NSTR]; + int i, j, k; + unsigned seed = 0; + + for (i = 0; i < NSTR; i++) + in[i] = 0; + array = NULL; + arraylen = arraysize = 0; + tree = newtree234(mycmp); + cmp = mycmp; + + verify(); + for (i = 0; i < 10000; i++) { + j = randomnumber(&seed); + j %= NSTR; + printf("trial: %d\n", i); + if (in[j]) { + printf("deleting %s (%d)\n", strings[j], j); + deltest(strings[j]); + in[j] = 0; + } else { + printf("adding %s (%d)\n", strings[j], j); + addtest(strings[j]); + in[j] = 1; + } + findtest(); + } + + while (arraylen > 0) { + j = randomnumber(&seed); + j %= arraylen; + deltest(array[j]); + } + + freetree234(tree); + + /* + * Now try an unsorted tree. We don't really need to test + * delpos234 because we know del234 is based on it, so it's + * already been tested in the above sorted-tree code; but for + * completeness we'll use it to tear down our unsorted tree + * once we've built it. + */ + tree = newtree234(NULL); + cmp = NULL; + verify(); + for (i = 0; i < 1000; i++) { + printf("trial: %d\n", i); + j = randomnumber(&seed); + j %= NSTR; + k = randomnumber(&seed); + k %= count234(tree) + 1; + printf("adding string %s at index %d\n", strings[j], k); + addpostest(strings[j], k); + } + while (count234(tree) > 0) { + printf("cleanup: tree size %d\n", count234(tree)); + j = randomnumber(&seed); + j %= count234(tree); + printf("deleting string %s from index %d\n", array[j], j); + delpostest(j); + } + + return 0; +} + +#endif diff --git a/putty/TREE234.H b/putty/TREE234.H new file mode 100644 index 0000000..a043f1f --- /dev/null +++ b/putty/TREE234.H @@ -0,0 +1,160 @@ +/* + * tree234.h: header defining functions in tree234.c. + * + * This file is copyright 1999-2001 Simon Tatham. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TREE234_H +#define TREE234_H + +/* + * This typedef is opaque outside tree234.c itself. + */ +typedef struct tree234_Tag tree234; + +typedef int (*cmpfn234) (void *, void *); + +/* + * Create a 2-3-4 tree. If `cmp' is NULL, the tree is unsorted, and + * lookups by key will fail: you can only look things up by numeric + * index, and you have to use addpos234() and delpos234(). + */ +tree234 *newtree234(cmpfn234 cmp); + +/* + * Free a 2-3-4 tree (not including freeing the elements). + */ +void freetree234(tree234 * t); + +/* + * Add an element e to a sorted 2-3-4 tree t. Returns e on success, + * or if an existing element compares equal, returns that. + */ +void *add234(tree234 * t, void *e); + +/* + * Add an element e to an unsorted 2-3-4 tree t. Returns e on + * success, NULL on failure. (Failure should only occur if the + * index is out of range or the tree is sorted.) + * + * Index range can be from 0 to the tree's current element count, + * inclusive. + */ +void *addpos234(tree234 * t, void *e, int index); + +/* + * Look up the element at a given numeric index in a 2-3-4 tree. + * Returns NULL if the index is out of range. + * + * One obvious use for this function is in iterating over the whole + * of a tree (sorted or unsorted): + * + * for (i = 0; (p = index234(tree, i)) != NULL; i++) consume(p); + * + * or + * + * int maxcount = count234(tree); + * for (i = 0; i < maxcount; i++) { + * p = index234(tree, i); + * assert(p != NULL); + * consume(p); + * } + */ +void *index234(tree234 * t, int index); + +/* + * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not + * found. e is always passed as the first argument to cmp, so cmp + * can be an asymmetric function if desired. cmp can also be passed + * as NULL, in which case the compare function from the tree proper + * will be used. + * + * Three of these functions are special cases of findrelpos234. The + * non-`pos' variants lack the `index' parameter: if the parameter + * is present and non-NULL, it must point to an integer variable + * which will be filled with the numeric index of the returned + * element. + * + * The non-`rel' variants lack the `relation' parameter. This + * parameter allows you to specify what relation the element you + * provide has to the element you're looking for. This parameter + * can be: + * + * REL234_EQ - find only an element that compares equal to e + * REL234_LT - find the greatest element that compares < e + * REL234_LE - find the greatest element that compares <= e + * REL234_GT - find the smallest element that compares > e + * REL234_GE - find the smallest element that compares >= e + * + * Non-`rel' variants assume REL234_EQ. + * + * If `rel' is REL234_GT or REL234_LT, the `e' parameter may be + * NULL. In this case, REL234_GT will return the smallest element + * in the tree, and REL234_LT will return the greatest. This gives + * an alternative means of iterating over a sorted tree, instead of + * using index234: + * + * // to loop forwards + * for (p = NULL; (p = findrel234(tree, p, NULL, REL234_GT)) != NULL ;) + * consume(p); + * + * // to loop backwards + * for (p = NULL; (p = findrel234(tree, p, NULL, REL234_LT)) != NULL ;) + * consume(p); + */ +enum { + REL234_EQ, REL234_LT, REL234_LE, REL234_GT, REL234_GE +}; +void *find234(tree234 * t, void *e, cmpfn234 cmp); +void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation); +void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index); +void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, int relation, + int *index); + +/* + * Delete an element e in a 2-3-4 tree. Does not free the element, + * merely removes all links to it from the tree nodes. + * + * delpos234 deletes the element at a particular tree index: it + * works on both sorted and unsorted trees. + * + * del234 deletes the element passed to it, so it only works on + * sorted trees. (It's equivalent to using findpos234 to determine + * the index of an element, and then passing that index to + * delpos234.) + * + * Both functions return a pointer to the element they delete, for + * the user to free or pass on elsewhere or whatever. If the index + * is out of range (delpos234) or the element is already not in the + * tree (del234) then they return NULL. + */ +void *del234(tree234 * t, void *e); +void *delpos234(tree234 * t, int index); + +/* + * Return the total element count of a tree234. + */ +int count234(tree234 * t); + +#endif /* TREE234_H */ diff --git a/putty/UNIX/CONFIGUR.AC b/putty/UNIX/CONFIGUR.AC new file mode 100644 index 0000000..382bf2a --- /dev/null +++ b/putty/UNIX/CONFIGUR.AC @@ -0,0 +1,107 @@ +# To compile this into a configure script, you need: +# * Autoconf 2.50 or newer +# * Gtk (for $prefix/share/aclocal/gtk.m4) +# * Automake (for aclocal) +# If you've got them, running "autoreconf" should work. + +AC_INIT +AC_CONFIG_FILES([Makefile]) +AC_CONFIG_HEADERS([uxconfig.h:uxconfig.in]) + +AC_PROG_INSTALL +AC_PROG_CC +if test "X$GCC" = Xyes; then + PUTTYCFLAGS="-Wall -Werror" +else + PUTTYCFLAGS="" +fi +AC_SUBST(PUTTYCFLAGS) + +AC_ARG_WITH([gssapi], + [AS_HELP_STRING([--without-gssapi], + [disable GSSAPI support])], + [], + [with_gssapi=yes]) + +WITH_GSSAPI= +AS_IF([test "x$with_gssapi" != xno], + [AC_DEFINE([WITH_GSSAPI], [1], [Define if building with GSSAPI support.])]) + +AC_CHECK_HEADERS([utmpx.h sys/select.h],,,[ +#include +#include ]) + +# Look for both GTK 1 and GTK 2. +AM_PATH_GTK([1.2.0], [gtk=1], [gtk=none]) +AM_PATH_GTK_2_0([2.0.0], [gtk=2], []) +if test "$gtk" = "none"; then + all_targets="all-cli" +else + all_targets="all-cli all-gtk" +fi +if test "$gtk" = "2"; 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_SUBST([all_targets]) + +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) + +AC_CHECK_FUNCS([getaddrinfo ptsname setresuid strsignal updwtmpx]) + +AC_OUTPUT + +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/putty/UNIX/GTKCFG.C b/putty/UNIX/GTKCFG.C new file mode 100644 index 0000000..2108935 --- /dev/null +++ b/putty/UNIX/GTKCFG.C @@ -0,0 +1,144 @@ +/* + * gtkcfg.c - the GTK-specific parts of the PuTTY configuration + * box. + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +static void about_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + if (event == EVENT_ACTION) { + about_box(ctrl->generic.context.p); + } +} + +void gtk_setup_config_box(struct controlbox *b, int midsession, void *win) +{ + struct controlset *s, *s2; + union control *c; + int i; + + if (!midsession) { + /* + * Add the About button to the standard panel. + */ + s = ctrl_getset(b, "", "", ""); + c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help), + about_handler, P(win)); + c->generic.column = 0; + } + + /* + * GTK makes it rather easier to put the scrollbar on the left + * than Windows does! + */ + s = ctrl_getset(b, "Window", "scrollback", + "Control the scrollback in the window"); + ctrl_checkbox(s, "Scrollbar on left", 'l', + HELPCTX(no_help), + dlg_stdcheckbox_handler, + I(offsetof(Config,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 == offsetof(Config,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--; + 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), + dlg_stdfontsel_handler, I(offsetof(Config,font))); + ctrl_fontsel(s, "Font used for wide (CJK) text", 'w', + HELPCTX(no_help), + dlg_stdfontsel_handler, I(offsetof(Config,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), + dlg_stdfontsel_handler, I(offsetof(Config,boldfont))); + ctrl_fontsel(s, "Font used for bold wide text", 'i', + HELPCTX(no_help), + dlg_stdfontsel_handler, I(offsetof(Config,wideboldfont))); + ctrl_checkbox(s, "Use shadow bold instead of bold fonts", 'u', + HELPCTX(no_help), + dlg_stdcheckbox_handler, + I(offsetof(Config,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), dlg_stdeditbox_handler, + I(offsetof(Config,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), + dlg_stdcheckbox_handler, + I(offsetof(Config,utf8_override))); + + if (!midsession) { + /* + * Allow the user to specify the window class as part of the saved + * configuration, so that they can have their window manager treat + * different kinds of PuTTY and pterm differently if they want to. + */ + s = ctrl_getset(b, "Window/Behaviour", "x11", + "X Window System settings"); + ctrl_editbox(s, "Window class name:", 'z', 50, + HELPCTX(no_help), dlg_stdeditbox_handler, + I(offsetof(Config,winclass)), + I(sizeof(((Config *)0)->winclass))); + } +} diff --git a/putty/UNIX/GTKCOLS.C b/putty/UNIX/GTKCOLS.C new file mode 100644 index 0000000..cb9fd5b --- /dev/null +++ b/putty/UNIX/GTKCOLS.C @@ -0,0 +1,752 @@ +/* + * gtkcols.c - implementation of the `Columns' GTK layout container. + */ + +#include "gtkcols.h" +#include + +static void columns_init(Columns *cols); +static void columns_class_init(ColumnsClass *klass); +static void columns_map(GtkWidget *widget); +static void columns_unmap(GtkWidget *widget); +#if !GTK_CHECK_VERSION(2,0,0) +static void columns_draw(GtkWidget *widget, GdkRectangle *area); +static gint columns_expose(GtkWidget *widget, GdkEventExpose *event); +#endif +static void columns_base_add(GtkContainer *container, GtkWidget *widget); +static void columns_remove(GtkContainer *container, GtkWidget *widget); +static void columns_forall(GtkContainer *container, gboolean include_internals, + GtkCallback callback, gpointer callback_data); +#if !GTK_CHECK_VERSION(2,0,0) +static gint columns_focus(GtkContainer *container, GtkDirectionType dir); +#endif +static GtkType columns_child_type(GtkContainer *container); +static void columns_size_request(GtkWidget *widget, GtkRequisition *req); +static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc); + +static GtkContainerClass *parent_class = NULL; + +#if !GTK_CHECK_VERSION(2,0,0) +GtkType columns_get_type(void) +{ + static GtkType columns_type = 0; + + if (!columns_type) { + static const GtkTypeInfo columns_info = { + "Columns", + sizeof(Columns), + sizeof(ColumnsClass), + (GtkClassInitFunc) columns_class_init, + (GtkObjectInitFunc) columns_init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info); + } + + return columns_type; +} +#else +GType columns_get_type(void) +{ + static GType columns_type = 0; + + if (!columns_type) { + static const GTypeInfo columns_info = { + sizeof(ColumnsClass), + NULL, + NULL, + (GClassInitFunc) columns_class_init, + NULL, + NULL, + sizeof(Columns), + 0, + (GInstanceInitFunc)columns_init, + }; + + columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns", + &columns_info, 0); + } + + return columns_type; +} +#endif + +#if !GTK_CHECK_VERSION(2,0,0) +static gint (*columns_inherited_focus)(GtkContainer *container, + GtkDirectionType direction); +#endif + +static void columns_class_init(ColumnsClass *klass) +{ +#if !GTK_CHECK_VERSION(2,0,0) + /* GtkObjectClass *object_class = (GtkObjectClass *)klass; */ + GtkWidgetClass *widget_class = (GtkWidgetClass *)klass; + GtkContainerClass *container_class = (GtkContainerClass *)klass; +#else + /* GObjectClass *object_class = G_OBJECT_CLASS(klass); */ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass); +#endif + +#if !GTK_CHECK_VERSION(2,0,0) + parent_class = gtk_type_class(GTK_TYPE_CONTAINER); +#else + parent_class = g_type_class_peek_parent(klass); +#endif + + widget_class->map = columns_map; + widget_class->unmap = columns_unmap; +#if !GTK_CHECK_VERSION(2,0,0) + widget_class->draw = columns_draw; + widget_class->expose_event = columns_expose; +#endif + widget_class->size_request = columns_size_request; + widget_class->size_allocate = columns_size_allocate; + + container_class->add = columns_base_add; + container_class->remove = columns_remove; + container_class->forall = columns_forall; + container_class->child_type = columns_child_type; +#if !GTK_CHECK_VERSION(2,0,0) + /* Save the previous value of this method. */ + if (!columns_inherited_focus) + columns_inherited_focus = container_class->focus; + container_class->focus = columns_focus; +#endif +} + +static void columns_init(Columns *cols) +{ + GTK_WIDGET_SET_FLAGS(cols, GTK_NO_WINDOW); + + cols->children = NULL; + cols->spacing = 0; +} + +/* + * These appear to be thoroughly tedious functions; the only reason + * we have to reimplement them at all is because we defined our own + * format for our GList of children... + */ +static void columns_map(GtkWidget *widget) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + + cols = COLUMNS(widget); + GTK_WIDGET_SET_FLAGS(cols, GTK_MAPPED); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && + GTK_WIDGET_VISIBLE(child->widget) && + !GTK_WIDGET_MAPPED(child->widget)) + gtk_widget_map(child->widget); + } +} +static void columns_unmap(GtkWidget *widget) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + + cols = COLUMNS(widget); + GTK_WIDGET_UNSET_FLAGS(cols, GTK_MAPPED); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && + GTK_WIDGET_VISIBLE(child->widget) && + GTK_WIDGET_MAPPED(child->widget)) + gtk_widget_unmap(child->widget); + } +} +#if !GTK_CHECK_VERSION(2,0,0) +static void columns_draw(GtkWidget *widget, GdkRectangle *area) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + GdkRectangle child_area; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + + if (GTK_WIDGET_DRAWABLE(widget)) { + cols = COLUMNS(widget); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && + GTK_WIDGET_DRAWABLE(child->widget) && + gtk_widget_intersect(child->widget, area, &child_area)) + gtk_widget_draw(child->widget, &child_area); + } + } +} +static gint columns_expose(GtkWidget *widget, GdkEventExpose *event) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + GdkEventExpose child_event; + + g_return_val_if_fail(widget != NULL, FALSE); + g_return_val_if_fail(IS_COLUMNS(widget), FALSE); + g_return_val_if_fail(event != NULL, FALSE); + + if (GTK_WIDGET_DRAWABLE(widget)) { + cols = COLUMNS(widget); + child_event = *event; + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && + GTK_WIDGET_DRAWABLE(child->widget) && + GTK_WIDGET_NO_WINDOW(child->widget) && + gtk_widget_intersect(child->widget, &event->area, + &child_event.area)) + gtk_widget_event(child->widget, (GdkEvent *)&child_event); + } + } + return FALSE; +} +#endif + +static void columns_base_add(GtkContainer *container, GtkWidget *widget) +{ + Columns *cols; + + g_return_if_fail(container != NULL); + g_return_if_fail(IS_COLUMNS(container)); + g_return_if_fail(widget != NULL); + + cols = COLUMNS(container); + + /* + * Default is to add a new widget spanning all columns. + */ + columns_add(cols, widget, 0, 0); /* 0 means ncols */ +} + +static void columns_remove(GtkContainer *container, GtkWidget *widget) +{ + Columns *cols; + ColumnsChild *child; + GtkWidget *childw; + GList *children; + gboolean was_visible; + + g_return_if_fail(container != NULL); + g_return_if_fail(IS_COLUMNS(container)); + g_return_if_fail(widget != NULL); + + cols = COLUMNS(container); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget != widget) + continue; + + was_visible = GTK_WIDGET_VISIBLE(widget); + gtk_widget_unparent(widget); + cols->children = g_list_remove_link(cols->children, children); + g_list_free(children); + g_free(child); + if (was_visible) + gtk_widget_queue_resize(GTK_WIDGET(container)); + break; + } + + for (children = cols->taborder; + children && (childw = children->data); + children = children->next) { + if (childw != widget) + continue; + + cols->taborder = g_list_remove_link(cols->taborder, children); + g_list_free(children); +#if GTK_CHECK_VERSION(2,0,0) + gtk_container_set_focus_chain(container, cols->taborder); +#endif + break; + } +} + +static void columns_forall(GtkContainer *container, gboolean include_internals, + GtkCallback callback, gpointer callback_data) +{ + Columns *cols; + ColumnsChild *child; + GList *children, *next; + + g_return_if_fail(container != NULL); + g_return_if_fail(IS_COLUMNS(container)); + g_return_if_fail(callback != NULL); + + cols = COLUMNS(container); + + for (children = cols->children; + children && (child = children->data); + children = next) { + /* + * We can't wait until after the callback to assign + * `children = children->next', because the callback might + * be gtk_widget_destroy, which would remove the link + * `children' from the list! So instead we must get our + * hands on the value of the `next' pointer _before_ the + * callback. + */ + next = children->next; + if (child->widget) + callback(child->widget, callback_data); + } +} + +static GtkType columns_child_type(GtkContainer *container) +{ + return GTK_TYPE_WIDGET; +} + +GtkWidget *columns_new(gint spacing) +{ + Columns *cols; + +#if !GTK_CHECK_VERSION(2,0,0) + cols = gtk_type_new(columns_get_type()); +#else + cols = g_object_new(TYPE_COLUMNS, NULL); +#endif + + cols->spacing = spacing; + + return GTK_WIDGET(cols); +} + +void columns_set_cols(Columns *cols, gint ncols, const gint *percentages) +{ + ColumnsChild *childdata; + gint i; + + g_return_if_fail(cols != NULL); + g_return_if_fail(IS_COLUMNS(cols)); + g_return_if_fail(ncols > 0); + g_return_if_fail(percentages != NULL); + + childdata = g_new(ColumnsChild, 1); + childdata->widget = NULL; + childdata->ncols = ncols; + childdata->percentages = g_new(gint, ncols); + childdata->force_left = FALSE; + for (i = 0; i < ncols; i++) + childdata->percentages[i] = percentages[i]; + + cols->children = g_list_append(cols->children, childdata); +} + +void columns_add(Columns *cols, GtkWidget *child, + gint colstart, gint colspan) +{ + ColumnsChild *childdata; + + g_return_if_fail(cols != NULL); + g_return_if_fail(IS_COLUMNS(cols)); + g_return_if_fail(child != NULL); + g_return_if_fail(child->parent == NULL); + + childdata = g_new(ColumnsChild, 1); + childdata->widget = child; + childdata->colstart = colstart; + childdata->colspan = colspan; + childdata->force_left = FALSE; + + cols->children = g_list_append(cols->children, childdata); + cols->taborder = g_list_append(cols->taborder, child); + + gtk_widget_set_parent(child, GTK_WIDGET(cols)); + +#if GTK_CHECK_VERSION(2,0,0) + gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder); +#endif + + if (GTK_WIDGET_REALIZED(cols)) + gtk_widget_realize(child); + + if (GTK_WIDGET_VISIBLE(cols) && GTK_WIDGET_VISIBLE(child)) { + if (GTK_WIDGET_MAPPED(cols)) + gtk_widget_map(child); + gtk_widget_queue_resize(child); + } +} + +void columns_force_left_align(Columns *cols, GtkWidget *widget) +{ + ColumnsChild *child; + GList *children; + + g_return_if_fail(cols != NULL); + g_return_if_fail(IS_COLUMNS(cols)); + g_return_if_fail(widget != NULL); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget != widget) + continue; + + child->force_left = TRUE; + if (GTK_WIDGET_VISIBLE(widget)) + gtk_widget_queue_resize(GTK_WIDGET(cols)); + break; + } +} + +void columns_taborder_last(Columns *cols, GtkWidget *widget) +{ + GtkWidget *childw; + GList *children; + + g_return_if_fail(cols != NULL); + g_return_if_fail(IS_COLUMNS(cols)); + g_return_if_fail(widget != NULL); + + for (children = cols->taborder; + children && (childw = children->data); + children = children->next) { + if (childw != widget) + continue; + + cols->taborder = g_list_remove_link(cols->taborder, children); + g_list_free(children); + cols->taborder = g_list_append(cols->taborder, widget); +#if GTK_CHECK_VERSION(2,0,0) + gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder); +#endif + break; + } +} + +#if !GTK_CHECK_VERSION(2,0,0) +/* + * Override GtkContainer's focus movement so the user can + * explicitly specify the tab order. + */ +static gint columns_focus(GtkContainer *container, GtkDirectionType dir) +{ + Columns *cols; + GList *pos; + GtkWidget *focuschild; + + g_return_val_if_fail(container != NULL, FALSE); + g_return_val_if_fail(IS_COLUMNS(container), FALSE); + + cols = COLUMNS(container); + + if (!GTK_WIDGET_DRAWABLE(cols) || + !GTK_WIDGET_IS_SENSITIVE(cols)) + return FALSE; + + if (!GTK_WIDGET_CAN_FOCUS(container) && + (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) { + + focuschild = container->focus_child; + gtk_container_set_focus_child(container, NULL); + + if (dir == GTK_DIR_TAB_FORWARD) + pos = cols->taborder; + else + pos = g_list_last(cols->taborder); + + while (pos) { + GtkWidget *child = pos->data; + + if (focuschild) { + if (focuschild == child) { + focuschild = NULL; /* now we can start looking in here */ + if (GTK_WIDGET_DRAWABLE(child) && + GTK_IS_CONTAINER(child) && + !GTK_WIDGET_HAS_FOCUS(child)) { + if (gtk_container_focus(GTK_CONTAINER(child), dir)) + return TRUE; + } + } + } else if (GTK_WIDGET_DRAWABLE(child)) { + if (GTK_IS_CONTAINER(child)) { + if (gtk_container_focus(GTK_CONTAINER(child), dir)) + return TRUE; + } else if (GTK_WIDGET_CAN_FOCUS(child)) { + gtk_widget_grab_focus(child); + return TRUE; + } + } + + if (dir == GTK_DIR_TAB_FORWARD) + pos = pos->next; + else + pos = pos->prev; + } + + return FALSE; + } else + return columns_inherited_focus(container, dir); +} +#endif + +/* + * Now here comes the interesting bit. The actual layout part is + * done in the following two functions: + * + * columns_size_request() examines the list of widgets held in the + * Columns, and returns a requisition stating the absolute minimum + * size it can bear to be. + * + * columns_size_allocate() is given an allocation telling it what + * size the whole container is going to be, and it calls + * gtk_widget_size_allocate() on all of its (visible) children to + * set their size and position relative to the top left of the + * container. + */ + +static void columns_size_request(GtkWidget *widget, GtkRequisition *req) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + gint i, ncols, colspan, *colypos; + const gint *percentages; + static const gint onecol[] = { 100 }; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + g_return_if_fail(req != NULL); + + cols = COLUMNS(widget); + + req->width = 0; + req->height = cols->spacing; + + ncols = 1; + colypos = g_new(gint, 1); + colypos[0] = 0; + percentages = onecol; + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + GtkRequisition creq; + + if (!child->widget) { + /* Column reconfiguration. */ + for (i = 1; i < ncols; i++) { + if (colypos[0] < colypos[i]) + colypos[0] = colypos[i]; + } + ncols = child->ncols; + percentages = child->percentages; + colypos = g_renew(gint, colypos, ncols); + for (i = 1; i < ncols; i++) + colypos[i] = colypos[0]; + continue; + } + + /* Only take visible widgets into account. */ + if (!GTK_WIDGET_VISIBLE(child->widget)) + continue; + + gtk_widget_size_request(child->widget, &creq); + colspan = child->colspan ? child->colspan : ncols-child->colstart; + + /* + * To compute width: we know that creq.width plus + * cols->spacing needs to equal a certain percentage of the + * full width of the container. So we work this value out, + * figure out how wide the container will need to be to + * make that percentage of it equal to that width, and + * ensure our returned width is at least that much. Very + * simple really. + */ + { + int percent, thiswid, fullwid; + + percent = 0; + for (i = 0; i < colspan; i++) + percent += percentages[child->colstart+i]; + + thiswid = creq.width + cols->spacing; + /* + * Since creq is the _minimum_ size the child needs, we + * must ensure that it gets _at least_ that size. + * Hence, when scaling thiswid up to fullwid, we must + * round up, which means adding percent-1 before + * dividing by percent. + */ + fullwid = (thiswid * 100 + percent - 1) / percent; + + /* + * The above calculation assumes every widget gets + * cols->spacing on the right. So we subtract + * cols->spacing here to account for the extra load of + * spacing on the right. + */ + if (req->width < fullwid - cols->spacing) + req->width = fullwid - cols->spacing; + } + + /* + * To compute height: the widget's top will be positioned + * at the largest y value so far reached in any of the + * columns it crosses. Then it will go down by creq.height + * plus padding; and the point it reaches at the bottom is + * the new y value in all those columns, and minus the + * padding it is also a lower bound on our own size + * request. + */ + { + int topy, boty; + + topy = 0; + for (i = 0; i < colspan; i++) { + if (topy < colypos[child->colstart+i]) + topy = colypos[child->colstart+i]; + } + boty = topy + creq.height + cols->spacing; + for (i = 0; i < colspan; i++) { + colypos[child->colstart+i] = boty; + } + + if (req->height < boty - cols->spacing) + req->height = boty - cols->spacing; + } + } + + req->width += 2*GTK_CONTAINER(cols)->border_width; + req->height += 2*GTK_CONTAINER(cols)->border_width; + + g_free(colypos); +} + +static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + gint i, ncols, colspan, border, *colxpos, *colypos; + const gint *percentages; + static const gint onecol[] = { 100 }; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + g_return_if_fail(alloc != NULL); + + cols = COLUMNS(widget); + widget->allocation = *alloc; + border = GTK_CONTAINER(cols)->border_width; + + ncols = 1; + percentages = onecol; + /* colxpos gives the starting x position of each column. + * We supply n+1 of them, so that we can find the RH edge easily. + * All ending x positions are expected to be adjusted afterwards by + * subtracting the spacing. */ + colxpos = g_new(gint, 2); + colxpos[0] = 0; + colxpos[1] = alloc->width - 2*border + cols->spacing; + /* As in size_request, colypos is the lowest y reached in each column. */ + colypos = g_new(gint, 1); + colypos[0] = 0; + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + GtkRequisition creq; + GtkAllocation call; + + if (!child->widget) { + gint percent; + + /* Column reconfiguration. */ + for (i = 1; i < ncols; i++) { + if (colypos[0] < colypos[i]) + colypos[0] = colypos[i]; + } + ncols = child->ncols; + percentages = child->percentages; + colypos = g_renew(gint, colypos, ncols); + for (i = 1; i < ncols; i++) + colypos[i] = colypos[0]; + colxpos = g_renew(gint, colxpos, ncols + 1); + colxpos[0] = 0; + percent = 0; + for (i = 0; i < ncols; i++) { + percent += percentages[i]; + colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing) + * percent / 100); + } + continue; + } + + /* Only take visible widgets into account. */ + if (!GTK_WIDGET_VISIBLE(child->widget)) + continue; + + gtk_widget_get_child_requisition(child->widget, &creq); + colspan = child->colspan ? child->colspan : ncols-child->colstart; + + /* + * Starting x position is cols[colstart]. + * Ending x position is cols[colstart+colspan] - spacing. + * + * Unless we're forcing left, in which case the width is + * exactly the requisition width. + */ + call.x = alloc->x + border + colxpos[child->colstart]; + if (child->force_left) + call.width = creq.width; + else + call.width = (colxpos[child->colstart+colspan] - + colxpos[child->colstart] - cols->spacing); + + /* + * To compute height: the widget's top will be positioned + * at the largest y value so far reached in any of the + * columns it crosses. Then it will go down by creq.height + * plus padding; and the point it reaches at the bottom is + * the new y value in all those columns. + */ + { + int topy, boty; + + topy = 0; + for (i = 0; i < colspan; i++) { + if (topy < colypos[child->colstart+i]) + topy = colypos[child->colstart+i]; + } + call.y = alloc->y + border + topy; + call.height = creq.height; + boty = topy + creq.height + cols->spacing; + for (i = 0; i < colspan; i++) { + colypos[child->colstart+i] = boty; + } + } + + gtk_widget_size_allocate(child->widget, &call); + } + + g_free(colxpos); + g_free(colypos); +} diff --git a/putty/UNIX/GTKCOLS.H b/putty/UNIX/GTKCOLS.H new file mode 100644 index 0000000..295e650 --- /dev/null +++ b/putty/UNIX/GTKCOLS.H @@ -0,0 +1,62 @@ +/* + * gtkcols.h - header file for a columns-based widget container + * capable of supporting the PuTTY portable dialog box layout + * mechanism. + */ + +#ifndef COLUMNS_H +#define COLUMNS_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define TYPE_COLUMNS (columns_get_type()) +#define COLUMNS(obj) (GTK_CHECK_CAST((obj), TYPE_COLUMNS, Columns)) +#define COLUMNS_CLASS(klass) \ + (GTK_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass)) +#define IS_COLUMNS(obj) (GTK_CHECK_TYPE((obj), TYPE_COLUMNS)) +#define IS_COLUMNS_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), TYPE_COLUMNS)) + +typedef struct Columns_tag Columns; +typedef struct ColumnsClass_tag ColumnsClass; +typedef struct ColumnsChild_tag ColumnsChild; + +struct Columns_tag { + GtkContainer container; + /* private after here */ + GList *children; /* this holds ColumnsChild structures */ + GList *taborder; /* this just holds GtkWidgets */ + gint spacing; +}; + +struct ColumnsClass_tag { + GtkContainerClass parent_class; +}; + +struct ColumnsChild_tag { + /* If `widget' is non-NULL, this entry represents an actual widget. */ + GtkWidget *widget; + gint colstart, colspan; + gboolean force_left; /* for recalcitrant GtkLabels */ + /* Otherwise, this entry represents a change in the column setup. */ + gint ncols; + gint *percentages; +}; + +GtkType columns_get_type(void); +GtkWidget *columns_new(gint spacing); +void columns_set_cols(Columns *cols, gint ncols, const gint *percentages); +void columns_add(Columns *cols, GtkWidget *child, + gint colstart, gint colspan); +void columns_taborder_last(Columns *cols, GtkWidget *child); +void columns_force_left_align(Columns *cols, GtkWidget *child); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* COLUMNS_H */ diff --git a/putty/UNIX/GTKDLG.C b/putty/UNIX/GTKDLG.C new file mode 100644 index 0000000..fc25e78 --- /dev/null +++ b/putty/UNIX/GTKDLG.C @@ -0,0 +1,3782 @@ +/* + * gtkdlg.c - GTK implementation of the PuTTY configuration box. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gtkcols.h" +#include "gtkfont.h" + +#ifdef TESTMODE +#define PUTTY_DO_GLOBALS /* actually _define_ globals */ +#endif + +#include "putty.h" +#include "storage.h" +#include "dialog.h" +#include "tree234.h" + +struct Shortcut { + GtkWidget *widget; + struct uctrl *uc; + int action; +}; + +struct Shortcuts { + struct Shortcut sc[128]; +}; + +struct uctrl { + union control *ctrl; + GtkWidget *toplevel; + void *privdata; + int privdata_needs_free; + GtkWidget **buttons; int nbuttons; /* for radio buttons */ + GtkWidget *entry; /* for editbox, filesel, fontsel */ + GtkWidget *button; /* for filesel, fontsel */ +#if !GTK_CHECK_VERSION(2,4,0) + GtkWidget *list; /* for listbox (in GTK1), combobox (<=GTK2.3) */ + GtkWidget *menu; /* for optionmenu (==droplist) */ + GtkWidget *optmenu; /* also for optionmenu */ +#else + GtkWidget *combo; /* for combo box (either editable or not) */ +#endif +#if GTK_CHECK_VERSION(2,0,0) + GtkWidget *treeview; /* for listbox (GTK2), droplist+combo (>=2.4) */ + GtkListStore *listmodel; /* for all types of list box */ +#endif + GtkWidget *text; /* for text */ + GtkWidget *label; /* for dlg_label_change */ + GtkAdjustment *adj; /* for the scrollbar in a list box */ + guint entrysig; + guint textsig; + int nclicks; +}; + +struct dlgparam { + tree234 *byctrl, *bywidget; + void *data; + struct { unsigned char r, g, b, ok; } coloursel_result; /* 0-255 */ + /* `flags' are set to indicate when a GTK signal handler is being called + * due to automatic processing and should not flag a user event. */ + int flags; + struct Shortcuts *shortcuts; + GtkWidget *window, *cancelbutton; + union control *currfocus, *lastfocus; +#if !GTK_CHECK_VERSION(2,0,0) + GtkWidget *currtreeitem, **treeitems; + int ntreeitems; +#endif + int retval; +}; +#define FLAG_UPDATING_COMBO_LIST 1 +#define FLAG_UPDATING_LISTBOX 2 + +enum { /* values for Shortcut.action */ + SHORTCUT_EMPTY, /* no shortcut on this key */ + SHORTCUT_TREE, /* focus a tree item */ + SHORTCUT_FOCUS, /* focus the supplied widget */ + SHORTCUT_UCTRL, /* do something sane with uctrl */ + SHORTCUT_UCTRL_UP, /* uctrl is a draglist, move Up */ + SHORTCUT_UCTRL_DOWN, /* uctrl is a draglist, move Down */ +}; + +#if GTK_CHECK_VERSION(2,0,0) +enum { + TREESTORE_PATH, + TREESTORE_PARAMS, + TREESTORE_NUM +}; +#endif + +/* + * Forward references. + */ +static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event, + gpointer data); +static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, + int chr, int action, void *ptr); +static void shortcut_highlight(GtkWidget *label, int chr); +#if !GTK_CHECK_VERSION(2,0,0) +static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event, + gpointer data); +static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event, + gpointer data); +static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, + gpointer data); +static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, + gpointer data); +#endif +#if !GTK_CHECK_VERSION(2,4,0) +static void menuitem_activate(GtkMenuItem *item, gpointer data); +#endif +static void coloursel_ok(GtkButton *button, gpointer data); +static void coloursel_cancel(GtkButton *button, gpointer data); +static void window_destroy(GtkWidget *widget, gpointer data); +int get_listitemheight(GtkWidget *widget); + +static int uctrl_cmp_byctrl(void *av, void *bv) +{ + struct uctrl *a = (struct uctrl *)av; + struct uctrl *b = (struct uctrl *)bv; + if (a->ctrl < b->ctrl) + return -1; + else if (a->ctrl > b->ctrl) + return +1; + return 0; +} + +static int uctrl_cmp_byctrl_find(void *av, void *bv) +{ + union control *a = (union control *)av; + struct uctrl *b = (struct uctrl *)bv; + if (a < b->ctrl) + return -1; + else if (a > b->ctrl) + return +1; + return 0; +} + +static int uctrl_cmp_bywidget(void *av, void *bv) +{ + struct uctrl *a = (struct uctrl *)av; + struct uctrl *b = (struct uctrl *)bv; + if (a->toplevel < b->toplevel) + return -1; + else if (a->toplevel > b->toplevel) + return +1; + return 0; +} + +static int uctrl_cmp_bywidget_find(void *av, void *bv) +{ + GtkWidget *a = (GtkWidget *)av; + struct uctrl *b = (struct uctrl *)bv; + if (a < b->toplevel) + return -1; + else if (a > b->toplevel) + return +1; + return 0; +} + +static void dlg_init(struct dlgparam *dp) +{ + dp->byctrl = newtree234(uctrl_cmp_byctrl); + dp->bywidget = newtree234(uctrl_cmp_bywidget); + dp->coloursel_result.ok = FALSE; + dp->window = dp->cancelbutton = NULL; +#if !GTK_CHECK_VERSION(2,0,0) + dp->treeitems = NULL; + dp->currtreeitem = NULL; +#endif + dp->flags = 0; + dp->currfocus = NULL; +} + +static void dlg_cleanup(struct dlgparam *dp) +{ + struct uctrl *uc; + + freetree234(dp->byctrl); /* doesn't free the uctrls inside */ + dp->byctrl = NULL; + while ( (uc = index234(dp->bywidget, 0)) != NULL) { + del234(dp->bywidget, uc); + if (uc->privdata_needs_free) + sfree(uc->privdata); + sfree(uc->buttons); + sfree(uc); + } + freetree234(dp->bywidget); + dp->bywidget = NULL; +#if !GTK_CHECK_VERSION(2,0,0) + sfree(dp->treeitems); +#endif +} + +static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc) +{ + add234(dp->byctrl, uc); + add234(dp->bywidget, uc); +} + +static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, union control *ctrl) +{ + if (!dp->byctrl) + return NULL; + return find234(dp->byctrl, ctrl, uctrl_cmp_byctrl_find); +} + +static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w) +{ + struct uctrl *ret = NULL; + if (!dp->bywidget) + return NULL; + do { + ret = find234(dp->bywidget, w, uctrl_cmp_bywidget_find); + if (ret) + return ret; + w = w->parent; + } while (w); + return ret; +} + +void *dlg_get_privdata(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + return uc->privdata; +} + +void dlg_set_privdata(union control *ctrl, void *dlg, void *ptr) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + uc->privdata = ptr; + uc->privdata_needs_free = FALSE; +} + +void *dlg_alloc_privdata(union control *ctrl, void *dlg, size_t size) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + /* + * This is an internal allocation routine, so it's allowed to + * use smalloc directly. + */ + uc->privdata = smalloc(size); + uc->privdata_needs_free = FALSE; + return uc->privdata; +} + +union control *dlg_last_focused(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + if (dp->currfocus != ctrl) + return dp->currfocus; + else + return dp->lastfocus; +} + +void dlg_radiobutton_set(union control *ctrl, void *dlg, int which) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_RADIO); + assert(uc->buttons != NULL); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), TRUE); +} + +int dlg_radiobutton_get(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + int i; + + assert(uc->ctrl->generic.type == CTRL_RADIO); + assert(uc->buttons != NULL); + for (i = 0; i < uc->nbuttons; i++) + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->buttons[i]))) + return i; + return 0; /* got to return something */ +} + +void dlg_checkbox_set(union control *ctrl, void *dlg, int checked) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_CHECKBOX); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked); +} + +int dlg_checkbox_get(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_CHECKBOX); + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel)); +} + +void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + GtkWidget *entry; + char *tmpstring; + assert(uc->ctrl->generic.type == CTRL_EDITBOX); + +#if GTK_CHECK_VERSION(2,4,0) + if (uc->combo) + entry = gtk_bin_get_child(GTK_BIN(uc->combo)); + else +#endif + entry = uc->entry; + + assert(entry != NULL); + + /* + * GTK 2 implements gtk_entry_set_text by means of two separate + * operations: first delete the previous text leaving the empty + * string, then insert the new text. This causes two calls to + * the "changed" signal. + * + * The first call to "changed", if allowed to proceed normally, + * will cause an EVENT_VALCHANGE event on the edit box, causing + * a call to dlg_editbox_get() which will read the empty string + * out of the GtkEntry - and promptly write it straight into + * the Config 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); +} + +void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_EDITBOX); + +#if GTK_CHECK_VERSION(2,4,0) + if (uc->combo) { +#if GTK_CHECK_VERSION(2,6,0) + strncpy(buffer, + gtk_combo_box_get_active_text(GTK_COMBO_BOX(uc->combo)), + length); +#else + strncpy(buffer, + gtk_entry_get_text + (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo)))), + length); +#endif + buffer[length-1] = '\0'; + return; + } +#endif + + if (uc->entry) { + strncpy(buffer, gtk_entry_get_text(GTK_ENTRY(uc->entry)), + length); + buffer[length-1] = '\0'; + return; + } + + assert(!"We shouldn't get here"); +} + +#if !GTK_CHECK_VERSION(2,4,0) +static void container_remove_and_destroy(GtkWidget *w, gpointer data) +{ + GtkContainer *cont = GTK_CONTAINER(data); + /* gtk_container_remove will unref the widget for us; we need not. */ + gtk_container_remove(cont, w); +} +#endif + +/* The `listbox' functions can also apply to combo boxes. */ +void dlg_listbox_clear(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu) { + gtk_container_foreach(GTK_CONTAINER(uc->menu), + container_remove_and_destroy, + GTK_CONTAINER(uc->menu)); + return; + } + if (uc->list) { + gtk_list_clear_items(GTK_LIST(uc->list), 0, -1); + return; + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->listmodel) { + gtk_list_store_clear(uc->listmodel); + return; + } +#endif + assert(!"We shouldn't get here"); +} + +void dlg_listbox_del(union control *ctrl, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu) { + gtk_container_remove + (GTK_CONTAINER(uc->menu), + g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index)); + return; + } + if (uc->list) { + gtk_list_clear_items(GTK_LIST(uc->list), index, index+1); + return; + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->listmodel) { + GtkTreePath *path; + GtkTreeIter iter; + assert(uc->listmodel != NULL); + path = gtk_tree_path_new_from_indices(index, -1); + gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); + gtk_list_store_remove(uc->listmodel, &iter); + gtk_tree_path_free(path); + return; + } +#endif + assert(!"We shouldn't get here"); +} + +void dlg_listbox_add(union control *ctrl, void *dlg, char const *text) +{ + dlg_listbox_addwithid(ctrl, dlg, text, 0); +} + +/* + * Each listbox entry may have a numeric id associated with it. + * Note that some front ends only permit a string to be stored at + * each position, which means that _if_ you put two identical + * strings in any listbox then you MUST not assign them different + * IDs and expect to get meaningful results back. + */ +void dlg_listbox_addwithid(union control *ctrl, void *dlg, + char const *text, int id) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + + /* + * This routine is long and complicated in both GTK 1 and 2, + * and completely different. Sigh. + */ + dp->flags |= FLAG_UPDATING_COMBO_LIST; + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu) { + /* + * List item in a drop-down (but non-combo) list. Tabs are + * ignored; we just provide a standard menu item with the + * text. + */ + GtkWidget *menuitem = gtk_menu_item_new_with_label(text); + + gtk_container_add(GTK_CONTAINER(uc->menu), menuitem); + gtk_widget_show(menuitem); + + gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", + GINT_TO_POINTER(id)); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + GTK_SIGNAL_FUNC(menuitem_activate), dp); + goto done; + } + if (uc->list && uc->entry) { + /* + * List item in a combo-box list, which means the sensible + * thing to do is make it a perfectly normal label. Hence + * tabs are disregarded. + */ + GtkWidget *listitem = gtk_list_item_new_with_label(text); + + gtk_container_add(GTK_CONTAINER(uc->list), listitem); + gtk_widget_show(listitem); + + gtk_object_set_data(GTK_OBJECT(listitem), "user-data", + GINT_TO_POINTER(id)); + goto done; + } +#endif +#if !GTK_CHECK_VERSION(2,0,0) + if (uc->list) { + /* + * List item in a non-combo-box list box. We make all of + * these Columns containing GtkLabels. This allows us to do + * the nasty force_left hack irrespective of whether there + * are tabs in the thing. + */ + GtkWidget *listitem = gtk_list_item_new(); + GtkWidget *cols = columns_new(10); + gint *percents; + int i, ncols; + + /* Count the tabs in the text, and hence determine # of columns. */ + ncols = 1; + for (i = 0; text[i]; i++) + if (text[i] == '\t') + ncols++; + + assert(ncols <= + (uc->ctrl->listbox.ncols ? uc->ctrl->listbox.ncols : 1)); + percents = snewn(ncols, gint); + percents[ncols-1] = 100; + for (i = 0; i < ncols-1; i++) { + percents[i] = uc->ctrl->listbox.percentages[i]; + percents[ncols-1] -= percents[i]; + } + columns_set_cols(COLUMNS(cols), ncols, percents); + sfree(percents); + + for (i = 0; i < ncols; i++) { + int len = strcspn(text, "\t"); + char *dup = dupprintf("%.*s", len, text); + GtkWidget *label; + + text += len; + if (*text) text++; + label = gtk_label_new(dup); + sfree(dup); + + columns_add(COLUMNS(cols), label, i, 1); + columns_force_left_align(COLUMNS(cols), label); + gtk_widget_show(label); + } + gtk_container_add(GTK_CONTAINER(listitem), cols); + gtk_widget_show(cols); + gtk_container_add(GTK_CONTAINER(uc->list), listitem); + gtk_widget_show(listitem); + + if (ctrl->listbox.multisel) { + gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event", + GTK_SIGNAL_FUNC(listitem_multi_key), uc->adj); + } else { + gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event", + GTK_SIGNAL_FUNC(listitem_single_key), uc->adj); + } + gtk_signal_connect(GTK_OBJECT(listitem), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(listitem), "button_press_event", + GTK_SIGNAL_FUNC(listitem_button_press), dp); + gtk_signal_connect(GTK_OBJECT(listitem), "button_release_event", + GTK_SIGNAL_FUNC(listitem_button_release), dp); + gtk_object_set_data(GTK_OBJECT(listitem), "user-data", + GINT_TO_POINTER(id)); + goto done; + } +#else + if (uc->listmodel) { + GtkTreeIter iter; + int i, cols; + + dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update */ + gtk_list_store_append(uc->listmodel, &iter); + dp->flags &= ~FLAG_UPDATING_LISTBOX; + gtk_list_store_set(uc->listmodel, &iter, 0, id, -1); + + /* + * Now go through text and divide it into columns at the tabs, + * as necessary. + */ + cols = (uc->ctrl->generic.type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1); + cols = cols ? cols : 1; + for (i = 0; i < cols; i++) { + int collen = strcspn(text, "\t"); + char *tmpstr = snewn(collen+1, char); + memcpy(tmpstr, text, collen); + tmpstr[collen] = '\0'; + gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1); + sfree(tmpstr); + text += collen; + if (*text) text++; + } + goto done; + } +#endif + assert(!"We shouldn't get here"); + done: + dp->flags &= ~FLAG_UPDATING_COMBO_LIST; +} + +int dlg_listbox_getid(union control *ctrl, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu || uc->list) { + GList *children; + GtkObject *item; + + children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : + uc->list)); + item = GTK_OBJECT(g_list_nth_data(children, index)); + g_list_free(children); + + return GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item), + "user-data")); + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->listmodel) { + GtkTreePath *path; + GtkTreeIter iter; + int ret; + + path = gtk_tree_path_new_from_indices(index, -1); + gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1); + gtk_tree_path_free(path); + + return ret; + } +#endif + assert(!"We shouldn't get here"); + return -1; /* placate dataflow analysis */ +} + +/* dlg_listbox_index returns <0 if no single element is selected. */ +int dlg_listbox_index(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu || uc->list) { + GList *children; + GtkWidget *item, *activeitem; + int i; + int selected = -1; + + if (uc->menu) + activeitem = gtk_menu_get_active(GTK_MENU(uc->menu)); + else + activeitem = NULL; /* unnecessarily placate gcc */ + + children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : + uc->list)); + for (i = 0; children!=NULL && (item = GTK_WIDGET(children->data))!=NULL; + i++, children = children->next) { + if (uc->menu ? activeitem == item : + GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED) { + if (selected == -1) + selected = i; + else + selected = -2; + } + } + g_list_free(children); + return selected < 0 ? -1 : selected; + } +#else + if (uc->combo) { + /* + * This API function already does the right thing in the + * case of no current selection. + */ + return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)); + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->treeview) { + GtkTreeSelection *treesel; + GtkTreePath *path; + GtkTreeModel *model; + GList *sellist; + gint *indices; + int ret; + + assert(uc->treeview != NULL); + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)); + + if (gtk_tree_selection_count_selected_rows(treesel) != 1) + return -1; + + sellist = gtk_tree_selection_get_selected_rows(treesel, &model); + + assert(sellist && sellist->data); + path = sellist->data; + + if (gtk_tree_path_get_depth(path) != 1) { + ret = -1; + } else { + indices = gtk_tree_path_get_indices(path); + if (!indices) { + ret = -1; + } else { + ret = indices[0]; + } + } + + g_list_foreach(sellist, (GFunc)gtk_tree_path_free, NULL); + g_list_free(sellist); + + return ret; + } +#endif + assert(!"We shouldn't get here"); + return -1; /* placate dataflow analysis */ +} + +int dlg_listbox_issel(union control *ctrl, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu || uc->list) { + GList *children; + GtkWidget *item, *activeitem; + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->menu != NULL || uc->list != NULL); + + children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : + uc->list)); + item = GTK_WIDGET(g_list_nth_data(children, index)); + g_list_free(children); + + if (uc->menu) { + activeitem = gtk_menu_get_active(GTK_MENU(uc->menu)); + return item == activeitem; + } else { + return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED; + } + } +#else + if (uc->combo) { + /* + * This API function already does the right thing in the + * case of no current selection. + */ + return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)) == index; + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->treeview) { + GtkTreeSelection *treesel; + GtkTreePath *path; + int ret; + + assert(uc->treeview != NULL); + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)); + + path = gtk_tree_path_new_from_indices(index, -1); + ret = gtk_tree_selection_path_is_selected(treesel, path); + gtk_tree_path_free(path); + + return ret; + } +#endif + assert(!"We shouldn't get here"); + return -1; /* placate dataflow analysis */ +} + +void dlg_listbox_select(union control *ctrl, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->optmenu) { + gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index); + return; + } + if (uc->list) { + int nitems; + GList *items; + gdouble newtop, newbot; + + gtk_list_select_item(GTK_LIST(uc->list), index); + + /* + * Scroll the list box if necessary to ensure the newly + * selected item is visible. + */ + items = gtk_container_children(GTK_CONTAINER(uc->list)); + nitems = g_list_length(items); + if (nitems > 0) { + int modified = FALSE; + g_list_free(items); + newtop = uc->adj->lower + + (uc->adj->upper - uc->adj->lower) * index / nitems; + newbot = uc->adj->lower + + (uc->adj->upper - uc->adj->lower) * (index+1) / nitems; + if (uc->adj->value > newtop) { + modified = TRUE; + uc->adj->value = newtop; + } else if (uc->adj->value < newbot - uc->adj->page_size) { + modified = TRUE; + uc->adj->value = newbot - uc->adj->page_size; + } + if (modified) + gtk_adjustment_value_changed(uc->adj); + } + return; + } +#else + if (uc->combo) { + gtk_combo_box_set_active(GTK_COMBO_BOX(uc->combo), index); + return; + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->treeview) { + GtkTreeSelection *treesel; + GtkTreePath *path; + + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)); + + path = gtk_tree_path_new_from_indices(index, -1); + gtk_tree_selection_select_path(treesel, path); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->treeview), + path, NULL, FALSE, 0.0, 0.0); + gtk_tree_path_free(path); + return; + } +#endif + assert(!"We shouldn't get here"); +} + +void dlg_text_set(union control *ctrl, void *dlg, char const *text) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + assert(uc->ctrl->generic.type == CTRL_TEXT); + assert(uc->text != NULL); + + gtk_label_set_text(GTK_LABEL(uc->text), text); +} + +void dlg_label_change(union control *ctrl, void *dlg, char const *text) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + switch (uc->ctrl->generic.type) { + case CTRL_BUTTON: + gtk_label_set_text(GTK_LABEL(uc->toplevel), text); + shortcut_highlight(uc->toplevel, ctrl->button.shortcut); + break; + case CTRL_CHECKBOX: + gtk_label_set_text(GTK_LABEL(uc->toplevel), text); + shortcut_highlight(uc->toplevel, ctrl->checkbox.shortcut); + break; + case CTRL_RADIO: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->radio.shortcut); + break; + case CTRL_EDITBOX: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->editbox.shortcut); + break; + case CTRL_FILESELECT: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->fileselect.shortcut); + break; + case CTRL_FONTSELECT: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->fontselect.shortcut); + break; + case CTRL_LISTBOX: + gtk_label_set_text(GTK_LABEL(uc->label), text); + shortcut_highlight(uc->label, ctrl->listbox.shortcut); + break; + default: + assert(!"This shouldn't happen"); + break; + } +} + +void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_FILESELECT); + assert(uc->entry != NULL); + gtk_entry_set_text(GTK_ENTRY(uc->entry), fn.path); +} + +void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_FILESELECT); + assert(uc->entry != NULL); + strncpy(fn->path, gtk_entry_get_text(GTK_ENTRY(uc->entry)), + lenof(fn->path)); + fn->path[lenof(fn->path)-1] = '\0'; +} + +void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->entry != NULL); + gtk_entry_set_text(GTK_ENTRY(uc->entry), fs.name); +} + +void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fs) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->entry != NULL); + strncpy(fs->name, gtk_entry_get_text(GTK_ENTRY(uc->entry)), + lenof(fs->name)); + fs->name[lenof(fs->name)-1] = '\0'; +} + +/* + * Bracketing a large set of updates in these two functions will + * cause the front end (if possible) to delay updating the screen + * until it's all complete, thus avoiding flicker. + */ +void dlg_update_start(union control *ctrl, void *dlg) +{ + /* + * Apparently we can't do this at all in GTK. GtkCList supports + * freeze and thaw, but not GtkList. Bah. + */ +} + +void dlg_update_done(union control *ctrl, void *dlg) +{ + /* + * Apparently we can't do this at all in GTK. GtkCList supports + * freeze and thaw, but not GtkList. Bah. + */ +} + +void dlg_set_focus(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + + switch (ctrl->generic.type) { + case CTRL_CHECKBOX: + case CTRL_BUTTON: + /* Check boxes and buttons get the focus _and_ get toggled. */ + gtk_widget_grab_focus(uc->toplevel); + break; + case CTRL_FILESELECT: + case CTRL_FONTSELECT: + case CTRL_EDITBOX: + if (uc->entry) { + /* Anything containing an edit box gets that focused. */ + gtk_widget_grab_focus(uc->entry); + } +#if GTK_CHECK_VERSION(2,4,0) + else if (uc->combo) { + /* Failing that, there'll be a combo box. */ + gtk_widget_grab_focus(uc->combo); + } +#endif + break; + case CTRL_RADIO: + /* + * Radio buttons: we find the currently selected button and + * focus it. + */ + { + int i; + for (i = 0; i < ctrl->radio.nbuttons; i++) + if (gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(uc->buttons[i]))) { + gtk_widget_grab_focus(uc->buttons[i]); + } + } + break; + case CTRL_LISTBOX: +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->optmenu) { + gtk_widget_grab_focus(uc->optmenu); + break; + } +#else + if (uc->combo) { + gtk_widget_grab_focus(uc->combo); + break; + } +#endif +#if !GTK_CHECK_VERSION(2,0,0) + if (uc->list) { + /* + * For GTK-1 style list boxes, we tell it to focus one + * of its children, which appears to do the Right + * Thing. + */ + gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD); + break; + } +#else + if (uc->treeview) { + gtk_widget_grab_focus(uc->treeview); + break; + } +#endif + assert(!"We shouldn't get here"); + break; + } +} + +/* + * During event processing, you might well want to give an error + * indication to the user. dlg_beep() is a quick and easy generic + * error; dlg_error() puts up a message-box or equivalent. + */ +void dlg_beep(void *dlg) +{ + gdk_beep(); +} + +static void errmsg_button_clicked(GtkButton *button, gpointer data) +{ + gtk_widget_destroy(GTK_WIDGET(data)); +} + +static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child) +{ + gint x, y, w, h, dx, dy; + GtkRequisition req; + gtk_window_set_position(GTK_WINDOW(child), GTK_WIN_POS_NONE); + gtk_widget_size_request(GTK_WIDGET(child), &req); + + gdk_window_get_origin(GTK_WIDGET(parent)->window, &x, &y); + gdk_window_get_size(GTK_WIDGET(parent)->window, &w, &h); + + /* + * One corner of the transient will be offset inwards, by 1/4 + * of the parent window's size, from the corresponding corner + * of the parent window. The corner will be chosen so as to + * place the transient closer to the centre of the screen; this + * should avoid transients going off the edge of the screen on + * a regular basis. + */ + if (x + w/2 < gdk_screen_width() / 2) + dx = x + w/4; /* work from left edges */ + else + dx = x + 3*w/4 - req.width; /* work from right edges */ + if (y + h/2 < gdk_screen_height() / 2) + dy = y + h/4; /* work from top edges */ + else + dy = y + 3*h/4 - req.height; /* work from bottom edges */ + gtk_widget_set_uposition(GTK_WIDGET(child), dx, dy); +} + +void dlg_error_msg(void *dlg, char *msg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + GtkWidget *window, *hbox, *text, *ok; + + window = gtk_dialog_new(); + text = gtk_label_new(msg); + gtk_misc_set_alignment(GTK_MISC(text), 0.0, 0.0); + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), + hbox, FALSE, FALSE, 20); + gtk_widget_show(text); + gtk_widget_show(hbox); + gtk_window_set_title(GTK_WINDOW(window), "Error"); + gtk_label_set_line_wrap(GTK_LABEL(text), TRUE); + ok = gtk_button_new_with_label("OK"); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area), + ok, FALSE, FALSE, 0); + gtk_widget_show(ok); + GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT); + gtk_window_set_default(GTK_WINDOW(window), ok); + gtk_signal_connect(GTK_OBJECT(ok), "clicked", + GTK_SIGNAL_FUNC(errmsg_button_clicked), window); + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(window_destroy), NULL); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(dp->window)); + set_transient_window_pos(dp->window, window); + gtk_widget_show(window); + gtk_main(); +} + +/* + * This function signals to the front end that the dialog's + * processing is completed, and passes an integer value (typically + * a success status). + */ +void dlg_end(void *dlg, int value) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + dp->retval = value; + gtk_widget_destroy(dp->window); +} + +void dlg_refresh(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc; + + if (ctrl) { + if (ctrl->generic.handler != NULL) + ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH); + } else { + int i; + + for (i = 0; (uc = index234(dp->byctrl, i)) != NULL; i++) { + assert(uc->ctrl != NULL); + if (uc->ctrl->generic.handler != NULL) + uc->ctrl->generic.handler(uc->ctrl, dp, + dp->data, EVENT_REFRESH); + } + } +} + +void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + gdouble cvals[4]; + + GtkWidget *coloursel = + gtk_color_selection_dialog_new("Select a colour"); + GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel); + + dp->coloursel_result.ok = FALSE; + + gtk_window_set_modal(GTK_WINDOW(coloursel), TRUE); +#if GTK_CHECK_VERSION(2,0,0) + gtk_color_selection_set_has_opacity_control(GTK_COLOR_SELECTION(ccs->colorsel), FALSE); +#else + gtk_color_selection_set_opacity(GTK_COLOR_SELECTION(ccs->colorsel), FALSE); +#endif + cvals[0] = r / 255.0; + cvals[1] = g / 255.0; + cvals[2] = b / 255.0; + cvals[3] = 1.0; /* fully opaque! */ + gtk_color_selection_set_color(GTK_COLOR_SELECTION(ccs->colorsel), cvals); + + gtk_object_set_data(GTK_OBJECT(ccs->ok_button), "user-data", + (gpointer)coloursel); + gtk_object_set_data(GTK_OBJECT(ccs->cancel_button), "user-data", + (gpointer)coloursel); + gtk_object_set_data(GTK_OBJECT(coloursel), "user-data", (gpointer)uc); + gtk_signal_connect(GTK_OBJECT(ccs->ok_button), "clicked", + GTK_SIGNAL_FUNC(coloursel_ok), (gpointer)dp); + gtk_signal_connect(GTK_OBJECT(ccs->cancel_button), "clicked", + GTK_SIGNAL_FUNC(coloursel_cancel), (gpointer)dp); + gtk_signal_connect_object(GTK_OBJECT(ccs->ok_button), "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), + (gpointer)coloursel); + gtk_signal_connect_object(GTK_OBJECT(ccs->cancel_button), "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), + (gpointer)coloursel); + gtk_widget_show(coloursel); +} + +int dlg_coloursel_results(union control *ctrl, void *dlg, + int *r, int *g, int *b) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + if (dp->coloursel_result.ok) { + *r = dp->coloursel_result.r; + *g = dp->coloursel_result.g; + *b = dp->coloursel_result.b; + return 1; + } else + return 0; +} + +/* ---------------------------------------------------------------------- + * Signal handlers while the dialog box is active. + */ + +static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, widget); + union control *focus; + + if (uc && uc->ctrl) + focus = uc->ctrl; + else + focus = NULL; + + if (focus != dp->currfocus) { + dp->lastfocus = dp->currfocus; + dp->currfocus = focus; + } + + return FALSE; +} + +static void button_clicked(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); +} + +static void button_toggled(GtkToggleButton *tb, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tb)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); +} + +static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + /* + * GtkEntry has a nasty habit of eating the Return key, which + * is unhelpful since it doesn't actually _do_ anything with it + * (it calls gtk_widget_activate, but our edit boxes never need + * activating). So I catch Return before GtkEntry sees it, and + * pass it straight on to the parent widget. Effect: hitting + * Return in an edit box will now activate the default button + * in the dialog just like it will everywhere else. + */ + if (event->keyval == GDK_Return && widget->parent != NULL) { + gboolean return_val; + gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event"); + gtk_signal_emit_by_name(GTK_OBJECT(widget->parent), "key_press_event", + event, &return_val); + return return_val; + } + return FALSE; +} + +static void editbox_changed(GtkEditable *ed, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + if (!(dp->flags & FLAG_UPDATING_COMBO_LIST)) { + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); + } +} + +static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_REFRESH); + return FALSE; +} + +#if !GTK_CHECK_VERSION(2,0,0) + +/* + * GTK 1 list box event handlers. + */ + +static gboolean listitem_key(GtkWidget *item, GdkEventKey *event, + gpointer data, int multiple) +{ + GtkAdjustment *adj = GTK_ADJUSTMENT(data); + + if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up || + event->keyval == GDK_Down || event->keyval == GDK_KP_Down || + event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up || + event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down) { + /* + * Up, Down, PgUp or PgDn have been pressed on a ListItem + * in a list box. So, if the list box is single-selection: + * + * - if the list item in question isn't already selected, + * we simply select it. + * - otherwise, we find the next one (or next + * however-far-away) in whichever direction we're going, + * and select that. + * + in this case, we must also fiddle with the + * scrollbar to ensure the newly selected item is + * actually visible. + * + * If it's multiple-selection, we do all of the above + * except actually selecting anything, so we move the focus + * and fiddle the scrollbar to follow it. + */ + GtkWidget *list = item->parent; + + gtk_signal_emit_stop_by_name(GTK_OBJECT(item), "key_press_event"); + + if (!multiple && + GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) { + gtk_list_select_child(GTK_LIST(list), item); + } else { + int direction = + (event->keyval==GDK_Up || event->keyval==GDK_KP_Up || + event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up) + ? -1 : +1; + int step = + (event->keyval==GDK_Page_Down || + event->keyval==GDK_KP_Page_Down || + event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up) + ? 2 : 1; + int i, n; + GList *children, *chead; + + chead = children = gtk_container_children(GTK_CONTAINER(list)); + + n = g_list_length(children); + + if (step == 2) { + /* + * Figure out how many list items to a screenful, + * and adjust the step appropriately. + */ + step = 0.5 + adj->page_size * n / (adj->upper - adj->lower); + step--; /* go by one less than that */ + } + + i = 0; + while (children != NULL) { + if (item == children->data) + break; + children = children->next; + i++; + } + + while (step > 0) { + if (direction < 0 && i > 0) + children = children->prev, i--; + else if (direction > 0 && i < n-1) + children = children->next, i++; + step--; + } + + if (children && children->data) { + if (!multiple) + gtk_list_select_child(GTK_LIST(list), + GTK_WIDGET(children->data)); + gtk_widget_grab_focus(GTK_WIDGET(children->data)); + gtk_adjustment_clamp_page + (adj, + adj->lower + (adj->upper-adj->lower) * i / n, + adj->lower + (adj->upper-adj->lower) * (i+1) / n); + } + + g_list_free(chead); + } + return TRUE; + } + + return FALSE; +} + +static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event, + gpointer data) +{ + return listitem_key(item, event, data, FALSE); +} + +static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event, + gpointer data) +{ + return listitem_key(item, event, data, TRUE); +} + +static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item)); + switch (event->type) { + default: + case GDK_BUTTON_PRESS: uc->nclicks = 1; break; + case GDK_2BUTTON_PRESS: uc->nclicks = 2; break; + case GDK_3BUTTON_PRESS: uc->nclicks = 3; break; + } + return FALSE; +} + +static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item)); + if (uc->nclicks>1) { + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); + return TRUE; + } + return FALSE; +} + +static void list_selchange(GtkList *list, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list)); + if (!uc) return; + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction) +{ + int index = dlg_listbox_index(uc->ctrl, dp); + GList *children = gtk_container_children(GTK_CONTAINER(uc->list)); + GtkWidget *child; + + if ((index < 0) || + (index == 0 && direction < 0) || + (index == g_list_length(children)-1 && direction > 0)) { + gdk_beep(); + return; + } + + child = g_list_nth_data(children, index); + gtk_widget_ref(child); + gtk_list_clear_items(GTK_LIST(uc->list), index, index+1); + g_list_free(children); + + children = NULL; + children = g_list_append(children, child); + gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction); + gtk_list_select_item(GTK_LIST(uc->list), index + direction); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); +} + +static void draglist_up(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); + draglist_move(dp, uc, -1); +} + +static void draglist_down(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); + draglist_move(dp, uc, +1); +} + +#else /* !GTK_CHECK_VERSION(2,0,0) */ + +/* + * GTK 2 list box event handlers. + */ + +static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(treeview)); + if (uc) + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); +} + +static void listbox_selchange(GtkTreeSelection *treeselection, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + GtkTreeView *tree = gtk_tree_selection_get_tree_view(treeselection); + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tree)); + if (uc) + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +struct draglist_valchange_ctx { + struct uctrl *uc; + struct dlgparam *dp; +}; + +static gboolean draglist_valchange(gpointer data) +{ + struct draglist_valchange_ctx *ctx = + (struct draglist_valchange_ctx *)data; + + ctx->uc->ctrl->generic.handler(ctx->uc->ctrl, ctx->dp, + ctx->dp->data, EVENT_VALCHANGE); + + sfree(ctx); + + return FALSE; +} + +static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + gpointer tree; + struct uctrl *uc; + + if (dp->flags & FLAG_UPDATING_LISTBOX) + return; /* not a user drag operation */ + + tree = g_object_get_data(G_OBJECT(treemodel), "user-data"); + uc = dlg_find_bywidget(dp, GTK_WIDGET(tree)); + if (uc) { + /* + * We should cause EVENT_VALCHANGE on the list box, now + * that its rows have been reordered. However, the GTK 2 + * docs say that at the point this signal is received the + * new row might not have actually been filled in yet. + * + * (So what smegging use is it then, eh? Don't suppose it + * occurred to you at any point that letting the + * application know _after_ the reordering was compelete + * might be helpful to someone?) + * + * To get round this, I schedule an idle function, which I + * hope won't be called until the main event loop is + * re-entered after the drag-and-drop handler has finished + * furtling with the list store. + */ + struct draglist_valchange_ctx *ctx = + snew(struct draglist_valchange_ctx); + ctx->uc = uc; + ctx->dp = dp; + g_idle_add(draglist_valchange, ctx); + } +} + +#endif /* !GTK_CHECK_VERSION(2,0,0) */ + +#if !GTK_CHECK_VERSION(2,4,0) + +static void menuitem_activate(GtkMenuItem *item, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + GtkWidget *menushell = GTK_WIDGET(item)->parent; + gpointer optmenu = gtk_object_get_data(GTK_OBJECT(menushell), "user-data"); + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +#else + +static void droplist_selchange(GtkComboBox *combo, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo)); + if (uc) + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +#endif /* !GTK_CHECK_VERSION(2,4,0) */ + +static void filesel_ok(GtkButton *button, gpointer data) +{ + /* struct dlgparam *dp = (struct dlgparam *)data; */ + gpointer filesel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); + struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(filesel), "user-data"); + const char *name = gtk_file_selection_get_filename + (GTK_FILE_SELECTION(filesel)); + gtk_entry_set_text(GTK_ENTRY(uc->entry), name); +} + +static void fontsel_ok(GtkButton *button, gpointer data) +{ + /* struct dlgparam *dp = (struct dlgparam *)data; */ + +#if !GTK_CHECK_VERSION(2,0,0) + + gpointer fontsel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); + struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(fontsel), "user-data"); + const char *name = gtk_font_selection_dialog_get_font_name + (GTK_FONT_SELECTION_DIALOG(fontsel)); + gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + +#else + + unifontsel *fontsel = (unifontsel *)gtk_object_get_data + (GTK_OBJECT(button), "user-data"); + struct uctrl *uc = (struct uctrl *)fontsel->user_data; + char *name = unifontsel_get_name(fontsel); + assert(name); /* should always be ok after OK pressed */ + gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + sfree(name); + +#endif +} + +static void coloursel_ok(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + gpointer coloursel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); + struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(coloursel), "user-data"); + gdouble cvals[4]; + gtk_color_selection_get_color + (GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(coloursel)->colorsel), + cvals); + dp->coloursel_result.r = (int) (255 * cvals[0]); + dp->coloursel_result.g = (int) (255 * cvals[1]); + dp->coloursel_result.b = (int) (255 * cvals[2]); + dp->coloursel_result.ok = TRUE; + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); +} + +static void coloursel_cancel(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + gpointer coloursel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); + struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(coloursel), "user-data"); + dp->coloursel_result.ok = FALSE; + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); +} + +static void filefont_clicked(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); + + if (uc->ctrl->generic.type == CTRL_FILESELECT) { + GtkWidget *filesel = + gtk_file_selection_new(uc->ctrl->fileselect.title); + gtk_window_set_modal(GTK_WINDOW(filesel), TRUE); + gtk_object_set_data + (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data", + (gpointer)filesel); + gtk_object_set_data(GTK_OBJECT(filesel), "user-data", (gpointer)uc); + gtk_signal_connect + (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", + GTK_SIGNAL_FUNC(filesel_ok), (gpointer)dp); + gtk_signal_connect_object + (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel); + gtk_signal_connect_object + (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked", + GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel); + gtk_widget_show(filesel); + } + + if (uc->ctrl->generic.type == CTRL_FONTSELECT) { + const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry)); + +#if !GTK_CHECK_VERSION(2,0,0) + + /* + * Use the GTK 1 standard font selector. + */ + + gchar *spacings[] = { "c", "m", NULL }; + GtkWidget *fontsel = + gtk_font_selection_dialog_new("Select a font"); + gtk_window_set_modal(GTK_WINDOW(fontsel), TRUE); + gtk_font_selection_dialog_set_filter + (GTK_FONT_SELECTION_DIALOG(fontsel), + GTK_FONT_FILTER_BASE, GTK_FONT_ALL, + NULL, NULL, NULL, NULL, spacings, NULL); + if (!gtk_font_selection_dialog_set_font_name + (GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) { + /* + * If the font name wasn't found as it was, try opening + * it and extracting its FONT property. This should + * have the effect of mapping short aliases into true + * XLFDs. + */ + GdkFont *font = gdk_font_load(fontname); + if (font) { + XFontStruct *xfs = GDK_FONT_XFONT(font); + Display *disp = GDK_FONT_XDISPLAY(font); + Atom fontprop = XInternAtom(disp, "FONT", False); + unsigned long ret; + gdk_font_ref(font); + if (XGetFontProperty(xfs, fontprop, &ret)) { + char *name = XGetAtomName(disp, (Atom)ret); + if (name) + gtk_font_selection_dialog_set_font_name + (GTK_FONT_SELECTION_DIALOG(fontsel), name); + } + gdk_font_unref(font); + } + } + gtk_object_set_data + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "user-data", (gpointer)fontsel); + gtk_object_set_data(GTK_OBJECT(fontsel), "user-data", (gpointer)uc); + gtk_signal_connect + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp); + gtk_signal_connect_object + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), + (gpointer)fontsel); + gtk_signal_connect_object + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button), + "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), + (gpointer)fontsel); + gtk_widget_show(fontsel); + +#else /* !GTK_CHECK_VERSION(2,0,0) */ + + /* + * Use the unifontsel code provided in gtkfont.c. + */ + + unifontsel *fontsel = unifontsel_new("Select a font"); + + gtk_window_set_modal(fontsel->window, TRUE); + unifontsel_set_name(fontsel, fontname); + + gtk_object_set_data(GTK_OBJECT(fontsel->ok_button), + "user-data", (gpointer)fontsel); + fontsel->user_data = uc; + gtk_signal_connect(GTK_OBJECT(fontsel->ok_button), "clicked", + GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp); + gtk_signal_connect_object(GTK_OBJECT(fontsel->ok_button), "clicked", + GTK_SIGNAL_FUNC(unifontsel_destroy), + (gpointer)fontsel); + gtk_signal_connect_object(GTK_OBJECT(fontsel->cancel_button),"clicked", + GTK_SIGNAL_FUNC(unifontsel_destroy), + (gpointer)fontsel); + + gtk_widget_show(GTK_WIDGET(fontsel->window)); + +#endif /* !GTK_CHECK_VERSION(2,0,0) */ + + } +} + +static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, widget); + + gtk_widget_set_usize(uc->text, alloc->width, -1); + gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->generic.label); + gtk_signal_disconnect(GTK_OBJECT(uc->text), uc->textsig); +} + +/* ---------------------------------------------------------------------- + * This function does the main layout work: it reads a controlset, + * it creates the relevant GTK controls, and returns a GtkWidget + * containing the result. (This widget might be a title of some + * sort, it might be a Columns containing many controls, or it + * might be a GtkFrame containing a Columns; whatever it is, it's + * definitely a GtkWidget and should probably be added to a + * GtkVbox.) + * + * `win' is required for setting the default button. If it is + * non-NULL, all buttons created will be default-capable (so they + * have extra space round them for the default highlight). + */ +GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, + struct controlset *s, GtkWindow *win) +{ + Columns *cols; + GtkWidget *ret; + int i; + + if (!s->boxname && s->boxtitle) { + /* This controlset is a panel title. */ + return gtk_label_new(s->boxtitle); + } + + /* + * Otherwise, we expect to be laying out actual controls, so + * we'll start by creating a Columns for the purpose. + */ + cols = COLUMNS(columns_new(4)); + ret = GTK_WIDGET(cols); + gtk_widget_show(ret); + + /* + * Create a containing frame if we have a box name. + */ + if (*s->boxname) { + ret = gtk_frame_new(s->boxtitle); /* NULL is valid here */ + gtk_container_set_border_width(GTK_CONTAINER(cols), 4); + gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols)); + gtk_widget_show(ret); + } + + /* + * Now iterate through the controls themselves, create them, + * and add them to the Columns. + */ + for (i = 0; i < s->ncontrols; i++) { + union control *ctrl = s->ctrls[i]; + struct uctrl *uc; + int left = FALSE; + GtkWidget *w = NULL; + + switch (ctrl->generic.type) { + case CTRL_COLUMNS: + { + static const int simplecols[1] = { 100 }; + columns_set_cols(cols, ctrl->columns.ncols, + (ctrl->columns.percentages ? + ctrl->columns.percentages : simplecols)); + } + continue; /* no actual control created */ + case CTRL_TABDELAY: + { + struct uctrl *uc = dlg_find_byctrl(dp, ctrl->tabdelay.ctrl); + if (uc) + columns_taborder_last(cols, uc->toplevel); + } + continue; /* no actual control created */ + } + + uc = snew(struct uctrl); + uc->ctrl = ctrl; + uc->privdata = NULL; + uc->privdata_needs_free = FALSE; + uc->buttons = NULL; + uc->entry = NULL; +#if !GTK_CHECK_VERSION(2,4,0) + uc->list = uc->menu = uc->optmenu = NULL; +#else + uc->combo = NULL; +#endif +#if GTK_CHECK_VERSION(2,0,0) + uc->treeview = NULL; + uc->listmodel = NULL; +#endif + uc->button = uc->text = NULL; + uc->label = NULL; + uc->nclicks = 0; + + switch (ctrl->generic.type) { + case CTRL_BUTTON: + w = gtk_button_new_with_label(ctrl->generic.label); + if (win) { + GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT); + if (ctrl->button.isdefault) + gtk_window_set_default(win, w); + if (ctrl->button.iscancel) + dp->cancelbutton = w; + } + gtk_signal_connect(GTK_OBJECT(w), "clicked", + GTK_SIGNAL_FUNC(button_clicked), dp); + gtk_signal_connect(GTK_OBJECT(w), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + shortcut_add(scs, GTK_BIN(w)->child, ctrl->button.shortcut, + SHORTCUT_UCTRL, uc); + break; + case CTRL_CHECKBOX: + w = gtk_check_button_new_with_label(ctrl->generic.label); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(button_toggled), dp); + gtk_signal_connect(GTK_OBJECT(w), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + shortcut_add(scs, GTK_BIN(w)->child, ctrl->checkbox.shortcut, + SHORTCUT_UCTRL, uc); + left = TRUE; + break; + case CTRL_RADIO: + /* + * Radio buttons get to go inside their own Columns, no + * matter what. + */ + { + gint i, *percentages; + GSList *group; + + w = columns_new(0); + if (ctrl->generic.label) { + GtkWidget *label = gtk_label_new(ctrl->generic.label); + columns_add(COLUMNS(w), label, 0, 1); + columns_force_left_align(COLUMNS(w), label); + gtk_widget_show(label); + shortcut_add(scs, label, ctrl->radio.shortcut, + SHORTCUT_UCTRL, uc); + uc->label = label; + } + percentages = g_new(gint, ctrl->radio.ncolumns); + for (i = 0; i < ctrl->radio.ncolumns; i++) { + percentages[i] = + ((100 * (i+1) / ctrl->radio.ncolumns) - + 100 * i / ctrl->radio.ncolumns); + } + columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns, + percentages); + g_free(percentages); + group = NULL; + + uc->nbuttons = ctrl->radio.nbuttons; + uc->buttons = snewn(uc->nbuttons, GtkWidget *); + + for (i = 0; i < ctrl->radio.nbuttons; i++) { + GtkWidget *b; + gint colstart; + + b = (gtk_radio_button_new_with_label + (group, ctrl->radio.buttons[i])); + uc->buttons[i] = b; + group = gtk_radio_button_group(GTK_RADIO_BUTTON(b)); + colstart = i % ctrl->radio.ncolumns; + columns_add(COLUMNS(w), b, colstart, + (i == ctrl->radio.nbuttons-1 ? + ctrl->radio.ncolumns - colstart : 1)); + columns_force_left_align(COLUMNS(w), b); + gtk_widget_show(b); + gtk_signal_connect(GTK_OBJECT(b), "toggled", + GTK_SIGNAL_FUNC(button_toggled), dp); + gtk_signal_connect(GTK_OBJECT(b), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + if (ctrl->radio.shortcuts) { + shortcut_add(scs, GTK_BIN(b)->child, + ctrl->radio.shortcuts[i], + SHORTCUT_UCTRL, uc); + } + } + } + break; + case CTRL_EDITBOX: + { + GtkRequisition req; + GtkWidget *signalobject; + + if (ctrl->editbox.has_list) { +#if !GTK_CHECK_VERSION(2,4,0) + /* + * GTK 1 combo box. + */ + w = gtk_combo_new(); + gtk_combo_set_value_in_list(GTK_COMBO(w), FALSE, TRUE); + uc->entry = GTK_COMBO(w)->entry; + uc->list = GTK_COMBO(w)->list; + signalobject = uc->entry; +#else + /* + * GTK 2 combo box. + */ + uc->listmodel = gtk_list_store_new(2, G_TYPE_INT, + G_TYPE_STRING); + w = gtk_combo_box_entry_new_with_model + (GTK_TREE_MODEL(uc->listmodel), 1); + /* We cannot support password combo boxes. */ + assert(!ctrl->editbox.password); + uc->combo = w; + signalobject = uc->combo; +#endif + } else { + w = gtk_entry_new(); + if (ctrl->editbox.password) + gtk_entry_set_visibility(GTK_ENTRY(w), FALSE); + uc->entry = w; + signalobject = w; + } + uc->entrysig = + gtk_signal_connect(GTK_OBJECT(signalobject), "changed", + GTK_SIGNAL_FUNC(editbox_changed), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "key_press_event", + GTK_SIGNAL_FUNC(editbox_key), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "focus_out_event", + GTK_SIGNAL_FUNC(editbox_lostfocus), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "focus_out_event", + GTK_SIGNAL_FUNC(editbox_lostfocus), dp); + /* + * Edit boxes, for some strange reason, have a minimum + * width of 150 in GTK 1.2. We don't want this - we'd + * rather the edit boxes acquired their natural width + * from the column layout of the rest of the box. + * + * Also, while we're here, we'll squirrel away the + * edit box height so we can use that to centre its + * label vertically beside it. + */ + gtk_widget_size_request(w, &req); + gtk_widget_set_usize(w, 10, req.height); + + if (ctrl->generic.label) { + GtkWidget *label, *container; + + label = gtk_label_new(ctrl->generic.label); + + shortcut_add(scs, label, ctrl->editbox.shortcut, + SHORTCUT_FOCUS, uc->entry); + + container = columns_new(4); + if (ctrl->editbox.percentwidth == 100) { + columns_add(COLUMNS(container), label, 0, 1); + columns_force_left_align(COLUMNS(container), label); + columns_add(COLUMNS(container), w, 0, 1); + } else { + gint percentages[2]; + percentages[1] = ctrl->editbox.percentwidth; + percentages[0] = 100 - ctrl->editbox.percentwidth; + columns_set_cols(COLUMNS(container), 2, percentages); + columns_add(COLUMNS(container), label, 0, 1); + columns_force_left_align(COLUMNS(container), label); + columns_add(COLUMNS(container), w, 1, 1); + /* Centre the label vertically. */ + gtk_widget_set_usize(label, -1, req.height); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + } + gtk_widget_show(label); + gtk_widget_show(w); + + w = container; + uc->label = label; + } + } + break; + case CTRL_FILESELECT: + case CTRL_FONTSELECT: + { + GtkWidget *ww; + GtkRequisition req; + char *browsebtn = + (ctrl->generic.type == CTRL_FILESELECT ? + "Browse..." : "Change..."); + + gint percentages[] = { 75, 25 }; + w = columns_new(4); + columns_set_cols(COLUMNS(w), 2, percentages); + + if (ctrl->generic.label) { + ww = gtk_label_new(ctrl->generic.label); + columns_add(COLUMNS(w), ww, 0, 2); + columns_force_left_align(COLUMNS(w), ww); + gtk_widget_show(ww); + shortcut_add(scs, ww, + (ctrl->generic.type == CTRL_FILESELECT ? + ctrl->fileselect.shortcut : + ctrl->fontselect.shortcut), + SHORTCUT_UCTRL, uc); + uc->label = ww; + } + + uc->entry = ww = gtk_entry_new(); + gtk_widget_size_request(ww, &req); + gtk_widget_set_usize(ww, 10, req.height); + columns_add(COLUMNS(w), ww, 0, 1); + gtk_widget_show(ww); + + uc->button = ww = gtk_button_new_with_label(browsebtn); + columns_add(COLUMNS(w), ww, 1, 1); + gtk_widget_show(ww); + + gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event", + GTK_SIGNAL_FUNC(editbox_key), dp); + uc->entrysig = + gtk_signal_connect(GTK_OBJECT(uc->entry), "changed", + GTK_SIGNAL_FUNC(editbox_changed), dp); + gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(uc->button), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(ww), "clicked", + GTK_SIGNAL_FUNC(filefont_clicked), dp); + } + break; + case CTRL_LISTBOX: + +#if GTK_CHECK_VERSION(2,0,0) + /* + * First construct the list data store, with the right + * number of columns. + */ +# if !GTK_CHECK_VERSION(2,4,0) + /* (For GTK 2.0 to 2.3, we do this for full listboxes only, + * because combo boxes are still done the old GTK1 way.) */ + if (ctrl->listbox.height > 0) +# endif + { + GType *types; + int i; + int cols; + + cols = ctrl->listbox.ncols; + cols = cols ? cols : 1; + types = snewn(1 + cols, GType); + + types[0] = G_TYPE_INT; + for (i = 0; i < cols; i++) + types[i+1] = G_TYPE_STRING; + + uc->listmodel = gtk_list_store_newv(1 + cols, types); + + sfree(types); + } +#endif + + /* + * See if it's a drop-down list (non-editable combo + * box). + */ + if (ctrl->listbox.height == 0) { +#if !GTK_CHECK_VERSION(2,4,0) + /* + * GTK1 and early-GTK2 option-menu style of + * drop-down list. + */ + uc->optmenu = w = gtk_option_menu_new(); + uc->menu = gtk_menu_new(); + gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu); + gtk_object_set_data(GTK_OBJECT(uc->menu), "user-data", + (gpointer)uc->optmenu); + gtk_signal_connect(GTK_OBJECT(uc->optmenu), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); +#else + /* + * Late-GTK2 style using a GtkComboBox. + */ + GtkCellRenderer *cr; + + /* + * Create a non-editable GtkComboBox (that is, not + * its subclass GtkComboBoxEntry). + */ + w = gtk_combo_box_new_with_model + (GTK_TREE_MODEL(uc->listmodel)); + uc->combo = w; + + /* + * Tell it how to render a list item (i.e. which + * column to look at in the list model). + */ + cr = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr, + "text", 1, NULL); + + /* + * And tell it to notify us when the selection + * changes. + */ + g_signal_connect(G_OBJECT(w), "changed", + G_CALLBACK(droplist_selchange), dp); +#endif + } else { +#if !GTK_CHECK_VERSION(2,0,0) + /* + * GTK1-style full list box. + */ + uc->list = gtk_list_new(); + if (ctrl->listbox.multisel == 2) { + gtk_list_set_selection_mode(GTK_LIST(uc->list), + GTK_SELECTION_EXTENDED); + } else if (ctrl->listbox.multisel == 1) { + gtk_list_set_selection_mode(GTK_LIST(uc->list), + GTK_SELECTION_MULTIPLE); + } else { + gtk_list_set_selection_mode(GTK_LIST(uc->list), + GTK_SELECTION_SINGLE); + } + w = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w), + uc->list); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + uc->adj = gtk_scrolled_window_get_vadjustment + (GTK_SCROLLED_WINDOW(w)); + + gtk_widget_show(uc->list); + gtk_signal_connect(GTK_OBJECT(uc->list), "selection-changed", + GTK_SIGNAL_FUNC(list_selchange), dp); + gtk_signal_connect(GTK_OBJECT(uc->list), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + + /* + * Adjust the height of the scrolled window to the + * minimum given by the height parameter. + * + * This piece of guesswork is a horrid hack based + * on looking inside the GTK 1.2 sources + * (specifically gtkviewport.c, which appears to be + * the widget which provides the border around the + * scrolling area). Anyone lets me know how I can + * do this in a way which isn't at risk from GTK + * upgrades, I'd be grateful. + */ + { + int edge; + edge = GTK_WIDGET(uc->list)->style->klass->ythickness; + gtk_widget_set_usize(w, 10, + 2*edge + (ctrl->listbox.height * + get_listitemheight(w))); + } + + if (ctrl->listbox.draglist) { + /* + * GTK doesn't appear to make it easy to + * implement a proper draggable list; so + * instead I'm just going to have to put an Up + * and a Down button to the right of the actual + * list box. Ah well. + */ + GtkWidget *cols, *button; + static const gint percentages[2] = { 80, 20 }; + + cols = columns_new(4); + columns_set_cols(COLUMNS(cols), 2, percentages); + columns_add(COLUMNS(cols), w, 0, 1); + gtk_widget_show(w); + button = gtk_button_new_with_label("Up"); + columns_add(COLUMNS(cols), button, 1, 1); + gtk_widget_show(button); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(draglist_up), dp); + gtk_signal_connect(GTK_OBJECT(button), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + button = gtk_button_new_with_label("Down"); + columns_add(COLUMNS(cols), button, 1, 1); + gtk_widget_show(button); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(draglist_down), dp); + gtk_signal_connect(GTK_OBJECT(button), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + + w = cols; + } +#else + /* + * GTK2 treeview-based full list box. + */ + GtkTreeSelection *sel; + + /* + * Create the list box itself, its columns, and + * its containing scrolled window. + */ + w = gtk_tree_view_new_with_model + (GTK_TREE_MODEL(uc->listmodel)); + g_object_set_data(G_OBJECT(uc->listmodel), "user-data", + (gpointer)w); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w)); + gtk_tree_selection_set_mode + (sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE : + GTK_SELECTION_SINGLE); + uc->treeview = w; + gtk_signal_connect(GTK_OBJECT(w), "row-activated", + GTK_SIGNAL_FUNC(listbox_doubleclick), dp); + g_signal_connect(G_OBJECT(sel), "changed", + G_CALLBACK(listbox_selchange), dp); + + if (ctrl->listbox.draglist) { + gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), TRUE); + g_signal_connect(G_OBJECT(uc->listmodel), "row-inserted", + G_CALLBACK(listbox_reorder), dp); + } + + { + int i; + int cols; + + cols = ctrl->listbox.ncols; + cols = cols ? cols : 1; + for (i = 0; i < cols; i++) { + GtkTreeViewColumn *column; + /* + * It appears that GTK 2 doesn't leave us any + * particularly sensible way to honour the + * "percentages" specification in the ctrl + * structure. + */ + column = gtk_tree_view_column_new_with_attributes + ("heading", gtk_cell_renderer_text_new(), + "text", i+1, (char *)NULL); + gtk_tree_view_column_set_sizing + (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + } + } + + { + GtkWidget *scroll; + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type + (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN); + gtk_widget_show(w); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + gtk_widget_set_size_request + (scroll, -1, + ctrl->listbox.height * get_listitemheight(w)); + + w = scroll; + } +#endif + } + + if (ctrl->generic.label) { + GtkWidget *label, *container; + GtkRequisition req; + + label = gtk_label_new(ctrl->generic.label); + + shortcut_add(scs, label, ctrl->listbox.shortcut, + SHORTCUT_FOCUS, w); + + container = columns_new(4); + if (ctrl->listbox.percentwidth == 100) { + columns_add(COLUMNS(container), label, 0, 1); + columns_force_left_align(COLUMNS(container), label); + columns_add(COLUMNS(container), w, 0, 1); + } else { + gint percentages[2]; + percentages[1] = ctrl->listbox.percentwidth; + percentages[0] = 100 - ctrl->listbox.percentwidth; + columns_set_cols(COLUMNS(container), 2, percentages); + columns_add(COLUMNS(container), label, 0, 1); + columns_force_left_align(COLUMNS(container), label); + columns_add(COLUMNS(container), w, 1, 1); + /* Centre the label vertically. */ + gtk_widget_size_request(w, &req); + gtk_widget_set_usize(label, -1, req.height); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + } + gtk_widget_show(label); + gtk_widget_show(w); + + w = container; + uc->label = label; + } + + break; + case CTRL_TEXT: + /* + * Wrapping text widgets don't sit well with the GTK + * layout model, in which widgets state a minimum size + * and the whole window then adjusts to the smallest + * size it can sensibly take given its contents. A + * wrapping text widget _has_ no clear minimum size; + * instead it has a range of possibilities. It can be + * one line deep but 2000 wide, or two lines deep and + * 1000 pixels, or three by 867, or four by 500 and so + * on. It can be as short as you like provided you + * don't mind it being wide, or as narrow as you like + * provided you don't mind it being tall. + * + * Therefore, it fits very badly into the layout model. + * Hence the only thing to do is pick a width and let + * it choose its own number of lines. To do this I'm + * going to cheat a little. All new wrapping text + * widgets will be created with a minimal text content + * "X"; then, after the rest of the dialog box is set + * up and its size calculated, the text widgets will be + * told their width and given their real text, which + * will cause the size to be recomputed in the y + * direction (because many of them will expand to more + * than one line). + */ + uc->text = w = gtk_label_new("X"); + gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.0); + gtk_label_set_line_wrap(GTK_LABEL(w), TRUE); + uc->textsig = + gtk_signal_connect(GTK_OBJECT(w), "size-allocate", + GTK_SIGNAL_FUNC(label_sizealloc), dp); + break; + } + + assert(w != NULL); + + columns_add(cols, w, + COLUMN_START(ctrl->generic.column), + COLUMN_SPAN(ctrl->generic.column)); + if (left) + columns_force_left_align(cols, w); + gtk_widget_show(w); + + uc->toplevel = w; + dlg_add_uctrl(dp, uc); + } + + return ret; +} + +struct selparam { + struct dlgparam *dp; + GtkNotebook *panels; + GtkWidget *panel; +#if !GTK_CHECK_VERSION(2,0,0) + GtkWidget *treeitem; +#else + int depth; + GtkTreePath *treepath; +#endif + struct Shortcuts shortcuts; +}; + +#if GTK_CHECK_VERSION(2,0,0) +static void treeselection_changed(GtkTreeSelection *treeselection, + gpointer data) +{ + struct selparam *sps = (struct selparam *)data, *sp; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + gint spindex; + gint page_num; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, TREESTORE_PARAMS, &spindex, -1); + sp = &sps[spindex]; + + page_num = gtk_notebook_page_num(sp->panels, sp->panel); + gtk_notebook_set_page(sp->panels, page_num); + + dlg_refresh(NULL, sp->dp); + + sp->dp->shortcuts = &sp->shortcuts; +} +#else +static void treeitem_sel(GtkItem *item, gpointer data) +{ + struct selparam *sp = (struct selparam *)data; + gint page_num; + + page_num = gtk_notebook_page_num(sp->panels, sp->panel); + gtk_notebook_set_page(sp->panels, page_num); + + dlg_refresh(NULL, sp->dp); + + sp->dp->shortcuts = &sp->shortcuts; + sp->dp->currtreeitem = sp->treeitem; +} +#endif + +static void window_destroy(GtkWidget *widget, gpointer data) +{ + gtk_main_quit(); +} + +#if !GTK_CHECK_VERSION(2,0,0) +static int tree_grab_focus(struct dlgparam *dp) +{ + int i, f; + + /* + * See if any of the treeitems has the focus. + */ + f = -1; + for (i = 0; i < dp->ntreeitems; i++) + if (GTK_WIDGET_HAS_FOCUS(dp->treeitems[i])) { + f = i; + break; + } + + if (f >= 0) + return FALSE; + else { + gtk_widget_grab_focus(dp->currtreeitem); + return TRUE; + } +} + +gint tree_focus(GtkContainer *container, GtkDirectionType direction, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + + gtk_signal_emit_stop_by_name(GTK_OBJECT(container), "focus"); + /* + * If there's a focused treeitem, we return FALSE to cause the + * focus to move on to some totally other control. If not, we + * focus the selected one. + */ + return tree_grab_focus(dp); +} +#endif + +int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + + if (event->keyval == GDK_Escape && dp->cancelbutton) { + gtk_signal_emit_by_name(GTK_OBJECT(dp->cancelbutton), "clicked"); + return TRUE; + } + + if ((event->state & GDK_MOD1_MASK) && + (unsigned char)event->string[0] > 0 && + (unsigned char)event->string[0] <= 127) { + int schr = (unsigned char)event->string[0]; + struct Shortcut *sc = &dp->shortcuts->sc[schr]; + + switch (sc->action) { + case SHORTCUT_TREE: +#if GTK_CHECK_VERSION(2,0,0) + gtk_widget_grab_focus(sc->widget); +#else + tree_grab_focus(dp); +#endif + break; + case SHORTCUT_FOCUS: + gtk_widget_grab_focus(sc->widget); + break; + case SHORTCUT_UCTRL: + /* + * We must do something sensible with a uctrl. + * Precisely what this is depends on the type of + * control. + */ + switch (sc->uc->ctrl->generic.type) { + case CTRL_CHECKBOX: + case CTRL_BUTTON: + /* Check boxes and buttons get the focus _and_ get toggled. */ + gtk_widget_grab_focus(sc->uc->toplevel); + gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->toplevel), + "clicked"); + break; + case CTRL_FILESELECT: + case CTRL_FONTSELECT: + /* File/font selectors have their buttons pressed (ooer), + * and focus transferred to the edit box. */ + gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->button), + "clicked"); + gtk_widget_grab_focus(sc->uc->entry); + break; + case CTRL_RADIO: + /* + * Radio buttons are fun, because they have + * multiple shortcuts. We must find whether the + * activated shortcut is the shortcut for the whole + * group, or for a particular button. In the former + * case, we find the currently selected button and + * focus it; in the latter, we focus-and-click the + * button whose shortcut was pressed. + */ + if (schr == sc->uc->ctrl->radio.shortcut) { + int i; + for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++) + if (gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) { + gtk_widget_grab_focus(sc->uc->buttons[i]); + } + } else if (sc->uc->ctrl->radio.shortcuts) { + int i; + for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++) + if (schr == sc->uc->ctrl->radio.shortcuts[i]) { + gtk_widget_grab_focus(sc->uc->buttons[i]); + gtk_signal_emit_by_name + (GTK_OBJECT(sc->uc->buttons[i]), "clicked"); + } + } + break; + case CTRL_LISTBOX: + +#if !GTK_CHECK_VERSION(2,4,0) + if (sc->uc->optmenu) { + GdkEventButton bev; + gint returnval; + + gtk_widget_grab_focus(sc->uc->optmenu); + /* Option menus don't work using the "clicked" signal. + * We need to manufacture a button press event :-/ */ + bev.type = GDK_BUTTON_PRESS; + bev.button = 1; + gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->optmenu), + "button_press_event", + &bev, &returnval); + break; + } +#else + if (sc->uc->combo) { + gtk_widget_grab_focus(sc->uc->combo); + gtk_combo_box_popup(GTK_COMBO_BOX(sc->uc->combo)); + break; + } +#endif +#if !GTK_CHECK_VERSION(2,0,0) + if (sc->uc->list) { + /* + * For GTK-1 style list boxes, we tell it to + * focus one of its children, which appears to + * do the Right Thing. + */ + gtk_container_focus(GTK_CONTAINER(sc->uc->list), + GTK_DIR_TAB_FORWARD); + break; + } +#else + if (sc->uc->treeview) { + gtk_widget_grab_focus(sc->uc->treeview); + break; + } +#endif + assert(!"We shouldn't get here"); + break; + } + break; + } + } + + return FALSE; +} + +#if !GTK_CHECK_VERSION(2,0,0) +int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + + if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up || + event->keyval == GDK_Down || event->keyval == GDK_KP_Down) { + int dir, i, j = -1; + for (i = 0; i < dp->ntreeitems; i++) + if (widget == dp->treeitems[i]) + break; + if (i < dp->ntreeitems) { + if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up) + dir = -1; + else + dir = +1; + + while (1) { + i += dir; + if (i < 0 || i >= dp->ntreeitems) + break; /* nothing in that dir to select */ + /* + * Determine if this tree item is visible. + */ + { + GtkWidget *w = dp->treeitems[i]; + int vis = TRUE; + while (w && (GTK_IS_TREE_ITEM(w) || GTK_IS_TREE(w))) { + if (!GTK_WIDGET_VISIBLE(w)) { + vis = FALSE; + break; + } + w = w->parent; + } + if (vis) { + j = i; /* got one */ + break; + } + } + } + } + gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), + "key_press_event"); + if (j >= 0) { + gtk_signal_emit_by_name(GTK_OBJECT(dp->treeitems[j]), "toggle"); + gtk_widget_grab_focus(dp->treeitems[j]); + } + return TRUE; + } + + /* + * It's nice for Left and Right to expand and collapse tree + * branches. + */ + if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left) { + gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), + "key_press_event"); + gtk_tree_item_collapse(GTK_TREE_ITEM(widget)); + return TRUE; + } + if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right) { + gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), + "key_press_event"); + gtk_tree_item_expand(GTK_TREE_ITEM(widget)); + return TRUE; + } + + return FALSE; +} +#endif + +static void shortcut_highlight(GtkWidget *labelw, int chr) +{ + GtkLabel *label = GTK_LABEL(labelw); + gchar *currstr, *pattern; + int i; + + gtk_label_get(label, &currstr); + for (i = 0; currstr[i]; i++) + if (tolower((unsigned char)currstr[i]) == chr) { + GtkRequisition req; + + pattern = dupprintf("%*s_", i, ""); + + gtk_widget_size_request(GTK_WIDGET(label), &req); + gtk_label_set_pattern(label, pattern); + gtk_widget_set_usize(GTK_WIDGET(label), -1, req.height); + + sfree(pattern); + break; + } +} + +void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, + int chr, int action, void *ptr) +{ + if (chr == NO_SHORTCUT) + return; + + chr = tolower((unsigned char)chr); + + assert(scs->sc[chr].action == SHORTCUT_EMPTY); + + scs->sc[chr].action = action; + + if (action == SHORTCUT_FOCUS) { + scs->sc[chr].uc = NULL; + scs->sc[chr].widget = (GtkWidget *)ptr; + } else { + scs->sc[chr].widget = NULL; + scs->sc[chr].uc = (struct uctrl *)ptr; + } + + shortcut_highlight(labelw, chr); +} + +int get_listitemheight(GtkWidget *w) +{ +#if !GTK_CHECK_VERSION(2,0,0) + GtkWidget *listitem = gtk_list_item_new_with_label("foo"); + GtkRequisition req; + gtk_widget_size_request(listitem, &req); + gtk_object_sink(GTK_OBJECT(listitem)); + return req.height; +#else + int height; + GtkCellRenderer *cr = gtk_cell_renderer_text_new(); + gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height); + g_object_ref(G_OBJECT(cr)); + gtk_object_sink(GTK_OBJECT(cr)); + g_object_unref(G_OBJECT(cr)); + return height; +#endif +} + +void set_dialog_action_area(GtkDialog *dlg, GtkWidget *w) +{ +#if !GTK_CHECK_VERSION(2,0,0) + + /* + * In GTK 1, laying out the buttons at the bottom of the + * configuration box is nice and easy, because a GtkDialog's + * action_area is a GtkHBox which stretches to cover the full + * width of the dialog. So we just put our Columns widget + * straight into that hbox, and it ends up just where we want + * it. + */ + gtk_box_pack_start(GTK_BOX(dlg->action_area), w, TRUE, TRUE, 0); + +#else + /* + * In GTK 2, the action area is now a GtkHButtonBox and its + * layout behaviour seems to be different: it doesn't stretch + * to cover the full width of the window, but instead finds its + * own preferred width and right-aligns that within the window. + * This isn't what we want, because we have both left-aligned + * and right-aligned buttons coming out of the above call to + * layout_ctrls(), and right-aligning the whole thing will + * result in the former being centred and looking weird. + * + * So instead we abandon the dialog's action area completely: + * we gtk_widget_hide() it in the below code, and we also call + * gtk_dialog_set_has_separator() to remove the separator above + * it. We then insert our own action area into the end of the + * dialog's main vbox, and add our own separator above that. + * + * (Ideally, if we were a native GTK app, we would use the + * GtkHButtonBox's _own_ innate ability to support one set of + * buttons being right-aligned and one left-aligned. But to do + * that here, we would have to either (a) pick apart our cross- + * platform layout structures and treat them specially for this + * particular set of controls, which would be painful, or else + * (b) develop a special and simpler cross-platform + * representation for these particular controls, and introduce + * special-case code into all the _other_ platforms to handle + * it. Neither appeals. Therefore, I regretfully discard the + * GTKHButtonBox and go it alone.) + */ + + GtkWidget *align; + align = gtk_alignment_new(0, 0, 1, 1); + gtk_container_add(GTK_CONTAINER(align), w); + /* + * The purpose of this GtkAlignment is to provide padding + * around the buttons. The padding we use is twice the padding + * used in our GtkColumns, because we nest two GtkColumns most + * of the time (one separating the tree view from the main + * controls, and another for the main controls themselves). + */ +#if GTK_CHECK_VERSION(2,4,0) + gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8); +#endif + gtk_widget_show(align); + gtk_box_pack_end(GTK_BOX(dlg->vbox), align, FALSE, TRUE, 0); + w = gtk_hseparator_new(); + gtk_box_pack_end(GTK_BOX(dlg->vbox), w, FALSE, TRUE, 0); + gtk_widget_show(w); + gtk_widget_hide(dlg->action_area); + gtk_dialog_set_has_separator(dlg, FALSE); +#endif +} + +int do_config_box(const char *title, Config *cfg, int midsession, + int protcfginfo) +{ + GtkWidget *window, *hbox, *vbox, *cols, *label, + *tree, *treescroll, *panels, *panelvbox; + int index, level; + struct controlbox *ctrlbox; + char *path; +#if GTK_CHECK_VERSION(2,0,0) + GtkTreeStore *treestore; + GtkCellRenderer *treerenderer; + GtkTreeViewColumn *treecolumn; + GtkTreeSelection *treeselection; + GtkTreeIter treeiterlevels[8]; +#else + GtkTreeItem *treeitemlevels[8]; + GtkTree *treelevels[8]; +#endif + struct dlgparam dp; + struct Shortcuts scs; + + struct selparam *selparams = NULL; + int nselparams = 0, selparamsize = 0; + + dlg_init(&dp); + + for (index = 0; index < lenof(scs.sc); index++) { + scs.sc[index].action = SHORTCUT_EMPTY; + } + + window = gtk_dialog_new(); + + ctrlbox = ctrl_new_box(); + setup_config_box(ctrlbox, midsession, cfg->protocol, protcfginfo); + unix_setup_config_box(ctrlbox, midsession, cfg->protocol); + gtk_setup_config_box(ctrlbox, midsession, window); + + gtk_window_set_title(GTK_WINDOW(window), title); + hbox = gtk_hbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), hbox, TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 10); + gtk_widget_show(hbox); + vbox = gtk_vbox_new(FALSE, 4); + gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show(vbox); + cols = columns_new(4); + gtk_box_pack_start(GTK_BOX(vbox), cols, FALSE, FALSE, 0); + gtk_widget_show(cols); + label = gtk_label_new("Category:"); + columns_add(COLUMNS(cols), label, 0, 1); + columns_force_left_align(COLUMNS(cols), label); + gtk_widget_show(label); + treescroll = gtk_scrolled_window_new(NULL, NULL); +#if GTK_CHECK_VERSION(2,0,0) + treestore = gtk_tree_store_new + (TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT); + tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE); + treerenderer = gtk_cell_renderer_text_new(); + treecolumn = gtk_tree_view_column_new_with_attributes + ("Label", treerenderer, "text", 0, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn); + treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)); + gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE); + gtk_container_add(GTK_CONTAINER(treescroll), tree); +#else + tree = gtk_tree_new(); + gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM); + gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE); + gtk_signal_connect(GTK_OBJECT(tree), "focus", + GTK_SIGNAL_FUNC(tree_focus), &dp); +#endif + gtk_signal_connect(GTK_OBJECT(tree), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), &dp); + shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree); + gtk_widget_show(treescroll); + gtk_box_pack_start(GTK_BOX(vbox), treescroll, TRUE, TRUE, 0); + panels = gtk_notebook_new(); + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(panels), FALSE); + gtk_notebook_set_show_border(GTK_NOTEBOOK(panels), FALSE); + gtk_box_pack_start(GTK_BOX(hbox), panels, TRUE, TRUE, 0); + gtk_widget_show(panels); + + panelvbox = NULL; + path = NULL; + level = 0; + for (index = 0; index < ctrlbox->nctrlsets; index++) { + struct controlset *s = ctrlbox->ctrlsets[index]; + GtkWidget *w; + + if (!*s->pathname) { + w = layout_ctrls(&dp, &scs, s, GTK_WINDOW(window)); + + set_dialog_action_area(GTK_DIALOG(window), w); + } else { + int j = path ? ctrl_path_compare(s->pathname, path) : 0; + if (j != INT_MAX) { /* add to treeview, start new panel */ + char *c; +#if GTK_CHECK_VERSION(2,0,0) + GtkTreeIter treeiter; +#else + GtkWidget *treeitem; +#endif + int first; + + /* + * We expect never to find an implicit path + * component. For example, we expect never to see + * A/B/C followed by A/D/E, because that would + * _implicitly_ create A/D. All our path prefixes + * are expected to contain actual controls and be + * selectable in the treeview; so we would expect + * to see A/D _explicitly_ before encountering + * A/D/E. + */ + assert(j == ctrl_path_elements(s->pathname) - 1); + + c = strrchr(s->pathname, '/'); + if (!c) + c = s->pathname; + else + c++; + + path = s->pathname; + + first = (panelvbox == NULL); + + panelvbox = gtk_vbox_new(FALSE, 4); + gtk_widget_show(panelvbox); + gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox, + NULL); + if (first) { + gint page_num; + + page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels), + panelvbox); + gtk_notebook_set_page(GTK_NOTEBOOK(panels), page_num); + } + + if (nselparams >= selparamsize) { + selparamsize += 16; + selparams = sresize(selparams, selparamsize, + struct selparam); + } + selparams[nselparams].dp = &dp; + selparams[nselparams].panels = GTK_NOTEBOOK(panels); + selparams[nselparams].panel = panelvbox; + selparams[nselparams].shortcuts = scs; /* structure copy */ + + assert(j-1 < level); + +#if GTK_CHECK_VERSION(2,0,0) + if (j > 0) + /* treeiterlevels[j-1] will always be valid because we + * don't allow implicit path components; see above. + */ + gtk_tree_store_append(treestore, &treeiter, + &treeiterlevels[j-1]); + else + gtk_tree_store_append(treestore, &treeiter, NULL); + gtk_tree_store_set(treestore, &treeiter, + TREESTORE_PATH, c, + TREESTORE_PARAMS, nselparams, + -1); + treeiterlevels[j] = treeiter; + + selparams[nselparams].depth = j; + if (j > 0) { + selparams[nselparams].treepath = + gtk_tree_model_get_path(GTK_TREE_MODEL(treestore), + &treeiterlevels[j-1]); + /* + * We are going to collapse all tree branches + * at depth greater than 2, but not _yet_; see + * the comment at the call to + * gtk_tree_view_collapse_row below. + */ + gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), + selparams[nselparams].treepath, + FALSE); + } else { + selparams[nselparams].treepath = NULL; + } +#else + treeitem = gtk_tree_item_new_with_label(c); + if (j > 0) { + if (!treelevels[j-1]) { + treelevels[j-1] = GTK_TREE(gtk_tree_new()); + gtk_tree_item_set_subtree + (treeitemlevels[j-1], + GTK_WIDGET(treelevels[j-1])); + if (j < 2) + gtk_tree_item_expand(treeitemlevels[j-1]); + else + gtk_tree_item_collapse(treeitemlevels[j-1]); + } + gtk_tree_append(treelevels[j-1], treeitem); + } else { + gtk_tree_append(GTK_TREE(tree), treeitem); + } + treeitemlevels[j] = GTK_TREE_ITEM(treeitem); + treelevels[j] = NULL; + + gtk_signal_connect(GTK_OBJECT(treeitem), "key_press_event", + GTK_SIGNAL_FUNC(tree_key_press), &dp); + gtk_signal_connect(GTK_OBJECT(treeitem), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), &dp); + + gtk_widget_show(treeitem); + + if (first) + gtk_tree_select_child(GTK_TREE(tree), treeitem); + selparams[nselparams].treeitem = treeitem; +#endif + + level = j+1; + nselparams++; + } + + w = layout_ctrls(&dp, &selparams[nselparams-1].shortcuts, s, NULL); + gtk_box_pack_start(GTK_BOX(panelvbox), w, FALSE, FALSE, 0); + gtk_widget_show(w); + } + } + +#if GTK_CHECK_VERSION(2,0,0) + { + GtkRequisition req; + int i; + + /* + * We want our tree view to come up with all branches at + * depth 2 or more collapsed. However, if we start off + * with those branches collapsed, then the tree view's + * size request will be calculated based on the width of + * the collapsed tree. So instead we start with them all + * expanded; then we ask for the current size request, + * collapse the relevant rows, and force the width to the + * value we just computed. This arranges that the tree + * view is wide enough to have all branches expanded + * safely. + */ + + gtk_widget_size_request(tree, &req); + + for (i = 0; i < nselparams; i++) + if (selparams[i].depth >= 2) + gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), + selparams[i].treepath); + + gtk_widget_set_size_request(tree, req.width, -1); + } +#endif + +#if GTK_CHECK_VERSION(2,0,0) + g_signal_connect(G_OBJECT(treeselection), "changed", + G_CALLBACK(treeselection_changed), selparams); +#else + dp.ntreeitems = nselparams; + dp.treeitems = snewn(dp.ntreeitems, GtkWidget *); + + for (index = 0; index < nselparams; index++) { + gtk_signal_connect(GTK_OBJECT(selparams[index].treeitem), "select", + GTK_SIGNAL_FUNC(treeitem_sel), + &selparams[index]); + dp.treeitems[index] = selparams[index].treeitem; + } +#endif + + dp.data = cfg; + dlg_refresh(NULL, &dp); + + dp.shortcuts = &selparams[0].shortcuts; +#if !GTK_CHECK_VERSION(2,0,0) + dp.currtreeitem = dp.treeitems[0]; +#endif + dp.lastfocus = NULL; + dp.retval = 0; + dp.window = window; + + { + /* in gtkwin.c */ + extern void set_window_icon(GtkWidget *window, + const char *const *const *icon, + int n_icon); + extern const char *const *const cfg_icon[]; + extern const int n_cfg_icon; + set_window_icon(window, cfg_icon, n_cfg_icon); + } + +#if !GTK_CHECK_VERSION(2,0,0) + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll), + tree); +#endif + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_widget_show(tree); + + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_widget_show(window); + + /* + * Set focus into the first available control. + */ + for (index = 0; index < ctrlbox->nctrlsets; index++) { + struct controlset *s = ctrlbox->ctrlsets[index]; + int done = 0; + int j; + + if (*s->pathname) { + for (j = 0; j < s->ncontrols; j++) + if (s->ctrls[j]->generic.type != CTRL_TABDELAY && + s->ctrls[j]->generic.type != CTRL_COLUMNS && + s->ctrls[j]->generic.type != CTRL_TEXT) { + dlg_set_focus(s->ctrls[j], &dp); + dp.lastfocus = s->ctrls[j]; + done = 1; + break; + } + } + if (done) + break; + } + + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(window_destroy), NULL); + gtk_signal_connect(GTK_OBJECT(window), "key_press_event", + GTK_SIGNAL_FUNC(win_key_press), &dp); + + gtk_main(); + + dlg_cleanup(&dp); + sfree(selparams); + + return dp.retval; +} + +static void messagebox_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + if (event == EVENT_ACTION) + dlg_end(dlg, ctrl->generic.context.i); +} +int messagebox(GtkWidget *parentwin, char *title, char *msg, int minwid, ...) +{ + GtkWidget *window, *w0, *w1; + struct controlbox *ctrlbox; + struct controlset *s0, *s1; + union control *c; + struct dlgparam dp; + struct Shortcuts scs; + int index, ncols; + va_list ap; + + dlg_init(&dp); + + for (index = 0; index < lenof(scs.sc); index++) { + scs.sc[index].action = SHORTCUT_EMPTY; + } + + ctrlbox = ctrl_new_box(); + + ncols = 0; + va_start(ap, minwid); + while (va_arg(ap, char *) != NULL) { + ncols++; + (void) va_arg(ap, int); /* shortcut */ + (void) va_arg(ap, int); /* normal/default/cancel */ + (void) va_arg(ap, int); /* end value */ + } + va_end(ap); + + s0 = ctrl_getset(ctrlbox, "", "", ""); + c = ctrl_columns(s0, 2, 50, 50); + c->columns.ncols = s0->ncolumns = ncols; + c->columns.percentages = sresize(c->columns.percentages, ncols, int); + for (index = 0; index < ncols; index++) + c->columns.percentages[index] = (index+1)*100/ncols - index*100/ncols; + va_start(ap, minwid); + index = 0; + while (1) { + char *title = va_arg(ap, char *); + int shortcut, type, value; + if (title == NULL) + break; + shortcut = va_arg(ap, int); + type = va_arg(ap, int); + value = va_arg(ap, int); + c = ctrl_pushbutton(s0, title, shortcut, HELPCTX(no_help), + messagebox_handler, I(value)); + c->generic.column = index++; + if (type > 0) + c->button.isdefault = TRUE; + else if (type < 0) + c->button.iscancel = TRUE; + } + va_end(ap); + + s1 = ctrl_getset(ctrlbox, "x", "", ""); + ctrl_text(s1, msg, HELPCTX(no_help)); + + window = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(window), title); + w0 = layout_ctrls(&dp, &scs, s0, GTK_WINDOW(window)); + set_dialog_action_area(GTK_DIALOG(window), w0); + gtk_widget_show(w0); + w1 = layout_ctrls(&dp, &scs, s1, GTK_WINDOW(window)); + gtk_container_set_border_width(GTK_CONTAINER(w1), 10); + gtk_widget_set_usize(w1, minwid+20, -1); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), + w1, TRUE, TRUE, 0); + gtk_widget_show(w1); + + dp.shortcuts = &scs; + dp.lastfocus = NULL; + dp.retval = 0; + dp.window = window; + + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + if (parentwin) { + set_transient_window_pos(parentwin, window); + gtk_window_set_transient_for(GTK_WINDOW(window), + GTK_WINDOW(parentwin)); + } else + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_widget_show(window); + + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(window_destroy), NULL); + gtk_signal_connect(GTK_OBJECT(window), "key_press_event", + GTK_SIGNAL_FUNC(win_key_press), &dp); + + gtk_main(); + + dlg_cleanup(&dp); + ctrl_free_box(ctrlbox); + + return dp.retval; +} + +static int string_width(char *text) +{ + GtkWidget *label = gtk_label_new(text); + GtkRequisition req; + gtk_widget_size_request(label, &req); + gtk_object_sink(GTK_OBJECT(label)); + return req.width; +} + +int reallyclose(void *frontend) +{ + char *title = dupcat(appname, " Exit Confirmation", NULL); + int ret = messagebox(GTK_WIDGET(get_window(frontend)), + title, "Are you sure you want to close this session?", + string_width("Most of the width of the above text"), + "Yes", 'y', +1, 1, + "No", 'n', -1, 0, + NULL); + sfree(title); + return ret; +} + +int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, + char *keystr, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char absenttxt[] = + "The server's host key is not cached. You have no guarantee " + "that the server is the computer you think it is.\n" + "The server's %s key fingerprint is:\n" + "%s\n" + "If you trust this host, press \"Accept\" to add the key to " + "PuTTY's cache and carry on connecting.\n" + "If you want to carry on connecting just once, without " + "adding the key to the cache, press \"Connect Once\".\n" + "If you do not trust this host, press \"Cancel\" to abandon the " + "connection."; + static const char wrongtxt[] = + "WARNING - POTENTIAL SECURITY BREACH!\n" + "The server's host key does not match the one PuTTY has " + "cached. This means that either the server administrator " + "has changed the host key, or you have actually connected " + "to another computer pretending to be the server.\n" + "The new %s key fingerprint is:\n" + "%s\n" + "If you were expecting this change and trust the new key, " + "press \"Accept\" to update PuTTY's cache and continue connecting.\n" + "If you want to carry on connecting but without updating " + "the cache, press \"Connect Once\".\n" + "If you want to abandon the connection completely, press " + "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed " + "safe choice."; + char *text; + int ret; + + /* + * Verify the key. + */ + ret = verify_host_key(host, port, keytype, keystr); + + if (ret == 0) /* success - key matched OK */ + return 1; + + text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint); + + ret = messagebox(GTK_WIDGET(get_window(frontend)), + "PuTTY Security Alert", text, + string_width(fingerprint), + "Accept", 'a', 0, 2, + "Connect Once", 'o', 0, 1, + "Cancel", 'c', -1, 0, + NULL); + + sfree(text); + + if (ret == 2) { + store_host_key(host, port, keytype, keystr); + return 1; /* continue with connection */ + } else if (ret == 1) + return 1; /* continue with connection */ + return 0; /* do not continue with connection */ +} + +/* + * Ask whether the selected algorithm is acceptable (since it was + * below the configured 'warn' threshold). + */ +int askalg(void *frontend, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msg[] = + "The first %s supported by the server is " + "%s, which is below the configured warning threshold.\n" + "Continue with connection?"; + char *text; + int ret; + + text = dupprintf(msg, algtype, algname); + ret = messagebox(GTK_WIDGET(get_window(frontend)), + "PuTTY Security Alert", text, + string_width("Continue with connection?"), + "Yes", 'y', 0, 1, + "No", 'n', 0, 0, + NULL); + sfree(text); + + if (ret) { + return 1; + } else { + return 0; + } +} + +void old_keyfile_warning(void) +{ + /* + * This should never happen on Unix. We hope. + */ +} + +void fatal_message_box(void *window, char *msg) +{ + messagebox(window, "PuTTY Fatal Error", msg, + string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), + "OK", 'o', 1, 1, NULL); +} + +void fatalbox(char *p, ...) +{ + va_list ap; + char *msg; + va_start(ap, p); + msg = dupvprintf(p, ap); + va_end(ap); + fatal_message_box(NULL, msg); + sfree(msg); + cleanup_exit(1); +} + +static GtkWidget *aboutbox = NULL; + +static void about_close_clicked(GtkButton *button, gpointer data) +{ + gtk_widget_destroy(aboutbox); + aboutbox = NULL; +} + +static void licence_clicked(GtkButton *button, gpointer data) +{ + char *title; + + char *licence = + "Copyright 1997-2011 Simon Tatham.\n\n" + + "Portions copyright Robert de Bath, Joris van Rantwijk, Delian " + "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas " + "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, " + "Markus Kuhn, Colin Watson, and CORE SDI S.A.\n\n" + + "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:\n\n" + + "The above copyright notice and this permission notice shall be " + "included in all copies or substantial portions of the Software.\n\n" + + "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT " + "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, " + "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF " + "MERCHANTABILITY, FITNESS FOR A PARTICULAR " + "PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE " + "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES " + "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, " + "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN " + "CONNECTION WITH THE SOFTWARE OR THE USE OR " + "OTHER DEALINGS IN THE SOFTWARE."; + + title = dupcat(appname, " Licence", NULL); + assert(aboutbox != NULL); + messagebox(aboutbox, title, licence, + string_width("LONGISH LINE OF TEXT SO THE LICENCE" + " BOX ISN'T EXCESSIVELY TALL AND THIN"), + "OK", 'o', 1, 1, NULL); + sfree(title); +} + +void about_box(void *window) +{ + GtkWidget *w; + char *title; + + if (aboutbox) { + gtk_widget_grab_focus(aboutbox); + return; + } + + aboutbox = gtk_dialog_new(); + gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10); + title = dupcat("About ", appname, NULL); + gtk_window_set_title(GTK_WINDOW(aboutbox), title); + sfree(title); + + w = gtk_button_new_with_label("Close"); + GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT); + gtk_window_set_default(GTK_WINDOW(aboutbox), w); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(aboutbox)->action_area), + w, FALSE, FALSE, 0); + gtk_signal_connect(GTK_OBJECT(w), "clicked", + GTK_SIGNAL_FUNC(about_close_clicked), NULL); + gtk_widget_show(w); + + w = gtk_button_new_with_label("View Licence"); + GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(aboutbox)->action_area), + w, FALSE, FALSE, 0); + gtk_signal_connect(GTK_OBJECT(w), "clicked", + GTK_SIGNAL_FUNC(licence_clicked), NULL); + gtk_widget_show(w); + + w = gtk_label_new(appname); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(aboutbox)->vbox), + w, FALSE, FALSE, 0); + gtk_widget_show(w); + + w = gtk_label_new(ver); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(aboutbox)->vbox), + w, FALSE, FALSE, 5); + gtk_widget_show(w); + + w = gtk_label_new("Copyright 1997-2011 Simon Tatham. All rights reserved"); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(aboutbox)->vbox), + w, FALSE, FALSE, 5); + gtk_widget_show(w); + + set_transient_window_pos(GTK_WIDGET(window), aboutbox); + gtk_window_set_transient_for(GTK_WINDOW(aboutbox), + GTK_WINDOW(window)); + gtk_widget_show(aboutbox); +} + +struct eventlog_stuff { + GtkWidget *parentwin, *window; + struct controlbox *eventbox; + struct Shortcuts scs; + struct dlgparam dp; + union control *listctrl; + char **events; + int nevents, negsize; + char *seldata; + int sellen; + int ignore_selchange; +}; + +static void eventlog_destroy(GtkWidget *widget, gpointer data) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)data; + + es->window = NULL; + sfree(es->seldata); + dlg_cleanup(&es->dp); + ctrl_free_box(es->eventbox); +} +static void eventlog_ok_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + if (event == EVENT_ACTION) + dlg_end(dlg, 0); +} +static void eventlog_list_handler(union control *ctrl, void *dlg, + void *data, int event) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)data; + + if (event == EVENT_REFRESH) { + int i; + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < es->nevents; i++) { + dlg_listbox_add(ctrl, dlg, es->events[i]); + } + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_SELCHANGE) { + int i; + int selsize = 0; + + /* + * If this SELCHANGE event is happening as a result of + * deliberate deselection because someone else has grabbed + * the selection, the last thing we want to do is pre-empt + * them. + */ + if (es->ignore_selchange) + return; + + /* + * Construct the data to use as the selection. + */ + sfree(es->seldata); + es->seldata = NULL; + es->sellen = 0; + for (i = 0; i < es->nevents; i++) { + if (dlg_listbox_issel(ctrl, dlg, i)) { + int extralen = strlen(es->events[i]); + + if (es->sellen + extralen + 2 > selsize) { + selsize = es->sellen + extralen + 512; + es->seldata = sresize(es->seldata, selsize, char); + } + + strcpy(es->seldata + es->sellen, es->events[i]); + es->sellen += extralen; + es->seldata[es->sellen++] = '\n'; + } + } + + if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY, + GDK_CURRENT_TIME)) { + extern GdkAtom compound_text_atom; + + gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, 1); + gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY, + compound_text_atom, 1); + } + + } +} + +void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata, + guint info, guint time_stamp, gpointer data) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)data; + + gtk_selection_data_set(seldata, seldata->target, 8, + (unsigned char *)es->seldata, es->sellen); +} + +gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, + gpointer data) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)data; + struct uctrl *uc; + + /* + * Deselect everything in the list box. + */ + uc = dlg_find_byctrl(&es->dp, es->listctrl); + es->ignore_selchange = 1; +#if !GTK_CHECK_VERSION(2,0,0) + assert(uc->list); + gtk_list_unselect_all(GTK_LIST(uc->list)); +#else + assert(uc->treeview); + gtk_tree_selection_unselect_all + (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview))); +#endif + es->ignore_selchange = 0; + + sfree(es->seldata); + es->sellen = 0; + es->seldata = NULL; + return TRUE; +} + +void showeventlog(void *estuff, void *parentwin) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)estuff; + GtkWidget *window, *w0, *w1; + GtkWidget *parent = GTK_WIDGET(parentwin); + struct controlset *s0, *s1; + union control *c; + int index; + char *title; + + if (es->window) { + gtk_widget_grab_focus(es->window); + return; + } + + dlg_init(&es->dp); + + for (index = 0; index < lenof(es->scs.sc); index++) { + es->scs.sc[index].action = SHORTCUT_EMPTY; + } + + es->eventbox = ctrl_new_box(); + + s0 = ctrl_getset(es->eventbox, "", "", ""); + ctrl_columns(s0, 3, 33, 34, 33); + c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help), + eventlog_ok_handler, P(NULL)); + c->button.column = 1; + c->button.isdefault = TRUE; + + s1 = ctrl_getset(es->eventbox, "x", "", ""); + es->listctrl = c = ctrl_listbox(s1, NULL, NO_SHORTCUT, HELPCTX(no_help), + eventlog_list_handler, P(es)); + c->listbox.height = 10; + c->listbox.multisel = 2; + c->listbox.ncols = 3; + c->listbox.percentages = snewn(3, int); + c->listbox.percentages[0] = 25; + c->listbox.percentages[1] = 10; + c->listbox.percentages[2] = 65; + + es->window = window = gtk_dialog_new(); + title = dupcat(appname, " Event Log", NULL); + gtk_window_set_title(GTK_WINDOW(window), title); + sfree(title); + w0 = layout_ctrls(&es->dp, &es->scs, s0, GTK_WINDOW(window)); + set_dialog_action_area(GTK_DIALOG(window), w0); + gtk_widget_show(w0); + w1 = layout_ctrls(&es->dp, &es->scs, s1, GTK_WINDOW(window)); + gtk_container_set_border_width(GTK_CONTAINER(w1), 10); + gtk_widget_set_usize(w1, 20 + + string_width("LINE OF TEXT GIVING WIDTH OF EVENT LOG" + " IS QUITE LONG 'COS SSH LOG ENTRIES" + " ARE WIDE"), -1); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), + w1, TRUE, TRUE, 0); + gtk_widget_show(w1); + + es->dp.data = es; + es->dp.shortcuts = &es->scs; + es->dp.lastfocus = NULL; + es->dp.retval = 0; + es->dp.window = window; + + dlg_refresh(NULL, &es->dp); + + if (parent) { + set_transient_window_pos(parent, window); + gtk_window_set_transient_for(GTK_WINDOW(window), + GTK_WINDOW(parent)); + } else + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_widget_show(window); + + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(eventlog_destroy), es); + gtk_signal_connect(GTK_OBJECT(window), "key_press_event", + GTK_SIGNAL_FUNC(win_key_press), &es->dp); + gtk_signal_connect(GTK_OBJECT(window), "selection_get", + GTK_SIGNAL_FUNC(eventlog_selection_get), es); + gtk_signal_connect(GTK_OBJECT(window), "selection_clear_event", + GTK_SIGNAL_FUNC(eventlog_selection_clear), es); +} + +void *eventlogstuff_new(void) +{ + struct eventlog_stuff *es; + es = snew(struct eventlog_stuff); + memset(es, 0, sizeof(*es)); + return es; +} + +void logevent_dlg(void *estuff, const char *string) +{ + struct eventlog_stuff *es = (struct eventlog_stuff *)estuff; + + char timebuf[40]; + struct tm tm; + + if (es->nevents >= es->negsize) { + es->negsize += 64; + es->events = sresize(es->events, es->negsize, char *); + } + + tm=ltime(); + strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); + + es->events[es->nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char); + strcpy(es->events[es->nevents], timebuf); + strcat(es->events[es->nevents], string); + if (es->window) { + dlg_listbox_add(es->listctrl, &es->dp, es->events[es->nevents]); + } + es->nevents++; +} + +int askappend(void *frontend, Filename filename, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msgtemplate[] = + "The session log file \"%.*s\" already exists. " + "You can overwrite it with a new session log, " + "append your session log to the end of it, " + "or disable session logging for this session."; + char *message; + char *mbtitle; + int mbret; + + message = dupprintf(msgtemplate, FILENAME_MAX, filename.path); + mbtitle = dupprintf("%s Log to File", appname); + + mbret = messagebox(get_window(frontend), mbtitle, message, + string_width("LINE OF TEXT SUITABLE FOR THE" + " ASKAPPEND WIDTH"), + "Overwrite", 'o', 1, 2, + "Append", 'a', 0, 1, + "Disable", 'd', -1, 0, + NULL); + + sfree(message); + sfree(mbtitle); + + return mbret; +} diff --git a/putty/UNIX/GTKFONT.C b/putty/UNIX/GTKFONT.C new file mode 100644 index 0000000..e382183 --- /dev/null +++ b/putty/UNIX/GTKFONT.C @@ -0,0 +1,2575 @@ +/* + * Unified font management for GTK. + * + * PuTTY is willing to use both old-style X server-side bitmap + * fonts _and_ GTK2/Pango client-side fonts. This requires us to + * do a bit of work to wrap the two wildly different APIs into + * forms the rest of the code can switch between seamlessly, and + * also requires a custom font selector capable of handling both + * types of font. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "gtkfont.h" +#include "tree234.h" + +/* + * Future work: + * + * - it would be nice to have a display of the current font name, + * and in particular whether it's client- or server-side, + * during the progress of the font selector. + * + * - all the GDK font functions used in the x11font subclass are + * deprecated, so one day they may go away. When this happens - + * or before, if I'm feeling proactive - it oughtn't to be too + * difficult in principle to convert the whole thing to use + * actual Xlib font calls. + * + * - it would be nice if we could move the processing of + * underline and VT100 double width into this module, so that + * instead of using the ghastly pixmap-stretching technique + * everywhere we could tell the Pango backend to scale its + * fonts to double size properly and at full resolution. + * However, this requires me to learn how to make Pango stretch + * text to an arbitrary aspect ratio (for double-width only + * text, which perversely is harder than DW+DH), and right now + * I haven't the energy. + */ + +/* + * Ad-hoc vtable mechanism to allow font structures to be + * polymorphic. + * + * Any instance of `unifont' used in the vtable functions will + * actually be the first element of a larger structure containing + * data specific to the subtype. This is permitted by the ISO C + * provision that one may safely cast between a pointer to a + * structure and a pointer to its first element. + */ + +#define FONTFLAG_CLIENTSIDE 0x0001 +#define FONTFLAG_SERVERSIDE 0x0002 +#define FONTFLAG_SERVERALIAS 0x0004 +#define FONTFLAG_NONMONOSPACED 0x0008 + +#define FONTFLAG_SORT_MASK 0x0007 /* used to disambiguate font families */ + +typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname, + const char *family, const char *charset, + const char *style, const char *stylekey, + int size, int flags, + const struct unifont_vtable *fontclass); + +struct unifont_vtable { + /* + * `Methods' of the `class'. + */ + unifont *(*create)(GtkWidget *widget, const char *name, int wide, int bold, + int shadowoffset, int shadowalways); + void (*destroy)(unifont *font); + void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, int wide, + int bold, int cellwidth); + void (*enum_fonts)(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx); + char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size, + int *flags, int resolve_aliases); + char *(*scale_fontname)(GtkWidget *widget, const char *name, int size); + + /* + * `Static data members' of the `class'. + */ + const char *prefix; +}; + +/* ---------------------------------------------------------------------- + * GDK-based X11 font implementation. + */ + +static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold, int cellwidth); +static unifont *x11font_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways); +static void x11font_destroy(unifont *font); +static void x11font_enum_fonts(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx); +static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases); +static char *x11font_scale_fontname(GtkWidget *widget, const char *name, + int size); + +struct x11font { + struct unifont u; + /* + * Actual font objects. We store a number of these, for + * automatically guessed bold and wide variants. + * + * The parallel array `allocated' indicates whether we've + * tried to fetch a subfont already (thus distinguishing NULL + * because we haven't tried yet from NULL because we tried and + * failed, so that we don't keep trying and failing + * subsequently). + */ + GdkFont *fonts[4]; + int allocated[4]; + /* + * `sixteen_bit' is true iff the font object is indexed by + * values larger than a byte. That is, this flag tells us + * whether we use gdk_draw_text_wc() or gdk_draw_text(). + */ + int sixteen_bit; + /* + * `variable' is true iff the font is non-fixed-pitch. This + * enables some code which takes greater care over character + * positioning during text drawing. + */ + int variable; + /* + * Data passed in to unifont_create(). + */ + int wide, bold, shadowoffset, shadowalways; +}; + +static const struct unifont_vtable x11font_vtable = { + x11font_create, + x11font_destroy, + x11font_draw_text, + x11font_enum_fonts, + x11font_canonify_fontname, + x11font_scale_fontname, + "server", +}; + +char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide) +{ + XFontStruct *xfs = GDK_FONT_XFONT(font); + Display *disp = GDK_FONT_XDISPLAY(font); + Atom fontprop = XInternAtom(disp, "FONT", False); + unsigned long ret; + if (XGetFontProperty(xfs, fontprop, &ret)) { + char *name = XGetAtomName(disp, (Atom)ret); + if (name && name[0] == '-') { + char *strings[13]; + char *dupname, *extrafree = NULL, *ret; + char *p, *q; + int nstr; + + p = q = dupname = dupstr(name); /* skip initial minus */ + nstr = 0; + + while (*p && nstr < lenof(strings)) { + if (*p == '-') { + *p = '\0'; + strings[nstr++] = p+1; + } + p++; + } + + if (nstr < lenof(strings)) + return NULL; /* XLFD was malformed */ + + if (bold) + strings[2] = "bold"; + + if (wide) { + /* 4 is `wideness', which obviously may have changed. */ + /* 5 is additional style, which may be e.g. `ja' or `ko'. */ + strings[4] = strings[5] = "*"; + strings[11] = extrafree = dupprintf("%d", 2*atoi(strings[11])); + } + + ret = dupcat("-", strings[ 0], "-", strings[ 1], "-", strings[ 2], + "-", strings[ 3], "-", strings[ 4], "-", strings[ 5], + "-", strings[ 6], "-", strings[ 7], "-", strings[ 8], + "-", strings[ 9], "-", strings[10], "-", strings[11], + "-", strings[12], NULL); + sfree(extrafree); + sfree(dupname); + + return ret; + } + } + return NULL; +} + +static int x11_font_width(GdkFont *font, int sixteen_bit) +{ + if (sixteen_bit) { + XChar2b space; + space.byte1 = 0; + space.byte2 = '0'; + return gdk_text_width(font, (const gchar *)&space, 2); + } else { + return gdk_char_width(font, '0'); + } +} + +static unifont *x11font_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + struct x11font *xfont; + GdkFont *font; + XFontStruct *xfs; + Display *disp; + Atom charset_registry, charset_encoding, spacing; + unsigned long registry_ret, encoding_ret, spacing_ret; + int pubcs, realcs, sixteen_bit, variable; + int i; + + font = gdk_font_load(name); + if (!font) + return NULL; + + xfs = GDK_FONT_XFONT(font); + disp = GDK_FONT_XDISPLAY(font); + + charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False); + charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False); + + pubcs = realcs = CS_NONE; + sixteen_bit = FALSE; + variable = TRUE; + + if (XGetFontProperty(xfs, charset_registry, ®istry_ret) && + XGetFontProperty(xfs, charset_encoding, &encoding_ret)) { + char *reg, *enc; + reg = XGetAtomName(disp, (Atom)registry_ret); + enc = XGetAtomName(disp, (Atom)encoding_ret); + if (reg && enc) { + char *encoding = dupcat(reg, "-", enc, NULL); + pubcs = realcs = charset_from_xenc(encoding); + + /* + * iso10646-1 is the only wide font encoding we + * support. In this case, we expect clients to give us + * UTF-8, which this module must internally convert + * into 16-bit Unicode. + */ + if (!strcasecmp(encoding, "iso10646-1")) { + sixteen_bit = TRUE; + pubcs = realcs = CS_UTF8; + } + + /* + * Hack for X line-drawing characters: if the primary + * font is encoded as ISO-8859-1, and has valid glyphs + * in the first 32 char positions, it is assumed that + * those glyphs are the VT100 line-drawing character + * set. + * + * Actually, we'll hack even harder by only checking + * position 0x19 (vertical line, VT100 linedrawing + * `x'). Then we can check it easily by seeing if the + * ascent and descent differ. + */ + if (pubcs == CS_ISO8859_1) { + int lb, rb, wid, asc, desc; + gchar text[2]; + + text[1] = '\0'; + text[0] = '\x12'; + gdk_string_extents(font, text, &lb, &rb, &wid, &asc, &desc); + if (asc != desc) + 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(font, sixteen_bit); + xfont->u.ascent = font->ascent; + xfont->u.descent = font->descent; + xfont->u.height = xfont->u.ascent + xfont->u.descent; + xfont->u.public_charset = pubcs; + xfont->u.real_charset = realcs; + xfont->fonts[0] = font; + xfont->allocated[0] = TRUE; + xfont->sixteen_bit = sixteen_bit; + xfont->variable = variable; + xfont->wide = wide; + xfont->bold = bold; + xfont->shadowoffset = shadowoffset; + xfont->shadowalways = shadowalways; + + for (i = 1; i < lenof(xfont->fonts); i++) { + xfont->fonts[i] = NULL; + xfont->allocated[i] = FALSE; + } + + return (unifont *)xfont; +} + +static void x11font_destroy(unifont *font) +{ + struct x11font *xfont = (struct x11font *)font; + int i; + + for (i = 0; i < lenof(xfont->fonts); i++) + if (xfont->fonts[i]) + gdk_font_unref(xfont->fonts[i]); + sfree(font); +} + +static void x11_alloc_subfont(struct x11font *xfont, int sfid) +{ + char *derived_name = x11_guess_derived_font_name + (xfont->fonts[0], sfid & 1, !!(sfid & 2)); + xfont->fonts[sfid] = gdk_font_load(derived_name); /* may be NULL */ + xfont->allocated[sfid] = TRUE; + sfree(derived_name); +} + +static void x11font_really_draw_text(GdkDrawable *target, GdkFont *font, + GdkGC *gc, int x, int y, + const gchar *string, int clen, int nchars, + int shadowbold, int shadowoffset, + int fontvariable, int cellwidth) +{ + int step = clen * nchars, nsteps = 1, centre = FALSE; + + if (fontvariable) { + /* + * In a variable-pitch font, we draw one character at a + * time, and centre it in the character cell. + */ + step = clen; + nsteps = nchars; + centre = TRUE; + } + + while (nsteps-- > 0) { + int X = x; + if (centre) + X += (cellwidth - gdk_text_width(font, string, step)) / 2; + + gdk_draw_text(target, font, gc, X, y, string, step); + if (shadowbold) + gdk_draw_text(target, font, gc, X + shadowoffset, y, string, step); + + x += cellwidth; + string += step; + } +} + +static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold, int cellwidth) +{ + struct x11font *xfont = (struct x11font *)font; + int sfid; + int shadowbold = FALSE; + int mult = (wide ? 2 : 1); + + wide -= xfont->wide; + bold -= xfont->bold; + + /* + * Decide which subfont we're using, and whether we have to + * use shadow bold. + */ + if (xfont->shadowalways && bold) { + shadowbold = TRUE; + bold = 0; + } + sfid = 2 * wide + bold; + if (!xfont->allocated[sfid]) + x11_alloc_subfont(xfont, sfid); + if (bold && !xfont->fonts[sfid]) { + bold = 0; + shadowbold = TRUE; + sfid = 2 * wide + bold; + if (!xfont->allocated[sfid]) + x11_alloc_subfont(xfont, sfid); + } + + if (!xfont->fonts[sfid]) + return; /* we've tried our best, but no luck */ + + if (xfont->sixteen_bit) { + /* + * This X font has 16-bit character indices, which means + * we expect our string to have been passed in UTF-8. + */ + XChar2b *xcs; + wchar_t *wcs; + int nchars, maxchars, i; + + /* + * Convert the input string to wide-character Unicode. + */ + maxchars = 0; + for (i = 0; i < len; i++) + if ((unsigned char)string[i] <= 0x7F || + (unsigned char)string[i] >= 0xC0) + maxchars++; + wcs = snewn(maxchars+1, wchar_t); + nchars = charset_to_unicode((char **)&string, &len, wcs, maxchars, + CS_UTF8, NULL, NULL, 0); + assert(nchars <= maxchars); + wcs[nchars] = L'\0'; + + xcs = snewn(nchars, XChar2b); + for (i = 0; i < nchars; i++) { + xcs[i].byte1 = wcs[i] >> 8; + xcs[i].byte2 = wcs[i]; + } + + x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, + (gchar *)xcs, 2, nchars, + shadowbold, xfont->shadowoffset, + xfont->variable, cellwidth * mult); + sfree(xcs); + sfree(wcs); + } else { + x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, + string, 1, len, + shadowbold, xfont->shadowoffset, + xfont->variable, cellwidth * mult); + } +} + +static void x11font_enum_fonts(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx) +{ + char **fontnames; + char *tmp = NULL; + int nnames, i, max, tmpsize; + + max = 32768; + while (1) { + fontnames = XListFonts(GDK_DISPLAY(), "*", max, &nnames); + if (nnames >= max) { + XFreeFontNames(fontnames); + max *= 2; + } else + break; + } + + tmpsize = 0; + + for (i = 0; i < nnames; i++) { + if (fontnames[i][0] == '-') { + /* + * Dismember an XLFD and convert it into the format + * we'll be using in the font selector. + */ + char *components[14]; + char *p, *font, *style, *stylekey, *charset; + int j, weightkey, slantkey, setwidthkey; + int thistmpsize, fontsize, flags; + + thistmpsize = 4 * strlen(fontnames[i]) + 256; + if (tmpsize < thistmpsize) { + tmpsize = thistmpsize; + tmp = sresize(tmp, tmpsize, char); + } + strcpy(tmp, fontnames[i]); + + p = tmp; + for (j = 0; j < 14; j++) { + if (*p) + *p++ = '\0'; + components[j] = p; + while (*p && *p != '-') + p++; + } + *p++ = '\0'; + + /* + * Font name is made up of fields 0 and 1, in reverse + * order with parentheses. (This is what the GTK 1.2 X + * font selector does, and it seems to come out + * looking reasonably sensible.) + */ + font = p; + p += 1 + sprintf(p, "%s (%s)", components[1], components[0]); + + /* + * Charset is made up of fields 12 and 13. + */ + charset = p; + p += 1 + sprintf(p, "%s-%s", components[12], components[13]); + + /* + * Style is a mixture of quite a lot of the fields, + * with some strange formatting. + */ + style = p; + p += sprintf(p, "%s", components[2][0] ? components[2] : + "regular"); + if (!g_strcasecmp(components[3], "i")) + p += sprintf(p, " italic"); + else if (!g_strcasecmp(components[3], "o")) + p += sprintf(p, " oblique"); + else if (!g_strcasecmp(components[3], "ri")) + p += sprintf(p, " reverse italic"); + else if (!g_strcasecmp(components[3], "ro")) + p += sprintf(p, " reverse oblique"); + else if (!g_strcasecmp(components[3], "ot")) + p += sprintf(p, " other-slant"); + if (components[4][0] && g_strcasecmp(components[4], "normal")) + p += sprintf(p, " %s", components[4]); + if (!g_strcasecmp(components[10], "m")) + p += sprintf(p, " [M]"); + if (!g_strcasecmp(components[10], "c")) + p += sprintf(p, " [C]"); + if (components[5][0]) + p += sprintf(p, " %s", components[5]); + + /* + * Style key is the same stuff as above, but with a + * couple of transformations done on it to make it + * sort more sensibly. + */ + p++; + stylekey = p; + if (!g_strcasecmp(components[2], "medium") || + !g_strcasecmp(components[2], "regular") || + !g_strcasecmp(components[2], "normal") || + !g_strcasecmp(components[2], "book")) + weightkey = 0; + else if (!g_strncasecmp(components[2], "demi", 4) || + !g_strncasecmp(components[2], "semi", 4)) + weightkey = 1; + else + weightkey = 2; + if (!g_strcasecmp(components[3], "r")) + slantkey = 0; + else if (!g_strncasecmp(components[3], "r", 1)) + slantkey = 2; + else + slantkey = 1; + if (!g_strcasecmp(components[4], "normal")) + setwidthkey = 0; + else + setwidthkey = 1; + + p += sprintf(p, "%04d%04d%s%04d%04d%s%04d%04d%s%04d%s%04d%s", + weightkey, + (int)strlen(components[2]), components[2], + slantkey, + (int)strlen(components[3]), components[3], + setwidthkey, + (int)strlen(components[4]), components[4], + (int)strlen(components[10]), components[10], + (int)strlen(components[5]), components[5]); + + assert(p - tmp < thistmpsize); + + /* + * Size is in pixels, for our application, so we + * derive it directly from the pixel size field, + * number 6. + */ + fontsize = atoi(components[6]); + + /* + * Flags: we need to know whether this is a monospaced + * font, which we do by examining the spacing field + * again. + */ + flags = FONTFLAG_SERVERSIDE; + if (!strchr("CcMm", components[10][0])) + flags |= FONTFLAG_NONMONOSPACED; + + /* + * Not sure why, but sometimes the X server will + * deliver dummy font types in which fontsize comes + * out as zero. Filter those out. + */ + if (fontsize) + callback(callback_ctx, fontnames[i], font, charset, + style, stylekey, fontsize, flags, &x11font_vtable); + } else { + /* + * This isn't an XLFD, so it must be an alias. + * Transmit it with mostly null data. + * + * It would be nice to work out if it's monospaced + * here, but at the moment I can't see that being + * anything but computationally hideous. Ah well. + */ + callback(callback_ctx, fontnames[i], fontnames[i], NULL, + NULL, NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable); + } + } + XFreeFontNames(fontnames); +} + +static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases) +{ + /* + * When given an X11 font name to try to make sense of for a + * font selector, we must attempt to load it (to see if it + * exists), and then canonify it by extracting its FONT + * property, which should give its full XLFD even if what we + * originally had was a wildcard. + * + * However, we must carefully avoid canonifying font + * _aliases_, unless specifically asked to, because the font + * selector treats them as worthwhile in their own right. + */ + GdkFont *font = gdk_font_load(name); + XFontStruct *xfs; + Display *disp; + Atom fontprop, fontprop2; + unsigned long ret; + + if (!font) + return NULL; /* didn't make sense to us, sorry */ + + gdk_font_ref(font); + + xfs = GDK_FONT_XFONT(font); + disp = GDK_FONT_XDISPLAY(font); + 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; + gdk_font_unref(font); + if (flags) { + if (name[0] == '-' || resolve_aliases) + *flags = FONTFLAG_SERVERSIDE; + else + *flags = FONTFLAG_SERVERALIAS; + } + return dupstr(name[0] == '-' || resolve_aliases ? + newname : name); + } + } + } + + gdk_font_unref(font); + return NULL; /* something went wrong */ +} + +static char *x11font_scale_fontname(GtkWidget *widget, const char *name, + int size) +{ + return NULL; /* shan't */ +} + +#if GTK_CHECK_VERSION(2,0,0) + +/* ---------------------------------------------------------------------- + * Pango font implementation (for GTK 2 only). + */ + +#if defined PANGO_PRE_1POINT4 && !defined PANGO_PRE_1POINT6 +#define PANGO_PRE_1POINT6 /* make life easier for pre-1.4 folk */ +#endif + +static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold, int cellwidth); +static unifont *pangofont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways); +static void pangofont_destroy(unifont *font); +static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, + void *callback_ctx); +static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases); +static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, + int size); + +struct pangofont { + struct unifont u; + /* + * Pango objects. + */ + PangoFontDescription *desc; + PangoFontset *fset; + /* + * The containing widget. + */ + GtkWidget *widget; + /* + * Data passed in to unifont_create(). + */ + int bold, shadowoffset, shadowalways; +}; + +static const struct unifont_vtable pangofont_vtable = { + pangofont_create, + pangofont_destroy, + pangofont_draw_text, + pangofont_enum_fonts, + pangofont_canonify_fontname, + pangofont_scale_fontname, + "client", +}; + +/* + * This function is used to rigorously validate a + * PangoFontDescription. Later versions of Pango have a nasty + * habit of accepting _any_ old string as input to + * pango_font_description_from_string and returning a font + * description which can actually be used to display text, even if + * they have to do it by falling back to their most default font. + * This is doubtless helpful in some situations, but not here, + * because we need to know if a Pango font string actually _makes + * sense_ in order to fall back to treating it as an X font name + * if it doesn't. So we check that the font family is actually one + * supported by Pango. + */ +static int pangofont_check_desc_makes_sense(PangoContext *ctx, + PangoFontDescription *desc) +{ +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontFamily **families; + int i, nfamilies, matched; + + /* + * Ask Pango for a list of font families, and iterate through + * them to see if one of them matches the family in the + * PangoFontDescription. + */ +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) + return FALSE; + pango_font_map_list_families(map, &families, &nfamilies); +#else + pango_context_list_families(ctx, &families, &nfamilies); +#endif + + matched = FALSE; + for (i = 0; i < nfamilies; i++) { + if (!g_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(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + struct pangofont *pfont; + PangoContext *ctx; +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontDescription *desc; + PangoFontset *fset; + PangoFontMetrics *metrics; + + 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; + } + + pfont = snew(struct pangofont); + pfont->u.vt = &pangofont_vtable; + pfont->u.width = + PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics)); + pfont->u.ascent = PANGO_PIXELS(pango_font_metrics_get_ascent(metrics)); + pfont->u.descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics)); + pfont->u.height = pfont->u.ascent + pfont->u.descent; + /* The Pango API is hardwired to UTF-8 */ + pfont->u.public_charset = CS_UTF8; + pfont->u.real_charset = CS_UTF8; + pfont->desc = desc; + pfont->fset = fset; + pfont->widget = widget; + pfont->bold = bold; + pfont->shadowoffset = shadowoffset; + pfont->shadowalways = shadowalways; + + pango_font_metrics_unref(metrics); + + return (unifont *)pfont; +} + +static void pangofont_destroy(unifont *font) +{ + struct pangofont *pfont = (struct pangofont *)font; + pango_font_description_free(pfont->desc); + g_object_unref(pfont->fset); + sfree(font); +} + +static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold, int cellwidth) +{ + struct pangofont *pfont = (struct pangofont *)font; + PangoLayout *layout; + PangoRectangle rect; + int shadowbold = FALSE; + + if (wide) + cellwidth *= 2; + + y -= pfont->u.ascent; + + layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget)); + pango_layout_set_font_description(layout, pfont->desc); + if (bold > pfont->bold) { + if (pfont->shadowalways) + shadowbold = TRUE; + else { + PangoFontDescription *desc2 = + pango_font_description_copy_static(pfont->desc); + pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD); + pango_layout_set_font_description(layout, desc2); + } + } + + while (len > 0) { + int clen, n; + + /* + * We want to display every character from this string in + * the centre of its own character cell. In the worst case, + * this requires a separate text-drawing call for each + * character; but in the common case where the font is + * properly fixed-width, we can draw many characters in one + * go which is much faster. + * + * This still isn't really ideal. If you look at what + * happens in the X protocol as a result of all of this, you + * find - naturally enough - that each call to + * gdk_draw_layout() generates a separate set of X RENDER + * operations involving creating a picture, setting a clip + * rectangle, doing some drawing and undoing the whole lot. + * In an ideal world, we should _always_ be able to turn the + * contents of this loop into a single RenderCompositeGlyphs + * operation which internally specifies inter-character + * deltas to get the spacing right, which would give us full + * speed _even_ in the worst case of a non-fixed-width font. + * However, Pango's architecture and documentation are so + * unhelpful that I have no idea how if at all to persuade + * them to do that. + */ + + /* + * Start by extracting a single UTF-8 character from the + * string. + */ + clen = 1; + while (clen < len && + (unsigned char)string[clen] >= 0x80 && + (unsigned char)string[clen] < 0xC0) + clen++; + n = 1; + + /* + * See if that character has the width we expect. + */ + pango_layout_set_text(layout, string, clen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + + if (rect.width == cellwidth) { + /* + * Try extracting more characters, for as long as they + * stay well-behaved. + */ + while (clen < len) { + int oldclen = clen; + clen++; /* skip UTF-8 introducer byte */ + while (clen < len && + (unsigned char)string[clen] >= 0x80 && + (unsigned char)string[clen] < 0xC0) + clen++; + n++; + pango_layout_set_text(layout, string, clen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + if (rect.width != n * cellwidth) { + clen = oldclen; + n--; + break; + } + } + } + + pango_layout_set_text(layout, string, clen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2, + y + (pfont->u.height - rect.height)/2, layout); + if (shadowbold) + gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset, + y + (pfont->u.height - rect.height)/2, layout); + + len -= clen; + string += clen; + x += n * cellwidth; + } + + g_object_unref(layout); +} + +/* + * Dummy size value to be used when converting a + * PangoFontDescription of a scalable font to a string for + * internal use. + */ +#define PANGO_DUMMY_SIZE 12 + +static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, + void *callback_ctx) +{ + PangoContext *ctx; +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontFamily **families; + int i, nfamilies; + + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) + return; + + /* + * Ask Pango for a list of font families, and iterate through + * them. + */ +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) + return; + pango_font_map_list_families(map, &families, &nfamilies); +#else + pango_context_list_families(ctx, &families, &nfamilies); +#endif + for (i = 0; i < nfamilies; i++) { + PangoFontFamily *family = families[i]; + const char *familyname; + int flags; + PangoFontFace **faces; + int j, nfaces; + + /* + * Set up our flags for this font family, and get the name + * string. + */ + flags = FONTFLAG_CLIENTSIDE; +#ifndef PANGO_PRE_1POINT4 + /* + * In very early versions of Pango, we can't tell + * monospaced fonts from non-monospaced. + */ + if (!pango_font_family_is_monospace(family)) + flags |= FONTFLAG_NONMONOSPACED; +#endif + familyname = pango_font_family_get_name(family); + + /* + * Go through the available font faces in this family. + */ + pango_font_family_list_faces(family, &faces, &nfaces); + for (j = 0; j < nfaces; j++) { + PangoFontFace *face = faces[j]; + PangoFontDescription *desc; + const char *facename; + int *sizes; + int k, nsizes, dummysize; + + /* + * Get the face name string. + */ + facename = pango_font_face_get_face_name(face); + + /* + * Set up a font description with what we've got so + * far. We'll fill in the size field manually and then + * call pango_font_description_to_string() to give the + * full real name of the specific font. + */ + desc = pango_font_face_describe(face); + + /* + * See if this font has a list of specific sizes. + */ +#ifndef PANGO_PRE_1POINT4 + pango_font_face_list_sizes(face, &sizes, &nsizes); +#else + /* + * In early versions of Pango, that call wasn't + * supported; we just have to assume everything is + * scalable. + */ + sizes = NULL; +#endif + if (!sizes) { + /* + * Write a single entry with a dummy size. + */ + dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE; + sizes = &dummysize; + nsizes = 1; + } + + /* + * If so, go through them one by one. + */ + for (k = 0; k < nsizes; k++) { + char *fullname; + char stylekey[128]; + + pango_font_description_set_size(desc, sizes[k]); + + fullname = pango_font_description_to_string(desc); + + /* + * Construct the sorting key for font styles. + */ + { + char *p = stylekey; + int n; + + n = pango_font_description_get_weight(desc); + /* Weight: normal, then lighter, then bolder */ + if (n <= PANGO_WEIGHT_NORMAL) + n = PANGO_WEIGHT_NORMAL - n; + p += sprintf(p, "%4d", n); + + n = pango_font_description_get_style(desc); + p += sprintf(p, " %2d", n); + + n = pango_font_description_get_stretch(desc); + /* Stretch: closer to normal sorts earlier */ + n = 2 * abs(PANGO_STRETCH_NORMAL - n) + + (n < PANGO_STRETCH_NORMAL); + p += sprintf(p, " %2d", n); + + n = pango_font_description_get_variant(desc); + p += sprintf(p, " %2d", n); + + } + + /* + * Got everything. Hand off to the callback. + * (The charset string is NULL, because only + * server-side X fonts use it.) + */ + callback(callback_ctx, fullname, familyname, NULL, facename, + stylekey, + (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])), + flags, &pangofont_vtable); + + g_free(fullname); + } + if (sizes != &dummysize) + g_free(sizes); + + pango_font_description_free(desc); + } + g_free(faces); + } + g_free(families); +} + +static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + int resolve_aliases) +{ + /* + * When given a Pango font name to try to make sense of for a + * font selector, we must normalise it to PANGO_DUMMY_SIZE and + * extract its original size (in pixels) into the `size' field. + */ + PangoContext *ctx; +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontDescription *desc; + PangoFontset *fset; + PangoFontMetrics *metrics; + char *newname, *retname; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + if (!pangofont_check_desc_makes_sense(ctx, desc)) { + pango_font_description_free(desc); + return NULL; + } +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) { + pango_font_description_free(desc); + return NULL; + } + fset = pango_font_map_load_fontset(map, ctx, desc, + pango_context_get_language(ctx)); +#else + fset = pango_context_load_fontset(ctx, desc, + pango_context_get_language(ctx)); +#endif + if (!fset) { + pango_font_description_free(desc); + return NULL; + } + metrics = pango_fontset_get_metrics(fset); + if (!metrics || + pango_font_metrics_get_approximate_digit_width(metrics) == 0) { + pango_font_description_free(desc); + g_object_unref(fset); + return NULL; + } + + *size = PANGO_PIXELS(pango_font_description_get_size(desc)); + *flags = FONTFLAG_CLIENTSIDE; + pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE); + newname = pango_font_description_to_string(desc); + retname = dupstr(newname); + g_free(newname); + + pango_font_metrics_unref(metrics); + pango_font_description_free(desc); + g_object_unref(fset); + + return retname; +} + +static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, + int size) +{ + PangoFontDescription *desc; + char *newname, *retname; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + pango_font_description_set_size(desc, size * PANGO_SCALE); + newname = pango_font_description_to_string(desc); + retname = dupstr(newname); + g_free(newname); + pango_font_description_free(desc); + + return retname; +} + +#endif /* GTK_CHECK_VERSION(2,0,0) */ + +/* ---------------------------------------------------------------------- + * Outermost functions which do the vtable dispatch. + */ + +/* + * Complete list of font-type subclasses. Listed in preference + * order for unifont_create(). (That is, in the extremely unlikely + * event that the same font name is valid as both a Pango and an + * X11 font, it will be interpreted as the former in the absence + * of an explicit type-disambiguating prefix.) + */ +static const struct unifont_vtable *unifont_types[] = { +#if GTK_CHECK_VERSION(2,0,0) + &pangofont_vtable, +#endif + &x11font_vtable, +}; + +/* + * Function which takes a font name and processes the optional + * scheme prefix. Returns the tail of the font name suitable for + * passing to individual font scheme functions, and also provides + * a subrange of the unifont_types[] array above. + * + * The return values `start' and `end' denote a half-open interval + * in unifont_types[]; that is, the correct way to iterate over + * them is + * + * for (i = start; i < end; i++) {...} + */ +static const char *unifont_do_prefix(const char *name, int *start, int *end) +{ + int colonpos = strcspn(name, ":"); + int i; + + if (name[colonpos]) { + /* + * There's a colon prefix on the font name. Use it to work + * out which subclass to use. + */ + for (i = 0; i < lenof(unifont_types); i++) { + if (strlen(unifont_types[i]->prefix) == colonpos && + !strncmp(unifont_types[i]->prefix, name, colonpos)) { + *start = i; + *end = i+1; + return name + colonpos + 1; + } + } + /* + * None matched, so return an empty scheme list to prevent + * any scheme from being called at all. + */ + *start = *end = 0; + return name + colonpos + 1; + } else { + /* + * No colon prefix, so just use all the subclasses. + */ + *start = 0; + *end = lenof(unifont_types); + return name; + } +} + +unifont *unifont_create(GtkWidget *widget, const char *name, int wide, + int bold, int shadowoffset, int shadowalways) +{ + int i, start, end; + + name = unifont_do_prefix(name, &start, &end); + + for (i = start; i < end; i++) { + unifont *ret = unifont_types[i]->create(widget, name, wide, bold, + shadowoffset, shadowalways); + if (ret) + return ret; + } + return NULL; /* font not found in any scheme */ +} + +void unifont_destroy(unifont *font) +{ + font->vt->destroy(font); +} + +void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold, int cellwidth) +{ + font->vt->draw_text(target, gc, font, x, y, string, len, + wide, bold, cellwidth); +} + +#if GTK_CHECK_VERSION(2,0,0) + +/* ---------------------------------------------------------------------- + * Implementation of a unified font selector. Used on GTK 2 only; + * for GTK 1 we still use the standard font selector. + */ + +typedef struct fontinfo fontinfo; + +typedef struct unifontsel_internal { + /* This must be the structure's first element, for cross-casting */ + unifontsel u; + GtkListStore *family_model, *style_model, *size_model; + GtkWidget *family_list, *style_list, *size_entry, *size_list; + GtkWidget *filter_buttons[4]; + GtkWidget *preview_area; + GdkPixmap *preview_pixmap; + int preview_width, preview_height; + GdkColor preview_fg, preview_bg; + int filter_flags; + tree234 *fonts_by_realname, *fonts_by_selorder; + fontinfo *selected; + int selsize, intendedsize; + int inhibit_response; /* inhibit callbacks when we change GUI controls */ +} unifontsel_internal; + +/* + * The structure held in the tree234s. All the string members are + * part of the same allocated area, so don't need freeing + * separately. + */ +struct fontinfo { + char *realname; + char *family, *charset, *style, *stylekey; + int size, flags; + /* + * Fallback sorting key, to permit multiple identical entries + * to exist in the selorder tree. + */ + int index; + /* + * Indices mapping fontinfo structures to indices in the list + * boxes. sizeindex is irrelevant if the font is scalable + * (size==0). + */ + int familyindex, styleindex, sizeindex; + /* + * The class of font. + */ + const struct unifont_vtable *fontclass; +}; + +struct fontinfo_realname_find { + const char *realname; + int flags; +}; + +static int strnullcasecmp(const char *a, const char *b) +{ + int i; + + /* + * If exactly one of the inputs is NULL, it compares before + * the other one. + */ + if ((i = (!b) - (!a)) != 0) + return i; + + /* + * NULL compares equal. + */ + if (!a) + return 0; + + /* + * Otherwise, ordinary strcasecmp. + */ + return g_strcasecmp(a, b); +} + +static int fontinfo_realname_compare(void *av, void *bv) +{ + fontinfo *a = (fontinfo *)av; + fontinfo *b = (fontinfo *)bv; + int i; + + if ((i = strnullcasecmp(a->realname, b->realname)) != 0) + return i; + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); + return 0; +} + +static int fontinfo_realname_find(void *av, void *bv) +{ + struct fontinfo_realname_find *a = (struct fontinfo_realname_find *)av; + fontinfo *b = (fontinfo *)bv; + int i; + + if ((i = strnullcasecmp(a->realname, b->realname)) != 0) + return i; + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); + return 0; +} + +static int fontinfo_selorder_compare(void *av, void *bv) +{ + fontinfo *a = (fontinfo *)av; + fontinfo *b = (fontinfo *)bv; + int i; + if ((i = strnullcasecmp(a->family, b->family)) != 0) + return i; + /* + * Font class comes immediately after family, so that fonts + * from different classes with the same family + */ + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); + if ((i = strnullcasecmp(a->charset, b->charset)) != 0) + return i; + if ((i = strnullcasecmp(a->stylekey, b->stylekey)) != 0) + return i; + if ((i = strnullcasecmp(a->style, b->style)) != 0) + return i; + if (a->size != b->size) + return (a->size < b->size ? -1 : +1); + if (a->index != b->index) + return (a->index < b->index ? -1 : +1); + return 0; +} + +static void unifontsel_deselect(unifontsel_internal *fs) +{ + fs->selected = NULL; + gtk_list_store_clear(fs->style_model); + gtk_list_store_clear(fs->size_model); + gtk_widget_set_sensitive(fs->u.ok_button, FALSE); + gtk_widget_set_sensitive(fs->size_entry, FALSE); +} + +static void unifontsel_setup_familylist(unifontsel_internal *fs) +{ + GtkTreeIter iter; + int i, listindex, minpos = -1, maxpos = -1; + char *currfamily = NULL; + int currflags = -1; + fontinfo *info; + + gtk_list_store_clear(fs->family_model); + listindex = 0; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its font + * name to the list box. + */ + for (i = 0 ;; i++) { + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + /* + * info may be NULL if we've just run off the end of the + * tree. We must still do a processing pass in that + * situation, in case we had an unfinished font record in + * progress. + */ + if (info && (info->flags &~ fs->filter_flags)) { + info->familyindex = -1; + continue; /* we're filtering out this font */ + } + if (!info || strnullcasecmp(currfamily, info->family) || + currflags != (info->flags & FONTFLAG_SORT_MASK)) { + /* + * We've either finished a family, or started a new + * one, or both. + */ + if (currfamily) { + gtk_list_store_append(fs->family_model, &iter); + gtk_list_store_set(fs->family_model, &iter, + 0, currfamily, 1, minpos, 2, maxpos+1, -1); + listindex++; + } + if (info) { + minpos = i; + currfamily = info->family; + currflags = info->flags & FONTFLAG_SORT_MASK; + } + } + if (!info) + break; /* now we're done */ + info->familyindex = listindex; + maxpos = i; + } + + /* + * If we've just filtered out the previously selected font, + * deselect it thoroughly. + */ + if (fs->selected && fs->selected->familyindex < 0) + unifontsel_deselect(fs); +} + +static void unifontsel_setup_stylelist(unifontsel_internal *fs, + int start, int end) +{ + GtkTreeIter iter; + int i, listindex, minpos = -1, maxpos = -1, started = FALSE; + char *currcs = NULL, *currstyle = NULL; + fontinfo *info; + + gtk_list_store_clear(fs->style_model); + listindex = 0; + started = FALSE; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its charset + * and/or style name to the list box. + */ + for (i = start; i <= end; i++) { + if (i == end) + info = NULL; + else + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + /* + * info may be NULL if we've just run off the end of the + * relevant data. We must still do a processing pass in + * that situation, in case we had an unfinished font + * record in progress. + */ + if (info && (info->flags &~ fs->filter_flags)) { + info->styleindex = -1; + continue; /* we're filtering out this font */ + } + if (!info || !started || strnullcasecmp(currcs, info->charset) || + strnullcasecmp(currstyle, info->style)) { + /* + * We've either finished a style/charset, or started a + * new one, or both. + */ + started = TRUE; + if (currstyle) { + gtk_list_store_append(fs->style_model, &iter); + gtk_list_store_set(fs->style_model, &iter, + 0, currstyle, 1, minpos, 2, maxpos+1, + 3, TRUE, -1); + listindex++; + } + if (info) { + minpos = i; + if (info->charset && strnullcasecmp(currcs, info->charset)) { + gtk_list_store_append(fs->style_model, &iter); + gtk_list_store_set(fs->style_model, &iter, + 0, info->charset, 1, -1, 2, -1, + 3, FALSE, -1); + listindex++; + } + currcs = info->charset; + currstyle = info->style; + } + } + if (!info) + break; /* now we're done */ + info->styleindex = listindex; + maxpos = i; + } +} + +static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 }; + +static void unifontsel_setup_sizelist(unifontsel_internal *fs, + int start, int end) +{ + GtkTreeIter iter; + int i, listindex; + char sizetext[40]; + fontinfo *info; + + gtk_list_store_clear(fs->size_model); + listindex = 0; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its font + * name to the list box. + */ + for (i = start; i < end; i++) { + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + if (info->flags &~ fs->filter_flags) { + info->sizeindex = -1; + continue; /* we're filtering out this font */ + } + if (info->size) { + sprintf(sizetext, "%d", info->size); + info->sizeindex = listindex; + gtk_list_store_append(fs->size_model, &iter); + gtk_list_store_set(fs->size_model, &iter, + 0, sizetext, 1, i, 2, info->size, -1); + listindex++; + } else { + int j; + + assert(i == start); + assert(i+1 == end); + + for (j = 0; j < lenof(unifontsel_default_sizes); j++) { + sprintf(sizetext, "%d", unifontsel_default_sizes[j]); + gtk_list_store_append(fs->size_model, &iter); + gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i, + 2, unifontsel_default_sizes[j], -1); + listindex++; + } + } + } +} + +static void unifontsel_set_filter_buttons(unifontsel_internal *fs) +{ + int i; + + for (i = 0; i < lenof(fs->filter_buttons); i++) { + int flagbit = GPOINTER_TO_INT(gtk_object_get_data + (GTK_OBJECT(fs->filter_buttons[i]), + "user-data")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]), + !!(fs->filter_flags & flagbit)); + } +} + +static void unifontsel_draw_preview_text(unifontsel_internal *fs) +{ + unifont *font; + char *sizename = NULL; + fontinfo *info = fs->selected; + + if (info) { + sizename = info->fontclass->scale_fontname + (GTK_WIDGET(fs->u.window), info->realname, fs->selsize); + font = info->fontclass->create(GTK_WIDGET(fs->u.window), + sizename ? sizename : info->realname, + FALSE, FALSE, 0, 0); + } else + font = NULL; + + if (fs->preview_pixmap) { + GdkGC *gc = gdk_gc_new(fs->preview_pixmap); + gdk_gc_set_foreground(gc, &fs->preview_bg); + gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0, + fs->preview_width, fs->preview_height); + gdk_gc_set_foreground(gc, &fs->preview_fg); + if (font) { + /* + * The pangram used here is rather carefully + * constructed: it contains a sequence of very narrow + * letters (`jil') and a pair of adjacent very wide + * letters (`wm'). + * + * If the user selects a proportional font, it will be + * coerced into fixed-width character cells when used + * in the actual terminal window. We therefore display + * it the same way in the preview pane, so as to show + * it the way it will actually be displayed - and we + * deliberately pick a pangram which will show the + * resulting miskerning at its worst. + * + * We aren't trying to sell people these fonts; we're + * trying to let them make an informed choice. Better + * that they find out the problems with using + * proportional fonts in terminal windows here than + * that they go to the effort of selecting their font + * and _then_ realise it was a mistake. + */ + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent, + "bankrupt jilted showmen quiz convex fogey", + 41, FALSE, FALSE, font->width); + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent + font->height, + "BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", + 41, FALSE, FALSE, font->width); + /* + * The ordering of punctuation here is also selected + * with some specific aims in mind. I put ` and ' + * together because some software (and people) still + * use them as matched quotes no matter what Unicode + * might say on the matter, so people can quickly + * check whether they look silly in a candidate font. + * The sequence #_@ is there to let people judge the + * suitability of the underscore as an effectively + * alphabetic character (since that's how it's often + * used in practice, at least by programmers). + */ + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent + font->height * 2, + "0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", + 42, FALSE, FALSE, font->width); + } + gdk_gc_unref(gc); + gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE); + } + if (font) + info->fontclass->destroy(font); + + sfree(sizename); +} + +static void unifontsel_select_font(unifontsel_internal *fs, + fontinfo *info, int size, int leftlist, + int size_is_explicit) +{ + int index; + int minval, maxval; + GtkTreePath *treepath; + GtkTreeIter iter; + + fs->inhibit_response = TRUE; + + fs->selected = info; + fs->selsize = size; + if (size_is_explicit) + fs->intendedsize = size; + + gtk_widget_set_sensitive(fs->u.ok_button, TRUE); + + /* + * Find the index of this fontinfo in the selorder list. + */ + index = -1; + findpos234(fs->fonts_by_selorder, info, NULL, &index); + assert(index >= 0); + + /* + * Adjust the font selector flags and redo the font family + * list box, if necessary. + */ + if (leftlist <= 0 && + (fs->filter_flags | info->flags) != fs->filter_flags) { + fs->filter_flags |= info->flags; + unifontsel_set_filter_buttons(fs); + unifontsel_setup_familylist(fs); + } + + /* + * Find the appropriate family name and select it in the list. + */ + assert(info->familyindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->familyindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, treepath); + gtk_tree_path_free(treepath); + + /* + * Now set up the font style list. + */ + gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, + 1, &minval, 2, &maxval, -1); + if (leftlist <= 1) + unifontsel_setup_stylelist(fs, minval, maxval); + + /* + * Find the appropriate style name and select it in the list. + */ + if (info->style) { + assert(info->styleindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->styleindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model), + &iter, treepath); + gtk_tree_path_free(treepath); + + /* + * And set up the size list. + */ + gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter, + 1, &minval, 2, &maxval, -1); + if (leftlist <= 2) + unifontsel_setup_sizelist(fs, minval, maxval); + + /* + * Find the appropriate size, and select it in the list. + */ + if (info->size) { + assert(info->sizeindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_path_free(treepath); + size = info->size; + } else { + int j; + for (j = 0; j < lenof(unifontsel_default_sizes); j++) + if (unifontsel_default_sizes[j] == size) { + treepath = gtk_tree_path_new_from_indices(j, -1); + gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE, 0.0, + 0.0); + gtk_tree_path_free(treepath); + } + } + + /* + * And set up the font size text entry box. + */ + { + char sizetext[40]; + sprintf(sizetext, "%d", size); + gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext); + } + } else { + if (leftlist <= 2) + unifontsel_setup_sizelist(fs, 0, 0); + gtk_entry_set_text(GTK_ENTRY(fs->size_entry), ""); + } + + /* + * Grey out the font size edit box if we're not using a + * scalable font. + */ + gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0); + gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0); + + unifontsel_draw_preview_text(fs); + + fs->inhibit_response = FALSE; +} + +static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + int newstate = gtk_toggle_button_get_active(tb); + int newflags; + int flagbit = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(tb), + "user-data")); + + if (newstate) + newflags = fs->filter_flags | flagbit; + else + newflags = fs->filter_flags & ~flagbit; + + if (fs->filter_flags != newflags) { + fs->filter_flags = newflags; + unifontsel_setup_familylist(fs); + } +} + +static void unifontsel_add_entry(void *ctx, const char *realfontname, + const char *family, const char *charset, + const char *style, const char *stylekey, + int size, int flags, + const struct unifont_vtable *fontclass) +{ + unifontsel_internal *fs = (unifontsel_internal *)ctx; + fontinfo *info; + int totalsize; + char *p; + + totalsize = sizeof(fontinfo) + strlen(realfontname) + + (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) + + (style ? strlen(style) : 0) + (stylekey ? strlen(stylekey) : 0) + 10; + info = (fontinfo *)smalloc(totalsize); + info->fontclass = fontclass; + p = (char *)info + sizeof(fontinfo); + info->realname = p; + strcpy(p, realfontname); + p += 1+strlen(p); + if (family) { + info->family = p; + strcpy(p, family); + p += 1+strlen(p); + } else + info->family = NULL; + if (charset) { + info->charset = p; + strcpy(p, charset); + p += 1+strlen(p); + } else + info->charset = NULL; + if (style) { + info->style = p; + strcpy(p, style); + p += 1+strlen(p); + } else + info->style = NULL; + if (stylekey) { + info->stylekey = p; + strcpy(p, stylekey); + p += 1+strlen(p); + } else + info->stylekey = NULL; + assert(p - (char *)info <= totalsize); + info->size = size; + info->flags = flags; + info->index = count234(fs->fonts_by_selorder); + + /* + * It's just conceivable that a misbehaving font enumerator + * might tell us about the same font real name more than once, + * in which case we should silently drop the new one. + */ + if (add234(fs->fonts_by_realname, info) != info) { + sfree(info); + return; + } + /* + * However, we should never get a duplicate key in the + * selorder tree, because the index field carefully + * disambiguates otherwise identical records. + */ + add234(fs->fonts_by_selorder, info); +} + +static fontinfo *update_for_intended_size(unifontsel_internal *fs, + fontinfo *info) +{ + fontinfo info2, *below, *above; + int pos; + + /* + * Copy the info structure. This doesn't copy its dynamic + * string fields, but that's unimportant because all we're + * going to do is to adjust the size field and use it in one + * tree search. + */ + info2 = *info; + info2.size = fs->intendedsize; + + /* + * Search in the tree to find the fontinfo structure which + * best approximates the size the user last requested. + */ + below = findrelpos234(fs->fonts_by_selorder, &info2, NULL, + REL234_LE, &pos); + 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 (!fontinfo_selorder_compare(&info2, below)) + return below; + + /* + * Now we've either found two suitable fonts, one smaller and + * one larger, or we're at one or other extreme end of the + * scale. Find out which, by NULLing out either of below and + * above if it differs from this one in any respect but size + * (and the disambiguating index field). Bear in mind, also, + * that either one might _already_ be NULL if we're at the + * extreme ends of the font list. + */ + if (below) { + info2.size = below->size; + info2.index = below->index; + if (fontinfo_selorder_compare(&info2, below)) + below = NULL; + } + if (above) { + info2.size = above->size; + info2.index = above->index; + if (fontinfo_selorder_compare(&info2, above)) + above = NULL; + } + + /* + * Now return whichever of above and below is non-NULL, if + * that's unambiguous. + */ + if (!above) + return below; + if (!below) + return above; + + /* + * And now we really do have to make a choice about whether to + * round up or down. We'll do it by rounding to nearest, + * breaking ties by rounding up. + */ + if (above->size - fs->intendedsize <= fs->intendedsize - below->size) + return above; + else + return below; +} + +static void family_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + info = update_for_intended_size(fs, info); + if (!info) + return; /* _shouldn't_ happen unless font list is completely funted */ + if (!info->size) + fs->selsize = fs->intendedsize; /* font is scalable */ + unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, + 1, FALSE); +} + +static void style_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); + if (minval < 0) + return; /* somehow a charset heading got clicked */ + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + info = update_for_intended_size(fs, info); + if (!info) + return; /* _shouldn't_ happen unless font list is completely funted */ + if (!info->size) + fs->selsize = fs->intendedsize; /* font is scalable */ + unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, + 2, FALSE); +} + +static void size_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval, size; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + unifontsel_select_font(fs, info, info->size ? info->size : size, 3, TRUE); +} + +static void size_entry_changed(GtkEditable *ed, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + const char *text; + int size; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + text = gtk_entry_get_text(GTK_ENTRY(ed)); + size = atoi(text); + + if (size > 0) { + assert(fs->selected->size == 0); + unifontsel_select_font(fs, fs->selected, size, 3, TRUE); + } +} + +static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeIter iter; + int minval, newsize; + fontinfo *info, *newinfo; + char *newname; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, 1,&minval, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + if (info) { + int flags; + struct fontinfo_realname_find f; + + newname = info->fontclass->canonify_fontname + (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, TRUE); + + f.realname = newname; + f.flags = flags; + newinfo = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); + + sfree(newname); + if (!newinfo) + return; /* font name not in our index */ + if (newinfo == info) + return; /* didn't change under canonification => not an alias */ + unifontsel_select_font(fs, newinfo, + newinfo->size ? newinfo->size : newsize, + 1, TRUE); + } +} + +static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event, + gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + + if (fs->preview_pixmap) { + gdk_draw_pixmap(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + fs->preview_pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + } + return TRUE; +} + +static gint unifontsel_configure_area(GtkWidget *widget, + GdkEventConfigure *event, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + int ox, oy, nx, ny, x, y; + + /* + * Enlarge the pixmap, but never shrink it. + */ + ox = fs->preview_width; + oy = fs->preview_height; + x = event->width; + y = event->height; + if (x > ox || y > oy) { + if (fs->preview_pixmap) + gdk_pixmap_unref(fs->preview_pixmap); + + nx = (x > ox ? x : ox); + ny = (y > oy ? y : oy); + fs->preview_pixmap = gdk_pixmap_new(widget->window, nx, ny, -1); + fs->preview_width = nx; + fs->preview_height = ny; + + unifontsel_draw_preview_text(fs); + } + + gdk_window_invalidate_rect(widget->window, NULL, FALSE); + + return TRUE; +} + +unifontsel *unifontsel_new(const char *wintitle) +{ + unifontsel_internal *fs = snew(unifontsel_internal); + GtkWidget *table, *label, *w, *ww, *scroll; + GtkListStore *model; + GtkTreeViewColumn *column; + int lists_height, preview_height, font_width, style_width, size_width; + int i; + + fs->inhibit_response = FALSE; + fs->selected = NULL; + + { + /* + * Invent some magic size constants. + */ + GtkRequisition req; + label = gtk_label_new("Quite Long Font Name (Foundry)"); + gtk_widget_size_request(label, &req); + font_width = req.width; + lists_height = 14 * req.height; + preview_height = 5 * req.height; + gtk_label_set_text(GTK_LABEL(label), "Italic Extra Condensed"); + gtk_widget_size_request(label, &req); + style_width = req.width; + gtk_label_set_text(GTK_LABEL(label), "48000"); + gtk_widget_size_request(label, &req); + size_width = req.width; +#if GTK_CHECK_VERSION(2,10,0) + g_object_ref_sink(label); + g_object_unref(label); +#else + gtk_object_sink(GTK_OBJECT(label)); +#endif + } + + /* + * Create the dialog box and initialise the user-visible + * fields in the returned structure. + */ + fs->u.user_data = NULL; + fs->u.window = GTK_WINDOW(gtk_dialog_new()); + gtk_window_set_title(fs->u.window, wintitle); + fs->u.cancel_button = gtk_dialog_add_button + (GTK_DIALOG(fs->u.window), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + fs->u.ok_button = gtk_dialog_add_button + (GTK_DIALOG(fs->u.window), GTK_STOCK_OK, GTK_RESPONSE_OK); + gtk_widget_grab_default(fs->u.ok_button); + + /* + * Now set up the internal fields, including in particular all + * the controls that actually allow the user to select fonts. + */ + table = gtk_table_new(8, 3, FALSE); + gtk_widget_show(table); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); +#if GTK_CHECK_VERSION(2,4,0) + /* GtkAlignment seems to be the simplest way to put padding round things */ + w = gtk_alignment_new(0, 0, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); + gtk_container_add(GTK_CONTAINER(w), table); + gtk_widget_show(w); +#else + w = table; +#endif + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox), + w, TRUE, TRUE, 0); + + label = gtk_label_new_with_mnemonic("_Font:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Font list box displays only a string, but additionally + * stores two integers which give the limits within the + * tree234 of the font entries covered by this list entry. + */ + model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Font", gtk_cell_renderer_text_new(), + "text", 0, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(family_changed), fs); + g_signal_connect(G_OBJECT(w), "row-activated", + G_CALLBACK(alias_resolve), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_widget_set_size_request(scroll, font_width, lists_height); + gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); + fs->family_model = model; + fs->family_list = w; + + label = gtk_label_new_with_mnemonic("_Style:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Style list box can contain insensitive elements + * (character set headings for server-side fonts), so we add + * an extra column to the list store to hold that information. + */ + model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, + G_TYPE_BOOLEAN); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Style", gtk_cell_renderer_text_new(), + "text", 0, "sensitive", 3, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(style_changed), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_widget_set_size_request(scroll, style_width, lists_height); + gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); + fs->style_model = model; + fs->style_list = w; + + label = gtk_label_new_with_mnemonic("Si_ze:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Size label attaches primarily to a text input box so + * that the user can select a size of their choice. The list + * of available sizes is secondary. + */ + fs->size_entry = w = gtk_entry_new(); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_set_size_request(w, size_width, -1); + gtk_widget_show(w); + gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed), + fs); + + model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Size", gtk_cell_renderer_text_new(), + "text", 0, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(size_changed), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); + fs->size_model = model; + fs->size_list = w; + + /* + * Preview widget. + */ + fs->preview_area = gtk_drawing_area_new(); + fs->preview_pixmap = NULL; + fs->preview_width = 0; + fs->preview_height = 0; + fs->preview_fg.pixel = fs->preview_bg.pixel = 0; + fs->preview_fg.red = fs->preview_fg.green = fs->preview_fg.blue = 0x0000; + fs->preview_bg.red = fs->preview_bg.green = fs->preview_bg.blue = 0xFFFF; + gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_fg, + FALSE, FALSE); + gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg, + FALSE, FALSE); + gtk_signal_connect(GTK_OBJECT(fs->preview_area), "expose_event", + GTK_SIGNAL_FUNC(unifontsel_expose_area), fs); + gtk_signal_connect(GTK_OBJECT(fs->preview_area), "configure_event", + GTK_SIGNAL_FUNC(unifontsel_configure_area), fs); + gtk_widget_set_size_request(fs->preview_area, 1, preview_height); + gtk_widget_show(fs->preview_area); + ww = fs->preview_area; + w = gtk_frame_new(NULL); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); +#if GTK_CHECK_VERSION(2,4,0) + ww = w; + /* GtkAlignment seems to be the simplest way to put padding round things */ + w = gtk_alignment_new(0, 0, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); +#endif + ww = w; + w = gtk_frame_new("Preview of font"); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8); + + i = 0; + w = gtk_check_button_new_with_label("Show client-side fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_CLIENTSIDE)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show server-side fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_SERVERSIDE)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show server-side font aliases"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_SERVERALIAS)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show non-monospaced fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_NONMONOSPACED)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0); + + assert(i == lenof(fs->filter_buttons)); + fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE | + FONTFLAG_SERVERALIAS; + unifontsel_set_filter_buttons(fs); + + /* + * Go and find all the font names, and set up our master font + * list. + */ + fs->fonts_by_realname = newtree234(fontinfo_realname_compare); + fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare); + for (i = 0; i < lenof(unifont_types); i++) + unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window), + unifontsel_add_entry, fs); + + /* + * And set up the initial font names list. + */ + unifontsel_setup_familylist(fs); + + fs->selsize = fs->intendedsize = 13; /* random default */ + gtk_widget_set_sensitive(fs->u.ok_button, FALSE); + + return (unifontsel *)fs; +} + +void unifontsel_destroy(unifontsel *fontsel) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + fontinfo *info; + + if (fs->preview_pixmap) + gdk_pixmap_unref(fs->preview_pixmap); + + freetree234(fs->fonts_by_selorder); + while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL) + sfree(info); + freetree234(fs->fonts_by_realname); + + gtk_widget_destroy(GTK_WIDGET(fs->u.window)); + sfree(fs); +} + +void unifontsel_set_name(unifontsel *fontsel, const char *fontname) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + int i, start, end, size, flags; + const char *fontname2 = NULL; + fontinfo *info; + + /* + * Provide a default if given an empty or null font name. + */ + if (!fontname || !*fontname) + fontname = "server:fixed"; + + /* + * Call the canonify_fontname function. + */ + fontname = unifont_do_prefix(fontname, &start, &end); + for (i = start; i < end; i++) { + fontname2 = unifont_types[i]->canonify_fontname + (GTK_WIDGET(fs->u.window), fontname, &size, &flags, FALSE); + if (fontname2) + break; + } + if (i == end) + return; /* font name not recognised */ + + /* + * Now look up the canonified font name in our index. + */ + { + struct fontinfo_realname_find f; + f.realname = fontname2; + f.flags = flags; + info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); + } + + /* + * If we've found the font, and its size field is either + * correct or zero (the latter indicating a scalable font), + * then we're done. Otherwise, try looking up the original + * font name instead. + */ + if (!info || (info->size != size && info->size != 0)) { + struct fontinfo_realname_find f; + f.realname = fontname; + f.flags = flags; + + info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); + if (!info || info->size != size) + return; /* font name not in our index */ + } + + /* + * Now we've got a fontinfo structure and a font size, so we + * know everything we need to fill in all the fields in the + * dialog. + */ + unifontsel_select_font(fs, info, size, 0, TRUE); +} + +char *unifontsel_get_name(unifontsel *fontsel) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + char *name; + + if (!fs->selected) + return NULL; + + if (fs->selected->size == 0) { + name = fs->selected->fontclass->scale_fontname + (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize); + if (name) { + char *ret = dupcat(fs->selected->fontclass->prefix, ":", + name, NULL); + sfree(name); + return ret; + } + } + + return dupcat(fs->selected->fontclass->prefix, ":", + fs->selected->realname, NULL); +} + +#endif /* GTK_CHECK_VERSION(2,0,0) */ diff --git a/putty/UNIX/GTKFONT.H b/putty/UNIX/GTKFONT.H new file mode 100644 index 0000000..41c0505 --- /dev/null +++ b/putty/UNIX/GTKFONT.H @@ -0,0 +1,67 @@ +/* + * Header file for gtkfont.c. Has to be separate from unix.h + * because it depends on GTK data types, hence can't be included + * from cross-platform code (which doesn't go near GTK). + */ + +#ifndef PUTTY_GTKFONT_H +#define PUTTY_GTKFONT_H + +/* + * Exports from gtkfont.c. + */ +struct unifont_vtable; /* contents internal to gtkfont.c */ +typedef struct unifont { + const struct unifont_vtable *vt; + /* + * `Non-static data members' of the `class', accessible to + * external code. + */ + + /* + * public_charset is the charset used when the user asks for + * `Use font encoding'. + * + * real_charset is the charset used when translating text into + * a form suitable for sending to unifont_draw_text(). + * + * They can differ. For example, public_charset might be + * CS_ISO8859_1 while real_charset is CS_ISO8859_1_X11. + */ + int public_charset, real_charset; + + /* + * Font dimensions needed by clients. + */ + int width, height, ascent, descent; +} unifont; + +unifont *unifont_create(GtkWidget *widget, const char *name, + int wide, int bold, + int shadowoffset, int shadowalways); +void unifont_destroy(unifont *font); +void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold, int cellwidth); + +/* + * 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/putty/UNIX/GTKWIN.C b/putty/UNIX/GTKWIN.C new file mode 100644 index 0000000..f2a13ae --- /dev/null +++ b/putty/UNIX/GTKWIN.C @@ -0,0 +1,3645 @@ +/* + * gtkwin.c: the main code that runs a PuTTY terminal emulator and + * backend in a GTK window. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PUTTY_DO_GLOBALS /* actually _define_ globals */ + +#include "putty.h" +#include "terminal.h" +#include "gtkfont.h" + +#define CAT2(x,y) x ## y +#define CAT(x,y) CAT2(x,y) +#define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)} + +#if GTK_CHECK_VERSION(2,0,0) +ASSERT(sizeof(long) <= sizeof(gsize)); +#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l) +#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p) +#else /* Gtk 1.2 */ +ASSERT(sizeof(long) <= sizeof(gpointer)); +#define LONG_TO_GPOINTER(l) ((gpointer)(long)(l)) +#define GPOINTER_TO_LONG(p) ((long)(p)) +#endif + +/* Colours come in two flavours: configurable, and xterm-extended. */ +#define NCFGCOLOURS (lenof(((Config *)0)->colours)) +#define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */ +#define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS) + +GdkAtom compound_text_atom, utf8_string_atom; + +extern char **pty_argv; /* declared in pty.c */ +extern int use_pty_argv; + +/* + * Timers are global across all sessions (even if we were handling + * multiple sessions, which we aren't), so the current timer ID is + * a global variable. + */ +static guint timer_id = 0; + +struct gui_data { + GtkWidget *window, *area, *sbar; + GtkBox *hbox; + GtkAdjustment *sbar_adjust; + GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2, + *restartitem; + GtkWidget *sessionsmenu; + GdkPixmap *pixmap; + unifont *fonts[4]; /* normal, bold, wide, widebold */ + int xpos, ypos, gotpos, gravity; + GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor; + GdkColor cols[NALLCOLOURS]; + GdkColormap *colmap; + wchar_t *pastein_data; + int direct_to_font; + int pastein_data_len; + char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8; + int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len; + int font_width, font_height; + int width, height; + int ignore_sbar; + int mouseptr_visible; + int busy_status; + guint term_paste_idle_id; + guint term_exit_idle_id; + int alt_keycode; + int alt_digits; + char wintitle[sizeof(((Config *)0)->wintitle)]; + char icontitle[sizeof(((Config *)0)->wintitle)]; + int master_fd, master_func_id; + void *ldisc; + Backend *back; + void *backhandle; + Terminal *term; + void *logctx; + int exited; + struct unicode_data ucsdata; + Config cfg; + void *eventlogstuff; + char *progname, **gtkargvstart; + int ngtkargs; + guint32 input_event_time; /* Timestamp of the most recent input event. */ + int reconfiguring; +}; + +struct draw_ctx { + GdkGC *gc; + struct gui_data *inst; +}; + +static int send_raw_mouse; + +static char *app_name = "pterm"; + +static void start_backend(struct gui_data *inst); + +char *x_get_default(const char *key) +{ + return XGetDefault(GDK_DISPLAY(), app_name, key); +} + +void connection_fatal(void *frontend, char *p, ...) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + va_list ap; + char *msg; + va_start(ap, p); + msg = dupvprintf(p, ap); + va_end(ap); + inst->exited = TRUE; + fatal_message_box(inst->window, msg); + sfree(msg); + if (inst->cfg.close_on_exit == FORCE_ON) + cleanup_exit(1); +} + +/* + * Default settings that are specific to pterm. + */ +FontSpec platform_default_fontspec(const char *name) +{ + FontSpec ret; + if (!strcmp(name, "Font")) + strcpy(ret.name, "server:fixed"); + else + *ret.name = '\0'; + return ret; +} + +Filename platform_default_filename(const char *name) +{ + Filename ret; + if (!strcmp(name, "LogFileName")) + strcpy(ret.path, "putty.log"); + else + *ret.path = '\0'; + return ret; +} + +char *platform_default_s(const char *name) +{ + if (!strcmp(name, "SerialLine")) + return dupstr("/dev/ttyS0"); + return NULL; +} + +int platform_default_i(const char *name, int def) +{ + if (!strcmp(name, "CloseOnExit")) + return 2; /* maps to FORCE_ON after painful rearrangement :-( */ + if (!strcmp(name, "WinNameAlways")) + return 0; /* X natively supports icon titles, so use 'em by default */ + return def; +} + +/* Dummy routine, only required in plink. */ +void ldisc_update(void *frontend, int echo, int edit) +{ +} + +char *get_ttymode(void *frontend, const char *mode) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return term_get_ttymode(inst->term, mode); +} + +int from_backend(void *frontend, int is_stderr, const char *data, int len) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return term_data(inst->term, is_stderr, data, len); +} + +int from_backend_untrusted(void *frontend, const char *data, int len) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return term_data_untrusted(inst->term, data, len); +} + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + struct gui_data *inst = (struct gui_data *)p->frontend; + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = term_get_userpass_input(inst->term, p, in, inlen); + return ret; +} + +void logevent(void *frontend, const char *string) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + log_eventlog(inst->logctx, string); + + logevent_dlg(inst->eventlogstuff, string); +} + +int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */ +{ + struct gui_data *inst = (struct gui_data *)frontend; + + if (which) + return inst->font_height; + else + return inst->font_width; +} + +/* + * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT) + * into a cooked one (SELECT, EXTEND, PASTE). + * + * In Unix, this is not configurable; the X button arrangement is + * rock-solid across all applications, everyone has a three-button + * mouse or a means of faking it, and there is no need to switch + * buttons around at all. + */ +static Mouse_Button translate_button(Mouse_Button button) +{ + /* struct gui_data *inst = (struct gui_data *)frontend; */ + + if (button == MBT_LEFT) + return MBT_SELECT; + if (button == MBT_MIDDLE) + return MBT_PASTE; + if (button == MBT_RIGHT) + return MBT_EXTEND; + return 0; /* shouldn't happen */ +} + +/* + * Return the top-level GtkWindow associated with a particular + * front end instance. + */ +void *get_window(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return inst->window; +} + +/* + * Minimise or restore the window in response to a server-side + * request. + */ +void set_iconic(void *frontend, int iconic) +{ + /* + * GTK 1.2 doesn't know how to do this. + */ +#if GTK_CHECK_VERSION(2,0,0) + struct gui_data *inst = (struct gui_data *)frontend; + if (iconic) + gtk_window_iconify(GTK_WINDOW(inst->window)); + else + gtk_window_deiconify(GTK_WINDOW(inst->window)); +#endif +} + +/* + * Move the window in response to a server-side request. + */ +void move_window(void *frontend, int x, int y) +{ + struct gui_data *inst = (struct gui_data *)frontend; + /* + * I assume that when the GTK version of this call is available + * we should use it. Not sure how it differs from the GDK one, + * though. + */ +#if GTK_CHECK_VERSION(2,0,0) + gtk_window_move(GTK_WINDOW(inst->window), x, y); +#else + gdk_window_move(inst->window->window, x, y); +#endif +} + +/* + * Move the window to the top or bottom of the z-order in response + * to a server-side request. + */ +void set_zorder(void *frontend, int top) +{ + struct gui_data *inst = (struct gui_data *)frontend; + if (top) + gdk_window_raise(inst->window->window); + else + gdk_window_lower(inst->window->window); +} + +/* + * Refresh the window in response to a server-side request. + */ +void refresh_window(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + term_invalidate(inst->term); +} + +/* + * Maximise or restore the window in response to a server-side + * request. + */ +void set_zoomed(void *frontend, int zoomed) +{ + /* + * GTK 1.2 doesn't know how to do this. + */ +#if GTK_CHECK_VERSION(2,0,0) + struct gui_data *inst = (struct gui_data *)frontend; + if (zoomed) + gtk_window_maximize(GTK_WINDOW(inst->window)); + else + gtk_window_unmaximize(GTK_WINDOW(inst->window)); +#endif +} + +/* + * Report whether the window is iconic, for terminal reports. + */ +int is_iconic(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return !gdk_window_is_viewable(inst->window->window); +} + +/* + * Report the window's position, for terminal reports. + */ +void get_window_pos(void *frontend, int *x, int *y) +{ + struct gui_data *inst = (struct gui_data *)frontend; + /* + * I assume that when the GTK version of this call is available + * we should use it. Not sure how it differs from the GDK one, + * though. + */ +#if GTK_CHECK_VERSION(2,0,0) + gtk_window_get_position(GTK_WINDOW(inst->window), x, y); +#else + gdk_window_get_position(inst->window->window, x, y); +#endif +} + +/* + * Report the window's pixel size, for terminal reports. + */ +void get_window_pixels(void *frontend, int *x, int *y) +{ + struct gui_data *inst = (struct gui_data *)frontend; + /* + * I assume that when the GTK version of this call is available + * we should use it. Not sure how it differs from the GDK one, + * though. + */ +#if GTK_CHECK_VERSION(2,0,0) + gtk_window_get_size(GTK_WINDOW(inst->window), x, y); +#else + gdk_window_get_size(inst->window->window, x, y); +#endif +} + +/* + * Return the window or icon title. + */ +char *get_window_title(void *frontend, int icon) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return icon ? inst->icontitle : inst->wintitle; +} + +gint delete_window(GtkWidget *widget, GdkEvent *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + if (!inst->exited && inst->cfg.warn_on_close) { + if (!reallyclose(inst)) + return TRUE; + } + return FALSE; +} + +static void update_mouseptr(struct gui_data *inst) +{ + switch (inst->busy_status) { + case BUSY_NOT: + if (!inst->mouseptr_visible) { + gdk_window_set_cursor(inst->area->window, inst->blankcursor); + } else if (send_raw_mouse) { + gdk_window_set_cursor(inst->area->window, inst->rawcursor); + } else { + gdk_window_set_cursor(inst->area->window, inst->textcursor); + } + break; + case BUSY_WAITING: /* XXX can we do better? */ + case BUSY_CPU: + /* We always display these cursors. */ + gdk_window_set_cursor(inst->area->window, inst->waitcursor); + break; + default: + assert(0); + } +} + +static void show_mouseptr(struct gui_data *inst, int show) +{ + if (!inst->cfg.hide_mouseptr) + show = 1; + inst->mouseptr_visible = show; + update_mouseptr(inst); +} + +void draw_backing_rect(struct gui_data *inst) +{ + GdkGC *gc = gdk_gc_new(inst->area->window); + gdk_gc_set_foreground(gc, &inst->cols[258]); /* default background */ + gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0, + inst->cfg.width * inst->font_width + 2*inst->cfg.window_border, + inst->cfg.height * inst->font_height + 2*inst->cfg.window_border); + gdk_gc_unref(gc); +} + +gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + int w, h, need_size = 0; + + /* + * See if the terminal size has changed, in which case we must + * let the terminal know. + */ + w = (event->width - 2*inst->cfg.window_border) / inst->font_width; + h = (event->height - 2*inst->cfg.window_border) / inst->font_height; + if (w != inst->width || h != inst->height) { + inst->cfg.width = inst->width = w; + inst->cfg.height = inst->height = h; + need_size = 1; + } + + if (inst->pixmap) { + gdk_pixmap_unref(inst->pixmap); + inst->pixmap = NULL; + } + + inst->pixmap = gdk_pixmap_new(widget->window, + (inst->cfg.width * inst->font_width + + 2*inst->cfg.window_border), + (inst->cfg.height * inst->font_height + + 2*inst->cfg.window_border), -1); + + draw_backing_rect(inst); + + if (need_size && inst->term) { + term_size(inst->term, h, w, inst->cfg.savelines); + } + + if (inst->term) + term_invalidate(inst->term); + + return TRUE; +} + +gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + /* + * Pass the exposed rectangle to terminal.c, which will call us + * back to do the actual painting. + */ + if (inst->pixmap) { + gdk_draw_pixmap(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + inst->pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + } + return TRUE; +} + +#define KEY_PRESSED(k) \ + (inst->keystate[(k) / 32] & (1 << ((k) % 32))) + +gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + char output[256]; + wchar_t ucsoutput[2]; + int ucsval, start, end, special, output_charset, use_ucsoutput; + + /* Remember the timestamp. */ + inst->input_event_time = event->time; + + /* By default, nothing is generated. */ + end = start = 0; + special = use_ucsoutput = FALSE; + output_charset = CS_ISO8859_1; + + /* + * If Alt is being released after typing an Alt+numberpad + * sequence, we should generate the code that was typed. + * + * Note that we only do this if more than one key was actually + * pressed - I don't think Alt+NumPad4 should be ^D or that + * Alt+NumPad3 should be ^C, for example. There's no serious + * inconvenience in having to type a zero before a single-digit + * character code. + */ + if (event->type == GDK_KEY_RELEASE && + (event->keyval == GDK_Meta_L || event->keyval == GDK_Alt_L || + event->keyval == GDK_Meta_R || event->keyval == GDK_Alt_R) && + inst->alt_keycode >= 0 && inst->alt_digits > 1) { +#ifdef KEY_DEBUGGING + printf("Alt key up, keycode = %d\n", inst->alt_keycode); +#endif + /* + * FIXME: we might usefully try to do something clever here + * about interpreting the generated key code in a way that's + * appropriate to the line code page. + */ + output[0] = inst->alt_keycode; + end = 1; + goto done; + } + + if (event->type == GDK_KEY_PRESS) { +#ifdef KEY_DEBUGGING + { + int i; + printf("keypress: keyval = %04x, state = %08x; string =", + event->keyval, event->state); + for (i = 0; event->string[i]; i++) + printf(" %02x", (unsigned char) event->string[i]); + printf("\n"); + } +#endif + + /* + * NYI: Compose key (!!! requires Unicode faff before even trying) + */ + + /* + * If Alt has just been pressed, we start potentially + * accumulating an Alt+numberpad code. We do this by + * setting alt_keycode to -1 (nothing yet but plausible). + */ + if ((event->keyval == GDK_Meta_L || event->keyval == GDK_Alt_L || + event->keyval == GDK_Meta_R || event->keyval == GDK_Alt_R)) { + inst->alt_keycode = -1; + inst->alt_digits = 0; + goto done; /* this generates nothing else */ + } + + /* + * If we're seeing a numberpad key press with Mod1 down, + * consider adding it to alt_keycode if that's sensible. + * Anything _else_ with Mod1 down cancels any possibility + * of an ALT keycode: we set alt_keycode to -2. + */ + if ((event->state & GDK_MOD1_MASK) && inst->alt_keycode != -2) { + int digit = -1; + switch (event->keyval) { + case GDK_KP_0: case GDK_KP_Insert: digit = 0; break; + case GDK_KP_1: case GDK_KP_End: digit = 1; break; + case GDK_KP_2: case GDK_KP_Down: digit = 2; break; + case GDK_KP_3: case GDK_KP_Page_Down: digit = 3; break; + case GDK_KP_4: case GDK_KP_Left: digit = 4; break; + case GDK_KP_5: case GDK_KP_Begin: digit = 5; break; + case GDK_KP_6: case GDK_KP_Right: digit = 6; break; + case GDK_KP_7: case GDK_KP_Home: digit = 7; break; + case GDK_KP_8: case GDK_KP_Up: digit = 8; break; + case GDK_KP_9: case GDK_KP_Page_Up: digit = 9; break; + } + if (digit < 0) + inst->alt_keycode = -2; /* it's invalid */ + else { +#ifdef KEY_DEBUGGING + printf("Adding digit %d to keycode %d", digit, + inst->alt_keycode); +#endif + if (inst->alt_keycode == -1) + inst->alt_keycode = digit; /* one-digit code */ + else + inst->alt_keycode = inst->alt_keycode * 10 + digit; + inst->alt_digits++; +#ifdef KEY_DEBUGGING + printf(" gives new code %d\n", inst->alt_keycode); +#endif + /* Having used this digit, we now do nothing more with it. */ + goto done; + } + } + + /* + * Shift-PgUp and Shift-PgDn don't even generate keystrokes + * at all. + */ + if (event->keyval == GDK_Page_Up && (event->state & GDK_SHIFT_MASK)) { + term_scroll(inst->term, 0, -inst->cfg.height/2); + return TRUE; + } + if (event->keyval == GDK_Page_Up && (event->state & GDK_CONTROL_MASK)) { + term_scroll(inst->term, 0, -1); + return TRUE; + } + if (event->keyval == GDK_Page_Down && (event->state & GDK_SHIFT_MASK)) { + term_scroll(inst->term, 0, +inst->cfg.height/2); + return TRUE; + } + if (event->keyval == GDK_Page_Down && (event->state & GDK_CONTROL_MASK)) { + term_scroll(inst->term, 0, +1); + return TRUE; + } + + /* + * Neither does Shift-Ins. + */ + if (event->keyval == GDK_Insert && (event->state & GDK_SHIFT_MASK)) { + request_paste(inst); + return TRUE; + } + + special = FALSE; + use_ucsoutput = FALSE; + + /* 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 + /* + * GDK 2.0 arranges to have done some translation for us: in + * GDK 2.0, event->string is encoded in the current locale. + * + * (However, it's also deprecated; we really ought to be + * using a GTKIMContext.) + * + * 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], *wp; + int wlen; + int ulen; + + wlen = mb_to_wc(DEFAULT_CODEPAGE, 0, + event->string, strlen(event->string), + widedata, lenof(widedata)-1); + + wp = widedata; + ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2, + CS_UTF8, NULL, NULL, 0); + output[1+ulen] = '\0'; + } +#endif + + if (!output[1] && + (ucsval = keysym_to_unicode(event->keyval)) >= 0) { + ucsoutput[0] = '\033'; + ucsoutput[1] = ucsval; + use_ucsoutput = TRUE; + end = 2; + } else { + output[lenof(output)-1] = '\0'; + end = strlen(output); + } + if (event->state & GDK_MOD1_MASK) { + start = 0; + if (end == 1) end = 0; + } else + start = 1; + + /* Control-` is the same as Control-\ (unless gtk has a better idea) */ + if (!output[1] && event->keyval == '`' && + (event->state & GDK_CONTROL_MASK)) { + output[1] = '\x1C'; + use_ucsoutput = FALSE; + end = 2; + } + + /* Control-Break sends a Break special to the backend */ + if (event->keyval == GDK_Break && + (event->state & GDK_CONTROL_MASK)) { + if (inst->back) + inst->back->special(inst->backhandle, TS_BRK); + return TRUE; + } + + /* We handle Return ourselves, because it needs to be flagged as + * special to ldisc. */ + if (event->keyval == GDK_Return) { + output[1] = '\015'; + use_ucsoutput = FALSE; + end = 2; + special = TRUE; + } + + /* Control-2, Control-Space and Control-@ are NUL */ + if (!output[1] && + (event->keyval == ' ' || event->keyval == '2' || + event->keyval == '@') && + (event->state & (GDK_SHIFT_MASK | + GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) { + output[1] = '\0'; + use_ucsoutput = FALSE; + end = 2; + } + + /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */ + if (!output[1] && event->keyval == ' ' && + (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == + (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) { + output[1] = '\240'; + output_charset = CS_ISO8859_1; + use_ucsoutput = FALSE; + end = 2; + } + + /* We don't let GTK tell us what Backspace is! We know better. */ + if (event->keyval == GDK_BackSpace && + !(event->state & GDK_SHIFT_MASK)) { + output[1] = inst->cfg.bksp_is_delete ? '\x7F' : '\x08'; + use_ucsoutput = FALSE; + end = 2; + special = TRUE; + } + /* For Shift Backspace, do opposite of what is configured. */ + if (event->keyval == GDK_BackSpace && + (event->state & GDK_SHIFT_MASK)) { + output[1] = inst->cfg.bksp_is_delete ? '\x08' : '\x7F'; + use_ucsoutput = FALSE; + end = 2; + special = TRUE; + } + + /* Shift-Tab is ESC [ Z */ + if (event->keyval == GDK_ISO_Left_Tab || + (event->keyval == GDK_Tab && (event->state & GDK_SHIFT_MASK))) { + end = 1 + sprintf(output+1, "\033[Z"); + use_ucsoutput = FALSE; + } + /* And normal Tab is Tab, if the keymap hasn't already told us. + * (Curiously, at least one version of the MacOS 10.5 X server + * doesn't translate Tab for us. */ + if (event->keyval == GDK_Tab && end <= 1) { + output[1] = '\t'; + end = 2; + } + + /* + * NetHack keypad mode. + */ + if (inst->cfg.nethack_keypad) { + char *keys = NULL; + switch (event->keyval) { + case GDK_KP_1: case GDK_KP_End: keys = "bB\002"; break; + case GDK_KP_2: case GDK_KP_Down: keys = "jJ\012"; break; + case GDK_KP_3: case GDK_KP_Page_Down: keys = "nN\016"; break; + case GDK_KP_4: case GDK_KP_Left: keys = "hH\010"; break; + case GDK_KP_5: case GDK_KP_Begin: keys = "..."; break; + case GDK_KP_6: case GDK_KP_Right: keys = "lL\014"; break; + case GDK_KP_7: case GDK_KP_Home: keys = "yY\031"; break; + case GDK_KP_8: case GDK_KP_Up: keys = "kK\013"; break; + case GDK_KP_9: case GDK_KP_Page_Up: keys = "uU\025"; break; + } + if (keys) { + end = 2; + if (event->state & GDK_CONTROL_MASK) + output[1] = keys[2]; + else if (event->state & GDK_SHIFT_MASK) + output[1] = keys[1]; + else + output[1] = keys[0]; + use_ucsoutput = FALSE; + goto done; + } + } + + /* + * Application keypad mode. + */ + if (inst->term->app_keypad_keys && !inst->cfg.no_applic_k) { + int xkey = 0; + switch (event->keyval) { + case GDK_Num_Lock: xkey = 'P'; break; + case GDK_KP_Divide: xkey = 'Q'; break; + case GDK_KP_Multiply: xkey = 'R'; break; + case GDK_KP_Subtract: xkey = 'S'; break; + /* + * Keypad + is tricky. It covers a space that would + * be taken up on the VT100 by _two_ keys; so we + * let Shift select between the two. Worse still, + * in xterm function key mode we change which two... + */ + case GDK_KP_Add: + if (inst->cfg.funky_type == FUNKY_XTERM) { + if (event->state & GDK_SHIFT_MASK) + xkey = 'l'; + else + xkey = 'k'; + } else if (event->state & GDK_SHIFT_MASK) + xkey = 'm'; + else + xkey = 'l'; + break; + case GDK_KP_Enter: xkey = 'M'; break; + case GDK_KP_0: case GDK_KP_Insert: xkey = 'p'; break; + case GDK_KP_1: case GDK_KP_End: xkey = 'q'; break; + case GDK_KP_2: case GDK_KP_Down: xkey = 'r'; break; + case GDK_KP_3: case GDK_KP_Page_Down: xkey = 's'; break; + case GDK_KP_4: case GDK_KP_Left: xkey = 't'; break; + case GDK_KP_5: case GDK_KP_Begin: xkey = 'u'; break; + case GDK_KP_6: case GDK_KP_Right: xkey = 'v'; break; + case GDK_KP_7: case GDK_KP_Home: xkey = 'w'; break; + case GDK_KP_8: case GDK_KP_Up: xkey = 'x'; break; + case GDK_KP_9: case GDK_KP_Page_Up: xkey = 'y'; break; + case GDK_KP_Decimal: case GDK_KP_Delete: xkey = 'n'; break; + } + if (xkey) { + if (inst->term->vt52_mode) { + if (xkey >= 'P' && xkey <= 'S') + end = 1 + sprintf(output+1, "\033%c", xkey); + else + end = 1 + sprintf(output+1, "\033?%c", xkey); + } else + end = 1 + sprintf(output+1, "\033O%c", xkey); + use_ucsoutput = FALSE; + goto done; + } + } + + /* + * Next, all the keys that do tilde codes. (ESC '[' nn '~', + * for integer decimal nn.) + * + * We also deal with the weird ones here. Linux VCs replace F1 + * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but + * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w + * respectively. + */ + { + int code = 0; + switch (event->keyval) { + case GDK_F1: + code = (event->state & GDK_SHIFT_MASK ? 23 : 11); + break; + case GDK_F2: + code = (event->state & GDK_SHIFT_MASK ? 24 : 12); + break; + case GDK_F3: + code = (event->state & GDK_SHIFT_MASK ? 25 : 13); + break; + case GDK_F4: + code = (event->state & GDK_SHIFT_MASK ? 26 : 14); + break; + case GDK_F5: + code = (event->state & GDK_SHIFT_MASK ? 28 : 15); + break; + case GDK_F6: + code = (event->state & GDK_SHIFT_MASK ? 29 : 17); + break; + case GDK_F7: + code = (event->state & GDK_SHIFT_MASK ? 31 : 18); + break; + case GDK_F8: + code = (event->state & GDK_SHIFT_MASK ? 32 : 19); + break; + case GDK_F9: + code = (event->state & GDK_SHIFT_MASK ? 33 : 20); + break; + case GDK_F10: + code = (event->state & GDK_SHIFT_MASK ? 34 : 21); + break; + case GDK_F11: + code = 23; + break; + case GDK_F12: + code = 24; + break; + case GDK_F13: + code = 25; + break; + case GDK_F14: + code = 26; + break; + case GDK_F15: + code = 28; + break; + case GDK_F16: + code = 29; + break; + case GDK_F17: + code = 31; + break; + case GDK_F18: + code = 32; + break; + case GDK_F19: + code = 33; + break; + case GDK_F20: + code = 34; + break; + } + if (!(event->state & GDK_CONTROL_MASK)) switch (event->keyval) { + case GDK_Home: case GDK_KP_Home: + code = 1; + break; + case GDK_Insert: case GDK_KP_Insert: + code = 2; + break; + case GDK_Delete: case GDK_KP_Delete: + code = 3; + break; + case GDK_End: case GDK_KP_End: + code = 4; + break; + case GDK_Page_Up: case GDK_KP_Page_Up: + code = 5; + break; + case GDK_Page_Down: case GDK_KP_Page_Down: + code = 6; + break; + } + /* Reorder edit keys to physical order */ + if (inst->cfg.funky_type == FUNKY_VT400 && code <= 6) + code = "\0\2\1\4\5\3\6"[code]; + + if (inst->term->vt52_mode && code > 0 && code <= 6) { + end = 1 + sprintf(output+1, "\x1B%c", " HLMEIG"[code]); + use_ucsoutput = FALSE; + goto done; + } + + if (inst->cfg.funky_type == FUNKY_SCO && /* SCO function keys */ + code >= 11 && code <= 34) { + char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{"; + int index = 0; + switch (event->keyval) { + case GDK_F1: index = 0; break; + case GDK_F2: index = 1; break; + case GDK_F3: index = 2; break; + case GDK_F4: index = 3; break; + case GDK_F5: index = 4; break; + case GDK_F6: index = 5; break; + case GDK_F7: index = 6; break; + case GDK_F8: index = 7; break; + case GDK_F9: index = 8; break; + case GDK_F10: index = 9; break; + case GDK_F11: index = 10; break; + case GDK_F12: index = 11; break; + } + if (event->state & GDK_SHIFT_MASK) index += 12; + if (event->state & GDK_CONTROL_MASK) index += 24; + end = 1 + sprintf(output+1, "\x1B[%c", codes[index]); + use_ucsoutput = FALSE; + goto done; + } + if (inst->cfg.funky_type == FUNKY_SCO && /* SCO small keypad */ + code >= 1 && code <= 6) { + char codes[] = "HL.FIG"; + if (code == 3) { + output[1] = '\x7F'; + end = 2; + } else { + end = 1 + sprintf(output+1, "\x1B[%c", codes[code-1]); + } + use_ucsoutput = FALSE; + goto done; + } + if ((inst->term->vt52_mode || inst->cfg.funky_type == FUNKY_VT100P) && + code >= 11 && code <= 24) { + int offt = 0; + if (code > 15) + offt++; + if (code > 21) + offt++; + if (inst->term->vt52_mode) + end = 1 + sprintf(output+1, + "\x1B%c", code + 'P' - 11 - offt); + else + end = 1 + sprintf(output+1, + "\x1BO%c", code + 'P' - 11 - offt); + use_ucsoutput = FALSE; + goto done; + } + if (inst->cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { + end = 1 + sprintf(output+1, "\x1B[[%c", code + 'A' - 11); + use_ucsoutput = FALSE; + goto done; + } + if (inst->cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { + if (inst->term->vt52_mode) + end = 1 + sprintf(output+1, "\x1B%c", code + 'P' - 11); + else + end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11); + use_ucsoutput = FALSE; + goto done; + } + if (inst->cfg.rxvt_homeend && (code == 1 || code == 4)) { + end = 1 + sprintf(output+1, code == 1 ? "\x1B[H" : "\x1BOw"); + use_ucsoutput = FALSE; + goto done; + } + if (code) { + end = 1 + sprintf(output+1, "\x1B[%d~", code); + use_ucsoutput = FALSE; + goto done; + } + } + + /* + * Cursor keys. (This includes the numberpad cursor keys, + * if we haven't already done them due to app keypad mode.) + * + * Here we also process un-numlocked un-appkeypadded KP5, + * which sends ESC [ G. + */ + { + int xkey = 0; + switch (event->keyval) { + case GDK_Up: case GDK_KP_Up: xkey = 'A'; break; + case GDK_Down: case GDK_KP_Down: xkey = 'B'; break; + case GDK_Right: case GDK_KP_Right: xkey = 'C'; break; + case GDK_Left: case GDK_KP_Left: xkey = 'D'; break; + case GDK_Begin: case GDK_KP_Begin: xkey = 'G'; break; + } + if (xkey) { + end = 1 + format_arrow_key(output+1, inst->term, xkey, + event->state & GDK_CONTROL_MASK); + use_ucsoutput = FALSE; + goto done; + } + } + goto done; + } + + done: + + if (end-start > 0) { +#ifdef KEY_DEBUGGING + int i; + printf("generating sequence:"); + for (i = start; i < end; i++) + printf(" %02x", (unsigned char) output[i]); + printf("\n"); +#endif + + if (special) { + /* + * For special control characters, the character set + * should never matter. + */ + output[end] = '\0'; /* NUL-terminate */ + if (inst->ldisc) + ldisc_send(inst->ldisc, output+start, -2, 1); + } else if (!inst->direct_to_font) { + if (!use_ucsoutput) { + if (inst->ldisc) + lpage_send(inst->ldisc, output_charset, output+start, + end-start, 1); + } else { + /* + * We generated our own Unicode key data from the + * keysym, so use that instead. + */ + if (inst->ldisc) + luni_send(inst->ldisc, ucsoutput+start, end-start, 1); + } + } else { + /* + * In direct-to-font mode, we just send the string + * exactly as we received it. + */ + if (inst->ldisc) + ldisc_send(inst->ldisc, output+start, end-start, 1); + } + + show_mouseptr(inst, 0); + term_seen_key_event(inst->term); + } + + return TRUE; +} + +gboolean button_internal(struct gui_data *inst, guint32 timestamp, + GdkEventType type, guint ebutton, guint state, + gdouble ex, gdouble ey) +{ + int shift, ctrl, alt, x, y, button, act; + + /* Remember the timestamp. */ + inst->input_event_time = timestamp; + + show_mouseptr(inst, 1); + + if (ebutton == 4 && type == GDK_BUTTON_PRESS) { + term_scroll(inst->term, 0, -5); + return TRUE; + } + if (ebutton == 5 && type == GDK_BUTTON_PRESS) { + term_scroll(inst->term, 0, +5); + return TRUE; + } + + shift = state & GDK_SHIFT_MASK; + ctrl = state & GDK_CONTROL_MASK; + alt = state & GDK_MOD1_MASK; + + if (ebutton == 3 && ctrl) { + gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL, + ebutton, timestamp); + return TRUE; + } + + if (ebutton == 1) + button = MBT_LEFT; + else if (ebutton == 2) + button = MBT_MIDDLE; + else if (ebutton == 3) + button = MBT_RIGHT; + else + return FALSE; /* don't even know what button! */ + + switch (type) { + case GDK_BUTTON_PRESS: act = MA_CLICK; break; + case GDK_BUTTON_RELEASE: act = MA_RELEASE; break; + case GDK_2BUTTON_PRESS: act = MA_2CLK; break; + case GDK_3BUTTON_PRESS: act = MA_3CLK; break; + default: return FALSE; /* don't know this event type */ + } + + if (send_raw_mouse && !(inst->cfg.mouse_override && shift) && + act != MA_CLICK && act != MA_RELEASE) + return TRUE; /* we ignore these in raw mouse mode */ + + x = (ex - inst->cfg.window_border) / inst->font_width; + y = (ey - inst->cfg.window_border) / inst->font_height; + + term_mouse(inst->term, button, translate_button(button), act, + x, y, shift, ctrl, alt); + + return TRUE; +} + +gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + return button_internal(inst, event->time, event->type, event->button, + event->state, event->x, event->y); +} + +#if GTK_CHECK_VERSION(2,0,0) +/* + * In GTK 2, mouse wheel events have become a new type of event. + * This handler translates them back into button-4 and button-5 + * presses so that I don't have to change my old code too much :-) + */ +gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + guint button; + + if (event->direction == GDK_SCROLL_UP) + button = 4; + else if (event->direction == GDK_SCROLL_DOWN) + button = 5; + else + return FALSE; + + return button_internal(inst, event->time, GDK_BUTTON_PRESS, + button, event->state, event->x, event->y); +} +#endif + +gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + int shift, ctrl, alt, x, y, button; + + /* Remember the timestamp. */ + inst->input_event_time = event->time; + + show_mouseptr(inst, 1); + + shift = event->state & GDK_SHIFT_MASK; + ctrl = event->state & GDK_CONTROL_MASK; + alt = event->state & GDK_MOD1_MASK; + if (event->state & GDK_BUTTON1_MASK) + button = MBT_LEFT; + else if (event->state & GDK_BUTTON2_MASK) + button = MBT_MIDDLE; + else if (event->state & GDK_BUTTON3_MASK) + button = MBT_RIGHT; + else + return FALSE; /* don't even know what button! */ + + x = (event->x - inst->cfg.window_border) / inst->font_width; + y = (event->y - inst->cfg.window_border) / inst->font_height; + + term_mouse(inst->term, button, translate_button(button), MA_DRAG, + x, y, shift, ctrl, alt); + + return TRUE; +} + +void frontend_keypress(void *handle) +{ + struct gui_data *inst = (struct gui_data *)handle; + + /* + * If our child process has exited but not closed, terminate on + * any keypress. + */ + if (inst->exited) + exit(0); +} + +static gint idle_exit_func(gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + int exitcode; + + if (!inst->exited && + (exitcode = inst->back->exitcode(inst->backhandle)) >= 0) { + inst->exited = TRUE; + if (inst->cfg.close_on_exit == FORCE_ON || + (inst->cfg.close_on_exit == AUTO && exitcode == 0)) + gtk_main_quit(); /* just go */ + if (inst->ldisc) { + ldisc_free(inst->ldisc); + inst->ldisc = NULL; + } + if (inst->back) { + inst->back->free(inst->backhandle); + inst->backhandle = NULL; + inst->back = NULL; + term_provide_resize_fn(inst->term, NULL, NULL); + update_specials_menu(inst); + } + gtk_widget_set_sensitive(inst->restartitem, TRUE); + } + + gtk_idle_remove(inst->term_exit_idle_id); + return TRUE; +} + +void notify_remote_exit(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + inst->term_exit_idle_id = gtk_idle_add(idle_exit_func, inst); +} + +static gint timer_trigger(gpointer data) +{ + long now = GPOINTER_TO_LONG(data); + long next; + long ticks; + + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + timer_id = gtk_timeout_add(ticks > 0 ? ticks : 1, timer_trigger, + LONG_TO_GPOINTER(next)); + } + + /* + * Never let a timer resume. If we need another one, we've + * asked for it explicitly above. + */ + return FALSE; +} + +void timer_change_notify(long next) +{ + long ticks; + + if (timer_id) + gtk_timeout_remove(timer_id); + + ticks = next - GETTICKCOUNT(); + if (ticks <= 0) + ticks = 1; /* just in case */ + + timer_id = gtk_timeout_add(ticks, timer_trigger, + LONG_TO_GPOINTER(next)); +} + +void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) +{ + /* + * We must process exceptional notifications before ordinary + * readability ones, or we may go straight past the urgent + * marker. + */ + if (condition & GDK_INPUT_EXCEPTION) + select_result(sourcefd, 4); + if (condition & GDK_INPUT_READ) + select_result(sourcefd, 1); + if (condition & GDK_INPUT_WRITE) + select_result(sourcefd, 2); +} + +void destroy(GtkWidget *widget, gpointer data) +{ + gtk_main_quit(); +} + +gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + term_set_focus(inst->term, event->in); + term_update(inst->term); + show_mouseptr(inst, 1); + return FALSE; +} + +void set_busy_status(void *frontend, int status) +{ + struct gui_data *inst = (struct gui_data *)frontend; + inst->busy_status = status; + update_mouseptr(inst); +} + +/* + * set or clear the "raw mouse message" mode + */ +void set_raw_mouse_mode(void *frontend, int activate) +{ + struct gui_data *inst = (struct gui_data *)frontend; + activate = activate && !inst->cfg.no_mouse_rep; + send_raw_mouse = activate; + update_mouseptr(inst); +} + +void request_resize(void *frontend, int w, int h) +{ + struct gui_data *inst = (struct gui_data *)frontend; + int large_x, large_y; + int offset_x, offset_y; + int area_x, area_y; + GtkRequisition inner, outer; + + /* + * This is a heinous hack dreamed up by the gnome-terminal + * people to get around a limitation in gtk. The problem is + * that in order to set the size correctly we really need to be + * calling gtk_window_resize - but that needs to know the size + * of the _whole window_, not the drawing area. So what we do + * is to set an artificially huge size request on the drawing + * area, recompute the resulting size request on the window, + * and look at the difference between the two. That gives us + * the x and y offsets we need to translate drawing area size + * into window size for real, and then we call + * gtk_window_resize. + */ + + /* + * We start by retrieving the current size of the whole window. + * Adding a bit to _that_ will give us a value we can use as a + * bogus size request which guarantees to be bigger than the + * current size of the drawing area. + */ + get_window_pixels(inst, &large_x, &large_y); + large_x += 32; + large_y += 32; + +#if GTK_CHECK_VERSION(2,0,0) + gtk_widget_set_size_request(inst->area, large_x, large_y); +#else + gtk_widget_set_usize(inst->area, large_x, large_y); +#endif + gtk_widget_size_request(inst->area, &inner); + gtk_widget_size_request(inst->window, &outer); + + offset_x = outer.width - inner.width; + offset_y = outer.height - inner.height; + + area_x = inst->font_width * w + 2*inst->cfg.window_border; + area_y = inst->font_height * h + 2*inst->cfg.window_border; + + /* + * Now we must set the size request on the drawing area back to + * something sensible before we commit the real resize. Best + * way to do this, I think, is to set it to what the size is + * really going to end up being. + */ +#if GTK_CHECK_VERSION(2,0,0) + gtk_widget_set_size_request(inst->area, area_x, area_y); + gtk_window_resize(GTK_WINDOW(inst->window), + area_x + offset_x, area_y + offset_y); +#else + gtk_widget_set_usize(inst->area, area_x, area_y); + gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y); + /* + * I can no longer remember what this call to + * gtk_container_dequeue_resize_handler is for. It was + * introduced in r3092 with no comment, and the commit log + * message was uninformative. I'm _guessing_ its purpose is to + * prevent gratuitous resize processing on the window given + * that we're about to resize it anyway, but I have no idea + * why that's so incredibly vital. + * + * I've tried removing the call, and nothing seems to go + * wrong. I've backtracked to r3092 and tried removing the + * call there, and still nothing goes wrong. So I'm going to + * adopt the working hypothesis that it's superfluous; I won't + * actually remove it from the GTK 1.2 code, but I won't + * attempt to replicate its functionality in the GTK 2 code + * above. + */ + gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window)); + gdk_window_resize(inst->window->window, + area_x + offset_x, area_y + offset_y); +#endif +} + +static void real_palette_set(struct gui_data *inst, int n, int r, int g, int b) +{ + gboolean success[1]; + + inst->cols[n].red = r * 0x0101; + inst->cols[n].green = g * 0x0101; + inst->cols[n].blue = b * 0x0101; + + gdk_colormap_free_colors(inst->colmap, inst->cols + n, 1); + gdk_colormap_alloc_colors(inst->colmap, inst->cols + n, 1, + FALSE, TRUE, success); + if (!success[0]) + g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", appname, + n, r, g, b); +} + +void set_window_background(struct gui_data *inst) +{ + if (inst->area && inst->area->window) + gdk_window_set_background(inst->area->window, &inst->cols[258]); + if (inst->window && inst->window->window) + gdk_window_set_background(inst->window->window, &inst->cols[258]); +} + +void palette_set(void *frontend, int n, int r, int g, int b) +{ + struct gui_data *inst = (struct gui_data *)frontend; + if (n >= 16) + n += 256 - 16; + if (n > NALLCOLOURS) + return; + real_palette_set(inst, n, r, g, b); + if (n == 258) { + /* Default Background changed. Ensure space between text area and + * window border is redrawn */ + set_window_background(inst); + draw_backing_rect(inst); + gtk_widget_queue_draw(inst->area); + } +} + +void palette_reset(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + /* This maps colour indices in inst->cfg to those used in inst->cols. */ + static const int ww[] = { + 256, 257, 258, 259, 260, 261, + 0, 8, 1, 9, 2, 10, 3, 11, + 4, 12, 5, 13, 6, 14, 7, 15 + }; + gboolean success[NALLCOLOURS]; + int i; + + assert(lenof(ww) == NCFGCOLOURS); + + if (!inst->colmap) { + inst->colmap = gdk_colormap_get_system(); + } else { + gdk_colormap_free_colors(inst->colmap, inst->cols, NALLCOLOURS); + } + + for (i = 0; i < NCFGCOLOURS; i++) { + inst->cols[ww[i]].red = inst->cfg.colours[i][0] * 0x0101; + inst->cols[ww[i]].green = inst->cfg.colours[i][1] * 0x0101; + inst->cols[ww[i]].blue = inst->cfg.colours[i][2] * 0x0101; + } + + for (i = 0; i < NEXTCOLOURS; i++) { + if (i < 216) { + int r = i / 36, g = (i / 6) % 6, b = i % 6; + inst->cols[i+16].red = r ? r * 0x2828 + 0x3737 : 0; + inst->cols[i+16].green = g ? g * 0x2828 + 0x3737 : 0; + inst->cols[i+16].blue = b ? b * 0x2828 + 0x3737 : 0; + } else { + int shade = i - 216; + shade = shade * 0x0a0a + 0x0808; + inst->cols[i+16].red = inst->cols[i+16].green = + inst->cols[i+16].blue = shade; + } + } + + gdk_colormap_alloc_colors(inst->colmap, inst->cols, NALLCOLOURS, + FALSE, TRUE, success); + for (i = 0; i < NALLCOLOURS; i++) { + if (!success[i]) + g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", + appname, i, inst->cfg.colours[i][0], + inst->cfg.colours[i][1], inst->cfg.colours[i][2]); + } + + /* Since Default Background may have changed, ensure that space + * between text area and window border is refreshed. */ + set_window_background(inst); + if (inst->area && inst->area->window) { + draw_backing_rect(inst); + gtk_widget_queue_draw(inst->area); + } +} + +/* Ensure that all the cut buffers exist - according to the ICCCM, we must + * do this before we start using cut buffers. + */ +void init_cutbuffers() +{ + unsigned char empty[] = ""; + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), + XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0); +} + +/* Store the data in a cut-buffer. */ +void store_cutbuffer(char * ptr, int len) +{ + /* ICCCM says we must rotate the buffers before storing to buffer 0. */ + XRotateBuffers(GDK_DISPLAY(), 1); + XStoreBytes(GDK_DISPLAY(), ptr, len); +} + +/* Retrieve data from a cut-buffer. + * Returned data needs to be freed with XFree(). + */ +char * retrieve_cutbuffer(int * nbytes) +{ + char * ptr; + ptr = XFetchBytes(GDK_DISPLAY(), nbytes); + if (*nbytes <= 0 && ptr != 0) { + XFree(ptr); + ptr = 0; + } + return ptr; +} + +void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect) +{ + struct gui_data *inst = (struct gui_data *)frontend; + if (inst->pasteout_data) + sfree(inst->pasteout_data); + if (inst->pasteout_data_ctext) + sfree(inst->pasteout_data_ctext); + if (inst->pasteout_data_utf8) + sfree(inst->pasteout_data_utf8); + + /* + * Set up UTF-8 and compound text paste data. This only happens + * if we aren't in direct-to-font mode using the D800 hack. + */ + if (!inst->direct_to_font) { + wchar_t *tmp = data; + int tmplen = len; + XTextProperty tp; + char *list[1]; + + inst->pasteout_data_utf8 = snewn(len*6, char); + inst->pasteout_data_utf8_len = len*6; + inst->pasteout_data_utf8_len = + charset_from_unicode(&tmp, &tmplen, inst->pasteout_data_utf8, + inst->pasteout_data_utf8_len, + CS_UTF8, NULL, NULL, 0); + if (inst->pasteout_data_utf8_len == 0) { + sfree(inst->pasteout_data_utf8); + inst->pasteout_data_utf8 = NULL; + } else { + inst->pasteout_data_utf8 = + sresize(inst->pasteout_data_utf8, + inst->pasteout_data_utf8_len + 1, char); + inst->pasteout_data_utf8[inst->pasteout_data_utf8_len] = '\0'; + } + + /* + * Now let Xlib convert our UTF-8 data into compound text. + */ + list[0] = inst->pasteout_data_utf8; + if (Xutf8TextListToTextProperty(GDK_DISPLAY(), list, 1, + XCompoundTextStyle, &tp) == 0) { + inst->pasteout_data_ctext = snewn(tp.nitems+1, char); + memcpy(inst->pasteout_data_ctext, tp.value, tp.nitems); + inst->pasteout_data_ctext_len = tp.nitems; + XFree(tp.value); + } else { + inst->pasteout_data_ctext = NULL; + inst->pasteout_data_ctext_len = 0; + } + } else { + inst->pasteout_data_utf8 = NULL; + inst->pasteout_data_utf8_len = 0; + inst->pasteout_data_ctext = NULL; + inst->pasteout_data_ctext_len = 0; + } + + inst->pasteout_data = snewn(len*6, char); + inst->pasteout_data_len = len*6; + inst->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, + data, len, inst->pasteout_data, + inst->pasteout_data_len, + NULL, NULL, NULL); + if (inst->pasteout_data_len == 0) { + sfree(inst->pasteout_data); + inst->pasteout_data = NULL; + } else { + inst->pasteout_data = + sresize(inst->pasteout_data, inst->pasteout_data_len, char); + } + + store_cutbuffer(inst->pasteout_data, inst->pasteout_data_len); + + if (gtk_selection_owner_set(inst->area, GDK_SELECTION_PRIMARY, + inst->input_event_time)) { +#if GTK_CHECK_VERSION(2,0,0) + gtk_selection_clear_targets(inst->area, GDK_SELECTION_PRIMARY); +#endif + gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, 1); + if (inst->pasteout_data_ctext) + gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + compound_text_atom, 1); + if (inst->pasteout_data_utf8) + gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + utf8_string_atom, 1); + } + + if (must_deselect) + term_deselect(inst->term); +} + +void selection_get(GtkWidget *widget, GtkSelectionData *seldata, + guint info, guint time_stamp, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + if (seldata->target == utf8_string_atom) + gtk_selection_data_set(seldata, seldata->target, 8, + (unsigned char *)inst->pasteout_data_utf8, + inst->pasteout_data_utf8_len); + else if (seldata->target == compound_text_atom) + gtk_selection_data_set(seldata, seldata->target, 8, + (unsigned char *)inst->pasteout_data_ctext, + inst->pasteout_data_ctext_len); + else + gtk_selection_data_set(seldata, seldata->target, 8, + (unsigned char *)inst->pasteout_data, + inst->pasteout_data_len); +} + +gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, + gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + term_deselect(inst->term); + if (inst->pasteout_data) + sfree(inst->pasteout_data); + if (inst->pasteout_data_ctext) + sfree(inst->pasteout_data_ctext); + if (inst->pasteout_data_utf8) + sfree(inst->pasteout_data_utf8); + inst->pasteout_data = NULL; + inst->pasteout_data_len = 0; + inst->pasteout_data_ctext = NULL; + inst->pasteout_data_ctext_len = 0; + inst->pasteout_data_utf8 = NULL; + inst->pasteout_data_utf8_len = 0; + return TRUE; +} + +void request_paste(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + /* + * In Unix, pasting is asynchronous: all we can do at the + * moment is to call gtk_selection_convert(), and when the data + * comes back _then_ we can call term_do_paste(). + */ + + if (!inst->direct_to_font) { + /* + * First we attempt to retrieve the selection as a UTF-8 + * string (which we will convert to the correct code page + * before sending to the session, of course). If that + * fails, selection_received() will be informed and will + * fall back to an ordinary string. + */ + gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + utf8_string_atom, + inst->input_event_time); + } else { + /* + * If we're in direct-to-font mode, we disable UTF-8 + * pasting, and go straight to ordinary string data. + */ + gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, + inst->input_event_time); + } +} + +gint idle_paste_func(gpointer data); /* forward ref */ + +void selection_received(GtkWidget *widget, GtkSelectionData *seldata, + guint time, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + XTextProperty tp; + char **list; + char *text; + int length, count, ret; + int free_list_required = 0; + int free_required = 0; + int charset; + + if (seldata->target == utf8_string_atom && seldata->length <= 0) { + /* + * Failed to get a UTF-8 selection string. Try compound + * text next. + */ + gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + compound_text_atom, + inst->input_event_time); + return; + } + + if (seldata->target == compound_text_atom && seldata->length <= 0) { + /* + * Failed to get UTF-8 or compound text. Try an ordinary + * string. + */ + gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, + inst->input_event_time); + return; + } + + /* + * If we have data, but it's not of a type we can deal with, + * we have to ignore the data. + */ + if (seldata->length > 0 && + seldata->type != GDK_SELECTION_TYPE_STRING && + seldata->type != compound_text_atom && + seldata->type != utf8_string_atom) + return; + + /* + * If we have no data, try looking in a cut buffer. + */ + if (seldata->length <= 0) { + text = retrieve_cutbuffer(&length); + if (length == 0) + return; + /* Xterm is rumoured to expect Latin-1, though I havn't checked the + * source, so use that as a de-facto standard. */ + charset = CS_ISO8859_1; + free_required = 1; + } else { + /* + * Convert COMPOUND_TEXT into UTF-8. + */ + if (seldata->type == compound_text_atom) { + tp.value = seldata->data; + tp.encoding = (Atom) seldata->type; + tp.format = seldata->format; + tp.nitems = seldata->length; + ret = Xutf8TextPropertyToTextList(GDK_DISPLAY(), &tp, + &list, &count); + if (ret != 0 || count != 1) { + /* + * Compound text failed; fall back to STRING. + */ + gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + GDK_SELECTION_TYPE_STRING, + inst->input_event_time); + return; + } + text = list[0]; + length = strlen(list[0]); + charset = CS_UTF8; + free_list_required = 1; + } else { + text = (char *)seldata->data; + length = seldata->length; + charset = (seldata->type == utf8_string_atom ? + CS_UTF8 : inst->ucsdata.line_codepage); + } + } + + if (inst->pastein_data) + sfree(inst->pastein_data); + + inst->pastein_data = snewn(length, wchar_t); + inst->pastein_data_len = length; + inst->pastein_data_len = + mb_to_wc(charset, 0, text, length, + inst->pastein_data, inst->pastein_data_len); + + term_do_paste(inst->term); + + if (term_paste_pending(inst->term)) + inst->term_paste_idle_id = gtk_idle_add(idle_paste_func, inst); + + if (free_list_required) + XFreeStringList(list); + if (free_required) + XFree(text); +} + +gint idle_paste_func(gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + if (term_paste_pending(inst->term)) + term_paste(inst->term); + else + gtk_idle_remove(inst->term_paste_idle_id); + + return TRUE; +} + + +void get_clip(void *frontend, wchar_t ** p, int *len) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + if (p) { + *p = inst->pastein_data; + *len = inst->pastein_data_len; + } +} + +static void set_window_titles(struct gui_data *inst) +{ + /* + * We must always call set_icon_name after calling set_title, + * since set_title will write both names. Irritating, but such + * is life. + */ + gtk_window_set_title(GTK_WINDOW(inst->window), inst->wintitle); + if (!inst->cfg.win_name_always) + gdk_window_set_icon_name(inst->window->window, inst->icontitle); +} + +void set_title(void *frontend, char *title) +{ + struct gui_data *inst = (struct gui_data *)frontend; + strncpy(inst->wintitle, title, lenof(inst->wintitle)); + inst->wintitle[lenof(inst->wintitle)-1] = '\0'; + set_window_titles(inst); +} + +void set_icon(void *frontend, char *title) +{ + struct gui_data *inst = (struct gui_data *)frontend; + strncpy(inst->icontitle, title, lenof(inst->icontitle)); + inst->icontitle[lenof(inst->icontitle)-1] = '\0'; + set_window_titles(inst); +} + +void set_sbar(void *frontend, int total, int start, int page) +{ + struct gui_data *inst = (struct gui_data *)frontend; + if (!inst->cfg.scrollbar) + return; + inst->sbar_adjust->lower = 0; + inst->sbar_adjust->upper = total; + inst->sbar_adjust->value = start; + inst->sbar_adjust->page_size = page; + inst->sbar_adjust->step_increment = 1; + inst->sbar_adjust->page_increment = page/2; + inst->ignore_sbar = TRUE; + gtk_adjustment_changed(inst->sbar_adjust); + inst->ignore_sbar = FALSE; +} + +void scrollbar_moved(GtkAdjustment *adj, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + if (!inst->cfg.scrollbar) + return; + if (!inst->ignore_sbar) + term_scroll(inst->term, 1, (int)adj->value); +} + +void sys_cursor(void *frontend, int x, int y) +{ + /* + * This is meaningless under X. + */ +} + +/* + * This is still called when mode==BELL_VISUAL, even though the + * visual bell is handled entirely within terminal.c, because we + * may want to perform additional actions on any kind of bell (for + * example, taskbar flashing in Windows). + */ +void do_beep(void *frontend, int mode) +{ + if (mode == BELL_DEFAULT) + gdk_beep(); +} + +int char_width(Context ctx, int uc) +{ + /* + * Under X, any fixed-width font really _is_ fixed-width. + * Double-width characters will be dealt with using a separate + * font. For the moment we can simply return 1. + * + * FIXME: but is that also true of Pango? + */ + return 1; +} + +Context get_ctx(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + struct draw_ctx *dctx; + + if (!inst->area->window) + return NULL; + + dctx = snew(struct draw_ctx); + dctx->inst = inst; + dctx->gc = gdk_gc_new(inst->area->window); + return dctx; +} + +void free_ctx(Context ctx) +{ + struct draw_ctx *dctx = (struct draw_ctx *)ctx; + /* struct gui_data *inst = dctx->inst; */ + GdkGC *gc = dctx->gc; + gdk_gc_unref(gc); + sfree(dctx); +} + +/* + * Draw a line of text in the window, at given character + * coordinates, in given attributes. + * + * We are allowed to fiddle with the contents of `text'. + */ +void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr) +{ + struct draw_ctx *dctx = (struct draw_ctx *)ctx; + struct gui_data *inst = dctx->inst; + GdkGC *gc = dctx->gc; + int ncombining, combining; + int nfg, nbg, t, fontid, shadow, rlen, widefactor, bold; + int monochrome = gtk_widget_get_visual(inst->area)->depth == 1; + + if (attr & TATTR_COMBINING) { + ncombining = len; + len = 1; + } else + ncombining = 1; + + nfg = ((monochrome ? ATTR_DEFFG : (attr & ATTR_FGMASK)) >> ATTR_FGSHIFT); + nbg = ((monochrome ? ATTR_DEFBG : (attr & ATTR_BGMASK)) >> ATTR_BGSHIFT); + if (!!(attr & ATTR_REVERSE) ^ (monochrome && (attr & TATTR_ACTCURS))) { + t = nfg; + nfg = nbg; + nbg = t; + } + if (inst->cfg.bold_colour && (attr & ATTR_BOLD)) { + if (nfg < 16) nfg |= 8; + else if (nfg >= 256) nfg |= 1; + } + if (inst->cfg.bold_colour && (attr & ATTR_BLINK)) { + if (nbg < 16) nbg |= 8; + else if (nbg >= 256) nbg |= 1; + } + if ((attr & TATTR_ACTCURS) && !monochrome) { + nfg = 260; + nbg = 261; + } + + fontid = shadow = 0; + + if (attr & ATTR_WIDE) { + widefactor = 2; + fontid |= 2; + } else { + widefactor = 1; + } + + if ((attr & ATTR_BOLD) && !inst->cfg.bold_colour) { + bold = 1; + fontid |= 1; + } else { + bold = 0; + } + + if (!inst->fonts[fontid]) { + int i; + /* + * Fall back through font ids with subsets of this one's + * set bits, in order. + */ + for (i = fontid; i-- > 0 ;) { + if (i & ~fontid) + continue; /* some other bit is set */ + if (inst->fonts[i]) { + fontid = i; + break; + } + } + assert(inst->fonts[fontid]); /* we should at least have hit zero */ + } + + if ((lattr & LATTR_MODE) != LATTR_NORM) { + x *= 2; + if (x >= inst->term->cols) + return; + if (x + len*2*widefactor > inst->term->cols) + len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */ + rlen = len * 2; + } else + rlen = len; + + { + GdkRectangle r; + + r.x = x*inst->font_width+inst->cfg.window_border; + r.y = y*inst->font_height+inst->cfg.window_border; + r.width = rlen*widefactor*inst->font_width; + r.height = inst->font_height; + gdk_gc_set_clip_rectangle(gc, &r); + } + + gdk_gc_set_foreground(gc, &inst->cols[nbg]); + gdk_draw_rectangle(inst->pixmap, gc, 1, + x*inst->font_width+inst->cfg.window_border, + y*inst->font_height+inst->cfg.window_border, + rlen*widefactor*inst->font_width, inst->font_height); + + gdk_gc_set_foreground(gc, &inst->cols[nfg]); + { + gchar *gcs; + + /* + * FIXME: this length is hardwired on the assumption that + * conversions from wide to multibyte characters will + * never generate more than 10 bytes for a single wide + * character. + */ + gcs = snewn(len*10+1, gchar); + + for (combining = 0; combining < ncombining; combining++) { + int mblen = wc_to_mb(inst->fonts[fontid]->real_charset, 0, + text + combining, len, gcs, len*10+1, ".", + NULL, NULL); + unifont_draw_text(inst->pixmap, gc, inst->fonts[fontid], + x*inst->font_width+inst->cfg.window_border, + y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent, + gcs, mblen, widefactor > 1, bold, inst->font_width); + } + + sfree(gcs); + } + + if (attr & ATTR_UNDER) { + int uheight = inst->fonts[0]->ascent + 1; + if (uheight >= inst->font_height) + uheight = inst->font_height - 1; + gdk_draw_line(inst->pixmap, gc, x*inst->font_width+inst->cfg.window_border, + y*inst->font_height + uheight + inst->cfg.window_border, + (x+len)*widefactor*inst->font_width-1+inst->cfg.window_border, + y*inst->font_height + uheight + inst->cfg.window_border); + } + + if ((lattr & LATTR_MODE) != LATTR_NORM) { + /* + * I can't find any plausible StretchBlt equivalent in the + * X server, so I'm going to do this the slow and painful + * way. This will involve repeated calls to + * gdk_draw_pixmap() to stretch the text horizontally. It's + * O(N^2) in time and O(N) in network bandwidth, but you + * try thinking of a better way. :-( + */ + int i; + for (i = 0; i < len * widefactor * inst->font_width; i++) { + gdk_draw_pixmap(inst->pixmap, gc, inst->pixmap, + x*inst->font_width+inst->cfg.window_border + 2*i, + y*inst->font_height+inst->cfg.window_border, + x*inst->font_width+inst->cfg.window_border + 2*i+1, + y*inst->font_height+inst->cfg.window_border, + len * widefactor * inst->font_width - i, inst->font_height); + } + len *= 2; + if ((lattr & LATTR_MODE) != LATTR_WIDE) { + int dt, db; + /* Now stretch vertically, in the same way. */ + if ((lattr & LATTR_MODE) == LATTR_BOT) + dt = 0, db = 1; + else + dt = 1, db = 0; + for (i = 0; i < inst->font_height; i+=2) { + gdk_draw_pixmap(inst->pixmap, gc, inst->pixmap, + x*inst->font_width+inst->cfg.window_border, + y*inst->font_height+inst->cfg.window_border+dt*i+db, + x*inst->font_width+inst->cfg.window_border, + y*inst->font_height+inst->cfg.window_border+dt*(i+1), + len * widefactor * inst->font_width, inst->font_height-i-1); + } + } + } +} + +void do_text(Context ctx, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr) +{ + struct draw_ctx *dctx = (struct draw_ctx *)ctx; + struct gui_data *inst = dctx->inst; + GdkGC *gc = dctx->gc; + int widefactor; + + do_text_internal(ctx, x, y, text, len, attr, lattr); + + if (attr & ATTR_WIDE) { + widefactor = 2; + } else { + widefactor = 1; + } + + if ((lattr & LATTR_MODE) != LATTR_NORM) { + x *= 2; + if (x >= inst->term->cols) + return; + if (x + len*2*widefactor > inst->term->cols) + len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */ + len *= 2; + } + + gdk_draw_pixmap(inst->area->window, gc, inst->pixmap, + x*inst->font_width+inst->cfg.window_border, + y*inst->font_height+inst->cfg.window_border, + x*inst->font_width+inst->cfg.window_border, + y*inst->font_height+inst->cfg.window_border, + len*widefactor*inst->font_width, inst->font_height); +} + +void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr) +{ + struct draw_ctx *dctx = (struct draw_ctx *)ctx; + struct gui_data *inst = dctx->inst; + GdkGC *gc = dctx->gc; + + int active, passive, widefactor; + + if (attr & TATTR_PASCURS) { + attr &= ~TATTR_PASCURS; + passive = 1; + } else + passive = 0; + if ((attr & TATTR_ACTCURS) && inst->cfg.cursor_type != 0) { + attr &= ~TATTR_ACTCURS; + active = 1; + } else + active = 0; + do_text_internal(ctx, x, y, text, len, attr, lattr); + + if (attr & TATTR_COMBINING) + len = 1; + + if (attr & ATTR_WIDE) { + widefactor = 2; + } else { + widefactor = 1; + } + + if ((lattr & LATTR_MODE) != LATTR_NORM) { + x *= 2; + if (x >= inst->term->cols) + return; + if (x + len*2*widefactor > inst->term->cols) + len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */ + len *= 2; + } + + if (inst->cfg.cursor_type == 0) { + /* + * An active block cursor will already have been done by + * the above do_text call, so we only need to do anything + * if it's passive. + */ + if (passive) { + gdk_gc_set_foreground(gc, &inst->cols[261]); + gdk_draw_rectangle(inst->pixmap, gc, 0, + x*inst->font_width+inst->cfg.window_border, + y*inst->font_height+inst->cfg.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->cfg.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->cfg.window_border; + starty = y * inst->font_height + inst->cfg.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->cfg.window_border + xadjust; + starty = y * inst->font_height + inst->cfg.window_border; + dx = 0; + dy = 1; + length = inst->font_height; + } + + gdk_gc_set_foreground(gc, &inst->cols[261]); + if (passive) { + for (i = 0; i < length; i++) { + if (i % 2 == 0) { + gdk_draw_point(inst->pixmap, gc, startx, starty); + } + startx += dx; + starty += dy; + } + } else if (active) { + gdk_draw_line(inst->pixmap, gc, startx, starty, + startx + (length-1) * dx, starty + (length-1) * dy); + } /* else no cursor (e.g., blinked off) */ + } + + gdk_draw_pixmap(inst->area->window, gc, inst->pixmap, + x*inst->font_width+inst->cfg.window_border, + y*inst->font_height+inst->cfg.window_border, + x*inst->font_width+inst->cfg.window_border, + y*inst->font_height+inst->cfg.window_border, + len*widefactor*inst->font_width, inst->font_height); +} + +GdkCursor *make_mouse_ptr(struct gui_data *inst, int cursor_val) +{ + /* + * Truly hideous hack: GTK doesn't allow us to set the mouse + * cursor foreground and background colours unless we've _also_ + * created our own cursor from bitmaps. Therefore, I need to + * load the `cursor' font and draw glyphs from it on to + * pixmaps, in order to construct my cursors with the fg and bg + * I want. This is a gross hack, but it's more self-contained + * than linking in Xlib to find the X window handle to + * inst->area and calling XRecolorCursor, and it's more + * futureproof than hard-coding the shapes as bitmap arrays. + */ + static GdkFont *cursor_font = NULL; + GdkPixmap *source, *mask; + GdkGC *gc; + GdkColor cfg = { 0, 65535, 65535, 65535 }; + GdkColor cbg = { 0, 0, 0, 0 }; + GdkColor dfg = { 1, 65535, 65535, 65535 }; + GdkColor dbg = { 0, 0, 0, 0 }; + GdkCursor *ret; + gchar text[2]; + gint lb, rb, wid, asc, desc, w, h, x, y; + + if (cursor_val == -2) { + gdk_font_unref(cursor_font); + return NULL; + } + + if (cursor_val >= 0 && !cursor_font) { + cursor_font = gdk_font_load("cursor"); + if (cursor_font) + gdk_font_ref(cursor_font); + } + + /* + * Get the text extent of the cursor in question. We use the + * mask character for this, because it's typically slightly + * bigger than the main character. + */ + if (cursor_val >= 0) { + text[1] = '\0'; + text[0] = (char)cursor_val + 1; + gdk_string_extents(cursor_font, text, &lb, &rb, &wid, &asc, &desc); + w = rb-lb; h = asc+desc; x = -lb; y = asc; + } else { + w = h = 1; + x = y = 0; + } + + source = gdk_pixmap_new(NULL, w, h, 1); + mask = gdk_pixmap_new(NULL, w, h, 1); + + /* + * Draw the mask character on the mask pixmap. + */ + gc = gdk_gc_new(mask); + gdk_gc_set_foreground(gc, &dbg); + gdk_draw_rectangle(mask, gc, 1, 0, 0, w, h); + if (cursor_val >= 0) { + text[1] = '\0'; + text[0] = (char)cursor_val + 1; + gdk_gc_set_foreground(gc, &dfg); + gdk_draw_text(mask, cursor_font, gc, x, y, text, 1); + } + gdk_gc_unref(gc); + + /* + * Draw the main character on the source pixmap. + */ + gc = gdk_gc_new(source); + gdk_gc_set_foreground(gc, &dbg); + gdk_draw_rectangle(source, gc, 1, 0, 0, w, h); + if (cursor_val >= 0) { + text[1] = '\0'; + text[0] = (char)cursor_val; + gdk_gc_set_foreground(gc, &dfg); + gdk_draw_text(source, cursor_font, gc, x, y, text, 1); + } + gdk_gc_unref(gc); + + /* + * Create the cursor. + */ + ret = gdk_cursor_new_from_pixmap(source, mask, &cfg, &cbg, x, y); + + /* + * Clean up. + */ + gdk_pixmap_unref(source); + gdk_pixmap_unref(mask); + + return ret; +} + +void modalfatalbox(char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +void cmdline_error(char *p, ...) +{ + va_list ap; + fprintf(stderr, "%s: ", appname); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +char *get_x_display(void *frontend) +{ + return gdk_get_display(); +} + +long get_windowid(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return (long)GDK_WINDOW_XWINDOW(inst->area->window); +} + +static void help(FILE *fp) { + if(fprintf(fp, +"pterm option summary:\n" +"\n" +" --display DISPLAY Specify X display to use (note '--')\n" +" -name PREFIX Prefix when looking up resources (default: pterm)\n" +" -fn FONT Normal text font\n" +" -fb FONT Bold text font\n" +" -geometry GEOMETRY Position and size of window (size in characters)\n" +" -sl LINES Number of lines of scrollback\n" +" -fg COLOUR, -bg COLOUR Foreground/background colour\n" +" -bfg COLOUR, -bbg COLOUR Foreground/background bold colour\n" +" -cfg COLOUR, -bfg COLOUR Foreground/background cursor colour\n" +" -T TITLE Window title\n" +" -ut, +ut Do(default) or do not update utmp\n" +" -ls, +ls Do(default) or do not make shell a login shell\n" +" -sb, +sb Do(default) or do not display a scrollbar\n" +" -log PATH 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); + } +} + +int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, + struct gui_data *inst, Config *cfg) +{ + int err = 0; + char *val; + + /* + * Macros to make argument handling easier. Note that because + * they need to call `continue', they cannot be contained in + * the usual do {...} while (0) wrapper to make them + * syntactically single statements; hence it is not legal to + * use one of these macros as an unbraced statement between + * `if' and `else'. + */ +#define EXPECTS_ARG { \ + if (--argc <= 0) { \ + err = 1; \ + fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ + continue; \ + } else \ + val = *++argv; \ +} +#define SECOND_PASS_ONLY { if (!do_everything) continue; } + + while (--argc > 0) { + char *p = *++argv; + int ret; + + /* + * Shameless cheating. Debian requires all X terminal + * emulators to support `-T title'; but + * cmdline_process_param will eat -T (it means no-pty) and + * complain that pterm doesn't support it. So, in pterm + * only, we convert -T into -title. + */ + if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) && + !strcmp(p, "-T")) + p = "-title"; + + ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), + do_everything ? 1 : -1, cfg); + + 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")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + strncpy(cfg->font.name, val, sizeof(cfg->font.name)); + cfg->font.name[sizeof(cfg->font.name)-1] = '\0'; + + } else if (!strcmp(p, "-fb")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + strncpy(cfg->boldfont.name, val, sizeof(cfg->boldfont.name)); + cfg->boldfont.name[sizeof(cfg->boldfont.name)-1] = '\0'; + + } else if (!strcmp(p, "-fw")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + strncpy(cfg->widefont.name, val, sizeof(cfg->widefont.name)); + cfg->widefont.name[sizeof(cfg->widefont.name)-1] = '\0'; + + } else if (!strcmp(p, "-fwb")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + strncpy(cfg->wideboldfont.name, val, sizeof(cfg->wideboldfont.name)); + cfg->wideboldfont.name[sizeof(cfg->wideboldfont.name)-1] = '\0'; + + } else if (!strcmp(p, "-cs")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + strncpy(cfg->line_codepage, val, sizeof(cfg->line_codepage)); + cfg->line_codepage[sizeof(cfg->line_codepage)-1] = '\0'; + + } else if (!strcmp(p, "-geometry")) { + int flags, x, y; + unsigned int w, h; + EXPECTS_ARG; + SECOND_PASS_ONLY; + + flags = XParseGeometry(val, &x, &y, &w, &h); + if (flags & WidthValue) + cfg->width = (int)w; + if (flags & HeightValue) + cfg->height = (int)h; + + if (flags & (XValue | YValue)) { + inst->xpos = x; + inst->ypos = y; + inst->gotpos = TRUE; + inst->gravity = ((flags & XNegative ? 1 : 0) | + (flags & YNegative ? 2 : 0)); + } + + } else if (!strcmp(p, "-sl")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + cfg->savelines = atoi(val); + + } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") || + !strcmp(p, "-bfg") || !strcmp(p, "-bbg") || + !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) { + GdkColor col; + + EXPECTS_ARG; + SECOND_PASS_ONLY; + if (!gdk_color_parse(val, &col)) { + err = 1; + fprintf(stderr, "%s: unable to parse colour \"%s\"\n", + appname, val); + } else { + int index; + index = (!strcmp(p, "-fg") ? 0 : + !strcmp(p, "-bg") ? 2 : + !strcmp(p, "-bfg") ? 1 : + !strcmp(p, "-bbg") ? 3 : + !strcmp(p, "-cfg") ? 4 : + !strcmp(p, "-cbg") ? 5 : -1); + assert(index != -1); + cfg->colours[index][0] = col.red / 256; + cfg->colours[index][1] = col.green / 256; + cfg->colours[index][2] = col.blue / 256; + } + + } else if (use_pty_argv && !strcmp(p, "-e")) { + /* This option swallows all further arguments. */ + if (!do_everything) + break; + + if (--argc > 0) { + int i; + pty_argv = snewn(argc+1, char *); + ++argv; + for (i = 0; i < argc; i++) + pty_argv[i] = argv[i]; + pty_argv[argc] = NULL; + break; /* finished command-line processing */ + } else + err = 1, fprintf(stderr, "%s: -e expects an argument\n", + appname); + + } else if (!strcmp(p, "-title")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + strncpy(cfg->wintitle, val, sizeof(cfg->wintitle)); + cfg->wintitle[sizeof(cfg->wintitle)-1] = '\0'; + + } else if (!strcmp(p, "-log")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + strncpy(cfg->logfilename.path, val, sizeof(cfg->logfilename.path)); + cfg->logfilename.path[sizeof(cfg->logfilename.path)-1] = '\0'; + cfg->logtype = LGTYP_DEBUG; + + } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) { + SECOND_PASS_ONLY; + cfg->stamp_utmp = 0; + + } else if (!strcmp(p, "-ut")) { + SECOND_PASS_ONLY; + cfg->stamp_utmp = 1; + + } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) { + SECOND_PASS_ONLY; + cfg->login_shell = 0; + + } else if (!strcmp(p, "-ls")) { + SECOND_PASS_ONLY; + cfg->login_shell = 1; + + } else if (!strcmp(p, "-nethack")) { + SECOND_PASS_ONLY; + cfg->nethack_keypad = 1; + + } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) { + SECOND_PASS_ONLY; + cfg->scrollbar = 0; + + } else if (!strcmp(p, "-sb")) { + SECOND_PASS_ONLY; + cfg->scrollbar = 0; + + } else if (!strcmp(p, "-name")) { + EXPECTS_ARG; + app_name = val; + + } else if (!strcmp(p, "-xrm")) { + EXPECTS_ARG; + provide_xrm_string(val); + + } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) { + help(stdout); + exit(0); + + } else if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints(); + exit(1); + + } else if(p[0] != '-' && (!do_everything || + process_nonoption_arg(p, cfg, + allow_launch))) { + /* do nothing */ + + } else { + err = 1; + fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p); + } + } + + return err; +} + +int uxsel_input_add(int fd, int rwx) { + int flags = 0; + if (rwx & 1) flags |= GDK_INPUT_READ; + if (rwx & 2) flags |= GDK_INPUT_WRITE; + if (rwx & 4) flags |= GDK_INPUT_EXCEPTION; + assert(flags); + return gdk_input_add(fd, flags, fd_input_func, NULL); +} + +void uxsel_input_remove(int id) { + gdk_input_remove(id); +} + +void setup_fonts_ucs(struct gui_data *inst) +{ + if (inst->fonts[0]) + unifont_destroy(inst->fonts[0]); + if (inst->fonts[1]) + unifont_destroy(inst->fonts[1]); + if (inst->fonts[2]) + unifont_destroy(inst->fonts[2]); + if (inst->fonts[3]) + unifont_destroy(inst->fonts[3]); + + inst->fonts[0] = unifont_create(inst->area, inst->cfg.font.name, + FALSE, FALSE, + inst->cfg.shadowboldoffset, + inst->cfg.shadowbold); + if (!inst->fonts[0]) { + fprintf(stderr, "%s: unable to load font \"%s\"\n", appname, + inst->cfg.font.name); + exit(1); + } + + if (inst->cfg.shadowbold || !inst->cfg.boldfont.name[0]) { + inst->fonts[1] = NULL; + } else { + inst->fonts[1] = unifont_create(inst->area, inst->cfg.boldfont.name, + FALSE, TRUE, + inst->cfg.shadowboldoffset, + inst->cfg.shadowbold); + if (!inst->fonts[1]) { + fprintf(stderr, "%s: unable to load bold font \"%s\"\n", appname, + inst->cfg.boldfont.name); + exit(1); + } + } + + if (inst->cfg.widefont.name[0]) { + inst->fonts[2] = unifont_create(inst->area, inst->cfg.widefont.name, + TRUE, FALSE, + inst->cfg.shadowboldoffset, + inst->cfg.shadowbold); + if (!inst->fonts[2]) { + fprintf(stderr, "%s: unable to load wide font \"%s\"\n", appname, + inst->cfg.widefont.name); + exit(1); + } + } else { + inst->fonts[2] = NULL; + } + + if (inst->cfg.shadowbold || !inst->cfg.wideboldfont.name[0]) { + inst->fonts[3] = NULL; + } else { + inst->fonts[3] = unifont_create(inst->area, + inst->cfg.wideboldfont.name, TRUE, + TRUE, inst->cfg.shadowboldoffset, + inst->cfg.shadowbold); + if (!inst->fonts[3]) { + fprintf(stderr, "%s: unable to load wide bold font \"%s\"\n", appname, + inst->cfg.boldfont.name); + exit(1); + } + } + + inst->font_width = inst->fonts[0]->width; + inst->font_height = inst->fonts[0]->height; + + inst->direct_to_font = init_ucs(&inst->ucsdata, inst->cfg.line_codepage, + inst->cfg.utf8_override, + inst->fonts[0]->public_charset, + inst->cfg.vtmode); +} + +void set_geom_hints(struct gui_data *inst) +{ + GdkGeometry geom; + geom.min_width = inst->font_width + 2*inst->cfg.window_border; + geom.min_height = inst->font_height + 2*inst->cfg.window_border; + geom.max_width = geom.max_height = -1; + geom.base_width = 2*inst->cfg.window_border; + geom.base_height = 2*inst->cfg.window_border; + geom.width_inc = inst->font_width; + geom.height_inc = inst->font_height; + geom.min_aspect = geom.max_aspect = 0; + gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), inst->area, &geom, + GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | + GDK_HINT_RESIZE_INC); +} + +void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + term_clrsb(inst->term); +} + +void reset_terminal_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + term_pwron(inst->term, TRUE); + if (inst->ldisc) + ldisc_send(inst->ldisc, NULL, 0, 0); +} + +void copy_all_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + term_copyall(inst->term); +} + +void special_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + int code = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item), + "user-data")); + + if (inst->back) + inst->back->special(inst->backhandle, code); +} + +void about_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + about_box(inst->window); +} + +void event_log_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + showeventlog(inst->eventlogstuff, inst->window); +} + +void change_settings_menuitem(GtkMenuItem *item, gpointer data) +{ + /* This maps colour indices in inst->cfg to those used in inst->cols. */ + static const int ww[] = { + 256, 257, 258, 259, 260, 261, + 0, 8, 1, 9, 2, 10, 3, 11, + 4, 12, 5, 13, 6, 14, 7, 15 + }; + struct gui_data *inst = (struct gui_data *)data; + char *title = dupcat(appname, " Reconfiguration", NULL); + Config cfg2, oldcfg; + int i, need_size; + + assert(lenof(ww) == NCFGCOLOURS); + + if (inst->reconfiguring) + return; + else + inst->reconfiguring = TRUE; + + cfg2 = inst->cfg; /* structure copy */ + + if (do_config_box(title, &cfg2, 1, + inst->back?inst->back->cfg_info(inst->backhandle):0)) { + + oldcfg = inst->cfg; /* structure copy */ + inst->cfg = cfg2; /* structure copy */ + + /* Pass new config data to the logging module */ + log_reconfig(inst->logctx, &cfg2); + /* + * Flush the line discipline's edit buffer in the case + * where local editing has just been disabled. + */ + if (inst->ldisc) + ldisc_send(inst->ldisc, NULL, 0, 0); + /* Pass new config data to the terminal */ + term_reconfig(inst->term, &cfg2); + /* Pass new config data to the back end */ + if (inst->back) + inst->back->reconfig(inst->backhandle, &cfg2); + + /* + * Just setting inst->cfg is sufficient to cause colour + * setting changes to appear on the next ESC]R palette + * reset. But we should also check whether any colour + * settings have been changed, and revert the ones that + * have to the new default, on the assumption that the user + * is most likely to want an immediate update. + */ + for (i = 0; i < NCFGCOLOURS; i++) { + if (oldcfg.colours[i][0] != cfg2.colours[i][0] || + oldcfg.colours[i][1] != cfg2.colours[i][1] || + oldcfg.colours[i][2] != cfg2.colours[i][2]) { + real_palette_set(inst, ww[i], cfg2.colours[i][0], + cfg2.colours[i][1], + cfg2.colours[i][2]); + + /* + * If the default background has changed, we must + * repaint the space in between the window border + * and the text area. + */ + if (i == 258) { + set_window_background(inst); + draw_backing_rect(inst); + } + } + } + + /* + * If the scrollbar needs to be shown, hidden, or moved + * from one end to the other of the window, do so now. + */ + if (oldcfg.scrollbar != cfg2.scrollbar) { + if (cfg2.scrollbar) + gtk_widget_show(inst->sbar); + else + gtk_widget_hide(inst->sbar); + } + if (oldcfg.scrollbar_on_left != cfg2.scrollbar_on_left) { + gtk_box_reorder_child(inst->hbox, inst->sbar, + cfg2.scrollbar_on_left ? 0 : 1); + } + + /* + * Change the window title, if required. + */ + if (strcmp(oldcfg.wintitle, cfg2.wintitle)) + set_title(inst, cfg2.wintitle); + set_window_titles(inst); + + /* + * Redo the whole tangled fonts and Unicode mess if + * necessary. + */ + if (strcmp(oldcfg.font.name, cfg2.font.name) || + strcmp(oldcfg.boldfont.name, cfg2.boldfont.name) || + strcmp(oldcfg.widefont.name, cfg2.widefont.name) || + strcmp(oldcfg.wideboldfont.name, cfg2.wideboldfont.name) || + strcmp(oldcfg.line_codepage, cfg2.line_codepage) || + oldcfg.vtmode != cfg2.vtmode || + oldcfg.shadowbold != cfg2.shadowbold) { + setup_fonts_ucs(inst); + need_size = 1; + } else + need_size = 0; + + /* + * Resize the window. + */ + if (oldcfg.width != cfg2.width || oldcfg.height != cfg2.height || + oldcfg.window_border != cfg2.window_border || need_size) { + set_geom_hints(inst); + request_resize(inst, cfg2.width, cfg2.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 (oldcfg.savelines != cfg2.savelines) + term_size(inst->term, inst->term->rows, inst->term->cols, + cfg2.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); + } + sfree(title); + inst->reconfiguring = FALSE; +} + +void fork_and_exec_self(struct gui_data *inst, int fd_to_close, ...) +{ + /* + * Re-execing ourself is not an exact science under Unix. I do + * the best I can by using /proc/self/exe if available and by + * assuming argv[0] can be found on $PATH if not. + * + * Note that we also have to reconstruct the elements of the + * original argv which gtk swallowed, since the user wants the + * new session to appear on the same X display as the old one. + */ + char **args; + va_list ap; + int i, n; + int pid; + + /* + * Collect the arguments with which to re-exec ourself. + */ + va_start(ap, fd_to_close); + n = 2; /* progname and terminating NULL */ + n += inst->ngtkargs; + while (va_arg(ap, char *) != NULL) + n++; + va_end(ap); + + args = snewn(n, char *); + args[0] = inst->progname; + args[n-1] = NULL; + for (i = 0; i < inst->ngtkargs; i++) + args[i+1] = inst->gtkargvstart[i]; + + i++; + va_start(ap, fd_to_close); + while ((args[i++] = va_arg(ap, char *)) != NULL); + va_end(ap); + + assert(i == n); + + /* + * Do the double fork. + */ + pid = fork(); + if (pid < 0) { + perror("fork"); + return; + } + + if (pid == 0) { + int pid2 = fork(); + if (pid2 < 0) { + perror("fork"); + _exit(1); + } else if (pid2 > 0) { + /* + * First child has successfully forked second child. My + * Work Here Is Done. Note the use of _exit rather than + * exit: the latter appears to cause destroy messages + * to be sent to the X server. I suspect gtk uses + * atexit. + */ + _exit(0); + } + + /* + * If we reach here, we are the second child, so we now + * actually perform the exec. + */ + if (fd_to_close >= 0) + close(fd_to_close); + + execv("/proc/self/exe", args); + execvp(inst->progname, args); + perror("exec"); + _exit(127); + + } else { + int status; + waitpid(pid, &status, 0); + } + +} + +void dup_session_menuitem(GtkMenuItem *item, gpointer gdata) +{ + struct gui_data *inst = (struct gui_data *)gdata; + /* + * For this feature we must marshal cfg 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, size; + char *data; + char option[80]; + int pipefd[2]; + + if (pipe(pipefd) < 0) { + perror("pipe"); + return; + } + + size = sizeof(inst->cfg); + if (use_pty_argv && pty_argv) { + for (i = 0; pty_argv[i]; i++) + size += strlen(pty_argv[i]) + 1; + } + + data = snewn(size, char); + memcpy(data, &inst->cfg, sizeof(inst->cfg)); + if (use_pty_argv && pty_argv) { + int p = sizeof(inst->cfg); + for (i = 0; pty_argv[i]; i++) { + strcpy(data + p, pty_argv[i]); + p += strlen(pty_argv[i]) + 1; + } + assert(p == size); + } + + sprintf(option, "---[%d,%d]", pipefd[0], size); + fcntl(pipefd[0], F_SETFD, 0); + fork_and_exec_self(inst, pipefd[1], option, NULL); + close(pipefd[0]); + + i = ret = 0; + while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0) + i += ret; + if (ret < 0) + perror("write to pipe"); + close(pipefd[1]); + sfree(data); +} + +int read_dupsession_data(struct gui_data *inst, Config *cfg, char *arg) +{ + int fd, i, ret, size; + char *data; + + if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) { + fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg); + exit(1); + } + + data = snewn(size, char); + i = ret = 0; + while (i < size && (ret = read(fd, data + i, size - i)) > 0) + i += ret; + if (ret < 0) { + perror("read from pipe"); + exit(1); + } else if (i < size) { + fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n", + appname); + exit(1); + } + + memcpy(cfg, data, sizeof(Config)); + if (use_pty_argv && size > sizeof(Config)) { + int n = 0; + i = sizeof(Config); + while (i < size) { + while (i < size && data[i]) i++; + if (i >= size) { + fprintf(stderr, "%s: malformed Duplicate Session data\n", + appname); + exit(1); + } + i++; + n++; + } + pty_argv = snewn(n+1, char *); + pty_argv[n] = NULL; + n = 0; + i = sizeof(Config); + while (i < size) { + char *p = data + i; + while (i < size && data[i]) i++; + assert(i < size); + i++; + pty_argv[n++] = dupstr(p); + } + } + + return 0; +} + +void new_session_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + fork_and_exec_self(inst, -1, NULL); +} + +void restart_session_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + + if (!inst->back) { + logevent(inst, "----- Session restarted -----"); + term_pwron(inst->term, FALSE); + start_backend(inst); + inst->exited = FALSE; + } +} + +void saved_session_menuitem(GtkMenuItem *item, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data"); + + fork_and_exec_self(inst, -1, "-load", str, NULL); +} + +void saved_session_freedata(GtkMenuItem *item, gpointer data) +{ + char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data"); + + sfree(str); +} + +static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + struct sesslist sesslist; + int i; + + gtk_container_foreach(GTK_CONTAINER(inst->sessionsmenu), + (GtkCallback)gtk_widget_destroy, NULL); + + get_sesslist(&sesslist, TRUE); + /* skip sesslist.sessions[0] == Default Settings */ + for (i = 1; i < sesslist.nsessions; i++) { + GtkWidget *menuitem = + gtk_menu_item_new_with_label(sesslist.sessions[i]); + gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem); + gtk_widget_show(menuitem); + gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", + dupstr(sesslist.sessions[i])); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + GTK_SIGNAL_FUNC(saved_session_menuitem), + inst); + gtk_signal_connect(GTK_OBJECT(menuitem), "destroy", + GTK_SIGNAL_FUNC(saved_session_freedata), + inst); + } + if (sesslist.nsessions <= 1) { + GtkWidget *menuitem = + gtk_menu_item_new_with_label("(No sessions)"); + gtk_widget_set_sensitive(menuitem, FALSE); + gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem); + gtk_widget_show(menuitem); + } + get_sesslist(&sesslist, FALSE); /* free up */ +} + +void set_window_icon(GtkWidget *window, const char *const *const *icon, + int n_icon) +{ + GdkPixmap *iconpm; + GdkBitmap *iconmask; +#if GTK_CHECK_VERSION(2,0,0) + GList *iconlist; + int n; +#endif + + if (!n_icon) + return; + + gtk_widget_realize(window); + iconpm = gdk_pixmap_create_from_xpm_d(window->window, &iconmask, + NULL, (gchar **)icon[0]); + gdk_window_set_icon(window->window, NULL, iconpm, iconmask); + +#if GTK_CHECK_VERSION(2,0,0) + iconlist = NULL; + for (n = 0; n < n_icon; n++) { + iconlist = + g_list_append(iconlist, + gdk_pixbuf_new_from_xpm_data((const gchar **) + icon[n])); + } + gdk_window_set_icon_list(window->window, iconlist); +#endif +} + +void update_specials_menu(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + const struct telnet_special *specials; + + if (inst->back) + specials = inst->back->get_specials(inst->backhandle); + else + specials = NULL; + + /* I believe this disposes of submenus too. */ + gtk_container_foreach(GTK_CONTAINER(inst->specialsmenu), + (GtkCallback)gtk_widget_destroy, NULL); + if (specials) { + int i; + GtkWidget *menu = inst->specialsmenu; + /* A lame "stack" for submenus that will do for now. */ + GtkWidget *saved_menu = NULL; + int nesting = 1; + for (i = 0; nesting > 0; i++) { + GtkWidget *menuitem = NULL; + switch (specials[i].code) { + case TS_SUBMENU: + assert (nesting < 2); + saved_menu = menu; /* XXX lame stacking */ + menu = gtk_menu_new(); + menuitem = gtk_menu_item_new_with_label(specials[i].name); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); + gtk_container_add(GTK_CONTAINER(saved_menu), menuitem); + gtk_widget_show(menuitem); + menuitem = NULL; + nesting++; + break; + case TS_EXITMENU: + nesting--; + if (nesting) { + menu = saved_menu; /* XXX lame stacking */ + saved_menu = NULL; + } + break; + case TS_SEP: + menuitem = gtk_menu_item_new(); + break; + default: + menuitem = gtk_menu_item_new_with_label(specials[i].name); + gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", + GINT_TO_POINTER(specials[i].code)); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + GTK_SIGNAL_FUNC(special_menuitem), inst); + break; + } + if (menuitem) { + gtk_container_add(GTK_CONTAINER(menu), menuitem); + gtk_widget_show(menuitem); + } + } + gtk_widget_show(inst->specialsitem1); + gtk_widget_show(inst->specialsitem2); + } else { + gtk_widget_hide(inst->specialsitem1); + gtk_widget_hide(inst->specialsitem2); + } +} + +static void start_backend(struct gui_data *inst) +{ + extern Backend *select_backend(Config *cfg); + char *realhost; + const char *error; + + inst->back = select_backend(&inst->cfg); + + error = inst->back->init((void *)inst, &inst->backhandle, + &inst->cfg, inst->cfg.host, inst->cfg.port, + &realhost, inst->cfg.tcp_nodelay, + inst->cfg.tcp_keepalives); + + if (error) { + char *msg = dupprintf("Unable to open connection to %s:\n%s", + inst->cfg.host, error); + inst->exited = TRUE; + fatal_message_box(inst->window, msg); + sfree(msg); + exit(0); + } + + if (inst->cfg.wintitle[0]) { + set_title(inst, inst->cfg.wintitle); + set_icon(inst, inst->cfg.wintitle); + } else { + char *title = make_default_wintitle(realhost); + set_title(inst, title); + set_icon(inst, title); + sfree(title); + } + sfree(realhost); + + inst->back->provide_logctx(inst->backhandle, inst->logctx); + + term_provide_resize_fn(inst->term, inst->back->size, inst->backhandle); + + inst->ldisc = + ldisc_create(&inst->cfg, inst->term, inst->back, inst->backhandle, + inst); + + gtk_widget_set_sensitive(inst->restartitem, FALSE); +} + +int pt_main(int argc, char **argv) +{ + extern int cfgbox(Config *cfg); + struct gui_data *inst; + + /* + * Create an instance structure and initialise to zeroes + */ + inst = snew(struct gui_data); + memset(inst, 0, sizeof(*inst)); + inst->alt_keycode = -1; /* this one needs _not_ to be zero */ + inst->busy_status = BUSY_NOT; + + /* defer any child exit handling until we're ready to deal with + * it */ + block_signal(SIGCHLD, 1); + + inst->progname = argv[0]; + /* + * Copy the original argv before letting gtk_init fiddle with + * it. It will be required later. + */ + { + int i, oldargc; + inst->gtkargvstart = snewn(argc-1, char *); + for (i = 1; i < argc; i++) + inst->gtkargvstart[i-1] = dupstr(argv[i]); + oldargc = argc; + gtk_init(&argc, &argv); + inst->ngtkargs = oldargc - argc; + } + + if (argc > 1 && !strncmp(argv[1], "---", 3)) { + read_dupsession_data(inst, &inst->cfg, argv[1]); + /* Splatter this argument so it doesn't clutter a ps listing */ + memset(argv[1], 0, strlen(argv[1])); + } else { + /* By default, we bring up the config dialog, rather than launching + * a session. This gets set to TRUE if something happens to change + * that (e.g., a hostname is specified on the command-line). */ + int allow_launch = FALSE; + if (do_cmdline(argc, argv, 0, &allow_launch, inst, &inst->cfg)) + exit(1); /* pre-defaults pass to get -class */ + do_defaults(NULL, &inst->cfg); + if (do_cmdline(argc, argv, 1, &allow_launch, inst, &inst->cfg)) + exit(1); /* post-defaults, do everything */ + + cmdline_run_saved(&inst->cfg); + + if (loaded_session) + allow_launch = TRUE; + + if ((!allow_launch || !cfg_launchable(&inst->cfg)) && + !cfgbox(&inst->cfg)) + exit(0); /* config box hit Cancel */ + } + + if (!compound_text_atom) + compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE); + if (!utf8_string_atom) + utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE); + + inst->area = gtk_drawing_area_new(); + + setup_fonts_ucs(inst); + init_cutbuffers(); + + inst->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + if (inst->cfg.winclass[0]) + gtk_window_set_wmclass(GTK_WINDOW(inst->window), + inst->cfg.winclass, inst->cfg.winclass); + + /* + * Set up the colour map. + */ + palette_reset(inst); + + inst->width = inst->cfg.width; + inst->height = inst->cfg.height; + + gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), + inst->font_width * inst->cfg.width + 2*inst->cfg.window_border, + inst->font_height * inst->cfg.height + 2*inst->cfg.window_border); + inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0)); + inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust); + inst->hbox = GTK_BOX(gtk_hbox_new(FALSE, 0)); + /* + * We always create the scrollbar; it remains invisible if + * unwanted, so we can pop it up quickly if it suddenly becomes + * desirable. + */ + if (inst->cfg.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 (!inst->cfg.scrollbar_on_left) + gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0); + + gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox)); + + set_geom_hints(inst); + + gtk_widget_show(inst->area); + if (inst->cfg.scrollbar) + gtk_widget_show(inst->sbar); + else + gtk_widget_hide(inst->sbar); + gtk_widget_show(GTK_WIDGET(inst->hbox)); + + if (inst->gotpos) { + int x = inst->xpos, y = inst->ypos; + GtkRequisition req; + gtk_widget_size_request(GTK_WIDGET(inst->window), &req); + if (inst->gravity & 1) x += gdk_screen_width() - req.width; + if (inst->gravity & 2) y += gdk_screen_height() - req.height; + gtk_window_set_position(GTK_WINDOW(inst->window), GTK_WIN_POS_NONE); + gtk_widget_set_uposition(GTK_WIDGET(inst->window), x, y); + } + + gtk_signal_connect(GTK_OBJECT(inst->window), "destroy", + GTK_SIGNAL_FUNC(destroy), inst); + gtk_signal_connect(GTK_OBJECT(inst->window), "delete_event", + GTK_SIGNAL_FUNC(delete_window), inst); + gtk_signal_connect(GTK_OBJECT(inst->window), "key_press_event", + GTK_SIGNAL_FUNC(key_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->window), "key_release_event", + GTK_SIGNAL_FUNC(key_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->window), "focus_in_event", + GTK_SIGNAL_FUNC(focus_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->window), "focus_out_event", + GTK_SIGNAL_FUNC(focus_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "configure_event", + GTK_SIGNAL_FUNC(configure_area), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "expose_event", + GTK_SIGNAL_FUNC(expose_area), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "button_press_event", + GTK_SIGNAL_FUNC(button_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "button_release_event", + GTK_SIGNAL_FUNC(button_event), inst); +#if GTK_CHECK_VERSION(2,0,0) + gtk_signal_connect(GTK_OBJECT(inst->area), "scroll_event", + GTK_SIGNAL_FUNC(scroll_event), inst); +#endif + gtk_signal_connect(GTK_OBJECT(inst->area), "motion_notify_event", + GTK_SIGNAL_FUNC(motion_event), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "selection_received", + GTK_SIGNAL_FUNC(selection_received), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "selection_get", + GTK_SIGNAL_FUNC(selection_get), inst); + gtk_signal_connect(GTK_OBJECT(inst->area), "selection_clear_event", + GTK_SIGNAL_FUNC(selection_clear), inst); + if (inst->cfg.scrollbar) + gtk_signal_connect(GTK_OBJECT(inst->sbar_adjust), "value_changed", + GTK_SIGNAL_FUNC(scrollbar_moved), inst); + gtk_widget_add_events(GTK_WIDGET(inst->area), + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK); + + { + extern const char *const *const main_icon[]; + extern const int n_main_icon; + set_window_icon(inst->window, main_icon, n_main_icon); + } + + gtk_widget_show(inst->window); + + set_window_background(inst); + + /* + * Set up the Ctrl+rightclick context menu. + */ + { + GtkWidget *menuitem; + char *s; + extern const int use_event_log, new_session, saved_sessions; + + inst->menu = gtk_menu_new(); + +#define MKMENUITEM(title, func) do \ + { \ + menuitem = gtk_menu_item_new_with_label(title); \ + gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \ + gtk_widget_show(menuitem); \ + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", \ + GTK_SIGNAL_FUNC(func), inst); \ + } while (0) + +#define MKSUBMENU(title) do \ + { \ + menuitem = gtk_menu_item_new_with_label(title); \ + gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \ + gtk_widget_show(menuitem); \ + } while (0) + +#define MKSEP() do \ + { \ + menuitem = gtk_menu_item_new(); \ + gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \ + gtk_widget_show(menuitem); \ + } while (0) + + if (new_session) + MKMENUITEM("New Session...", new_session_menuitem); + MKMENUITEM("Restart Session", restart_session_menuitem); + inst->restartitem = menuitem; + gtk_widget_set_sensitive(inst->restartitem, FALSE); + MKMENUITEM("Duplicate Session", dup_session_menuitem); + if (saved_sessions) { + inst->sessionsmenu = gtk_menu_new(); + /* sessionsmenu will be updated when it's invoked */ + /* XXX is this the right way to do dynamic menus in Gtk? */ + MKMENUITEM("Saved Sessions", update_savedsess_menu); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), + inst->sessionsmenu); + } + MKSEP(); + MKMENUITEM("Change Settings...", change_settings_menuitem); + MKSEP(); + if (use_event_log) + MKMENUITEM("Event Log", event_log_menuitem); + MKSUBMENU("Special Commands"); + inst->specialsmenu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), inst->specialsmenu); + inst->specialsitem1 = menuitem; + MKSEP(); + inst->specialsitem2 = menuitem; + gtk_widget_hide(inst->specialsitem1); + gtk_widget_hide(inst->specialsitem2); + MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem); + MKMENUITEM("Reset Terminal", reset_terminal_menuitem); + MKMENUITEM("Copy All", copy_all_menuitem); + MKSEP(); + s = dupcat("About ", appname, NULL); + MKMENUITEM(s, about_menuitem); + sfree(s); +#undef MKMENUITEM +#undef MKSUBMENU +#undef MKSEP + } + + inst->textcursor = make_mouse_ptr(inst, GDK_XTERM); + inst->rawcursor = make_mouse_ptr(inst, GDK_LEFT_PTR); + inst->waitcursor = make_mouse_ptr(inst, GDK_WATCH); + inst->blankcursor = make_mouse_ptr(inst, -1); + make_mouse_ptr(inst, -2); /* clean up cursor font */ + inst->currcursor = inst->textcursor; + show_mouseptr(inst, 1); + + inst->eventlogstuff = eventlogstuff_new(); + + inst->term = term_init(&inst->cfg, &inst->ucsdata, inst); + inst->logctx = log_init(inst, &inst->cfg); + term_provide_logctx(inst->term, inst->logctx); + + uxsel_init(); + + term_size(inst->term, inst->cfg.height, inst->cfg.width, inst->cfg.savelines); + + start_backend(inst); + + ldisc_send(inst->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */ + + /* now we're reday to deal with the child exit handler being + * called */ + block_signal(SIGCHLD, 0); + + /* + * Block SIGPIPE: if we attempt Duplicate Session or similar + * and it falls over in some way, we certainly don't want + * SIGPIPE terminating the main pterm/PuTTY. Note that we do + * this _after_ (at least pterm) forks off its child process, + * since the child wants SIGPIPE handled in the usual way. + */ + block_signal(SIGPIPE, 1); + + inst->exited = FALSE; + + gtk_main(); + + return 0; +} diff --git a/putty/UNIX/MAKEFILE.GTK b/putty/UNIX/MAKEFILE.GTK new file mode 100644 index 0000000..4809191 --- /dev/null +++ b/putty/UNIX/MAKEFILE.GTK @@ -0,0 +1,889 @@ +# Makefile for putty under X/GTK and Unix. +# +# This file was created by `mkfiles.pl' from the `Recipe' file. +# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead. +# +# Extra options you can set: +# +# - VER="-DSNAPSHOT=1999-01-25 -DSVN_REV=1234" +# Generates executables whose About box report them as being a +# development snapshot. SVN_REV is a Subversion revision number. +# +# - VER=-DRELEASE=0.43 +# Generates executables whose About box report them as being a +# release version. +# +# - COMPAT=-DAUTO_WINSOCK (Windows only) +# Causes PuTTY to assume that includes its own WinSock +# header file, so that it won't try to include . +# +# - COMPAT=-DWINSOCK_TWO (Windows only) +# Causes the PuTTY utilities to include instead of +# , except Plink which _needs_ WinSock 2 so it already +# does this. +# +# - COMPAT=-DNO_SECURITY (Windows only) +# Disables Pageant's use of , which is not available +# with some development environments (such as older versions of +# the Cygwin/mingw GNU toolchain). This means that 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). +# +# - COMPAT=-DNO_MULTIMON (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. This means that PuTTY's +# full-screen mode (configurable to work on Alt-Enter) will +# not behave usefully in a multi-monitor environment. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin. +# +# - COMPAT=-DNO_HTMLHELP (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. The resulting binary +# will only look for an old-style WinHelp file (.HLP/.CNT), and +# will ignore any .CHM file. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin (although you can use +# the htmlhelp.h 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. +# +# - RCFL=-DASCIICTLS (Windows only) +# Uses ASCII rather than Unicode to specify the tab control in +# the resource file. Probably most useful when compiling with +# Cygnus/mingw32, whose resource compiler may have less of a +# problem with it. +# +# - XFLAGS=-DTELNET_DEFAULT +# Causes PuTTY to default to the Telnet protocol (in the absence +# of Default Settings and so on to the contrary). Normally PuTTY +# will default to SSH. +# +# - 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. +# + +# You can define this path to point at your tools if you need to +# TOOLPATH = /opt/gcc/bin +CC = $(TOOLPATH)cc +# If necessary set the path to krb5-config here +KRB5CONFIG=krb5-config +# You can manually set this to `gtk-config' or `pkg-config gtk+-1.2' +# (depending on what works on your system) if you want to enforce +# building with GTK 1.2, or you can set it to `pkg-config gtk+-2.0 x11' +# if you want to enforce 2.0. The default is to try 2.0 and fall back +# to 1.2 if it isn't found. +GTK_CONFIG = sh -c 'pkg-config gtk+-2.0 x11 $$0 2>/dev/null || gtk-config $$0' + +-include Makefile.local + +unexport CFLAGS # work around a weird issue with krb5-config + +CFLAGS = -O2 -Wall -Werror -g -I.././ -I../charset/ -I../windows/ -I../unix/ \ + -I../macosx/ $(shell $(GTK_CONFIG) --cflags) -D _FILE_OFFSET_BITS=64 +XLDFLAGS = $(LDFLAGS) $(shell $(GTK_CONFIG) --libs) +ULDFLAGS = $(LDFLAGS) +ifeq (,$(findstring NO_GSSAPI,$(COMPAT))) +ifeq (,$(findstring STATIC_GSSAPI,$(COMPAT))) +XLDFLAGS+= -ldl +ULDFLAGS+= -ldl +else +CFLAGS+= -DNO_LIBDL $(shell $(KRB5CONFIG) --cflags gssapi) +XLDFLAGS+= $(shell $(KRB5CONFIG) --libs gssapi) +ULDFLAGS+= $(shell $(KRB5CONFIG) --libs gssapi) +endif +endif +INSTALL=install +INSTALL_PROGRAM=$(INSTALL) +INSTALL_DATA=$(INSTALL) +prefix=/usr/local +exec_prefix=$(prefix) +bindir=$(exec_prefix)/bin +mandir=$(prefix)/man +man1dir=$(mandir)/man1 + + +.SUFFIXES: + + +all: plink pscp psftp pterm putty puttygen puttytel + +plink: be_all_s.o cmdline.o cproxy.o ldisc.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o raw.o rlogin.o settings.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + telnet.o time.o timing.o tree234.o ux_x11.o uxagentc.o \ + uxcons.o uxgss.o uxmisc.o uxnet.o uxnoise.o uxplink.o \ + uxproxy.o uxsel.o uxser.o uxsignal.o uxstore.o version.o \ + wildcard.o x11fwd.o + $(CC) -o $@ be_all_s.o cmdline.o cproxy.o ldisc.o logging.o misc.o \ + pgssapi.o pinger.o portfwd.o proxy.o raw.o rlogin.o \ + settings.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o \ + sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o \ + sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o \ + sshsha.o sshzlib.o telnet.o time.o timing.o tree234.o \ + ux_x11.o uxagentc.o uxcons.o uxgss.o uxmisc.o uxnet.o \ + uxnoise.o uxplink.o uxproxy.o uxsel.o uxser.o uxsignal.o \ + uxstore.o version.o wildcard.o x11fwd.o $(ULDFLAGS) + +pscp: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o pscp.o settings.o sftp.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + time.o timing.o tree234.o uxagentc.o uxcons.o uxgss.o \ + uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o uxsftp.o \ + uxstore.o version.o wildcard.o x11fwd.o + $(CC) -o $@ be_none.o cmdline.o cproxy.o int64.o logging.o misc.o \ + pgssapi.o pinger.o portfwd.o proxy.o pscp.o settings.o \ + sftp.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o time.o timing.o tree234.o uxagentc.o uxcons.o \ + uxgss.o uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o \ + uxsftp.o uxstore.o version.o wildcard.o x11fwd.o $(ULDFLAGS) + +psftp: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o psftp.o settings.o sftp.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + time.o timing.o tree234.o uxagentc.o uxcons.o uxgss.o \ + uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o uxsftp.o \ + uxstore.o version.o wildcard.o x11fwd.o + $(CC) -o $@ be_none.o cmdline.o cproxy.o int64.o logging.o misc.o \ + pgssapi.o pinger.o portfwd.o proxy.o psftp.o settings.o \ + sftp.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o time.o timing.o tree234.o uxagentc.o uxcons.o \ + uxgss.o uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o \ + uxsftp.o uxstore.o version.o wildcard.o x11fwd.o $(ULDFLAGS) + +pterm: be_none.o cmdline.o config.o dialog.o fromucs.o gtkcfg.o gtkcols.o \ + gtkdlg.o gtkfont.o gtkwin.o ldisc.o ldiscucs.o localenc.o \ + logging.o macenc.o mimeenc.o minibidi.o misc.o nocproxy.o \ + nogss.o sbcs.o sbcsdat.o sercfg.o settings.o slookup.o \ + terminal.o time.o timing.o toucs.o tree234.o utf8.o uxcfg.o \ + uxmisc.o uxprint.o uxpterm.o uxpty.o uxsel.o uxsignal.o \ + uxstore.o uxucs.o version.o wcwidth.o xenc.o xkeysym.o \ + xpmptcfg.o xpmpterm.o + $(CC) -o $@ be_none.o cmdline.o config.o dialog.o fromucs.o gtkcfg.o \ + gtkcols.o gtkdlg.o gtkfont.o gtkwin.o ldisc.o ldiscucs.o \ + localenc.o logging.o macenc.o mimeenc.o minibidi.o misc.o \ + nocproxy.o nogss.o sbcs.o sbcsdat.o sercfg.o settings.o \ + slookup.o terminal.o time.o timing.o toucs.o tree234.o \ + utf8.o uxcfg.o uxmisc.o uxprint.o uxpterm.o uxpty.o uxsel.o \ + uxsignal.o uxstore.o uxucs.o version.o wcwidth.o xenc.o \ + xkeysym.o xpmptcfg.o xpmpterm.o $(XLDFLAGS) + +putty: be_all_s.o cmdline.o config.o cproxy.o dialog.o fromucs.o gtkcfg.o \ + gtkcols.o gtkdlg.o gtkfont.o gtkwin.o ldisc.o ldiscucs.o \ + localenc.o logging.o macenc.o mimeenc.o minibidi.o misc.o \ + pgssapi.o pinger.o portfwd.o proxy.o raw.o rlogin.o sbcs.o \ + sbcsdat.o sercfg.o settings.o slookup.o ssh.o sshaes.o \ + ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o sshdes.o \ + sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o sshrand.o \ + sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o telnet.o \ + terminal.o time.o timing.o toucs.o tree234.o utf8.o ux_x11.o \ + uxagentc.o uxcfg.o uxgss.o uxmisc.o uxnet.o uxnoise.o \ + uxprint.o uxproxy.o uxputty.o uxsel.o uxser.o uxsignal.o \ + uxstore.o uxucs.o version.o wcwidth.o wildcard.o x11fwd.o \ + xenc.o xkeysym.o xpmpucfg.o xpmputty.o + $(CC) -o $@ be_all_s.o cmdline.o config.o cproxy.o dialog.o \ + fromucs.o gtkcfg.o gtkcols.o gtkdlg.o gtkfont.o gtkwin.o \ + ldisc.o ldiscucs.o localenc.o logging.o macenc.o mimeenc.o \ + minibidi.o misc.o pgssapi.o pinger.o portfwd.o proxy.o raw.o \ + rlogin.o sbcs.o sbcsdat.o sercfg.o settings.o slookup.o \ + ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o telnet.o terminal.o time.o timing.o toucs.o \ + tree234.o utf8.o ux_x11.o uxagentc.o uxcfg.o uxgss.o \ + uxmisc.o uxnet.o uxnoise.o uxprint.o uxproxy.o uxputty.o \ + uxsel.o uxser.o uxsignal.o uxstore.o uxucs.o version.o \ + wcwidth.o wildcard.o x11fwd.o xenc.o xkeysym.o xpmpucfg.o \ + xpmputty.o $(XLDFLAGS) + +puttygen: cmdgen.o import.o misc.o notiming.o sshaes.o sshbn.o sshdes.o \ + sshdss.o sshdssg.o sshmd5.o sshprime.o sshpubk.o sshrand.o \ + sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o time.o \ + tree234.o uxcons.o uxgen.o uxmisc.o uxnoise.o uxstore.o \ + version.o + $(CC) -o $@ cmdgen.o import.o misc.o notiming.o sshaes.o sshbn.o \ + sshdes.o sshdss.o sshdssg.o sshmd5.o sshprime.o sshpubk.o \ + sshrand.o sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o \ + time.o tree234.o uxcons.o uxgen.o uxmisc.o uxnoise.o \ + uxstore.o version.o $(ULDFLAGS) + +puttytel: be_nos_s.o cmdline.o config.o dialog.o fromucs.o gtkcfg.o \ + gtkcols.o gtkdlg.o gtkfont.o gtkwin.o ldisc.o ldiscucs.o \ + localenc.o logging.o macenc.o mimeenc.o minibidi.o misc.o \ + nocproxy.o nogss.o pinger.o proxy.o raw.o rlogin.o sbcs.o \ + sbcsdat.o sercfg.o settings.o slookup.o telnet.o terminal.o \ + time.o timing.o toucs.o tree234.o utf8.o uxcfg.o uxmisc.o \ + uxnet.o uxprint.o uxproxy.o uxputty.o uxsel.o uxser.o \ + uxsignal.o uxstore.o uxucs.o version.o wcwidth.o xenc.o \ + xkeysym.o xpmpucfg.o xpmputty.o + $(CC) -o $@ be_nos_s.o cmdline.o config.o dialog.o fromucs.o \ + gtkcfg.o gtkcols.o gtkdlg.o gtkfont.o gtkwin.o ldisc.o \ + ldiscucs.o localenc.o logging.o macenc.o mimeenc.o \ + minibidi.o misc.o nocproxy.o nogss.o pinger.o proxy.o raw.o \ + rlogin.o sbcs.o sbcsdat.o sercfg.o settings.o slookup.o \ + telnet.o terminal.o time.o timing.o toucs.o tree234.o utf8.o \ + uxcfg.o uxmisc.o uxnet.o uxprint.o uxproxy.o uxputty.o \ + uxsel.o uxser.o uxsignal.o uxstore.o uxucs.o version.o \ + wcwidth.o xenc.o xkeysym.o xpmpucfg.o xpmputty.o $(XLDFLAGS) + +be_all_s.o: ../be_all_s.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_all_s.c +be_none.o: ../be_none.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_none.c +be_nos_s.o: ../be_nos_s.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_nos_s.c +cmdgen.o: ../cmdgen.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cmdgen.c +cmdline.o: ../cmdline.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cmdline.c +config.o: ../config.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../config.c +cproxy.o: ../cproxy.c ../putty.h ../ssh.h ../network.h ../proxy.h \ + ../puttyps.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cproxy.c +dialog.o: ../dialog.c ../putty.h ../dialog.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../dialog.c +fromucs.o: ../charset/fromucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/fromucs.c +gtkcfg.o: ../unix/gtkcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkcfg.c +gtkcols.o: ../unix/gtkcols.c ../unix/gtkcols.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkcols.c +gtkdlg.o: ../unix/gtkdlg.c ../unix/gtkcols.h ../unix/gtkfont.h ../putty.h \ + ../storage.h ../dialog.h ../tree234.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkdlg.c +gtkfont.o: ../unix/gtkfont.c ../putty.h ../unix/gtkfont.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkfont.c +gtkwin.o: ../unix/gtkwin.c ../putty.h ../terminal.h ../unix/gtkfont.h \ + ../puttyps.h ../network.h ../misc.h ../tree234.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkwin.c +import.o: ../import.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../import.c +int64.o: ../int64.c ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../int64.c +ldisc.o: ../ldisc.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \ + ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ldisc.c +ldiscucs.o: ../ldiscucs.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \ + ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ldiscucs.c +localenc.o: ../charset/localenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/localenc.c +logging.o: ../logging.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../logging.c +macenc.o: ../charset/macenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/macenc.c +mimeenc.o: ../charset/mimeenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/mimeenc.c +minibidi.o: ../minibidi.c ../misc.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../minibidi.c +misc.o: ../misc.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../misc.c +nocproxy.o: ../nocproxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nocproxy.c +nogss.o: ../nogss.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nogss.c +notiming.o: ../notiming.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../notiming.c +osxctrls.o: ../macosx/osxctrls.m ../putty.h ../dialog.h ../macosx/osxclass.h \ + ../tree234.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxctrls.m +osxdlg.o: ../macosx/osxdlg.m ../putty.h ../storage.h ../dialog.h \ + ../macosx/osxclass.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxdlg.m +osxmain.o: ../macosx/osxmain.m ../putty.h ../macosx/osxclass.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxmain.m +osxsel.o: ../macosx/osxsel.m ../putty.h ../macosx/osxclass.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxsel.m +osxwin.o: ../macosx/osxwin.m ../putty.h ../terminal.h ../macosx/osxclass.h \ + ../puttyps.h ../network.h ../misc.h ../tree234.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxwin.m +pgssapi.o: ../pgssapi.c ../putty.h ../pgssapi.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pgssapi.c +pinger.o: ../pinger.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pinger.c +portfwd.o: ../portfwd.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../portfwd.c +proxy.o: ../proxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../proxy.c +pscp.o: ../pscp.c ../putty.h ../psftp.h ../ssh.h ../sftp.h ../storage.h \ + ../int64.h ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pscp.c +psftp.o: ../psftp.c ../putty.h ../psftp.h ../storage.h ../ssh.h ../sftp.h \ + ../int64.h ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../psftp.c +raw.o: ../raw.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../raw.c +rlogin.o: ../rlogin.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../rlogin.c +sbcs.o: ../charset/sbcs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/sbcs.c +sbcsdat.o: ../charset/sbcsdat.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/sbcsdat.c +sercfg.o: ../sercfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sercfg.c +settings.o: ../settings.c ../putty.h ../storage.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../settings.c +sftp.o: ../sftp.c ../misc.h ../int64.h ../tree234.h ../sftp.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sftp.c +sizetip.o: ../windows/sizetip.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/sizetip.c +slookup.o: ../charset/slookup.c ../charset/charset.h ../charset/internal.h \ + ../charset/enum.c ../charset/sbcsdat.c ../charset/utf8.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/slookup.c +ssh.o: ../ssh.c ../putty.h ../tree234.h ../ssh.h ../sshgssc.h ../sshgss.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h ../int64.h \ + ../pgssapi.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh.c +sshaes.o: ../sshaes.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshaes.c +ssharcf.o: ../ssharcf.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssharcf.c +sshblowf.o: ../sshblowf.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshblowf.c +sshbn.o: ../sshbn.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshbn.c +sshcrc.o: ../sshcrc.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshcrc.c +sshcrcda.o: ../sshcrcda.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshcrcda.c +sshdes.o: ../sshdes.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdes.c +sshdh.o: ../sshdh.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdh.c +sshdss.o: ../sshdss.c ../ssh.h ../misc.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdss.c +sshdssg.o: ../sshdssg.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdssg.c +sshgssc.o: ../sshgssc.c ../putty.h ../sshgssc.h ../misc.h ../puttyps.h \ + ../network.h ../pgssapi.h ../sshgss.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../tree234.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshgssc.c +sshmd5.o: ../sshmd5.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshmd5.c +sshprime.o: ../sshprime.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshprime.c +sshpubk.o: ../sshpubk.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshpubk.c +sshrand.o: ../sshrand.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrand.c +sshrsa.o: ../sshrsa.c ../ssh.h ../misc.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrsa.c +sshrsag.o: ../sshrsag.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrsag.c +sshsh256.o: ../sshsh256.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsh256.c +sshsh512.o: ../sshsh512.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsh512.c +sshsha.o: ../sshsha.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsha.c +sshzlib.o: ../sshzlib.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshzlib.c +telnet.o: ../telnet.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../telnet.c +terminal.o: ../terminal.c ../putty.h ../terminal.h ../puttyps.h ../network.h \ + ../misc.h ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../terminal.c +testback.o: ../testback.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../testback.c +time.o: ../time.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../time.c +timing.o: ../timing.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../timing.c +toucs.o: ../charset/toucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/toucs.c +tree234.o: ../tree234.c ../puttymem.h ../tree234.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../tree234.c +utf8.o: ../charset/utf8.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/utf8.c +ux_x11.o: ../unix/ux_x11.c ../putty.h ../ssh.h ../network.h ../puttyps.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/ux_x11.c +uxagentc.o: ../unix/uxagentc.c ../putty.h ../misc.h ../tree234.h \ + ../puttymem.h ../puttyps.h ../network.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxagentc.c +uxcfg.o: ../unix/uxcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxcfg.c +uxcons.o: ../unix/uxcons.c ../putty.h ../storage.h ../ssh.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxcons.c +uxgen.o: ../unix/uxgen.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxgen.c +uxgss.o: ../unix/uxgss.c ../putty.h ../pgssapi.h ../sshgss.h ../sshgssc.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxgss.c +uxmisc.o: ../unix/uxmisc.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxmisc.c +uxnet.o: ../unix/uxnet.c ../putty.h ../network.h ../tree234.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxnet.c +uxnoise.o: ../unix/uxnoise.c ../putty.h ../ssh.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxnoise.c +uxplink.o: ../unix/uxplink.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxplink.c +uxprint.o: ../unix/uxprint.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxprint.c +uxproxy.o: ../unix/uxproxy.c ../tree234.h ../putty.h ../network.h ../proxy.h \ + ../puttyps.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxproxy.c +uxpterm.o: ../unix/uxpterm.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpterm.c +uxpty.o: ../unix/uxpty.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpty.c +uxputty.o: ../unix/uxputty.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxputty.c +uxsel.o: ../unix/uxsel.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsel.c +uxser.o: ../unix/uxser.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxser.c +uxsftp.o: ../unix/uxsftp.c ../putty.h ../ssh.h ../psftp.h ../int64.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsftp.c +uxsignal.o: ../unix/uxsignal.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsignal.c +uxstore.o: ../unix/uxstore.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxstore.c +uxucs.o: ../unix/uxucs.c ../putty.h ../charset/charset.h ../terminal.h \ + ../misc.h ../puttyps.h ../network.h ../tree234.h \ + ../puttymem.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxucs.c +wcwidth.o: ../wcwidth.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../wcwidth.c +wildcard.o: ../wildcard.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../wildcard.c +wincfg.o: ../windows/wincfg.c ../putty.h ../dialog.h ../storage.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincfg.c +wincons.o: ../windows/wincons.c ../putty.h ../storage.h ../ssh.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincons.c +winctrls.o: ../windows/winctrls.c ../putty.h ../misc.h ../dialog.h \ + ../puttyps.h ../network.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../tree234.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winctrls.c +windefs.o: ../windows/windefs.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/windefs.c +windlg.o: ../windows/windlg.c ../putty.h ../ssh.h ../windows/win_res.h \ + ../storage.h ../dialog.h ../puttyps.h ../network.h ../misc.h \ + ../puttymem.h ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/windlg.c +window.o: ../windows/window.c ../putty.h ../terminal.h ../storage.h \ + ../windows/win_res.h ../puttyps.h ../network.h ../misc.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/window.c +wingss.o: ../windows/wingss.c ../putty.h ../pgssapi.h ../sshgss.h \ + ../sshgssc.h ../misc.h ../puttyps.h ../network.h \ + ../puttymem.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wingss.c +winhandl.o: ../windows/winhandl.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winhandl.c +winhelp.o: ../windows/winhelp.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winhelp.c +winjump.o: ../windows/winjump.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winjump.c +winmisc.o: ../windows/winmisc.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winmisc.c +winnet.o: ../windows/winnet.c ../putty.h ../network.h ../tree234.h \ + ../puttyps.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnet.c +winnoise.o: ../windows/winnoise.c ../putty.h ../ssh.h ../storage.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnoise.c +winnojmp.o: ../windows/winnojmp.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnojmp.c +winpgen.o: ../windows/winpgen.c ../putty.h ../ssh.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgen.c +winpgnt.o: ../windows/winpgnt.c ../putty.h ../ssh.h ../misc.h ../tree234.h \ + ../puttyps.h ../network.h ../puttymem.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgnt.c +winpgntc.o: ../windows/winpgntc.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgntc.c +winplink.o: ../windows/winplink.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winplink.c +winprint.o: ../windows/winprint.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winprint.c +winproxy.o: ../windows/winproxy.c ../tree234.h ../putty.h ../network.h \ + ../proxy.h ../puttyps.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winproxy.c +winser.o: ../windows/winser.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winser.c +winsftp.o: ../windows/winsftp.c ../putty.h ../psftp.h ../ssh.h ../int64.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winsftp.c +winstore.o: ../windows/winstore.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winstore.c +wintime.o: ../windows/wintime.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wintime.c +winucs.o: ../windows/winucs.c ../putty.h ../terminal.h ../misc.h \ + ../puttyps.h ../network.h ../tree234.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winucs.c +winutils.o: ../windows/winutils.c ../putty.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winutils.c +winx11.o: ../windows/winx11.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winx11.c +x11fwd.o: ../x11fwd.c ../putty.h ../ssh.h ../tree234.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../x11fwd.c +xenc.o: ../charset/xenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/xenc.c +xkeysym.o: ../unix/xkeysym.c ../misc.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xkeysym.c +xpmptcfg.o: ../unix/xpmptcfg.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmptcfg.c +xpmpterm.o: ../unix/xpmpterm.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmpterm.c +xpmpucfg.o: ../unix/xpmpucfg.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmpucfg.c +xpmputty.o: ../unix/xpmputty.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmputty.c + +version.o: FORCE + if test -z "$(VER)" && (cd ..; md5sum -c manifest); then \ + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) `cat ../version.def` -c ../version.c; \ + else \ + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) $(VER) -c ../version.c; \ + fi +install: + mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir) + $(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/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" + +clean: + rm -f *.o plink pscp psftp pterm putty puttygen puttytel + +FORCE: diff --git a/putty/UNIX/MAKEFILE.IN b/putty/UNIX/MAKEFILE.IN new file mode 100644 index 0000000..8893846 --- /dev/null +++ b/putty/UNIX/MAKEFILE.IN @@ -0,0 +1,872 @@ +# Makefile.in for putty under Unix with Autoconf. +# +# This file was created by `mkfiles.pl' from the `Recipe' file. +# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead. +# +# Extra options you can set: +# +# - VER="-DSNAPSHOT=1999-01-25 -DSVN_REV=1234" +# Generates executables whose About box report them as being a +# development snapshot. SVN_REV is a Subversion revision number. +# +# - VER=-DRELEASE=0.43 +# Generates executables whose About box report them as being a +# release version. +# +# - COMPAT=-DAUTO_WINSOCK (Windows only) +# Causes PuTTY to assume that includes its own WinSock +# header file, so that it won't try to include . +# +# - COMPAT=-DWINSOCK_TWO (Windows only) +# Causes the PuTTY utilities to include instead of +# , except Plink which _needs_ WinSock 2 so it already +# does this. +# +# - COMPAT=-DNO_SECURITY (Windows only) +# Disables Pageant's use of , which is not available +# with some development environments (such as older versions of +# the Cygwin/mingw GNU toolchain). This means that 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). +# +# - COMPAT=-DNO_MULTIMON (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. This means that PuTTY's +# full-screen mode (configurable to work on Alt-Enter) will +# not behave usefully in a multi-monitor environment. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin. +# +# - COMPAT=-DNO_HTMLHELP (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. The resulting binary +# will only look for an old-style WinHelp file (.HLP/.CNT), and +# will ignore any .CHM file. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin (although you can use +# the htmlhelp.h 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. +# +# - RCFL=-DASCIICTLS (Windows only) +# Uses ASCII rather than Unicode to specify the tab control in +# the resource file. Probably most useful when compiling with +# Cygnus/mingw32, whose resource compiler may have less of a +# problem with it. +# +# - XFLAGS=-DTELNET_DEFAULT +# Causes PuTTY to default to the Telnet protocol (in the absence +# of Default Settings and so on to the contrary). Normally PuTTY +# will default to SSH. +# +# - 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. +# + +CC = @CC@ + +CFLAGS = @CFLAGS@ @PUTTYCFLAGS@ @CPPFLAGS@ @DEFS@ @GTK_CFLAGS@ -I.././ \ + -I../charset/ -I../windows/ -I../unix/ -I../macosx/ +XLDFLAGS = @LDFLAGS@ @LIBS@ @GTK_LIBS@ +ULDFLAGS = @LDFLAGS@ @LIBS@ +INSTALL=@INSTALL@ +INSTALL_PROGRAM=$(INSTALL) +INSTALL_DATA=$(INSTALL) +prefix=@prefix@ +exec_prefix=@exec_prefix@ +bindir=@bindir@ +datarootdir=@datarootdir@ +mandir=@mandir@ +man1dir=$(mandir)/man1 + + +.SUFFIXES: + + +all: @all_targets@ +all-cli: plink pscp psftp puttygen +all-gtk: pterm putty puttytel + +plink: be_all_s.o cmdline.o cproxy.o ldisc.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o raw.o rlogin.o settings.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + telnet.o time.o timing.o tree234.o ux_x11.o uxagentc.o \ + uxcons.o uxgss.o uxmisc.o uxnet.o uxnoise.o uxplink.o \ + uxproxy.o uxsel.o uxser.o uxsignal.o uxstore.o version.o \ + wildcard.o x11fwd.o + $(CC) -o $@ be_all_s.o cmdline.o cproxy.o ldisc.o logging.o misc.o \ + pgssapi.o pinger.o portfwd.o proxy.o raw.o rlogin.o \ + settings.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o \ + sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o \ + sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o \ + sshsha.o sshzlib.o telnet.o time.o timing.o tree234.o \ + ux_x11.o uxagentc.o uxcons.o uxgss.o uxmisc.o uxnet.o \ + uxnoise.o uxplink.o uxproxy.o uxsel.o uxser.o uxsignal.o \ + uxstore.o version.o wildcard.o x11fwd.o $(ULDFLAGS) + +pscp: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o pscp.o settings.o sftp.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + time.o timing.o tree234.o uxagentc.o uxcons.o uxgss.o \ + uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o uxsftp.o \ + uxstore.o version.o wildcard.o x11fwd.o + $(CC) -o $@ be_none.o cmdline.o cproxy.o int64.o logging.o misc.o \ + pgssapi.o pinger.o portfwd.o proxy.o pscp.o settings.o \ + sftp.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o time.o timing.o tree234.o uxagentc.o uxcons.o \ + uxgss.o uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o \ + uxsftp.o uxstore.o version.o wildcard.o x11fwd.o $(ULDFLAGS) + +psftp: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o psftp.o settings.o sftp.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + time.o timing.o tree234.o uxagentc.o uxcons.o uxgss.o \ + uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o uxsftp.o \ + uxstore.o version.o wildcard.o x11fwd.o + $(CC) -o $@ be_none.o cmdline.o cproxy.o int64.o logging.o misc.o \ + pgssapi.o pinger.o portfwd.o proxy.o psftp.o settings.o \ + sftp.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o time.o timing.o tree234.o uxagentc.o uxcons.o \ + uxgss.o uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o \ + uxsftp.o uxstore.o version.o wildcard.o x11fwd.o $(ULDFLAGS) + +pterm: be_none.o cmdline.o config.o dialog.o fromucs.o gtkcfg.o gtkcols.o \ + gtkdlg.o gtkfont.o gtkwin.o ldisc.o ldiscucs.o localenc.o \ + logging.o macenc.o mimeenc.o minibidi.o misc.o nocproxy.o \ + nogss.o sbcs.o sbcsdat.o sercfg.o settings.o slookup.o \ + terminal.o time.o timing.o toucs.o tree234.o utf8.o uxcfg.o \ + uxmisc.o uxprint.o uxpterm.o uxpty.o uxsel.o uxsignal.o \ + uxstore.o uxucs.o version.o wcwidth.o xenc.o xkeysym.o \ + xpmptcfg.o xpmpterm.o + $(CC) -o $@ be_none.o cmdline.o config.o dialog.o fromucs.o gtkcfg.o \ + gtkcols.o gtkdlg.o gtkfont.o gtkwin.o ldisc.o ldiscucs.o \ + localenc.o logging.o macenc.o mimeenc.o minibidi.o misc.o \ + nocproxy.o nogss.o sbcs.o sbcsdat.o sercfg.o settings.o \ + slookup.o terminal.o time.o timing.o toucs.o tree234.o \ + utf8.o uxcfg.o uxmisc.o uxprint.o uxpterm.o uxpty.o uxsel.o \ + uxsignal.o uxstore.o uxucs.o version.o wcwidth.o xenc.o \ + xkeysym.o xpmptcfg.o xpmpterm.o $(XLDFLAGS) + +putty: be_all_s.o cmdline.o config.o cproxy.o dialog.o fromucs.o gtkcfg.o \ + gtkcols.o gtkdlg.o gtkfont.o gtkwin.o ldisc.o ldiscucs.o \ + localenc.o logging.o macenc.o mimeenc.o minibidi.o misc.o \ + pgssapi.o pinger.o portfwd.o proxy.o raw.o rlogin.o sbcs.o \ + sbcsdat.o sercfg.o settings.o slookup.o ssh.o sshaes.o \ + ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o sshdes.o \ + sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o sshrand.o \ + sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o telnet.o \ + terminal.o time.o timing.o toucs.o tree234.o utf8.o ux_x11.o \ + uxagentc.o uxcfg.o uxgss.o uxmisc.o uxnet.o uxnoise.o \ + uxprint.o uxproxy.o uxputty.o uxsel.o uxser.o uxsignal.o \ + uxstore.o uxucs.o version.o wcwidth.o wildcard.o x11fwd.o \ + xenc.o xkeysym.o xpmpucfg.o xpmputty.o + $(CC) -o $@ be_all_s.o cmdline.o config.o cproxy.o dialog.o \ + fromucs.o gtkcfg.o gtkcols.o gtkdlg.o gtkfont.o gtkwin.o \ + ldisc.o ldiscucs.o localenc.o logging.o macenc.o mimeenc.o \ + minibidi.o misc.o pgssapi.o pinger.o portfwd.o proxy.o raw.o \ + rlogin.o sbcs.o sbcsdat.o sercfg.o settings.o slookup.o \ + ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o telnet.o terminal.o time.o timing.o toucs.o \ + tree234.o utf8.o ux_x11.o uxagentc.o uxcfg.o uxgss.o \ + uxmisc.o uxnet.o uxnoise.o uxprint.o uxproxy.o uxputty.o \ + uxsel.o uxser.o uxsignal.o uxstore.o uxucs.o version.o \ + wcwidth.o wildcard.o x11fwd.o xenc.o xkeysym.o xpmpucfg.o \ + xpmputty.o $(XLDFLAGS) + +puttygen: cmdgen.o import.o misc.o notiming.o sshaes.o sshbn.o sshdes.o \ + sshdss.o sshdssg.o sshmd5.o sshprime.o sshpubk.o sshrand.o \ + sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o time.o \ + tree234.o uxcons.o uxgen.o uxmisc.o uxnoise.o uxstore.o \ + version.o + $(CC) -o $@ cmdgen.o import.o misc.o notiming.o sshaes.o sshbn.o \ + sshdes.o sshdss.o sshdssg.o sshmd5.o sshprime.o sshpubk.o \ + sshrand.o sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o \ + time.o tree234.o uxcons.o uxgen.o uxmisc.o uxnoise.o \ + uxstore.o version.o $(ULDFLAGS) + +puttytel: be_nos_s.o cmdline.o config.o dialog.o fromucs.o gtkcfg.o \ + gtkcols.o gtkdlg.o gtkfont.o gtkwin.o ldisc.o ldiscucs.o \ + localenc.o logging.o macenc.o mimeenc.o minibidi.o misc.o \ + nocproxy.o nogss.o pinger.o proxy.o raw.o rlogin.o sbcs.o \ + sbcsdat.o sercfg.o settings.o slookup.o telnet.o terminal.o \ + time.o timing.o toucs.o tree234.o utf8.o uxcfg.o uxmisc.o \ + uxnet.o uxprint.o uxproxy.o uxputty.o uxsel.o uxser.o \ + uxsignal.o uxstore.o uxucs.o version.o wcwidth.o xenc.o \ + xkeysym.o xpmpucfg.o xpmputty.o + $(CC) -o $@ be_nos_s.o cmdline.o config.o dialog.o fromucs.o \ + gtkcfg.o gtkcols.o gtkdlg.o gtkfont.o gtkwin.o ldisc.o \ + ldiscucs.o localenc.o logging.o macenc.o mimeenc.o \ + minibidi.o misc.o nocproxy.o nogss.o pinger.o proxy.o raw.o \ + rlogin.o sbcs.o sbcsdat.o sercfg.o settings.o slookup.o \ + telnet.o terminal.o time.o timing.o toucs.o tree234.o utf8.o \ + uxcfg.o uxmisc.o uxnet.o uxprint.o uxproxy.o uxputty.o \ + uxsel.o uxser.o uxsignal.o uxstore.o uxucs.o version.o \ + wcwidth.o xenc.o xkeysym.o xpmpucfg.o xpmputty.o $(XLDFLAGS) + +be_all_s.o: ../be_all_s.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_all_s.c +be_none.o: ../be_none.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_none.c +be_nos_s.o: ../be_nos_s.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_nos_s.c +cmdgen.o: ../cmdgen.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cmdgen.c +cmdline.o: ../cmdline.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cmdline.c +config.o: ../config.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../config.c +cproxy.o: ../cproxy.c ../putty.h ../ssh.h ../network.h ../proxy.h \ + ../puttyps.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cproxy.c +dialog.o: ../dialog.c ../putty.h ../dialog.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../dialog.c +fromucs.o: ../charset/fromucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/fromucs.c +gtkcfg.o: ../unix/gtkcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkcfg.c +gtkcols.o: ../unix/gtkcols.c ../unix/gtkcols.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkcols.c +gtkdlg.o: ../unix/gtkdlg.c ../unix/gtkcols.h ../unix/gtkfont.h ../putty.h \ + ../storage.h ../dialog.h ../tree234.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkdlg.c +gtkfont.o: ../unix/gtkfont.c ../putty.h ../unix/gtkfont.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkfont.c +gtkwin.o: ../unix/gtkwin.c ../putty.h ../terminal.h ../unix/gtkfont.h \ + ../puttyps.h ../network.h ../misc.h ../tree234.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkwin.c +import.o: ../import.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../import.c +int64.o: ../int64.c ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../int64.c +ldisc.o: ../ldisc.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \ + ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ldisc.c +ldiscucs.o: ../ldiscucs.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \ + ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ldiscucs.c +localenc.o: ../charset/localenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/localenc.c +logging.o: ../logging.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../logging.c +macenc.o: ../charset/macenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/macenc.c +mimeenc.o: ../charset/mimeenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/mimeenc.c +minibidi.o: ../minibidi.c ../misc.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../minibidi.c +misc.o: ../misc.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../misc.c +nocproxy.o: ../nocproxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nocproxy.c +nogss.o: ../nogss.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nogss.c +notiming.o: ../notiming.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../notiming.c +osxctrls.o: ../macosx/osxctrls.m ../putty.h ../dialog.h ../macosx/osxclass.h \ + ../tree234.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxctrls.m +osxdlg.o: ../macosx/osxdlg.m ../putty.h ../storage.h ../dialog.h \ + ../macosx/osxclass.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxdlg.m +osxmain.o: ../macosx/osxmain.m ../putty.h ../macosx/osxclass.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxmain.m +osxsel.o: ../macosx/osxsel.m ../putty.h ../macosx/osxclass.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxsel.m +osxwin.o: ../macosx/osxwin.m ../putty.h ../terminal.h ../macosx/osxclass.h \ + ../puttyps.h ../network.h ../misc.h ../tree234.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxwin.m +pgssapi.o: ../pgssapi.c ../putty.h ../pgssapi.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pgssapi.c +pinger.o: ../pinger.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pinger.c +portfwd.o: ../portfwd.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../portfwd.c +proxy.o: ../proxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../proxy.c +pscp.o: ../pscp.c ../putty.h ../psftp.h ../ssh.h ../sftp.h ../storage.h \ + ../int64.h ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pscp.c +psftp.o: ../psftp.c ../putty.h ../psftp.h ../storage.h ../ssh.h ../sftp.h \ + ../int64.h ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../psftp.c +raw.o: ../raw.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../raw.c +rlogin.o: ../rlogin.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../rlogin.c +sbcs.o: ../charset/sbcs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/sbcs.c +sbcsdat.o: ../charset/sbcsdat.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/sbcsdat.c +sercfg.o: ../sercfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sercfg.c +settings.o: ../settings.c ../putty.h ../storage.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../settings.c +sftp.o: ../sftp.c ../misc.h ../int64.h ../tree234.h ../sftp.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sftp.c +sizetip.o: ../windows/sizetip.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/sizetip.c +slookup.o: ../charset/slookup.c ../charset/charset.h ../charset/internal.h \ + ../charset/enum.c ../charset/sbcsdat.c ../charset/utf8.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/slookup.c +ssh.o: ../ssh.c ../putty.h ../tree234.h ../ssh.h ../sshgssc.h ../sshgss.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h ../int64.h \ + ../pgssapi.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh.c +sshaes.o: ../sshaes.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshaes.c +ssharcf.o: ../ssharcf.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssharcf.c +sshblowf.o: ../sshblowf.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshblowf.c +sshbn.o: ../sshbn.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshbn.c +sshcrc.o: ../sshcrc.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshcrc.c +sshcrcda.o: ../sshcrcda.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshcrcda.c +sshdes.o: ../sshdes.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdes.c +sshdh.o: ../sshdh.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdh.c +sshdss.o: ../sshdss.c ../ssh.h ../misc.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdss.c +sshdssg.o: ../sshdssg.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdssg.c +sshgssc.o: ../sshgssc.c ../putty.h ../sshgssc.h ../misc.h ../puttyps.h \ + ../network.h ../pgssapi.h ../sshgss.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../tree234.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshgssc.c +sshmd5.o: ../sshmd5.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshmd5.c +sshprime.o: ../sshprime.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshprime.c +sshpubk.o: ../sshpubk.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshpubk.c +sshrand.o: ../sshrand.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrand.c +sshrsa.o: ../sshrsa.c ../ssh.h ../misc.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrsa.c +sshrsag.o: ../sshrsag.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrsag.c +sshsh256.o: ../sshsh256.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsh256.c +sshsh512.o: ../sshsh512.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsh512.c +sshsha.o: ../sshsha.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsha.c +sshzlib.o: ../sshzlib.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshzlib.c +telnet.o: ../telnet.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../telnet.c +terminal.o: ../terminal.c ../putty.h ../terminal.h ../puttyps.h ../network.h \ + ../misc.h ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../terminal.c +testback.o: ../testback.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../testback.c +time.o: ../time.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../time.c +timing.o: ../timing.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../timing.c +toucs.o: ../charset/toucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/toucs.c +tree234.o: ../tree234.c ../puttymem.h ../tree234.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../tree234.c +utf8.o: ../charset/utf8.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/utf8.c +ux_x11.o: ../unix/ux_x11.c ../putty.h ../ssh.h ../network.h ../puttyps.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/ux_x11.c +uxagentc.o: ../unix/uxagentc.c ../putty.h ../misc.h ../tree234.h \ + ../puttymem.h ../puttyps.h ../network.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxagentc.c +uxcfg.o: ../unix/uxcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxcfg.c +uxcons.o: ../unix/uxcons.c ../putty.h ../storage.h ../ssh.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxcons.c +uxgen.o: ../unix/uxgen.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxgen.c +uxgss.o: ../unix/uxgss.c ../putty.h ../pgssapi.h ../sshgss.h ../sshgssc.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxgss.c +uxmisc.o: ../unix/uxmisc.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxmisc.c +uxnet.o: ../unix/uxnet.c ../putty.h ../network.h ../tree234.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxnet.c +uxnoise.o: ../unix/uxnoise.c ../putty.h ../ssh.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxnoise.c +uxplink.o: ../unix/uxplink.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxplink.c +uxprint.o: ../unix/uxprint.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxprint.c +uxproxy.o: ../unix/uxproxy.c ../tree234.h ../putty.h ../network.h ../proxy.h \ + ../puttyps.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxproxy.c +uxpterm.o: ../unix/uxpterm.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpterm.c +uxpty.o: ../unix/uxpty.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpty.c +uxputty.o: ../unix/uxputty.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxputty.c +uxsel.o: ../unix/uxsel.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsel.c +uxser.o: ../unix/uxser.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxser.c +uxsftp.o: ../unix/uxsftp.c ../putty.h ../ssh.h ../psftp.h ../int64.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsftp.c +uxsignal.o: ../unix/uxsignal.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsignal.c +uxstore.o: ../unix/uxstore.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxstore.c +uxucs.o: ../unix/uxucs.c ../putty.h ../charset/charset.h ../terminal.h \ + ../misc.h ../puttyps.h ../network.h ../tree234.h \ + ../puttymem.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxucs.c +wcwidth.o: ../wcwidth.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../wcwidth.c +wildcard.o: ../wildcard.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../wildcard.c +wincfg.o: ../windows/wincfg.c ../putty.h ../dialog.h ../storage.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincfg.c +wincons.o: ../windows/wincons.c ../putty.h ../storage.h ../ssh.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincons.c +winctrls.o: ../windows/winctrls.c ../putty.h ../misc.h ../dialog.h \ + ../puttyps.h ../network.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../tree234.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winctrls.c +windefs.o: ../windows/windefs.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/windefs.c +windlg.o: ../windows/windlg.c ../putty.h ../ssh.h ../windows/win_res.h \ + ../storage.h ../dialog.h ../puttyps.h ../network.h ../misc.h \ + ../puttymem.h ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/windlg.c +window.o: ../windows/window.c ../putty.h ../terminal.h ../storage.h \ + ../windows/win_res.h ../puttyps.h ../network.h ../misc.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/window.c +wingss.o: ../windows/wingss.c ../putty.h ../pgssapi.h ../sshgss.h \ + ../sshgssc.h ../misc.h ../puttyps.h ../network.h \ + ../puttymem.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wingss.c +winhandl.o: ../windows/winhandl.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winhandl.c +winhelp.o: ../windows/winhelp.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winhelp.c +winjump.o: ../windows/winjump.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winjump.c +winmisc.o: ../windows/winmisc.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winmisc.c +winnet.o: ../windows/winnet.c ../putty.h ../network.h ../tree234.h \ + ../puttyps.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnet.c +winnoise.o: ../windows/winnoise.c ../putty.h ../ssh.h ../storage.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnoise.c +winnojmp.o: ../windows/winnojmp.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnojmp.c +winpgen.o: ../windows/winpgen.c ../putty.h ../ssh.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgen.c +winpgnt.o: ../windows/winpgnt.c ../putty.h ../ssh.h ../misc.h ../tree234.h \ + ../puttyps.h ../network.h ../puttymem.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgnt.c +winpgntc.o: ../windows/winpgntc.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgntc.c +winplink.o: ../windows/winplink.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winplink.c +winprint.o: ../windows/winprint.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winprint.c +winproxy.o: ../windows/winproxy.c ../tree234.h ../putty.h ../network.h \ + ../proxy.h ../puttyps.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winproxy.c +winser.o: ../windows/winser.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winser.c +winsftp.o: ../windows/winsftp.c ../putty.h ../psftp.h ../ssh.h ../int64.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winsftp.c +winstore.o: ../windows/winstore.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winstore.c +wintime.o: ../windows/wintime.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wintime.c +winucs.o: ../windows/winucs.c ../putty.h ../terminal.h ../misc.h \ + ../puttyps.h ../network.h ../tree234.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winucs.c +winutils.o: ../windows/winutils.c ../putty.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winutils.c +winx11.o: ../windows/winx11.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winx11.c +x11fwd.o: ../x11fwd.c ../putty.h ../ssh.h ../tree234.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../x11fwd.c +xenc.o: ../charset/xenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/xenc.c +xkeysym.o: ../unix/xkeysym.c ../misc.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xkeysym.c +xpmptcfg.o: ../unix/xpmptcfg.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmptcfg.c +xpmpterm.o: ../unix/xpmpterm.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmpterm.c +xpmpucfg.o: ../unix/xpmpucfg.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmpucfg.c +xpmputty.o: ../unix/xpmputty.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmputty.c + +version.o: FORCE + if test -z "$(VER)" && (cd ..; md5sum -c manifest); then \ + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) `cat ../version.def` -c ../version.c; \ + else \ + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) $(VER) -c ../version.c; \ + fi +install: + mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir) + $(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/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" + +clean: + rm -f *.o plink pscp psftp pterm putty puttygen puttytel + +distclean: clean + rm -f config.status config.cache config.log configure.lineno \ + config.status.lineno Makefile + +FORCE: diff --git a/putty/UNIX/MAKEFILE.UX b/putty/UNIX/MAKEFILE.UX new file mode 100644 index 0000000..ac582b3 --- /dev/null +++ b/putty/UNIX/MAKEFILE.UX @@ -0,0 +1,776 @@ +# Makefile for putty under Unix. +# +# This file was created by `mkfiles.pl' from the `Recipe' file. +# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead. +# +# Extra options you can set: +# +# - VER="-DSNAPSHOT=1999-01-25 -DSVN_REV=1234" +# Generates executables whose About box report them as being a +# development snapshot. SVN_REV is a Subversion revision number. +# +# - VER=-DRELEASE=0.43 +# Generates executables whose About box report them as being a +# release version. +# +# - COMPAT=-DAUTO_WINSOCK (Windows only) +# Causes PuTTY to assume that includes its own WinSock +# header file, so that it won't try to include . +# +# - COMPAT=-DWINSOCK_TWO (Windows only) +# Causes the PuTTY utilities to include instead of +# , except Plink which _needs_ WinSock 2 so it already +# does this. +# +# - COMPAT=-DNO_SECURITY (Windows only) +# Disables Pageant's use of , which is not available +# with some development environments (such as older versions of +# the Cygwin/mingw GNU toolchain). This means that 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). +# +# - COMPAT=-DNO_MULTIMON (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. This means that PuTTY's +# full-screen mode (configurable to work on Alt-Enter) will +# not behave usefully in a multi-monitor environment. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin. +# +# - COMPAT=-DNO_HTMLHELP (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. The resulting binary +# will only look for an old-style WinHelp file (.HLP/.CNT), and +# will ignore any .CHM file. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin (although you can use +# the htmlhelp.h 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. +# +# - RCFL=-DASCIICTLS (Windows only) +# Uses ASCII rather than Unicode to specify the tab control in +# the resource file. Probably most useful when compiling with +# Cygnus/mingw32, whose resource compiler may have less of a +# problem with it. +# +# - XFLAGS=-DTELNET_DEFAULT +# Causes PuTTY to default to the Telnet protocol (in the absence +# of Default Settings and so on to the contrary). Normally PuTTY +# will default to SSH. +# +# - 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. +# + +# You can define this path to point at your tools if you need to +# TOOLPATH = /opt/gcc/bin +CC = $(TOOLPATH)cc + +-include Makefile.local + +unexport CFLAGS # work around a weird issue with krb5-config + +CFLAGS = -O2 -Wall -Werror -g -I.././ -I../charset/ -I../windows/ -I../unix/ \ + -I../macosx/ -D _FILE_OFFSET_BITS=64 +ULDFLAGS = $(LDFLAGS) +INSTALL=install +INSTALL_PROGRAM=$(INSTALL) +INSTALL_DATA=$(INSTALL) +prefix=/usr/local +exec_prefix=$(prefix) +bindir=$(exec_prefix)/bin +mandir=$(prefix)/man +man1dir=$(mandir)/man1 + + +.SUFFIXES: + + +all: plink pscp psftp puttygen + +plink: be_all_s.o cmdline.o cproxy.o ldisc.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o raw.o rlogin.o settings.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + telnet.o time.o timing.o tree234.o ux_x11.o uxagentc.o \ + uxcons.o uxgss.o uxmisc.o uxnet.o uxnoise.o uxplink.o \ + uxproxy.o uxsel.o uxser.o uxsignal.o uxstore.o version.o \ + wildcard.o x11fwd.o + $(CC) -o $@ be_all_s.o cmdline.o cproxy.o ldisc.o logging.o misc.o \ + pgssapi.o pinger.o portfwd.o proxy.o raw.o rlogin.o \ + settings.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o \ + sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o \ + sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o \ + sshsha.o sshzlib.o telnet.o time.o timing.o tree234.o \ + ux_x11.o uxagentc.o uxcons.o uxgss.o uxmisc.o uxnet.o \ + uxnoise.o uxplink.o uxproxy.o uxsel.o uxser.o uxsignal.o \ + uxstore.o version.o wildcard.o x11fwd.o $(ULDFLAGS) + +pscp: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o pscp.o settings.o sftp.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + time.o timing.o tree234.o uxagentc.o uxcons.o uxgss.o \ + uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o uxsftp.o \ + uxstore.o version.o wildcard.o x11fwd.o + $(CC) -o $@ be_none.o cmdline.o cproxy.o int64.o logging.o misc.o \ + pgssapi.o pinger.o portfwd.o proxy.o pscp.o settings.o \ + sftp.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o time.o timing.o tree234.o uxagentc.o uxcons.o \ + uxgss.o uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o \ + uxsftp.o uxstore.o version.o wildcard.o x11fwd.o $(ULDFLAGS) + +psftp: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o psftp.o settings.o sftp.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + time.o timing.o tree234.o uxagentc.o uxcons.o uxgss.o \ + uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o uxsftp.o \ + uxstore.o version.o wildcard.o x11fwd.o + $(CC) -o $@ be_none.o cmdline.o cproxy.o int64.o logging.o misc.o \ + pgssapi.o pinger.o portfwd.o proxy.o psftp.o settings.o \ + sftp.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o time.o timing.o tree234.o uxagentc.o uxcons.o \ + uxgss.o uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o \ + uxsftp.o uxstore.o version.o wildcard.o x11fwd.o $(ULDFLAGS) + +puttygen: cmdgen.o import.o misc.o notiming.o sshaes.o sshbn.o sshdes.o \ + sshdss.o sshdssg.o sshmd5.o sshprime.o sshpubk.o sshrand.o \ + sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o time.o \ + tree234.o uxcons.o uxgen.o uxmisc.o uxnoise.o uxstore.o \ + version.o + $(CC) -o $@ cmdgen.o import.o misc.o notiming.o sshaes.o sshbn.o \ + sshdes.o sshdss.o sshdssg.o sshmd5.o sshprime.o sshpubk.o \ + sshrand.o sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o \ + time.o tree234.o uxcons.o uxgen.o uxmisc.o uxnoise.o \ + uxstore.o version.o $(ULDFLAGS) + +be_all_s.o: ../be_all_s.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_all_s.c +be_none.o: ../be_none.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_none.c +be_nos_s.o: ../be_nos_s.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_nos_s.c +cmdgen.o: ../cmdgen.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cmdgen.c +cmdline.o: ../cmdline.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cmdline.c +config.o: ../config.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../config.c +cproxy.o: ../cproxy.c ../putty.h ../ssh.h ../network.h ../proxy.h \ + ../puttyps.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cproxy.c +dialog.o: ../dialog.c ../putty.h ../dialog.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../dialog.c +fromucs.o: ../charset/fromucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/fromucs.c +gtkcfg.o: ../unix/gtkcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkcfg.c +gtkcols.o: ../unix/gtkcols.c ../unix/gtkcols.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkcols.c +gtkdlg.o: ../unix/gtkdlg.c ../unix/gtkcols.h ../unix/gtkfont.h ../putty.h \ + ../storage.h ../dialog.h ../tree234.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkdlg.c +gtkfont.o: ../unix/gtkfont.c ../putty.h ../unix/gtkfont.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkfont.c +gtkwin.o: ../unix/gtkwin.c ../putty.h ../terminal.h ../unix/gtkfont.h \ + ../puttyps.h ../network.h ../misc.h ../tree234.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkwin.c +import.o: ../import.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../import.c +int64.o: ../int64.c ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../int64.c +ldisc.o: ../ldisc.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \ + ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ldisc.c +ldiscucs.o: ../ldiscucs.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \ + ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ldiscucs.c +localenc.o: ../charset/localenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/localenc.c +logging.o: ../logging.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../logging.c +macenc.o: ../charset/macenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/macenc.c +mimeenc.o: ../charset/mimeenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/mimeenc.c +minibidi.o: ../minibidi.c ../misc.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../minibidi.c +misc.o: ../misc.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../misc.c +nocproxy.o: ../nocproxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nocproxy.c +nogss.o: ../nogss.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nogss.c +notiming.o: ../notiming.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../notiming.c +osxctrls.o: ../macosx/osxctrls.m ../putty.h ../dialog.h ../macosx/osxclass.h \ + ../tree234.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxctrls.m +osxdlg.o: ../macosx/osxdlg.m ../putty.h ../storage.h ../dialog.h \ + ../macosx/osxclass.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxdlg.m +osxmain.o: ../macosx/osxmain.m ../putty.h ../macosx/osxclass.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxmain.m +osxsel.o: ../macosx/osxsel.m ../putty.h ../macosx/osxclass.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxsel.m +osxwin.o: ../macosx/osxwin.m ../putty.h ../terminal.h ../macosx/osxclass.h \ + ../puttyps.h ../network.h ../misc.h ../tree234.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxwin.m +pgssapi.o: ../pgssapi.c ../putty.h ../pgssapi.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pgssapi.c +pinger.o: ../pinger.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pinger.c +portfwd.o: ../portfwd.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../portfwd.c +proxy.o: ../proxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../proxy.c +pscp.o: ../pscp.c ../putty.h ../psftp.h ../ssh.h ../sftp.h ../storage.h \ + ../int64.h ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pscp.c +psftp.o: ../psftp.c ../putty.h ../psftp.h ../storage.h ../ssh.h ../sftp.h \ + ../int64.h ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../psftp.c +raw.o: ../raw.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../raw.c +rlogin.o: ../rlogin.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../rlogin.c +sbcs.o: ../charset/sbcs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/sbcs.c +sbcsdat.o: ../charset/sbcsdat.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/sbcsdat.c +sercfg.o: ../sercfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sercfg.c +settings.o: ../settings.c ../putty.h ../storage.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../settings.c +sftp.o: ../sftp.c ../misc.h ../int64.h ../tree234.h ../sftp.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sftp.c +sizetip.o: ../windows/sizetip.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/sizetip.c +slookup.o: ../charset/slookup.c ../charset/charset.h ../charset/internal.h \ + ../charset/enum.c ../charset/sbcsdat.c ../charset/utf8.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/slookup.c +ssh.o: ../ssh.c ../putty.h ../tree234.h ../ssh.h ../sshgssc.h ../sshgss.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h ../int64.h \ + ../pgssapi.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh.c +sshaes.o: ../sshaes.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshaes.c +ssharcf.o: ../ssharcf.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssharcf.c +sshblowf.o: ../sshblowf.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshblowf.c +sshbn.o: ../sshbn.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshbn.c +sshcrc.o: ../sshcrc.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshcrc.c +sshcrcda.o: ../sshcrcda.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshcrcda.c +sshdes.o: ../sshdes.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdes.c +sshdh.o: ../sshdh.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdh.c +sshdss.o: ../sshdss.c ../ssh.h ../misc.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdss.c +sshdssg.o: ../sshdssg.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdssg.c +sshgssc.o: ../sshgssc.c ../putty.h ../sshgssc.h ../misc.h ../puttyps.h \ + ../network.h ../pgssapi.h ../sshgss.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../tree234.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshgssc.c +sshmd5.o: ../sshmd5.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshmd5.c +sshprime.o: ../sshprime.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshprime.c +sshpubk.o: ../sshpubk.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshpubk.c +sshrand.o: ../sshrand.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrand.c +sshrsa.o: ../sshrsa.c ../ssh.h ../misc.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrsa.c +sshrsag.o: ../sshrsag.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrsag.c +sshsh256.o: ../sshsh256.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsh256.c +sshsh512.o: ../sshsh512.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsh512.c +sshsha.o: ../sshsha.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsha.c +sshzlib.o: ../sshzlib.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshzlib.c +telnet.o: ../telnet.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../telnet.c +terminal.o: ../terminal.c ../putty.h ../terminal.h ../puttyps.h ../network.h \ + ../misc.h ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../terminal.c +testback.o: ../testback.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../testback.c +time.o: ../time.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../time.c +timing.o: ../timing.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../timing.c +toucs.o: ../charset/toucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/toucs.c +tree234.o: ../tree234.c ../puttymem.h ../tree234.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../tree234.c +utf8.o: ../charset/utf8.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/utf8.c +ux_x11.o: ../unix/ux_x11.c ../putty.h ../ssh.h ../network.h ../puttyps.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/ux_x11.c +uxagentc.o: ../unix/uxagentc.c ../putty.h ../misc.h ../tree234.h \ + ../puttymem.h ../puttyps.h ../network.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxagentc.c +uxcfg.o: ../unix/uxcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxcfg.c +uxcons.o: ../unix/uxcons.c ../putty.h ../storage.h ../ssh.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxcons.c +uxgen.o: ../unix/uxgen.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxgen.c +uxgss.o: ../unix/uxgss.c ../putty.h ../pgssapi.h ../sshgss.h ../sshgssc.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxgss.c +uxmisc.o: ../unix/uxmisc.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxmisc.c +uxnet.o: ../unix/uxnet.c ../putty.h ../network.h ../tree234.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxnet.c +uxnoise.o: ../unix/uxnoise.c ../putty.h ../ssh.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxnoise.c +uxplink.o: ../unix/uxplink.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxplink.c +uxprint.o: ../unix/uxprint.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxprint.c +uxproxy.o: ../unix/uxproxy.c ../tree234.h ../putty.h ../network.h ../proxy.h \ + ../puttyps.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxproxy.c +uxpterm.o: ../unix/uxpterm.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpterm.c +uxpty.o: ../unix/uxpty.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpty.c +uxputty.o: ../unix/uxputty.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxputty.c +uxsel.o: ../unix/uxsel.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsel.c +uxser.o: ../unix/uxser.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxser.c +uxsftp.o: ../unix/uxsftp.c ../putty.h ../ssh.h ../psftp.h ../int64.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsftp.c +uxsignal.o: ../unix/uxsignal.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsignal.c +uxstore.o: ../unix/uxstore.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxstore.c +uxucs.o: ../unix/uxucs.c ../putty.h ../charset/charset.h ../terminal.h \ + ../misc.h ../puttyps.h ../network.h ../tree234.h \ + ../puttymem.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxucs.c +version.o: ../version.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../version.c +wcwidth.o: ../wcwidth.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../wcwidth.c +wildcard.o: ../wildcard.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../wildcard.c +wincfg.o: ../windows/wincfg.c ../putty.h ../dialog.h ../storage.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincfg.c +wincons.o: ../windows/wincons.c ../putty.h ../storage.h ../ssh.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincons.c +winctrls.o: ../windows/winctrls.c ../putty.h ../misc.h ../dialog.h \ + ../puttyps.h ../network.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../tree234.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winctrls.c +windefs.o: ../windows/windefs.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/windefs.c +windlg.o: ../windows/windlg.c ../putty.h ../ssh.h ../windows/win_res.h \ + ../storage.h ../dialog.h ../puttyps.h ../network.h ../misc.h \ + ../puttymem.h ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/windlg.c +window.o: ../windows/window.c ../putty.h ../terminal.h ../storage.h \ + ../windows/win_res.h ../puttyps.h ../network.h ../misc.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/window.c +wingss.o: ../windows/wingss.c ../putty.h ../pgssapi.h ../sshgss.h \ + ../sshgssc.h ../misc.h ../puttyps.h ../network.h \ + ../puttymem.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wingss.c +winhandl.o: ../windows/winhandl.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winhandl.c +winhelp.o: ../windows/winhelp.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winhelp.c +winjump.o: ../windows/winjump.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winjump.c +winmisc.o: ../windows/winmisc.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winmisc.c +winnet.o: ../windows/winnet.c ../putty.h ../network.h ../tree234.h \ + ../puttyps.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnet.c +winnoise.o: ../windows/winnoise.c ../putty.h ../ssh.h ../storage.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnoise.c +winnojmp.o: ../windows/winnojmp.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnojmp.c +winpgen.o: ../windows/winpgen.c ../putty.h ../ssh.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgen.c +winpgnt.o: ../windows/winpgnt.c ../putty.h ../ssh.h ../misc.h ../tree234.h \ + ../puttyps.h ../network.h ../puttymem.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgnt.c +winpgntc.o: ../windows/winpgntc.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgntc.c +winplink.o: ../windows/winplink.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winplink.c +winprint.o: ../windows/winprint.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winprint.c +winproxy.o: ../windows/winproxy.c ../tree234.h ../putty.h ../network.h \ + ../proxy.h ../puttyps.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winproxy.c +winser.o: ../windows/winser.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winser.c +winsftp.o: ../windows/winsftp.c ../putty.h ../psftp.h ../ssh.h ../int64.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winsftp.c +winstore.o: ../windows/winstore.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winstore.c +wintime.o: ../windows/wintime.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wintime.c +winucs.o: ../windows/winucs.c ../putty.h ../terminal.h ../misc.h \ + ../puttyps.h ../network.h ../tree234.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winucs.c +winutils.o: ../windows/winutils.c ../putty.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winutils.c +winx11.o: ../windows/winx11.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winx11.c +x11fwd.o: ../x11fwd.c ../putty.h ../ssh.h ../tree234.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../x11fwd.c +xenc.o: ../charset/xenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/xenc.c +xkeysym.o: ../unix/xkeysym.c ../misc.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xkeysym.c +xpmptcfg.o: ../unix/xpmptcfg.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmptcfg.c +xpmpterm.o: ../unix/xpmpterm.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmpterm.c +xpmpucfg.o: ../unix/xpmpucfg.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmpucfg.c +xpmputty.o: ../unix/xpmputty.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmputty.c + + +clean: + rm -f *.o plink pscp psftp puttygen + +FORCE: diff --git a/putty/UNIX/UNIX.H b/putty/UNIX/UNIX.H new file mode 100644 index 0000000..198085f --- /dev/null +++ b/putty/UNIX/UNIX.H @@ -0,0 +1,186 @@ +#ifndef PUTTY_UNIX_H +#define PUTTY_UNIX_H + +#ifdef HAVE_CONFIG_H +# include "uxconfig.h" /* Space to hide it from mkfiles.pl */ +#endif + +#include /* for FILENAME_MAX */ +#include /* C99 int types */ +#ifndef NO_LIBDL +#include /* Dynamic library loading */ +#endif /* NO_LIBDL */ +#include "charset.h" + +struct Filename { + char path[FILENAME_MAX]; +}; +FILE *f_open(struct Filename, char const *, int); + +struct FontSpec { + char name[256]; +}; + +typedef void *Context; /* FIXME: probably needs changing */ + +typedef int OSSocket; +#define OSSOCKET_DEFINED /* stop network.h using its default */ + +extern Backend pty_backend; + +typedef uint32_t uint32; /* C99: uint32_t defined in stdint.h */ +#define PUTTY_UINT32_DEFINED + +/* + * Under GTK, we send MA_CLICK _and_ MA_2CLK, or MA_CLICK _and_ + * MA_3CLK, when a button is pressed for the second or third time. + */ +#define MULTICLICK_ONLY_EVENT 0 + +/* + * Under GTK, there is no context help available. + */ +#define HELPCTX(x) P(NULL) +#define FILTER_KEY_FILES NULL /* FIXME */ +#define FILTER_DYNLIB_FILES NULL /* FIXME */ + +/* + * Under X, selection data must not be NUL-terminated. + */ +#define SELECTION_NUL_TERMINATED 0 + +/* + * Under X, copying to the clipboard terminates lines with just LF. + */ +#define SEL_NL { 10 } + +/* Simple wraparound timer function */ +unsigned long getticks(void); /* based on gettimeofday(2) */ +#define GETTICKCOUNT getticks +#define TICKSPERSEC 1000 /* we choose to use milliseconds */ +#define CURSORBLINK 450 /* no standard way to set this */ +/* getticks() works using gettimeofday(), so it's vulnerable to system clock + * changes causing chaos. Therefore, we provide a compensation mechanism. */ +#define TIMING_SYNC +#define TIMING_SYNC_ANOW +extern long tickcount_offset; + +#define WCHAR wchar_t +#define BYTE unsigned char + +/* + * Unix-specific global flag + * + * FLAG_STDERR_TTY indicates that standard error might be a terminal and + * might get its configuration munged, so anything trying to output plain + * text (i.e. with newlines in it) will need to put it back into cooked + * mode first. Applications setting this flag should also call + * stderr_tty_init() before messing with any terminal modes, and can call + * premsg() before outputting text to stderr and postmsg() afterwards. + */ +#define FLAG_STDERR_TTY 0x1000 + +/* Things pty.c needs from pterm.c */ +char *get_x_display(void *frontend); +int font_dimension(void *frontend, int which);/* 0 for width, 1 for height */ +long get_windowid(void *frontend); + +/* Things gtkdlg.c needs from pterm.c */ +void *get_window(void *frontend); /* void * to avoid depending on gtk.h */ + +/* Things pterm.c needs from gtkdlg.c */ +int do_config_box(const char *title, Config *cfg, + int midsession, int protcfginfo); +void fatal_message_box(void *window, char *msg); +void about_box(void *window); +void *eventlogstuff_new(void); +void showeventlog(void *estuff, void *parentwin); +void logevent_dlg(void *estuff, const char *string); +int reallyclose(void *frontend); + +/* Things pterm.c needs from {ptermm,uxputty}.c */ +char *make_default_wintitle(char *hostname); +int process_nonoption_arg(char *arg, Config *cfg, int *allow_launch); + +/* pterm.c needs this special function in xkeysym.c */ +int keysym_to_unicode(int keysym); + +/* Things uxstore.c needs from pterm.c */ +char *x_get_default(const char *key); + +/* Things uxstore.c provides to pterm.c */ +void provide_xrm_string(char *string); + +/* Things provided by uxcons.c */ +struct termios; +void stderr_tty_init(void); +void premsg(struct termios *); +void postmsg(struct termios *); + +/* The interface used by uxsel.c */ +void uxsel_init(void); +typedef int (*uxsel_callback_fn)(int fd, int event); +void uxsel_set(int fd, int rwx, uxsel_callback_fn callback); +void uxsel_del(int fd); +int select_result(int fd, int event); +int first_fd(int *state, int *rwx); +int next_fd(int *state, int *rwx); +/* The following are expected to be provided _to_ uxsel.c by the frontend */ +int uxsel_input_add(int fd, int rwx); /* returns an id */ +void uxsel_input_remove(int id); + +/* uxcfg.c */ +struct controlbox; +void unix_setup_config_box(struct controlbox *b, int midsession, int protocol); + +/* gtkcfg.c */ +void gtk_setup_config_box(struct controlbox *b, int midsession, void *window); + +/* + * In the Unix Unicode layer, DEFAULT_CODEPAGE is a special value + * which causes mb_to_wc and wc_to_mb to call _libc_ rather than + * libcharset. That way, we can interface the various charsets + * supported by libcharset with the one supported by mbstowcs and + * wcstombs (which will be the character set in which stuff read + * from the command line or config files is assumed to be encoded). + */ +#define DEFAULT_CODEPAGE 0xFFFF +#define CP_UTF8 CS_UTF8 /* from libcharset */ + +#define strnicmp strncasecmp +#define stricmp strcasecmp + +/* BSD-semantics version of signal(), and another helpful function */ +void (*putty_signal(int sig, void (*func)(int)))(int); +void block_signal(int sig, int block_it); + +/* uxmisc.c */ +int cloexec(int); + +/* + * Exports from unicode.c. + */ +struct unicode_data; +int init_ucs(struct unicode_data *ucsdata, char *line_codepage, + int utf8_override, int font_charset, int vtmode); + +/* + * Spare function exported directly from uxnet.c. + */ +void *sk_getxdmdata(void *sock, int *lenp); + +/* + * General helpful Unix stuff: more helpful version of the FD_SET + * macro, which also handles maxfd. + */ +#define FD_SET_MAX(fd, max, set) do { \ + FD_SET(fd, &set); \ + if (max < fd + 1) max = fd + 1; \ +} while (0) + +/* + * Exports from winser.c. + */ +extern Backend serial_backend; + +#endif diff --git a/putty/UNIX/UXAGENTC.C b/putty/UNIX/UXAGENTC.C new file mode 100644 index 0000000..74cd9cd --- /dev/null +++ b/putty/UNIX/UXAGENTC.C @@ -0,0 +1,162 @@ +/* + * SSH agent client code. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "misc.h" +#include "tree234.h" +#include "puttymem.h" + +int agent_exists(void) +{ + if (getenv("SSH_AUTH_SOCK") != NULL) + return TRUE; + return FALSE; +} + +static tree234 *agent_connections; +struct agent_connection { + int fd; + char *retbuf; + char sizebuf[4]; + int retsize, retlen; + void (*callback)(void *, void *, int); + void *callback_ctx; +}; +static int agent_conncmp(void *av, void *bv) +{ + struct agent_connection *a = (struct agent_connection *) av; + struct agent_connection *b = (struct agent_connection *) bv; + if (a->fd < b->fd) + return -1; + if (a->fd > b->fd) + return +1; + return 0; +} +static int agent_connfind(void *av, void *bv) +{ + int afd = *(int *) av; + struct agent_connection *b = (struct agent_connection *) bv; + if (afd < b->fd) + return -1; + if (afd > b->fd) + return +1; + return 0; +} + +static int agent_select_result(int fd, int event) +{ + int ret; + struct agent_connection *conn; + + assert(event == 1); /* not selecting for anything but R */ + + conn = find234(agent_connections, &fd, agent_connfind); + if (!conn) { + uxsel_del(fd); + return 1; + } + + ret = read(fd, conn->retbuf+conn->retlen, conn->retsize-conn->retlen); + if (ret <= 0) { + if (conn->retbuf != conn->sizebuf) sfree(conn->retbuf); + conn->retbuf = NULL; + conn->retlen = 0; + goto done; + } + conn->retlen += ret; + if (conn->retsize == 4 && conn->retlen == 4) { + conn->retsize = GET_32BIT(conn->retbuf); + if (conn->retsize <= 0) { + conn->retbuf = NULL; + conn->retlen = 0; + goto done; + } + conn->retsize += 4; + assert(conn->retbuf == conn->sizebuf); + conn->retbuf = snewn(conn->retsize, char); + memcpy(conn->retbuf, conn->sizebuf, 4); + } + + if (conn->retlen < conn->retsize) + return 0; /* more data to come */ + + done: + /* + * We have now completed the agent query. Do the callback, and + * clean up. (Of course we don't free retbuf, since ownership + * of that passes to the callback.) + */ + conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen); + uxsel_del(fd); + close(fd); + del234(agent_connections, conn); + sfree(conn); + return 0; +} + +int agent_query(void *in, int inlen, void **out, int *outlen, + void (*callback)(void *, void *, int), void *callback_ctx) +{ + char *name; + int sock; + struct sockaddr_un addr; + int done; + struct agent_connection *conn; + + name = getenv("SSH_AUTH_SOCK"); + if (!name) + goto failure; + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket(PF_UNIX)"); + exit(1); + } + + cloexec(sock); + + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, name, sizeof(addr.sun_path)); + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(sock); + goto failure; + } + + for (done = 0; done < inlen ;) { + int ret = write(sock, (char *)in + done, inlen - done); + if (ret <= 0) { + close(sock); + goto failure; + } + done += ret; + } + + if (!agent_connections) + agent_connections = newtree234(agent_conncmp); + + conn = snew(struct agent_connection); + conn->fd = sock; + conn->retbuf = conn->sizebuf; + conn->retsize = 4; + conn->retlen = 0; + conn->callback = callback; + conn->callback_ctx = callback_ctx; + add234(agent_connections, conn); + + uxsel_set(sock, 1, agent_select_result); + return 0; + + failure: + *out = NULL; + *outlen = 0; + return 1; +} diff --git a/putty/UNIX/UXCFG.C b/putty/UNIX/UXCFG.C new file mode 100644 index 0000000..2e872cc --- /dev/null +++ b/putty/UNIX/UXCFG.C @@ -0,0 +1,80 @@ +/* + * uxcfg.c - the Unix-specific parts of the PuTTY configuration + * box. + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +void unix_setup_config_box(struct controlbox *b, int midsession, int protocol) +{ + struct controlset *s; + union control *c; + + /* + * The Config structure contains two Unix-specific elements + * which are not configured in here: stamp_utmp and + * login_shell. This is because pterm does not put up a + * configuration box right at the start, which is the only time + * when these elements would be useful to configure. + */ + + /* + * On Unix, we don't have a drop-down list for the printer + * control. + */ + s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing"); + assert(s->ncontrols == 1 && s->ctrls[0]->generic.type == CTRL_EDITBOX); + s->ctrls[0]->editbox.has_list = 0; + + /* + * Unix supports a local-command proxy. This also means we must + * adjust the text on the `Telnet command' control. + */ + if (!midsession) { + int i; + s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); + for (i = 0; i < s->ncontrols; i++) { + c = s->ctrls[i]; + if (c->generic.type == CTRL_RADIO && + c->generic.context.i == offsetof(Config, proxy_type)) { + assert(c->generic.handler == dlg_stdradiobutton_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 == + offsetof(Config, proxy_telnet_command)) { + assert(c->generic.handler == dlg_stdeditbox_handler); + sfree(c->generic.label); + c->generic.label = dupstr("Telnet command, or local" + " proxy command"); + break; + } + } + } + + /* + * Serial back end is available on Unix. However, we have to + * mask out a couple of the configuration options: mark and + * space parity are not conveniently supported, and neither is + * DSR/DTR flow control. + */ + if (!midsession || (protocol == PROT_SERIAL)) + ser_setup_config_box(b, midsession, 0x07, 0x07); +} diff --git a/putty/UNIX/UXCONS.C b/putty/UNIX/UXCONS.C new file mode 100644 index 0000000..2e71df4 --- /dev/null +++ b/putty/UNIX/UXCONS.C @@ -0,0 +1,440 @@ +/* + * uxcons.c: various interactive-prompt routines shared between the + * Unix console PuTTY tools + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "storage.h" +#include "ssh.h" + +int console_batch_mode = FALSE; + +static void *console_logctx = NULL; + +static struct termios orig_termios_stderr; +static int stderr_is_a_tty; + +void stderr_tty_init() +{ + /* Ensure that if stderr is a tty, we can get it back to a sane state. */ + if ((flags & FLAG_STDERR_TTY) && isatty(STDERR_FILENO)) { + stderr_is_a_tty = TRUE; + tcgetattr(STDERR_FILENO, &orig_termios_stderr); + } +} + +void premsg(struct termios *cf) +{ + if (stderr_is_a_tty) { + tcgetattr(STDERR_FILENO, cf); + tcsetattr(STDERR_FILENO, TCSADRAIN, &orig_termios_stderr); + } +} +void postmsg(struct termios *cf) +{ + if (stderr_is_a_tty) + tcsetattr(STDERR_FILENO, TCSADRAIN, cf); +} + +/* + * Clean up and exit. + */ +void cleanup_exit(int code) +{ + /* + * Clean up. + */ + sk_cleanup(); + random_save_seed(); + exit(code); +} + +void set_busy_status(void *frontend, int status) +{ +} + +void update_specials_menu(void *frontend) +{ +} + +void notify_remote_exit(void *frontend) +{ +} + +void timer_change_notify(long next) +{ +} + +int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, + char *keystr, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx) +{ + int ret; + + static const char absentmsg_batch[] = + "The server's host key is not cached. You have no guarantee\n" + "that the server is the computer you think it is.\n" + "The server's %s key fingerprint is:\n" + "%s\n" + "Connection abandoned.\n"; + static const char absentmsg[] = + "The server's host key is not cached. You have no guarantee\n" + "that the server is the computer you think it is.\n" + "The server's %s key fingerprint is:\n" + "%s\n" + "If you trust this host, enter \"y\" to add the key to\n" + "PuTTY's cache and carry on connecting.\n" + "If you want to carry on connecting just once, without\n" + "adding the key to the cache, enter \"n\".\n" + "If you do not trust this host, press Return to abandon the\n" + "connection.\n" + "Store key in cache? (y/n) "; + + static const char wrongmsg_batch[] = + "WARNING - POTENTIAL SECURITY BREACH!\n" + "The server's host key does not match the one PuTTY has\n" + "cached. This means that either the server administrator\n" + "has changed the host key, or you have actually connected\n" + "to another computer pretending to be the server.\n" + "The new %s key fingerprint is:\n" + "%s\n" + "Connection abandoned.\n"; + static const char wrongmsg[] = + "WARNING - POTENTIAL SECURITY BREACH!\n" + "The server's host key does not match the one PuTTY has\n" + "cached. This means that either the server administrator\n" + "has changed the host key, or you have actually connected\n" + "to another computer pretending to be the server.\n" + "The new %s key fingerprint is:\n" + "%s\n" + "If you were expecting this change and trust the new key,\n" + "enter \"y\" to update PuTTY's cache and continue connecting.\n" + "If you want to carry on connecting but without updating\n" + "the cache, enter \"n\".\n" + "If you want to abandon the connection completely, press\n" + "Return to cancel. Pressing Return is the ONLY guaranteed\n" + "safe choice.\n" + "Update cached key? (y/n, Return cancels connection) "; + + static const char abandoned[] = "Connection abandoned.\n"; + + char line[32]; + struct termios cf; + + /* + * Verify the key. + */ + ret = verify_host_key(host, port, keytype, keystr); + + if (ret == 0) /* success - key matched OK */ + return 1; + + premsg(&cf); + if (ret == 2) { /* key was different */ + if (console_batch_mode) { + fprintf(stderr, wrongmsg_batch, keytype, fingerprint); + return 0; + } + fprintf(stderr, wrongmsg, keytype, fingerprint); + fflush(stderr); + } + if (ret == 1) { /* key was absent */ + if (console_batch_mode) { + fprintf(stderr, absentmsg_batch, keytype, fingerprint); + return 0; + } + fprintf(stderr, absentmsg, keytype, fingerprint); + fflush(stderr); + } + + { + struct termios oldmode, newmode; + tcgetattr(0, &oldmode); + newmode = oldmode; + newmode.c_lflag |= ECHO | ISIG | ICANON; + tcsetattr(0, TCSANOW, &newmode); + line[0] = '\0'; + if (read(0, line, sizeof(line) - 1) <= 0) + /* handled below */; + tcsetattr(0, TCSANOW, &oldmode); + } + + if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') { + if (line[0] == 'y' || line[0] == 'Y') + store_host_key(host, port, keytype, keystr); + postmsg(&cf); + return 1; + } else { + fprintf(stderr, abandoned); + postmsg(&cf); + return 0; + } +} + +/* + * Ask whether the selected algorithm is acceptable (since it was + * below the configured 'warn' threshold). + */ +int askalg(void *frontend, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msg[] = + "The first %s supported by the server is\n" + "%s, which is below the configured warning threshold.\n" + "Continue with connection? (y/n) "; + static const char msg_batch[] = + "The first %s supported by the server is\n" + "%s, which is below the configured warning threshold.\n" + "Connection abandoned.\n"; + static const char abandoned[] = "Connection abandoned.\n"; + + char line[32]; + struct termios cf; + + premsg(&cf); + if (console_batch_mode) { + fprintf(stderr, msg_batch, algtype, algname); + return 0; + } + + fprintf(stderr, msg, algtype, algname); + fflush(stderr); + + { + struct termios oldmode, newmode; + tcgetattr(0, &oldmode); + newmode = oldmode; + newmode.c_lflag |= ECHO | ISIG | ICANON; + tcsetattr(0, TCSANOW, &newmode); + line[0] = '\0'; + if (read(0, line, sizeof(line) - 1) <= 0) + /* handled below */; + tcsetattr(0, TCSANOW, &oldmode); + } + + if (line[0] == 'y' || line[0] == 'Y') { + postmsg(&cf); + return 1; + } else { + fprintf(stderr, abandoned); + postmsg(&cf); + return 0; + } +} + +/* + * Ask whether to wipe a session log file before writing to it. + * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). + */ +int askappend(void *frontend, Filename filename, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msgtemplate[] = + "The session log file \"%.*s\" already exists.\n" + "You can overwrite it with a new session log,\n" + "append your session log to the end of it,\n" + "or disable session logging for this session.\n" + "Enter \"y\" to wipe the file, \"n\" to append to it,\n" + "or just press Return to disable logging.\n" + "Wipe the log file? (y/n, Return cancels logging) "; + + static const char msgtemplate_batch[] = + "The session log file \"%.*s\" already exists.\n" + "Logging will not be enabled.\n"; + + char line[32]; + struct termios cf; + + premsg(&cf); + if (console_batch_mode) { + fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename.path); + fflush(stderr); + return 0; + } + fprintf(stderr, msgtemplate, FILENAME_MAX, filename.path); + fflush(stderr); + + { + struct termios oldmode, newmode; + tcgetattr(0, &oldmode); + newmode = oldmode; + newmode.c_lflag |= ECHO | ISIG | ICANON; + tcsetattr(0, TCSANOW, &newmode); + line[0] = '\0'; + if (read(0, line, sizeof(line) - 1) <= 0) + /* handled below */; + tcsetattr(0, TCSANOW, &oldmode); + } + + postmsg(&cf); + if (line[0] == 'y' || line[0] == 'Y') + return 2; + else if (line[0] == 'n' || line[0] == 'N') + return 1; + else + return 0; +} + +/* + * Warn about the obsolescent key file format. + * + * Uniquely among these functions, this one does _not_ expect a + * frontend handle. This means that if PuTTY is ported to a + * platform which requires frontend handles, this function will be + * an anomaly. Fortunately, the problem it addresses will not have + * been present on that platform, so it can plausibly be + * implemented as an empty function. + */ +void old_keyfile_warning(void) +{ + static const char message[] = + "You are loading an SSH-2 private key which has an\n" + "old version of the file format. This means your key\n" + "file is not fully tamperproof. Future versions of\n" + "PuTTY may stop supporting this private key format,\n" + "so we recommend you convert your key to the new\n" + "format.\n" + "\n" + "Once the key is loaded into PuTTYgen, you can perform\n" + "this conversion simply by saving it again.\n"; + + struct termios cf; + premsg(&cf); + fputs(message, stderr); + postmsg(&cf); +} + +void console_provide_logctx(void *logctx) +{ + console_logctx = logctx; +} + +void logevent(void *frontend, const char *string) +{ + struct termios cf; + premsg(&cf); + if (console_logctx) + log_eventlog(console_logctx, string); + postmsg(&cf); +} + +/* + * Special function to print text to the console for password + * prompts and the like. Uses /dev/tty or 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_prompt_text(FILE **confp, const char *data, int len) +{ + int i; + + if (!*confp) { + if ((*confp = fopen("/dev/tty", "w")) == NULL) + *confp = stderr; + } + + for (i = 0; i < len; i++) + if ((data[i] & 0x60) || (data[i] == '\n')) + fputc(data[i], *confp); + fflush(*confp); +} + +int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + size_t curr_prompt; + FILE *confp = NULL; + + /* + * Zero all the results, in case we abort half-way through. + */ + { + int i; + for (i = 0; i < p->n_prompts; i++) + memset(p->prompts[i]->result, 0, p->prompts[i]->result_len); + } + + if (p->n_prompts && console_batch_mode) + return 0; + + /* + * Preamble. + */ + /* We only print the `name' caption if we have to... */ + if (p->name_reqd && p->name) { + size_t l = strlen(p->name); + console_prompt_text(&confp, p->name, l); + if (p->name[l-1] != '\n') + console_prompt_text(&confp, "\n", 1); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + size_t l = strlen(p->instruction); + console_prompt_text(&confp, p->instruction, l); + if (p->instruction[l-1] != '\n') + console_prompt_text(&confp, "\n", 1); + } + + for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { + + struct termios oldmode, newmode; + int i; + prompt_t *pr = p->prompts[curr_prompt]; + + tcgetattr(0, &oldmode); + newmode = oldmode; + newmode.c_lflag |= ISIG | ICANON; + if (!pr->echo) + newmode.c_lflag &= ~ECHO; + else + newmode.c_lflag |= ECHO; + tcsetattr(0, TCSANOW, &newmode); + + console_prompt_text(&confp, pr->prompt, strlen(pr->prompt)); + + i = read(0, pr->result, pr->result_len - 1); + + tcsetattr(0, TCSANOW, &oldmode); + + if (i > 0 && pr->result[i-1] == '\n') + i--; + pr->result[i] = '\0'; + + if (!pr->echo) + console_prompt_text(&confp, "\n", 1); + + } + + if (confp && confp != stderr) + fclose(confp); + + return 1; /* success */ +} + +void frontend_keypress(void *handle) +{ + /* + * This is nothing but a stub, in console code. + */ + return; +} + +int is_interactive(void) +{ + return isatty(0); +} + +/* + * X11-forwarding-related things suitable for console. + */ + +char *platform_get_x_display(void) { + return dupstr(getenv("DISPLAY")); +} diff --git a/putty/UNIX/UXGEN.C b/putty/UNIX/UXGEN.C new file mode 100644 index 0000000..b1b4d19 --- /dev/null +++ b/putty/UNIX/UXGEN.C @@ -0,0 +1,38 @@ +/* + * uxgen.c: Unix implementation of get_heavy_noise() from cmdgen.c. + */ + +#include +#include +#include + +#include "putty.h" + +char *get_random_data(int len) +{ + char *buf = snewn(len, char); + int fd; + int ngot, ret; + + fd = open("/dev/random", O_RDONLY); + if (fd < 0) { + sfree(buf); + perror("puttygen: unable to open /dev/random"); + return NULL; + } + + ngot = 0; + while (ngot < len) { + ret = read(fd, buf+ngot, len-ngot); + if (ret < 0) { + close(fd); + perror("puttygen: unable to read /dev/random"); + return NULL; + } + ngot += ret; + } + + close(fd); + + return buf; +} diff --git a/putty/UNIX/UXGSS.C b/putty/UNIX/UXGSS.C new file mode 100644 index 0000000..1bb803c --- /dev/null +++ b/putty/UNIX/UXGSS.C @@ -0,0 +1,168 @@ +#include "putty.h" +#ifndef NO_GSSAPI +#include "pgssapi.h" +#include "sshgss.h" +#include "sshgssc.h" + +/* Unix code to set up the GSSAPI library list. */ + +#if !defined NO_LIBDL && !defined NO_GSSAPI + +const int ngsslibs = 4; +const char *const gsslibnames[4] = { + "libgssapi (Heimdal)", + "libgssapi_krb5 (MIT Kerberos)", + "libgss (Sun)", + "User-specified GSSAPI library", +}; +const struct keyvalwhere gsslibkeywords[] = { + { "libgssapi", 0, -1, -1 }, + { "libgssapi_krb5", 1, -1, -1 }, + { "libgss", 2, -1, -1 }, + { "custom", 3, -1, -1 }, +}; + +/* + * Run-time binding against a choice of GSSAPI implementations. We + * try loading several libraries, and produce an entry in + * ssh_gss_libraries[] for each one. + */ + +static void gss_init(struct ssh_gss_library *lib, void *dlhandle, + int id, const char *msg) +{ + lib->id = id; + lib->gsslogmsg = msg; + lib->handle = dlhandle; + +#define BIND_GSS_FN(name) \ + lib->u.gssapi.name = (t_gss_##name) dlsym(dlhandle, "gss_" #name) + + BIND_GSS_FN(delete_sec_context); + BIND_GSS_FN(display_status); + BIND_GSS_FN(get_mic); + BIND_GSS_FN(import_name); + BIND_GSS_FN(init_sec_context); + BIND_GSS_FN(release_buffer); + BIND_GSS_FN(release_cred); + BIND_GSS_FN(release_name); + +#undef BIND_GSS_FN + + ssh_gssapi_bind_fns(lib); +} + +/* Dynamically load gssapi libs. */ +struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg) +{ + void *gsslib; + 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 */ + if (cfg->ssh_gss_custom.path[0] && + (gsslib = dlopen(cfg->ssh_gss_custom.path, RTLD_LAZY)) != NULL) + gss_init(&list->libraries[list->nlibraries++], gsslib, + 3, dupprintf("Using GSSAPI from user-specified" + " library '%s'", cfg->ssh_gss_custom.path)); + + return list; +} + +void ssh_gss_cleanup(struct ssh_gss_liblist *list) +{ + int i; + + /* + * dlopen and dlclose are defined to employ reference counting + * in the case where the same library is repeatedly dlopened, so + * even in a multiple-sessions-per-process context it's safe to + * naively dlclose everything here without worrying about + * destroying it under the feet of another SSH instance still + * using it. + */ + for (i = 0; i < list->nlibraries; i++) { + dlclose(list->libraries[i].handle); + if (list->libraries[i].id == 3) { + /* The 'custom' id involves a dynamically allocated message. + * Note that we must cast away the 'const' to free it. */ + sfree((char *)list->libraries[i].gsslogmsg); + } + } + sfree(list->libraries); + sfree(list); +} + +#elif !defined NO_GSSAPI + +const int ngsslibs = 1; +const char *const gsslibnames[1] = { + "static", +}; +const struct keyvalwhere gsslibkeywords[] = { + { "static", 0, -1, -1 }, +}; + +/* + * Link-time binding against GSSAPI. Here we just construct a single + * library structure containing pointers to the functions we linked + * against. + */ + +#include + +/* Dynamically load gssapi libs. */ +struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg) +{ + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + + list->libraries = snew(struct ssh_gss_library); + list->nlibraries = 1; + + list->libraries[0].gsslogmsg = "Using statically linked GSSAPI"; + +#define BIND_GSS_FN(name) \ + list->libraries[0].u.gssapi.name = (t_gss_##name) gss_##name + + BIND_GSS_FN(delete_sec_context); + BIND_GSS_FN(display_status); + BIND_GSS_FN(get_mic); + BIND_GSS_FN(import_name); + BIND_GSS_FN(init_sec_context); + BIND_GSS_FN(release_buffer); + BIND_GSS_FN(release_cred); + BIND_GSS_FN(release_name); + +#undef BIND_GSS_FN + + ssh_gssapi_bind_fns(&list->libraries[0]); + + return list; +} + +void ssh_gss_cleanup(struct ssh_gss_liblist *list) +{ + sfree(list->libraries); + sfree(list); +} + +#endif /* NO_LIBDL */ + +#endif /* NO_GSSAPI */ diff --git a/putty/UNIX/UXMISC.C b/putty/UNIX/UXMISC.C new file mode 100644 index 0000000..4bda059 --- /dev/null +++ b/putty/UNIX/UXMISC.C @@ -0,0 +1,152 @@ +/* + * PuTTY miscellaneous Unix stuff + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" + +long tickcount_offset = 0; + +unsigned long getticks(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + /* + * We want to use milliseconds rather than microseconds, + * because we need a decent number of them to fit into a 32-bit + * word so it can be used for keepalives. + */ + return tv.tv_sec * 1000 + tv.tv_usec / 1000 + tickcount_offset; +} + +Filename filename_from_str(const char *str) +{ + Filename ret; + strncpy(ret.path, str, sizeof(ret.path)); + ret.path[sizeof(ret.path)-1] = '\0'; + return ret; +} + +const char *filename_to_str(const Filename *fn) +{ + return fn->path; +} + +int filename_equal(Filename f1, Filename f2) +{ + return !strcmp(f1.path, f2.path); +} + +int filename_is_null(Filename fn) +{ + return !*fn.path; +} + +#ifdef DEBUG +static FILE *debug_fp = NULL; + +void dputs(char *buf) +{ + if (!debug_fp) { + debug_fp = fopen("debug.log", "w"); + } + + write(1, buf, strlen(buf)); + + fputs(buf, debug_fp); + fflush(debug_fp); +} +#endif + +char *get_username(void) +{ + struct passwd *p; + uid_t uid = getuid(); + char *user, *ret = NULL; + + /* + * First, find who we think we are using getlogin. If this + * agrees with our uid, we'll go along with it. This should + * allow sharing of uids between several login names whilst + * coping correctly with people who have su'ed. + */ + user = getlogin(); + setpwent(); + if (user) + p = getpwnam(user); + else + p = NULL; + if (p && p->pw_uid == uid) { + /* + * The result of getlogin() really does correspond to + * our uid. Fine. + */ + ret = user; + } else { + /* + * If that didn't work, for whatever reason, we'll do + * the simpler version: look up our uid in the password + * file and map it straight to a name. + */ + p = getpwuid(uid); + if (!p) + return NULL; + ret = p->pw_name; + } + endpwent(); + + return dupstr(ret); +} + +/* + * Display the fingerprints of the PGP Master Keys to the user. + * (This is here rather than in uxcons because it's appropriate even for + * Unix GUI apps.) + */ +void pgp_fingerprints(void) +{ + fputs("These are the fingerprints of the PuTTY PGP Master Keys. They can\n" + "be used to establish a trust path from this executable to another\n" + "one. See the manual for more information.\n" + "(Note: these fingerprints have nothing to do with SSH!)\n" + "\n" + "PuTTY Master Key (RSA), 1024-bit:\n" + " " PGP_RSA_MASTER_KEY_FP "\n" + "PuTTY Master Key (DSA), 1024-bit:\n" + " " PGP_DSA_MASTER_KEY_FP "\n", stdout); +} + +/* + * Set FD_CLOEXEC on a file descriptor + */ +int cloexec(int fd) { + int fdflags; + + fdflags = fcntl(fd, F_GETFD); + if (fdflags == -1) return -1; + return fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC); +} + +FILE *f_open(struct Filename filename, char const *mode, int is_private) +{ + if (!is_private) { + return fopen(filename.path, mode); + } else { + int fd; + assert(mode[0] == 'w'); /* is_private is meaningless for read, + and tricky for append */ + fd = open(filename.path, O_WRONLY | O_CREAT | O_TRUNC, + 0700); + if (fd < 0) + return NULL; + return fdopen(fd, mode); + } +} diff --git a/putty/UNIX/UXNET.C b/putty/UNIX/UXNET.C new file mode 100644 index 0000000..0882638 --- /dev/null +++ b/putty/UNIX/UXNET.C @@ -0,0 +1,1435 @@ +/* + * Unix networking abstraction. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "network.h" +#include "tree234.h" + +/* Solaris needs for SIOCATMARK. */ +#ifndef SIOCATMARK +#include +#endif + +#ifndef X11_UNIX_PATH +# define X11_UNIX_PATH "/tmp/.X11-unix/X" +#endif + +/* + * Access to sockaddr types without breaking C strict aliasing rules. + */ +union sockaddr_union { +#ifdef NO_IPV6 + struct sockaddr_in storage; +#else + struct sockaddr_storage storage; + struct sockaddr_in6 sin6; +#endif + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_un su; +}; + +/* + * We used to typedef struct Socket_tag *Socket. + * + * Since we have made the networking abstraction slightly more + * abstract, Socket no longer means a tcp socket (it could mean + * an ssl socket). So now we must use Actual_Socket when we know + * we are talking about a tcp socket. + */ +typedef struct Socket_tag *Actual_Socket; + +/* + * Mutable state that goes with a SockAddr: stores information + * about where in the list of candidate IP(v*) addresses we've + * currently got to. + */ +typedef struct SockAddrStep_tag SockAddrStep; +struct SockAddrStep_tag { +#ifndef NO_IPV6 + struct addrinfo *ai; /* steps along addr->ais */ +#endif + int curraddr; +}; + +struct Socket_tag { + struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + const char *error; + int s; + Plug plug; + void *private_ptr; + bufchain output_data; + int connected; /* irrelevant for listening sockets */ + int writable; + int frozen; /* this causes readability notifications to be ignored */ + int frozen_readable; /* this means we missed at least one readability + * notification while we were frozen */ + int localhost_only; /* for listening sockets */ + char oobdata[1]; + int sending_oob; + int oobpending; /* is there OOB data available to read? */ + int oobinline; + int pending_error; /* in case send() returns error */ + int listener; + int nodelay, keepalive; /* for connect()-type sockets */ + int privport, port; /* and again */ + SockAddr addr; + SockAddrStep step; + /* + * We sometimes need pairs of Socket structures to be linked: + * if we are listening on the same IPv6 and v4 port, for + * example. So here we define `parent' and `child' pointers to + * track this link. + */ + Actual_Socket parent, child; +}; + +struct SockAddr_tag { + int refcount; + const char *error; + enum { UNRESOLVED, UNIX, IP } superfamily; +#ifndef NO_IPV6 + struct addrinfo *ais; /* Addresses IPv6 style. */ +#else + unsigned long *addresses; /* Addresses IPv4 style. */ + int naddresses; +#endif + char hostname[512]; /* Store an unresolved host name. */ +}; + +/* + * Which address family this address belongs to. AF_INET for IPv4; + * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has + * not been done and a simple host name is held in this SockAddr + * structure. + */ +#ifndef NO_IPV6 +#define SOCKADDR_FAMILY(addr, step) \ + ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \ + (addr)->superfamily == UNIX ? AF_UNIX : \ + (step).ai ? (step).ai->ai_family : AF_INET) +#else +#define SOCKADDR_FAMILY(addr, step) \ + ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \ + (addr)->superfamily == UNIX ? AF_UNIX : AF_INET) +#endif + +/* + * Start a SockAddrStep structure to step through multiple + * addresses. + */ +#ifndef NO_IPV6 +#define START_STEP(addr, step) \ + ((step).ai = (addr)->ais, (step).curraddr = 0) +#else +#define START_STEP(addr, step) \ + ((step).curraddr = 0) +#endif + +static tree234 *sktree; + +static void uxsel_tell(Actual_Socket s); + +static int cmpfortree(void *av, void *bv) +{ + Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv; + int as = a->s, bs = b->s; + if (as < bs) + return -1; + if (as > bs) + return +1; + if (a < b) + return -1; + if (a > b) + return +1; + return 0; +} + +static int cmpforsearch(void *av, void *bv) +{ + Actual_Socket b = (Actual_Socket) bv; + int as = *(int *)av, bs = b->s; + if (as < bs) + return -1; + if (as > bs) + return +1; + return 0; +} + +void sk_init(void) +{ + sktree = newtree234(cmpfortree); +} + +void sk_cleanup(void) +{ + Actual_Socket s; + int i; + + if (sktree) { + for (i = 0; (s = index234(sktree, i)) != NULL; i++) { + close(s->s); + } + } +} + +SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family) +{ + SockAddr ret = snew(struct SockAddr_tag); +#ifndef NO_IPV6 + struct addrinfo hints; + int err; +#else + unsigned long a; + struct hostent *h = NULL; + int n; +#endif + char realhost[8192]; + + /* Clear the structure and default to IPv4. */ + memset(ret, 0, sizeof(struct SockAddr_tag)); + ret->superfamily = UNRESOLVED; + *realhost = '\0'; + ret->error = NULL; + ret->refcount = 1; + +#ifndef NO_IPV6 + hints.ai_flags = AI_CANONNAME; + hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : + address_family == ADDRTYPE_IPV6 ? AF_INET6 : + AF_UNSPEC); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + hints.ai_addrlen = 0; + hints.ai_addr = NULL; + hints.ai_canonname = NULL; + hints.ai_next = NULL; + err = getaddrinfo(host, NULL, &hints, &ret->ais); + if (err != 0) { + ret->error = gai_strerror(err); + return ret; + } + ret->superfamily = IP; + *realhost = '\0'; + if (ret->ais->ai_canonname != NULL) + strncat(realhost, ret->ais->ai_canonname, sizeof(realhost) - 1); + else + strncat(realhost, host, sizeof(realhost) - 1); +#else + if ((a = inet_addr(host)) == (unsigned long)(in_addr_t)(-1)) { + /* + * Otherwise use the IPv4-only gethostbyname... (NOTE: + * we don't use gethostbyname as a fallback!) + */ + if (ret->superfamily == UNRESOLVED) { + /*debug(("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host)); */ + if ( (h = gethostbyname(host)) ) + ret->superfamily = IP; + } + if (ret->superfamily == UNRESOLVED) { + ret->error = (h_errno == HOST_NOT_FOUND || + h_errno == NO_DATA || + h_errno == NO_ADDRESS ? "Host does not exist" : + h_errno == TRY_AGAIN ? + "Temporary name service failure" : + "gethostbyname: unknown error"); + return ret; + } + /* This way we are always sure the h->h_name is valid :) */ + strncpy(realhost, h->h_name, sizeof(realhost)); + for (n = 0; h->h_addr_list[n]; n++); + ret->addresses = snewn(n, unsigned long); + ret->naddresses = n; + for (n = 0; n < ret->naddresses; n++) { + memcpy(&a, h->h_addr_list[n], sizeof(a)); + ret->addresses[n] = ntohl(a); + } + } else { + /* + * This must be a numeric IPv4 address because it caused a + * success return from inet_addr. + */ + ret->superfamily = IP; + strncpy(realhost, host, sizeof(realhost)); + ret->addresses = snew(unsigned long); + ret->naddresses = 1; + ret->addresses[0] = ntohl(a); + } +#endif + realhost[lenof(realhost)-1] = '\0'; + *canonicalname = snewn(1+strlen(realhost), char); + strcpy(*canonicalname, realhost); + return ret; +} + +SockAddr sk_nonamelookup(const char *host) +{ + SockAddr ret = snew(struct SockAddr_tag); + ret->error = NULL; + ret->superfamily = UNRESOLVED; + strncpy(ret->hostname, host, lenof(ret->hostname)); + ret->hostname[lenof(ret->hostname)-1] = '\0'; +#ifndef NO_IPV6 + ret->ais = NULL; +#else + ret->addresses = NULL; +#endif + ret->refcount = 1; + return ret; +} + +static int sk_nextaddr(SockAddr addr, SockAddrStep *step) +{ +#ifndef NO_IPV6 + if (step->ai && step->ai->ai_next) { + step->ai = step->ai->ai_next; + return TRUE; + } else + return FALSE; +#else + if (step->curraddr+1 < addr->naddresses) { + step->curraddr++; + return TRUE; + } else { + return FALSE; + } +#endif +} + +void sk_getaddr(SockAddr addr, char *buf, int buflen) +{ + /* XXX not clear what we should return for Unix-domain sockets; let's + * hope the question never arises */ + assert(addr->superfamily != UNIX); + if (addr->superfamily == UNRESOLVED) { + strncpy(buf, addr->hostname, buflen); + buf[buflen-1] = '\0'; + } else { +#ifndef NO_IPV6 + if (getnameinfo(addr->ais->ai_addr, addr->ais->ai_addrlen, buf, buflen, + NULL, 0, NI_NUMERICHOST) != 0) { + buf[0] = '\0'; + strncat(buf, "", buflen - 1); + } +#else + struct in_addr a; + SockAddrStep step; + START_STEP(addr, step); + assert(SOCKADDR_FAMILY(addr, step) == AF_INET); + a.s_addr = htonl(addr->addresses[0]); + strncpy(buf, inet_ntoa(a), buflen); + buf[buflen-1] = '\0'; +#endif + } +} + +int sk_hostname_is_local(char *name) +{ + return !strcmp(name, "localhost") || + !strcmp(name, "::1") || + !strncmp(name, "127.", 4); +} + +#define ipv4_is_loopback(addr) \ + (((addr).s_addr & htonl(0xff000000)) == htonl(0x7f000000)) + +static int sockaddr_is_loopback(struct sockaddr *sa) +{ + union sockaddr_union *u = (union sockaddr_union *)sa; + switch (u->sa.sa_family) { + case AF_INET: + return ipv4_is_loopback(u->sin.sin_addr); +#ifndef NO_IPV6 + case AF_INET6: + return IN6_IS_ADDR_LOOPBACK(&u->sin6.sin6_addr); +#endif + case AF_UNIX: + return TRUE; + default: + return FALSE; + } +} + +int sk_address_is_local(SockAddr addr) +{ + if (addr->superfamily == UNRESOLVED) + return 0; /* we don't know; assume not */ + else if (addr->superfamily == UNIX) + return 1; + else { +#ifndef NO_IPV6 + return sockaddr_is_loopback(addr->ais->ai_addr); +#else + struct in_addr a; + SockAddrStep step; + START_STEP(addr, step); + assert(SOCKADDR_FAMILY(addr, step) == AF_INET); + a.s_addr = htonl(addr->addresses[0]); + return ipv4_is_loopback(a); +#endif + } +} + +int sk_addrtype(SockAddr addr) +{ + SockAddrStep step; + int family; + START_STEP(addr, step); + family = SOCKADDR_FAMILY(addr, step); + + return (family == AF_INET ? ADDRTYPE_IPV4 : +#ifndef NO_IPV6 + family == AF_INET6 ? ADDRTYPE_IPV6 : +#endif + ADDRTYPE_NAME); +} + +void sk_addrcopy(SockAddr addr, char *buf) +{ + SockAddrStep step; + int family; + START_STEP(addr, step); + family = SOCKADDR_FAMILY(addr, step); + +#ifndef NO_IPV6 + if (family == AF_INET) + memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr, + sizeof(struct in_addr)); + else if (family == AF_INET6) + memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr, + sizeof(struct in6_addr)); + else + assert(FALSE); +#else + struct in_addr a; + + assert(family == AF_INET); + a.s_addr = htonl(addr->addresses[step.curraddr]); + memcpy(buf, (char*) &a.s_addr, 4); +#endif +} + +void sk_addr_free(SockAddr addr) +{ + if (--addr->refcount > 0) + return; +#ifndef NO_IPV6 + if (addr->ais != NULL) + freeaddrinfo(addr->ais); +#else + sfree(addr->addresses); +#endif + sfree(addr); +} + +SockAddr sk_addr_dup(SockAddr addr) +{ + addr->refcount++; + return addr; +} + +static Plug sk_tcp_plug(Socket sock, Plug p) +{ + Actual_Socket s = (Actual_Socket) sock; + Plug ret = s->plug; + if (p) + s->plug = p; + return ret; +} + +static void sk_tcp_flush(Socket s) +{ + /* + * We send data to the socket as soon as we can anyway, + * so we don't need to do anything here. :-) + */ +} + +static void sk_tcp_close(Socket s); +static int sk_tcp_write(Socket s, const char *data, int len); +static int sk_tcp_write_oob(Socket s, const char *data, int len); +static void sk_tcp_set_private_ptr(Socket s, void *ptr); +static void *sk_tcp_get_private_ptr(Socket s); +static void sk_tcp_set_frozen(Socket s, int is_frozen); +static const char *sk_tcp_socket_error(Socket s); + +static struct socket_function_table tcp_fn_table = { + sk_tcp_plug, + sk_tcp_close, + sk_tcp_write, + sk_tcp_write_oob, + sk_tcp_flush, + sk_tcp_set_private_ptr, + sk_tcp_get_private_ptr, + sk_tcp_set_frozen, + sk_tcp_socket_error +}; + +Socket sk_register(OSSocket sockfd, Plug plug) +{ + Actual_Socket ret; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &tcp_fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = 1; /* to start with */ + ret->sending_oob = 0; + ret->frozen = 1; + ret->frozen_readable = 0; + ret->localhost_only = 0; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->oobpending = FALSE; + ret->listener = 0; + ret->parent = ret->child = NULL; + ret->addr = NULL; + ret->connected = 1; + + ret->s = sockfd; + + if (ret->s < 0) { + ret->error = strerror(errno); + return (Socket) ret; + } + + ret->oobinline = 0; + + uxsel_tell(ret); + add234(sktree, ret); + + return (Socket) ret; +} + +static int try_connect(Actual_Socket sock) +{ + int s; + union sockaddr_union u; + const union sockaddr_union *sa; + int err = 0; + short localport; + int fl, salen, family; + + /* + * Remove the socket from the tree before we overwrite its + * internal socket id, because that forms part of the tree's + * sorting criterion. We'll add it back before exiting this + * function, whether we changed anything or not. + */ + del234(sktree, sock); + + if (sock->s >= 0) + close(sock->s); + + plug_log(sock->plug, 0, sock->addr, sock->port, NULL, 0); + + /* + * Open socket. + */ + family = SOCKADDR_FAMILY(sock->addr, sock->step); + assert(family != AF_UNSPEC); + s = socket(family, SOCK_STREAM, 0); + sock->s = s; + + if (s < 0) { + err = errno; + goto ret; + } + + cloexec(s); + + if (sock->oobinline) { + int b = TRUE; + setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b)); + } + + if (sock->nodelay) { + int b = TRUE; + setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b)); + } + + if (sock->keepalive) { + int b = TRUE; + 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 kernel picks) */ + + /* BSD IP stacks need sockaddr_in zeroed before filling in */ + memset(&u,'\0',sizeof(u)); + + /* We don't try to bind to a local address for UNIX domain sockets. (Why + * do we bother doing the bind when localport == 0 anyway?) */ + if (family != AF_UNIX) { + /* Loop round trying to bind */ + while (1) { + int retcode; + +#ifndef NO_IPV6 + if (family == AF_INET6) { + /* XXX use getaddrinfo to get a local address? */ + u.sin6.sin6_family = AF_INET6; + u.sin6.sin6_addr = in6addr_any; + u.sin6.sin6_port = htons(localport); + retcode = bind(s, &u.sa, sizeof(u.sin6)); + } else +#endif + { + assert(family == AF_INET); + u.sin.sin_family = AF_INET; + u.sin.sin_addr.s_addr = htonl(INADDR_ANY); + u.sin.sin_port = htons(localport); + retcode = bind(s, &u.sa, sizeof(u.sin)); + } + if (retcode >= 0) { + err = 0; + break; /* done */ + } else { + err = errno; + if (err != EADDRINUSE) /* failed, for a bad reason */ + break; + } + + if (localport == 0) + break; /* we're only looping once */ + localport--; + if (localport == 0) + break; /* we might have got to the end */ + } + + if (err) + goto ret; + } + + /* + * Connect to remote address. + */ + switch(family) { +#ifndef NO_IPV6 + case AF_INET: + /* XXX would be better to have got getaddrinfo() to fill in the port. */ + ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port = + htons(sock->port); + sa = (const union sockaddr_union *)sock->step.ai->ai_addr; + salen = sock->step.ai->ai_addrlen; + break; + case AF_INET6: + ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port = + htons(sock->port); + sa = (const union sockaddr_union *)sock->step.ai->ai_addr; + salen = sock->step.ai->ai_addrlen; + break; +#else + case AF_INET: + u.sin.sin_family = AF_INET; + u.sin.sin_addr.s_addr = htonl(sock->addr->addresses[sock->step.curraddr]); + u.sin.sin_port = htons((short) sock->port); + sa = &u; + salen = sizeof u.sin; + break; +#endif + case AF_UNIX: + assert(sock->port == 0); /* to catch confused people */ + assert(strlen(sock->addr->hostname) < sizeof u.su.sun_path); + u.su.sun_family = AF_UNIX; + strcpy(u.su.sun_path, sock->addr->hostname); + sa = &u; + salen = sizeof u.su; + break; + + default: + assert(0 && "unknown address family"); + exit(1); /* XXX: GCC doesn't understand assert() on some systems. */ + } + + fl = fcntl(s, F_GETFL); + if (fl != -1) + fcntl(s, F_SETFL, fl | O_NONBLOCK); + + if ((connect(s, &(sa->sa), salen)) < 0) { + if ( errno != EINPROGRESS ) { + err = errno; + goto ret; + } + } else { + /* + * If we _don't_ get EWOULDBLOCK, the connect has completed + * and we should set the socket as connected and writable. + */ + sock->connected = 1; + sock->writable = 1; + } + + uxsel_tell(sock); + + ret: + + /* + * No matter what happened, put the socket back in the tree. + */ + add234(sktree, sock); + + if (err) + plug_log(sock->plug, 1, sock->addr, sock->port, strerror(err), err); + return err; +} + +Socket sk_new(SockAddr addr, int port, int privport, int oobinline, + int nodelay, int keepalive, Plug plug) +{ + Actual_Socket ret; + int err; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &tcp_fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->connected = 0; /* to start with */ + ret->writable = 0; /* to start with */ + ret->sending_oob = 0; + ret->frozen = 0; + ret->frozen_readable = 0; + ret->localhost_only = 0; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobpending = FALSE; + ret->listener = 0; + ret->addr = addr; + START_STEP(ret->addr, ret->step); + ret->s = -1; + ret->oobinline = oobinline; + ret->nodelay = nodelay; + ret->keepalive = keepalive; + ret->privport = privport; + ret->port = port; + + err = 0; + do { + err = try_connect(ret); + } while (err && sk_nextaddr(ret->addr, &ret->step)); + + if (err) + ret->error = strerror(err); + + return (Socket) ret; +} + +Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int orig_address_family) +{ + int s; +#ifndef NO_IPV6 + struct addrinfo hints, *ai; + char portstr[6]; +#endif + union sockaddr_union u; + union sockaddr_union *addr; + int addrlen; + Actual_Socket ret; + int retcode; + int address_family; + int on = 1; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &tcp_fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = 0; /* to start with */ + ret->sending_oob = 0; + ret->frozen = 0; + ret->frozen_readable = 0; + ret->localhost_only = local_host_only; + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobpending = FALSE; + ret->listener = 1; + 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); + +#ifndef NO_IPV6 + /* Let's default to IPv6. + * If the stack doesn't support IPv6, we will fall back to IPv4. */ + if (address_family == AF_UNSPEC) address_family = AF_INET6; +#else + /* No other choice, default to IPv4 */ + if (address_family == AF_UNSPEC) address_family = AF_INET; +#endif + + /* + * Open socket. + */ + s = socket(address_family, SOCK_STREAM, 0); + +#ifndef NO_IPV6 + /* If the host doesn't support IPv6 try fallback to IPv4. */ + if (s < 0 && address_family == AF_INET6) { + address_family = AF_INET; + s = socket(address_family, SOCK_STREAM, 0); + } +#endif + + if (s < 0) { + ret->error = strerror(errno); + return (Socket) ret; + } + + cloexec(s); + + ret->oobinline = 0; + + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)); + + 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); + retcode = getaddrinfo(srcaddr, portstr, &hints, &ai); + 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); + if (retcode < 0) { + close(s); + ret->error = strerror(errno); + return (Socket) ret; + } + + if (listen(s, SOMAXCONN) < 0) { + close(s); + ret->error = strerror(errno); + return (Socket) ret; + } + +#ifndef NO_IPV6 + /* + * If we were given ADDRTYPE_UNSPEC, we must also create an + * IPv4 listening socket and link it to this one. + */ + if (address_family == AF_INET6 && orig_address_family == ADDRTYPE_UNSPEC) { + Actual_Socket other; + + other = (Actual_Socket) sk_newlistener(srcaddr, port, plug, + local_host_only, ADDRTYPE_IPV4); + + if (other) { + if (!other->error) { + other->parent = ret; + ret->child = other; + } else { + /* If we couldn't create a listening socket on IPv4 as well + * as IPv6, we must return an error overall. */ + close(s); + sfree(ret); + return (Socket) other; + } + } + } +#endif + + ret->s = s; + + uxsel_tell(ret); + add234(sktree, ret); + + return (Socket) ret; +} + +static void sk_tcp_close(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + + if (s->child) + sk_tcp_close((Socket)s->child); + + uxsel_del(s->s); + del234(sktree, s); + close(s->s); + if (s->addr) + sk_addr_free(s->addr); + sfree(s); +} + +void *sk_getxdmdata(void *sock, int *lenp) +{ + Actual_Socket s = (Actual_Socket) sock; + union sockaddr_union u; + socklen_t addrlen; + char *buf; + static unsigned int unix_addr = 0xFFFFFFFF; + + /* + * We must check that this socket really _is_ an Actual_Socket. + */ + if (s->fn != &tcp_fn_table) + return NULL; /* failure */ + + addrlen = sizeof(u); + if (getsockname(s->s, &u.sa, &addrlen) < 0) + return NULL; + switch(u.sa.sa_family) { + case AF_INET: + *lenp = 6; + buf = snewn(*lenp, char); + PUT_32BIT_MSB_FIRST(buf, ntohl(u.sin.sin_addr.s_addr)); + PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin.sin_port)); + break; +#ifndef NO_IPV6 + case AF_INET6: + *lenp = 6; + buf = snewn(*lenp, char); + if (IN6_IS_ADDR_V4MAPPED(&u.sin6.sin6_addr)) { + memcpy(buf, u.sin6.sin6_addr.s6_addr + 12, 4); + PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin6.sin6_port)); + } else + /* This is stupid, but it's what XLib does. */ + memset(buf, 0, 6); + break; +#endif + case AF_UNIX: + *lenp = 6; + buf = snewn(*lenp, char); + PUT_32BIT_MSB_FIRST(buf, unix_addr--); + PUT_16BIT_MSB_FIRST(buf+4, getpid()); + break; + + /* XXX IPV6 */ + + default: + return NULL; + } + + return buf; +} + +/* + * The function which tries to send on a socket once it's deemed + * writable. + */ +void try_send(Actual_Socket s) +{ + while (s->sending_oob || bufchain_size(&s->output_data) > 0) { + int nsent; + int err; + void *data; + int len, urgentflag; + + if (s->sending_oob) { + urgentflag = MSG_OOB; + len = s->sending_oob; + data = &s->oobdata; + } else { + urgentflag = 0; + bufchain_prefix(&s->output_data, &data, &len); + } + nsent = send(s->s, data, len, urgentflag); + noise_ultralight(nsent); + if (nsent <= 0) { + err = (nsent < 0 ? errno : 0); + if (err == EWOULDBLOCK) { + /* + * Perfectly normal: we've sent all we can for the moment. + */ + s->writable = FALSE; + return; + } else { + /* + * We unfortunately can't just call plug_closing(), + * because it's quite likely that we're currently + * _in_ a call from the code we'd be calling back + * to, so we'd have to make half the SSH code + * reentrant. Instead we flag a pending error on + * the socket, to be dealt with (by calling + * plug_closing()) at some suitable future moment. + */ + s->pending_error = err; + 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); + } + } + } + uxsel_tell(s); +} + +static int sk_tcp_write(Socket sock, const char *buf, int len) +{ + Actual_Socket s = (Actual_Socket) sock; + + /* + * Add the data to the buffer list on the socket. + */ + bufchain_add(&s->output_data, buf, len); + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); + + /* + * Update the select() status to correctly reflect whether or + * not we should be selecting for write. + */ + uxsel_tell(s); + + return bufchain_size(&s->output_data); +} + +static int sk_tcp_write_oob(Socket sock, const char *buf, int len) +{ + Actual_Socket s = (Actual_Socket) sock; + + /* + * 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 int net_select_result(int fd, int event) +{ + int ret; + char buf[20480]; /* nice big buffer for plenty of speed */ + Actual_Socket s; + u_long atmark; + + /* Find the Socket structure */ + s = find234(sktree, &fd, cmpforsearch); + if (!s) + return 1; /* boggle */ + + noise_ultralight(event); + + switch (event) { + case 4: /* exceptional */ + if (!s->oobinline) { + /* + * On a non-oobinline socket, this indicates that we + * can immediately perform an OOB read and get back OOB + * data, which we will send to the back end with + * type==2 (urgent data). + */ + ret = recv(s->s, buf, sizeof(buf), MSG_OOB); + noise_ultralight(ret); + if (ret <= 0) { + return plug_closing(s->plug, + ret == 0 ? "Internal networking trouble" : + strerror(errno), errno, 0); + } else { + /* + * Receiving actual data on a socket means we can + * stop falling back through the candidate + * addresses to connect to. + */ + if (s->addr) { + sk_addr_free(s->addr); + s->addr = NULL; + } + return plug_receive(s->plug, 2, buf, ret); + } + break; + } + + /* + * If we reach here, this is an oobinline socket, which + * means we should set s->oobpending and then deal with it + * when we get called for the readability event (which + * should also occur). + */ + s->oobpending = TRUE; + break; + case 1: /* readable; also acceptance */ + if (s->listener) { + /* + * On a listening socket, the readability event means a + * connection is ready to be accepted. + */ + union sockaddr_union su; + socklen_t addrlen = sizeof(su); + int t; /* socket of connection */ + int fl; + + memset(&su, 0, addrlen); + t = accept(s->s, &su.sa, &addrlen); + if (t < 0) { + break; + } + + fl = fcntl(t, F_GETFL); + if (fl != -1) + fcntl(t, F_SETFL, fl | O_NONBLOCK); + + if (s->localhost_only && + !sockaddr_is_loopback(&su.sa)) { + close(t); /* someone let nonlocal through?! */ + } else if (plug_accepting(s->plug, t)) { + 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) { + s->frozen_readable = 1; + break; + } + + /* + * We have received data on the socket. For an oobinline + * socket, this might be data _before_ an urgent pointer, + * in which case we send it to the back end with type==1 + * (data prior to urgent). + */ + if (s->oobinline && s->oobpending) { + atmark = 1; + if (ioctl(s->s, SIOCATMARK, &atmark) == 0 && atmark) + s->oobpending = FALSE; /* clear this indicator */ + } else + atmark = 1; + + ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0); + noise_ultralight(ret); + if (ret < 0) { + if (errno == EWOULDBLOCK) { + break; + } + } + if (ret < 0) { + /* + * An error at this point _might_ be an error reported + * by a non-blocking connect(). So before we return a + * panic status to the user, let's just see whether + * that's the case. + */ + int err = errno; + if (s->addr) { + plug_log(s->plug, 1, s->addr, s->port, strerror(err), err); + while (s->addr && sk_nextaddr(s->addr, &s->step)) { + err = try_connect(s); + } + } + if (err != 0) + return plug_closing(s->plug, strerror(err), err, 0); + } else if (0 == ret) { + return plug_closing(s->plug, NULL, 0, 0); + } else { + /* + * Receiving actual data on a socket means we can + * stop falling back through the candidate + * addresses to connect to. + */ + if (s->addr) { + sk_addr_free(s->addr); + s->addr = NULL; + } + return plug_receive(s->plug, atmark ? 0 : 1, buf, ret); + } + break; + case 2: /* writable */ + if (!s->connected) { + /* + * select() reports a socket as _writable_ when an + * asynchronous connection is completed. + */ + s->connected = s->writable = 1; + uxsel_tell(s); + break; + } else { + int bufsize_before, bufsize_after; + s->writable = 1; + bufsize_before = s->sending_oob + bufchain_size(&s->output_data); + try_send(s); + bufsize_after = s->sending_oob + bufchain_size(&s->output_data); + if (bufsize_after < bufsize_before) + plug_sent(s->plug, bufsize_after); + } + break; + } + + return 1; +} + +/* + * Deal with socket errors detected in try_send(). + */ +void net_pending_errors(void) +{ + int i; + Actual_Socket s; + + /* + * This might be a fiddly business, because it's just possible + * that handling a pending error on one socket might cause + * others to be closed. (I can't think of any reason this might + * happen in current SSH implementation, but to maintain + * generality of this network layer I'll assume the worst.) + * + * So what we'll do is search the socket list for _one_ socket + * with a pending error, and then handle it, and then search + * the list again _from the beginning_. Repeat until we make a + * pass with no socket errors present. That way we are + * protected against the socket list changing under our feet. + */ + + do { + for (i = 0; (s = index234(sktree, i)) != NULL; i++) { + if (s->pending_error) { + /* + * An error has occurred on this socket. Pass it to the + * plug. + */ + plug_closing(s->plug, strerror(s->pending_error), + s->pending_error, 0); + break; + } + } + } while (s); +} + +/* + * Each socket abstraction contains a `void *' private field in + * which the client can keep state. + */ +static void sk_tcp_set_private_ptr(Socket sock, void *ptr) +{ + Actual_Socket s = (Actual_Socket) sock; + s->private_ptr = ptr; +} + +static void *sk_tcp_get_private_ptr(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + return s->private_ptr; +} + +/* + * Special error values are returned from sk_namelookup and sk_new + * if there's a problem. These functions extract an error message, + * or return NULL if there's no problem. + */ +const char *sk_addr_error(SockAddr addr) +{ + return addr->error; +} +static const char *sk_tcp_socket_error(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + return s->error; +} + +static void sk_tcp_set_frozen(Socket sock, int is_frozen) +{ + Actual_Socket s = (Actual_Socket) sock; + if (s->frozen == is_frozen) + return; + s->frozen = is_frozen; + if (!is_frozen && s->frozen_readable) { + char c; + recv(s->s, &c, 1, MSG_PEEK); + } + s->frozen_readable = 0; + uxsel_tell(s); +} + +static void uxsel_tell(Actual_Socket s) +{ + int rwx = 0; + if (s->listener) { + rwx |= 1; /* read == accept */ + } else { + if (!s->connected) + rwx |= 2; /* write == connect */ + if (s->connected && !s->frozen) + rwx |= 1 | 4; /* read, except */ + if (bufchain_size(&s->output_data)) + rwx |= 2; /* write */ + } + uxsel_set(s->s, rwx, net_select_result); +} + +int net_service_lookup(char *service) +{ + struct servent *se; + se = getservbyname(service, NULL); + if (se != NULL) + return ntohs(se->s_port); + else + return 0; +} + +char *get_hostname(void) +{ + int len = 128; + char *hostname = NULL; + do { + len *= 2; + hostname = sresize(hostname, len, char); + if ((gethostname(hostname, len) < 0) && + (errno != ENAMETOOLONG)) { + sfree(hostname); + hostname = NULL; + break; + } + } while (strlen(hostname) >= len-1); + return hostname; +} + +SockAddr platform_get_x11_unix_address(const char *sockpath, int displaynum) +{ + SockAddr ret = snew(struct SockAddr_tag); + int n; + + memset(ret, 0, sizeof *ret); + ret->superfamily = UNIX; + /* + * In special circumstances (notably Mac OS X Leopard), we'll + * have been passed an explicit Unix socket path. + */ + if (sockpath) { + n = snprintf(ret->hostname, sizeof ret->hostname, + "%s", sockpath); + } else { + n = snprintf(ret->hostname, sizeof ret->hostname, + "%s%d", X11_UNIX_PATH, displaynum); + } + + if (n < 0) + ret->error = "snprintf failed"; + else if (n >= sizeof ret->hostname) + ret->error = "X11 UNIX name too long"; + +#ifndef NO_IPV6 + ret->ais = NULL; +#else + ret->addresses = NULL; + ret->naddresses = 0; +#endif + ret->refcount = 1; + return ret; +} diff --git a/putty/UNIX/UXNOISE.C b/putty/UNIX/UXNOISE.C new file mode 100644 index 0000000..bbb1f73 --- /dev/null +++ b/putty/UNIX/UXNOISE.C @@ -0,0 +1,147 @@ +/* + * Noise generation for PuTTY's cryptographic random number + * generator. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "storage.h" + +static int read_dev_urandom(char *buf, int len) +{ + int fd; + int ngot, ret; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return 0; + + ngot = 0; + while (ngot < len) { + ret = read(fd, buf+ngot, len-ngot); + if (ret < 0) { + close(fd); + return 0; + } + ngot += ret; + } + + close(fd); + + return 1; +} + +/* + * This function is called once, at PuTTY startup. It will do some + * slightly silly things such as fetching an entire process listing + * and scanning /tmp, load the saved random seed from disk, and + * also read 32 bytes out of /dev/urandom. + */ + +void noise_get_heavy(void (*func) (void *, int)) +{ + char buf[512]; + FILE *fp; + int ret; + int got_dev_urandom = 0; + + if (read_dev_urandom(buf, 32)) { + got_dev_urandom = 1; + func(buf, 32); + } + + fp = popen("ps -axu 2>/dev/null", "r"); + if (fp) { + while ( (ret = fread(buf, 1, sizeof(buf), fp)) > 0) + func(buf, ret); + pclose(fp); + } else if (!got_dev_urandom) { + fprintf(stderr, "popen: %s\n" + "Unable to access fallback entropy source\n", strerror(errno)); + exit(1); + } + + fp = popen("ls -al /tmp 2>/dev/null", "r"); + if (fp) { + while ( (ret = fread(buf, 1, sizeof(buf), fp)) > 0) + func(buf, ret); + pclose(fp); + } else if (!got_dev_urandom) { + fprintf(stderr, "popen: %s\n" + "Unable to access fallback entropy source\n", strerror(errno)); + exit(1); + } + + read_random_seed(func); + random_save_seed(); +} + +void random_save_seed(void) +{ + int len; + void *data; + + if (random_active) { + random_get_savedata(&data, &len); + write_random_seed(data, len); + sfree(data); + } +} + +/* + * This function is called every time the random pool needs + * stirring, and will acquire the system time. + */ +void noise_get_light(void (*func) (void *, int)) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + func(&tv, sizeof(tv)); +} + +/* + * This function is called on a timer, and grabs as much changeable + * system data as it can quickly get its hands on. + */ +void noise_regular(void) +{ + int fd; + int ret; + char buf[512]; + struct rusage rusage; + + if ((fd = open("/proc/meminfo", O_RDONLY)) >= 0) { + while ( (ret = read(fd, buf, sizeof(buf))) > 0) + random_add_noise(buf, ret); + close(fd); + } + if ((fd = open("/proc/stat", O_RDONLY)) >= 0) { + while ( (ret = read(fd, buf, sizeof(buf))) > 0) + random_add_noise(buf, ret); + close(fd); + } + getrusage(RUSAGE_SELF, &rusage); + random_add_noise(&rusage, sizeof(rusage)); +} + +/* + * This function is called on every keypress or mouse move, and + * will add the current time to the noise pool. It gets the scan + * code or mouse position passed in, and adds that too. + */ +void noise_ultralight(unsigned long data) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + random_add_noise(&tv, sizeof(tv)); + random_add_noise(&data, sizeof(data)); +} diff --git a/putty/UNIX/UXPLINK.C b/putty/UNIX/UXPLINK.C new file mode 100644 index 0000000..90baa68 --- /dev/null +++ b/putty/UNIX/UXPLINK.C @@ -0,0 +1,1087 @@ +/* + * PLink - a command-line (stdin/stdout) variant of PuTTY. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef HAVE_NO_SYS_SELECT_H +#include +#endif + +#define PUTTY_DO_GLOBALS /* actually _define_ globals */ +#include "putty.h" +#include "storage.h" +#include "tree234.h" + +#define MAX_STDIN_BACKLOG 4096 + +void *logctx; + +static struct termios orig_termios; + +void fatalbox(char *p, ...) +{ + struct termios cf; + va_list ap; + premsg(&cf); + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + postmsg(&cf); + if (logctx) { + log_free(logctx); + logctx = NULL; + } + cleanup_exit(1); +} +void modalfatalbox(char *p, ...) +{ + struct termios cf; + va_list ap; + premsg(&cf); + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + postmsg(&cf); + if (logctx) { + log_free(logctx); + logctx = NULL; + } + cleanup_exit(1); +} +void connection_fatal(void *frontend, char *p, ...) +{ + struct termios cf; + va_list ap; + premsg(&cf); + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + postmsg(&cf); + if (logctx) { + log_free(logctx); + logctx = NULL; + } + cleanup_exit(1); +} +void cmdline_error(char *p, ...) +{ + struct termios cf; + va_list ap; + premsg(&cf); + fprintf(stderr, "plink: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + postmsg(&cf); + exit(1); +} + +static int local_tty = FALSE; /* do we have a local tty? */ + +static Backend *back; +static void *backhandle; +static Config cfg; + +/* + * Default settings that are specific to pterm. + */ +char *platform_default_s(const char *name) +{ + if (!strcmp(name, "TermType")) + return dupstr(getenv("TERM")); + if (!strcmp(name, "UserName")) + return get_username(); + if (!strcmp(name, "SerialLine")) + return dupstr("/dev/ttyS0"); + return NULL; +} + +int platform_default_i(const char *name, int def) +{ + if (!strcmp(name, "TermWidth") || + !strcmp(name, "TermHeight")) { + struct winsize size; + if (ioctl(STDIN_FILENO, TIOCGWINSZ, (void *)&size) >= 0) + return (!strcmp(name, "TermWidth") ? size.ws_col : size.ws_row); + } + return def; +} + +FontSpec platform_default_fontspec(const char *name) +{ + FontSpec ret; + *ret.name = '\0'; + return ret; +} + +Filename platform_default_filename(const char *name) +{ + Filename ret; + if (!strcmp(name, "LogFileName")) + strcpy(ret.path, "putty.log"); + else + *ret.path = '\0'; + return ret; +} + +char *x_get_default(const char *key) +{ + return NULL; /* this is a stub */ +} +int term_ldisc(Terminal *term, int mode) +{ + return FALSE; +} +void ldisc_update(void *frontend, int echo, int edit) +{ + /* Update stdin read mode to reflect changes in line discipline. */ + struct termios mode; + + if (!local_tty) return; + + mode = orig_termios; + + if (echo) + mode.c_lflag |= ECHO; + else + mode.c_lflag &= ~ECHO; + + if (edit) { + mode.c_iflag |= ICRNL; + mode.c_lflag |= ISIG | ICANON; + mode.c_oflag |= OPOST; + } else { + mode.c_iflag &= ~ICRNL; + mode.c_lflag &= ~(ISIG | ICANON); + mode.c_oflag &= ~OPOST; + /* Solaris sets these to unhelpful values */ + mode.c_cc[VMIN] = 1; + mode.c_cc[VTIME] = 0; + /* FIXME: perhaps what we do with IXON/IXOFF should be an + * argument to ldisc_update(), to allow implementation of SSH-2 + * "xon-xoff" and Rlogin's equivalent? */ + mode.c_iflag &= ~IXON; + mode.c_iflag &= ~IXOFF; + } + /* + * Mark parity errors and (more important) BREAK on input. This + * is more complex than it need be because POSIX-2001 suggests + * that escaping of valid 0xff in the input stream is dependent on + * IGNPAR being clear even though marking of BREAK isn't. NetBSD + * 2.0 goes one worse and makes it dependent on INPCK too. We + * deal with this by forcing these flags into a useful state and + * then faking the state in which we found them in from_tty() if + * we get passed a parity or framing error. + */ + mode.c_iflag = (mode.c_iflag | INPCK | PARMRK) & ~IGNPAR; + + tcsetattr(STDIN_FILENO, TCSANOW, &mode); +} + +/* Helper function to extract a special character from a termios. */ +static char *get_ttychar(struct termios *t, int index) +{ + cc_t c = t->c_cc[index]; +#if defined(_POSIX_VDISABLE) + if (c == _POSIX_VDISABLE) + return dupprintf(""); +#endif + return dupprintf("^<%d>", c); +} + +char *get_ttymode(void *frontend, const char *mode) +{ + /* + * Propagate appropriate terminal modes from the local terminal, + * if any. + */ + if (!local_tty) return NULL; + +#define GET_CHAR(ourname, uxname) \ + do { \ + if (strcmp(mode, ourname) == 0) \ + return get_ttychar(&orig_termios, uxname); \ + } while(0) +#define GET_BOOL(ourname, uxname, uxmemb, transform) \ + do { \ + if (strcmp(mode, ourname) == 0) { \ + int b = (orig_termios.uxmemb & uxname) != 0; \ + transform; \ + return dupprintf("%d", b); \ + } \ + } while (0) + + /* + * Modes that want to be the same on all terminal devices involved. + */ + /* All the special characters supported by SSH */ +#if defined(VINTR) + GET_CHAR("INTR", VINTR); +#endif +#if defined(VQUIT) + GET_CHAR("QUIT", VQUIT); +#endif +#if defined(VERASE) + GET_CHAR("ERASE", VERASE); +#endif +#if defined(VKILL) + GET_CHAR("KILL", VKILL); +#endif +#if defined(VEOF) + GET_CHAR("EOF", VEOF); +#endif +#if defined(VEOL) + GET_CHAR("EOL", VEOL); +#endif +#if defined(VEOL2) + GET_CHAR("EOL2", VEOL2); +#endif +#if defined(VSTART) + GET_CHAR("START", VSTART); +#endif +#if defined(VSTOP) + GET_CHAR("STOP", VSTOP); +#endif +#if defined(VSUSP) + GET_CHAR("SUSP", VSUSP); +#endif +#if defined(VDSUSP) + GET_CHAR("DSUSP", VDSUSP); +#endif +#if defined(VREPRINT) + GET_CHAR("REPRINT", VREPRINT); +#endif +#if defined(VWERASE) + GET_CHAR("WERASE", VWERASE); +#endif +#if defined(VLNEXT) + GET_CHAR("LNEXT", VLNEXT); +#endif +#if defined(VFLUSH) + GET_CHAR("FLUSH", VFLUSH); +#endif +#if defined(VSWTCH) + GET_CHAR("SWTCH", VSWTCH); +#endif +#if defined(VSTATUS) + GET_CHAR("STATUS", VSTATUS); +#endif +#if defined(VDISCARD) + GET_CHAR("DISCARD", VDISCARD); +#endif + /* Modes that "configure" other major modes. These should probably be + * considered as user preferences. */ + /* Configuration of ICANON */ +#if defined(ECHOK) + GET_BOOL("ECHOK", ECHOK, c_lflag, ); +#endif +#if defined(ECHOKE) + GET_BOOL("ECHOKE", ECHOKE, c_lflag, ); +#endif +#if defined(ECHOE) + GET_BOOL("ECHOE", ECHOE, c_lflag, ); +#endif +#if defined(ECHONL) + GET_BOOL("ECHONL", ECHONL, c_lflag, ); +#endif +#if defined(XCASE) + GET_BOOL("XCASE", XCASE, c_lflag, ); +#endif + /* Configuration of ECHO */ +#if defined(ECHOCTL) + GET_BOOL("ECHOCTL", ECHOCTL, c_lflag, ); +#endif + /* Configuration of IXON/IXOFF */ +#if defined(IXANY) + GET_BOOL("IXANY", IXANY, c_iflag, ); +#endif + /* Configuration of OPOST */ +#if defined(OLCUC) + GET_BOOL("OLCUC", OLCUC, c_oflag, ); +#endif +#if defined(ONLCR) + GET_BOOL("ONLCR", ONLCR, c_oflag, ); +#endif +#if defined(OCRNL) + GET_BOOL("OCRNL", OCRNL, c_oflag, ); +#endif +#if defined(ONOCR) + GET_BOOL("ONOCR", ONOCR, c_oflag, ); +#endif +#if defined(ONLRET) + GET_BOOL("ONLRET", ONLRET, c_oflag, ); +#endif + + /* + * Modes that want to be set in only one place, and that we have + * squashed locally. + */ +#if defined(ISIG) + GET_BOOL("ISIG", ISIG, c_lflag, ); +#endif +#if defined(ICANON) + GET_BOOL("ICANON", ICANON, c_lflag, ); +#endif +#if defined(ECHO) + GET_BOOL("ECHO", ECHO, c_lflag, ); +#endif +#if defined(IXON) + GET_BOOL("IXON", IXON, c_iflag, ); +#endif +#if defined(IXOFF) + GET_BOOL("IXOFF", IXOFF, c_iflag, ); +#endif +#if defined(OPOST) + GET_BOOL("OPOST", OPOST, c_oflag, ); +#endif + + /* + * We do not propagate the following modes: + * - Parity/serial settings, which are a local affair and don't + * make sense propagated over SSH's 8-bit byte-stream. + * IGNPAR PARMRK INPCK CS7 CS8 PARENB PARODD + * - Things that want to be enabled in one place that we don't + * squash locally. + * IUCLC + * - Status bits. + * PENDIN + * - Things I don't know what to do with. (FIXME) + * ISTRIP IMAXBEL NOFLSH TOSTOP IEXTEN + * INLCR IGNCR ICRNL + */ + +#undef GET_CHAR +#undef GET_BOOL + + /* Fall through to here for unrecognised names, or ones that are + * unsupported on this platform */ + return NULL; +} + +void cleanup_termios(void) +{ + if (local_tty) + tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); +} + +bufchain stdout_data, stderr_data; + +int try_output(int is_stderr) +{ + bufchain *chain = (is_stderr ? &stderr_data : &stdout_data); + int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO); + void *senddata; + int sendlen, ret, fl; + + if (bufchain_size(chain) == 0) + return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); + + fl = fcntl(fd, F_GETFL); + if (fl != -1 && !(fl & O_NONBLOCK)) + fcntl(fd, F_SETFL, fl | O_NONBLOCK); + do { + bufchain_prefix(chain, &senddata, &sendlen); + ret = write(fd, senddata, sendlen); + if (ret > 0) + bufchain_consume(chain, ret); + } while (ret == sendlen && bufchain_size(chain) != 0); + if (fl != -1 && !(fl & O_NONBLOCK)) + fcntl(fd, F_SETFL, fl); + if (ret < 0 && errno != EAGAIN) { + perror(is_stderr ? "stderr: write" : "stdout: write"); + exit(1); + } + return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); +} + +int from_backend(void *frontend_handle, int is_stderr, + const char *data, int len) +{ + if (is_stderr) { + bufchain_add(&stderr_data, data, len); + return try_output(TRUE); + } else { + bufchain_add(&stdout_data, data, len); + return try_output(FALSE); + } +} + +int from_backend_untrusted(void *frontend_handle, const char *data, int len) +{ + /* + * No "untrusted" output should get here (the way the code is + * currently, it's all diverted by FLAG_STDERR). + */ + assert(!"Unexpected call to from_backend_untrusted()"); + return 0; /* not reached */ +} + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = console_get_userpass_input(p, in, inlen); + return ret; +} + +/* + * Handle data from a local tty in PARMRK format. + */ +static void from_tty(void *vbuf, unsigned len) +{ + char *p, *q, *end, *buf = vbuf; + static enum {NORMAL, FF, FF00} state = NORMAL; + + p = buf; end = buf + len; + while (p < end) { + switch (state) { + case NORMAL: + if (*p == '\xff') { + p++; + state = FF; + } else { + q = memchr(p, '\xff', end - p); + if (q == NULL) q = end; + back->send(backhandle, p, q - p); + p = q; + } + break; + case FF: + if (*p == '\xff') { + back->send(backhandle, p, 1); + p++; + state = NORMAL; + } else if (*p == '\0') { + p++; + state = FF00; + } else abort(); + break; + case FF00: + if (*p == '\0') { + back->special(backhandle, TS_BRK); + } else { + /* + * Pretend that PARMRK wasn't set. This involves + * faking what INPCK and IGNPAR would have done if + * we hadn't overridden them. Unfortunately, we + * can't do this entirely correctly because INPCK + * distinguishes between framing and parity + * errors, but PARMRK format represents both in + * the same way. We assume that parity errors are + * more common than framing errors, and hence + * treat all input errors as being subject to + * INPCK. + */ + if (orig_termios.c_iflag & INPCK) { + /* If IGNPAR is set, we throw away the character. */ + if (!(orig_termios.c_iflag & IGNPAR)) { + /* PE/FE get passed on as NUL. */ + *p = 0; + back->send(backhandle, p, 1); + } + } else { + /* INPCK not set. Assume we got a parity error. */ + back->send(backhandle, p, 1); + } + } + p++; + state = NORMAL; + } + } +} + +int signalpipe[2]; + +void sigwinch(int signum) +{ + if (write(signalpipe[1], "x", 1) <= 0) + /* not much we can do about it */; +} + +/* + * In Plink our selects are synchronous, so these functions are + * empty stubs. + */ +int uxsel_input_add(int fd, int rwx) { return 0; } +void uxsel_input_remove(int id) { } + +/* + * Short description of parameters. + */ +static void usage(void) +{ + printf("PuTTY Link: command-line connection utility\n"); + printf("%s\n", ver); + printf("Usage: plink [options] [user@]host [command]\n"); + printf(" (\"host\" can also be a PuTTY saved session name)\n"); + printf("Options:\n"); + printf(" -V print version information and exit\n"); + printf(" -pgpfp print PGP key fingerprints and exit\n"); + printf(" -v show verbose messages\n"); + printf(" -load sessname Load settings from saved session\n"); + printf(" -ssh -telnet -rlogin -raw -serial\n"); + printf(" force use of a particular protocol\n"); + printf(" -P port connect to specified port\n"); + printf(" -l user connect with specified username\n"); + printf(" -batch disable all interactive prompts\n"); + printf("The following options only apply to SSH connections:\n"); + printf(" -pw passw login with specified password\n"); + printf(" -D [listen-IP:]listen-port\n"); + printf(" Dynamic SOCKS-based port forwarding\n"); + printf(" -L [listen-IP:]listen-port:host:port\n"); + printf(" Forward local port to remote address\n"); + printf(" -R [listen-IP:]listen-port:host:port\n"); + printf(" Forward remote port to local address\n"); + printf(" -X -x enable / disable X11 forwarding\n"); + printf(" -A -a enable / disable agent forwarding\n"); + printf(" -t -T enable / disable pty allocation\n"); + printf(" -1 -2 force use of particular protocol version\n"); + printf(" -4 -6 force use of IPv4 or IPv6\n"); + printf(" -C enable compression\n"); + printf(" -i key private key file for authentication\n"); + printf(" -noagent disable use of Pageant\n"); + printf(" -agent enable use of Pageant\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(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n"); + printf(" Specify the serial configuration (serial only)\n"); + exit(1); +} + +static void version(void) +{ + printf("plink: %s\n", ver); + exit(1); +} + +int main(int argc, char **argv) +{ + int sending; + int portnumber = -1; + int *fdlist; + int fd; + int i, fdcount, fdsize, fdstate; + int connopen; + int exitcode; + int errors; + int use_subsystem = 0; + int got_host = FALSE; + long now; + + fdlist = NULL; + fdcount = fdsize = 0; + /* + * Initialise port and protocol to sensible defaults. (These + * will be overridden by more or less anything.) + */ + default_protocol = PROT_SSH; + default_port = 22; + + flags = FLAG_STDERR | FLAG_STDERR_TTY; + + stderr_tty_init(); + /* + * Process the command line. + */ + do_defaults(NULL, &cfg); + loaded_session = FALSE; + default_protocol = cfg.protocol; + default_port = cfg.port; + errors = 0; + { + /* + * Override the default protocol if PLINK_PROTOCOL is set. + */ + char *p = getenv("PLINK_PROTOCOL"); + if (p) { + const Backend *b = backend_from_name(p); + if (b) { + default_protocol = cfg.protocol = b->protocol; + default_port = cfg.port = b->default_port; + } + } + } + while (--argc) { + char *p = *++argv; + if (*p == '-') { + int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), + 1, &cfg); + if (ret == -2) { + fprintf(stderr, + "plink: option \"%s\" requires an argument\n", p); + errors = 1; + } else if (ret == 2) { + --argc, ++argv; + } else if (ret == 1) { + continue; + } else if (!strcmp(p, "-batch")) { + console_batch_mode = 1; + } else if (!strcmp(p, "-s")) { + /* Save status to write to cfg later. */ + use_subsystem = 1; + } else if (!strcmp(p, "-V")) { + version(); + } else if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints(); + exit(1); + } else if (!strcmp(p, "-o")) { + if (argc <= 1) { + fprintf(stderr, + "plink: option \"-o\" requires an argument\n"); + errors = 1; + } else { + --argc; + provide_xrm_string(*++argv); + } + } else { + fprintf(stderr, "plink: unknown option \"%s\"\n", p); + errors = 1; + } + } else if (*p) { + if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) { + char *q = p; + + /* + * If the hostname starts with "telnet:", set the + * protocol to Telnet and process the string as a + * Telnet URL. + */ + if (!strncmp(q, "telnet:", 7)) { + char c; + + q += 7; + if (q[0] == '/' && q[1] == '/') + q += 2; + cfg.protocol = PROT_TELNET; + p = q; + while (*p && *p != ':' && *p != '/') + p++; + c = *p; + if (*p) + *p++ = '\0'; + if (c == ':') + cfg.port = atoi(p); + else + cfg.port = -1; + strncpy(cfg.host, q, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + got_host = TRUE; + } else { + char *r, *user, *host; + /* + * Before we process the [user@]host string, we + * first check for the presence of a protocol + * prefix (a protocol name followed by ","). + */ + r = strchr(p, ','); + if (r) { + const Backend *b; + *r = '\0'; + b = backend_from_name(p); + if (b) { + default_protocol = cfg.protocol = b->protocol; + portnumber = b->default_port; + } + p = r + 1; + } + + /* + * A nonzero length string followed by an @ is treated + * as a username. (We discount an _initial_ @.) The + * rest of the string (or the whole string if no @) + * is treated as a session name and/or hostname. + */ + r = strrchr(p, '@'); + if (r == p) + p++, r = NULL; /* discount initial @ */ + if (r) { + *r++ = '\0'; + user = p, host = r; + } else { + user = NULL, host = p; + } + + /* + * Now attempt to load a saved session with the + * same name as the hostname. + */ + { + Config cfg2; + do_defaults(host, &cfg2); + if (loaded_session || !cfg_launchable(&cfg2)) { + /* No settings for this host; use defaults */ + /* (or session was already loaded with -load) */ + strncpy(cfg.host, host, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + cfg.port = default_port; + got_host = TRUE; + } else { + cfg = cfg2; + loaded_session = TRUE; + } + } + + if (user) { + /* Patch in specified username. */ + strncpy(cfg.username, user, + sizeof(cfg.username) - 1); + cfg.username[sizeof(cfg.username) - 1] = '\0'; + } + + } + } else { + char *command; + int cmdlen, cmdsize; + cmdlen = cmdsize = 0; + command = NULL; + + while (argc) { + while (*p) { + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = sresize(command, cmdsize, char); + } + command[cmdlen++]=*p++; + } + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = sresize(command, cmdsize, char); + } + command[cmdlen++]=' '; /* always add trailing space */ + if (--argc) p = *++argv; + } + if (cmdlen) command[--cmdlen]='\0'; + /* change trailing blank to NUL */ + cfg.remote_cmd_ptr = command; + cfg.remote_cmd_ptr2 = NULL; + cfg.nopty = TRUE; /* command => no terminal */ + + break; /* done with cmdline */ + } + } + } + + if (errors) + return 1; + + if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) { + usage(); + } + + /* + * Trim leading whitespace off the hostname if it's there. + */ + { + int space = strspn(cfg.host, " \t"); + memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space); + } + + /* See if host is of the form user@host */ + if (cfg.host[0] != '\0') { + char *atsign = strrchr(cfg.host, '@'); + /* Make sure we're not overflowing the user field */ + if (atsign) { + if (atsign - cfg.host < sizeof cfg.username) { + strncpy(cfg.username, cfg.host, atsign - cfg.host); + cfg.username[atsign - cfg.host] = '\0'; + } + memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1)); + } + } + + /* + * Perform command-line overrides on session configuration. + */ + cmdline_run_saved(&cfg); + + /* + * Apply subsystem status. + */ + if (use_subsystem) + cfg.ssh_subsys = TRUE; + + /* + * Trim a colon suffix off the hostname if it's there. + */ + cfg.host[strcspn(cfg.host, ":")] = '\0'; + + /* + * Remove any remaining whitespace from the hostname. + */ + { + int p1 = 0, p2 = 0; + while (cfg.host[p2] != '\0') { + if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') { + cfg.host[p1] = cfg.host[p2]; + p1++; + } + p2++; + } + cfg.host[p1] = '\0'; + } + + if (!cfg.remote_cmd_ptr && !*cfg.remote_cmd && !*cfg.ssh_nc_host) + flags |= FLAG_INTERACTIVE; + + /* + * Select protocol. This is farmed out into a table in a + * separate file to enable an ssh-free variant. + */ + back = backend_from_proto(cfg.protocol); + if (back == NULL) { + fprintf(stderr, + "Internal fault: Unsupported protocol found\n"); + return 1; + } + + /* + * Select port. + */ + if (portnumber != -1) + cfg.port = portnumber; + + /* + * Set up the pipe we'll use to tell us about SIGWINCH. + */ + if (pipe(signalpipe) < 0) { + perror("pipe"); + exit(1); + } + putty_signal(SIGWINCH, sigwinch); + + sk_init(); + uxsel_init(); + + /* + * Unix Plink doesn't provide any way to add forwardings after the + * connection is set up, so if there are none now, we can safely set + * the "simple" flag. + */ + if (cfg.protocol == PROT_SSH && !cfg.x11_forward && !cfg.agentfwd && + cfg.portfwd[0] == '\0' && cfg.portfwd[1] == '\0') + cfg.ssh_simple = TRUE; + /* + * Start up the connection. + */ + logctx = log_init(NULL, &cfg); + console_provide_logctx(logctx); + { + const char *error; + char *realhost; + /* nodelay is only useful if stdin is a terminal device */ + int nodelay = cfg.tcp_nodelay && isatty(0); + + error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, + &realhost, nodelay, cfg.tcp_keepalives); + if (error) { + fprintf(stderr, "Unable to open connection:\n%s\n", error); + return 1; + } + back->provide_logctx(backhandle, logctx); + ldisc_create(&cfg, NULL, back, backhandle, NULL); + sfree(realhost); + } + connopen = 1; + + /* + * Set up the initial console mode. We don't care if this call + * fails, because we know we aren't necessarily running in a + * console. + */ + local_tty = (tcgetattr(STDIN_FILENO, &orig_termios) == 0); + atexit(cleanup_termios); + ldisc_update(NULL, 1, 1); + sending = FALSE; + now = GETTICKCOUNT(); + + while (1) { + fd_set rset, wset, xset; + int maxfd; + int rwx; + int ret; + + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&xset); + maxfd = 0; + + FD_SET_MAX(signalpipe[0], maxfd, rset); + + if (connopen && !sending && + back->connected(backhandle) && + back->sendok(backhandle) && + back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) { + /* If we're OK to send, then try to read from stdin. */ + FD_SET_MAX(STDIN_FILENO, maxfd, rset); + } + + if (bufchain_size(&stdout_data) > 0) { + /* If we have data for stdout, try to write to stdout. */ + FD_SET_MAX(STDOUT_FILENO, maxfd, wset); + } + + if (bufchain_size(&stderr_data) > 0) { + /* If we have data for stderr, try to write to stderr. */ + FD_SET_MAX(STDERR_FILENO, maxfd, wset); + } + + /* Count the currently active fds. */ + i = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) i++; + + /* Expand the fdlist buffer if necessary. */ + if (i > fdsize) { + fdsize = i + 16; + fdlist = sresize(fdlist, fdsize, int); + } + + /* + * Add all currently open fds to the select sets, and store + * them in fdlist as well. + */ + fdcount = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) { + fdlist[fdcount++] = fd; + if (rwx & 1) + FD_SET_MAX(fd, maxfd, rset); + if (rwx & 2) + FD_SET_MAX(fd, maxfd, wset); + if (rwx & 4) + FD_SET_MAX(fd, maxfd, xset); + } + + do { + long next, ticks; + struct timeval tv, *ptv; + + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + if (ticks < 0) ticks = 0; /* just in case */ + tv.tv_sec = ticks / 1000; + tv.tv_usec = ticks % 1000 * 1000; + ptv = &tv; + } else { + ptv = NULL; + } + ret = select(maxfd, &rset, &wset, &xset, ptv); + if (ret == 0) + now = next; + else { + long newnow = GETTICKCOUNT(); + /* + * Check to see whether the system clock has + * changed massively during the select. + */ + if (newnow - now < 0 || newnow - now > next - now) { + /* + * If so, look at the elapsed time in the + * select and use it to compute a new + * tickcount_offset. + */ + long othernow = now + tv.tv_sec * 1000 + tv.tv_usec / 1000; + /* So we'd like GETTICKCOUNT to have returned othernow, + * but instead it return newnow. Hence ... */ + tickcount_offset += othernow - newnow; + now = othernow; + } else { + now = newnow; + } + } + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + perror("select"); + exit(1); + } + + for (i = 0; i < fdcount; i++) { + fd = fdlist[i]; + /* + * We must process exceptional notifications before + * ordinary readability ones, or we may go straight + * past the urgent marker. + */ + if (FD_ISSET(fd, &xset)) + select_result(fd, 4); + if (FD_ISSET(fd, &rset)) + select_result(fd, 1); + if (FD_ISSET(fd, &wset)) + select_result(fd, 2); + } + + if (FD_ISSET(signalpipe[0], &rset)) { + char c[1]; + struct winsize size; + if (read(signalpipe[0], c, 1) <= 0) + /* ignore error */; + /* ignore its value; it'll be `x' */ + if (ioctl(0, TIOCGWINSZ, (void *)&size) >= 0) + back->size(backhandle, size.ws_col, size.ws_row); + } + + if (FD_ISSET(STDIN_FILENO, &rset)) { + char buf[4096]; + int ret; + + if (connopen && back->connected(backhandle)) { + ret = read(STDIN_FILENO, buf, sizeof(buf)); + if (ret < 0) { + perror("stdin: read"); + exit(1); + } else if (ret == 0) { + back->special(backhandle, TS_EOF); + sending = FALSE; /* send nothing further after this */ + } else { + if (local_tty) + from_tty(buf, ret); + else + back->send(backhandle, buf, ret); + } + } + } + + if (FD_ISSET(STDOUT_FILENO, &wset)) { + back->unthrottle(backhandle, try_output(FALSE)); + } + + if (FD_ISSET(STDERR_FILENO, &wset)) { + back->unthrottle(backhandle, try_output(TRUE)); + } + + if ((!connopen || !back->connected(backhandle)) && + bufchain_size(&stdout_data) == 0 && + bufchain_size(&stderr_data) == 0) + break; /* we closed the connection */ + } + exitcode = back->exitcode(backhandle); + if (exitcode < 0) { + fprintf(stderr, "Remote process exit code unavailable\n"); + exitcode = 1; /* this is an error condition */ + } + cleanup_exit(exitcode); + return exitcode; /* shouldn't happen, but placates gcc */ +} diff --git a/putty/UNIX/UXPRINT.C b/putty/UNIX/UXPRINT.C new file mode 100644 index 0000000..fb3d16c --- /dev/null +++ b/putty/UNIX/UXPRINT.C @@ -0,0 +1,58 @@ +/* + * Printing interface for PuTTY. + */ + +#include +#include +#include "putty.h" + +struct printer_job_tag { + FILE *fp; +}; + +printer_job *printer_start_job(char *printer) +{ + printer_job *ret = snew(printer_job); + /* + * On Unix, we treat the printer string as the name of a + * command to pipe to - typically lpr, of course. + */ + ret->fp = popen(printer, "w"); + if (!ret->fp) { + sfree(ret); + ret = NULL; + } + return ret; +} + +void printer_job_data(printer_job *pj, void *data, int len) +{ + if (!pj) + return; + + if (fwrite(data, 1, len, pj->fp) < len) + /* ignore */; +} + +void printer_finish_job(printer_job *pj) +{ + if (!pj) + return; + + pclose(pj->fp); + sfree(pj); +} + +/* + * There's no sensible way to enumerate printers under Unix, since + * practically any valid Unix command is a valid printer :-) So + * these are useless stub functions, and uxcfg.c will disable the + * drop-down list in the printer configurer. + */ +printer_enum *printer_start_enum(int *nprinters_ptr) { + *nprinters_ptr = 0; + return NULL; +} +char *printer_get_name(printer_enum *pe, int i) { return NULL; +} +void printer_finish_enum(printer_enum *pe) { } diff --git a/putty/UNIX/UXPROXY.C b/putty/UNIX/UXPROXY.C new file mode 100644 index 0000000..147df6e --- /dev/null +++ b/putty/UNIX/UXPROXY.C @@ -0,0 +1,311 @@ +/* + * uxproxy.c: Unix implementation of platform_new_connection(), + * supporting an OpenSSH-like proxy command. + */ + +#include +#include +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" + +typedef struct Socket_localproxy_tag * Local_Proxy_Socket; + +struct Socket_localproxy_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + int to_cmd, from_cmd; /* fds */ + + char *error; + + Plug plug; + + bufchain pending_output_data; + bufchain pending_input_data; + + void *privptr; +}; + +static int localproxy_select_result(int fd, int event); + +/* + * Trees to look up the pipe fds in. + */ +static tree234 *localproxy_by_fromfd, *localproxy_by_tofd; +static int localproxy_fromfd_cmp(void *av, void *bv) +{ + Local_Proxy_Socket a = (Local_Proxy_Socket)av; + Local_Proxy_Socket b = (Local_Proxy_Socket)bv; + if (a->from_cmd < b->from_cmd) + return -1; + if (a->from_cmd > b->from_cmd) + return +1; + return 0; +} +static int localproxy_fromfd_find(void *av, void *bv) +{ + int a = *(int *)av; + Local_Proxy_Socket b = (Local_Proxy_Socket)bv; + if (a < b->from_cmd) + return -1; + if (a > b->from_cmd) + return +1; + return 0; +} +static int localproxy_tofd_cmp(void *av, void *bv) +{ + Local_Proxy_Socket a = (Local_Proxy_Socket)av; + Local_Proxy_Socket b = (Local_Proxy_Socket)bv; + if (a->to_cmd < b->to_cmd) + return -1; + if (a->to_cmd > b->to_cmd) + return +1; + return 0; +} +static int localproxy_tofd_find(void *av, void *bv) +{ + int a = *(int *)av; + Local_Proxy_Socket b = (Local_Proxy_Socket)bv; + if (a < b->to_cmd) + return -1; + if (a > b->to_cmd) + return +1; + return 0; +} + +/* basic proxy socket functions */ + +static Plug sk_localproxy_plug (Socket s, Plug p) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_localproxy_close (Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + del234(localproxy_by_fromfd, ps); + del234(localproxy_by_tofd, ps); + + uxsel_del(ps->to_cmd); + uxsel_del(ps->from_cmd); + close(ps->to_cmd); + close(ps->from_cmd); + + sfree(ps); +} + +static int localproxy_try_send(Local_Proxy_Socket ps) +{ + int sent = 0; + + while (bufchain_size(&ps->pending_output_data) > 0) { + void *data; + int len, ret; + + bufchain_prefix(&ps->pending_output_data, &data, &len); + ret = write(ps->to_cmd, data, len); + if (ret < 0 && errno != EWOULDBLOCK) { + /* We're inside the Unix frontend here, so we know + * that the frontend handle is unnecessary. */ + logevent(NULL, strerror(errno)); + fatalbox("%s", strerror(errno)); + } else if (ret <= 0) { + break; + } else { + bufchain_consume(&ps->pending_output_data, ret); + sent += ret; + } + } + + if (bufchain_size(&ps->pending_output_data) == 0) + uxsel_del(ps->to_cmd); + else + uxsel_set(ps->to_cmd, 2, localproxy_select_result); + + return sent; +} + +static int sk_localproxy_write (Socket s, const char *data, int len) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + bufchain_add(&ps->pending_output_data, data, len); + + localproxy_try_send(ps); + + return bufchain_size(&ps->pending_output_data); +} + +static int sk_localproxy_write_oob (Socket s, const char *data, int len) +{ + /* + * oob data is treated as inband; nasty, but nothing really + * better we can do + */ + return sk_localproxy_write(s, data, len); +} + +static void sk_localproxy_flush (Socket s) +{ + /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */ + /* do nothing */ +} + +static void sk_localproxy_set_private_ptr (Socket s, void *ptr) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + ps->privptr = ptr; +} + +static void * sk_localproxy_get_private_ptr (Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + return ps->privptr; +} + +static void sk_localproxy_set_frozen (Socket s, int is_frozen) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + if (is_frozen) + uxsel_del(ps->from_cmd); + else + uxsel_set(ps->from_cmd, 1, localproxy_select_result); +} + +static const char * sk_localproxy_socket_error (Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + return ps->error; +} + +static int localproxy_select_result(int fd, int event) +{ + Local_Proxy_Socket s; + char buf[20480]; + int ret; + + if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) && + !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) ) + return 1; /* boggle */ + + if (event == 1) { + assert(fd == s->from_cmd); + ret = read(fd, buf, sizeof(buf)); + if (ret < 0) { + return plug_closing(s->plug, strerror(errno), errno, 0); + } else if (ret == 0) { + return plug_closing(s->plug, NULL, 0, 0); + } else { + return plug_receive(s->plug, 0, buf, ret); + } + } else if (event == 2) { + assert(fd == s->to_cmd); + if (localproxy_try_send(s)) + plug_sent(s->plug, bufchain_size(&s->pending_output_data)); + return 1; + } + + return 1; +} + +Socket platform_new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, const Config *cfg) +{ + char *cmd; + + static const struct socket_function_table socket_fn_table = { + sk_localproxy_plug, + sk_localproxy_close, + sk_localproxy_write, + sk_localproxy_write_oob, + sk_localproxy_flush, + sk_localproxy_set_private_ptr, + sk_localproxy_get_private_ptr, + sk_localproxy_set_frozen, + sk_localproxy_socket_error + }; + + Local_Proxy_Socket ret; + int to_cmd_pipe[2], from_cmd_pipe[2], pid; + + if (cfg->proxy_type != PROXY_CMD) + return NULL; + + cmd = format_telnet_command(addr, port, cfg); + + ret = snew(struct Socket_localproxy_tag); + ret->fn = &socket_fn_table; + ret->plug = plug; + ret->error = NULL; + + bufchain_init(&ret->pending_input_data); + bufchain_init(&ret->pending_output_data); + + /* + * Create the pipes to the proxy command, and spawn the proxy + * command process. + */ + if (pipe(to_cmd_pipe) < 0 || + pipe(from_cmd_pipe) < 0) { + ret->error = dupprintf("pipe: %s", strerror(errno)); + return (Socket)ret; + } + cloexec(to_cmd_pipe[1]); + cloexec(from_cmd_pipe[0]); + + pid = fork(); + + if (pid < 0) { + ret->error = dupprintf("fork: %s", strerror(errno)); + return (Socket)ret; + } else if (pid == 0) { + close(0); + close(1); + dup2(to_cmd_pipe[0], 0); + dup2(from_cmd_pipe[1], 1); + close(to_cmd_pipe[0]); + close(from_cmd_pipe[1]); + fcntl(0, F_SETFD, 0); + fcntl(1, F_SETFD, 0); + execl("/bin/sh", "sh", "-c", cmd, (void *)NULL); + _exit(255); + } + + sfree(cmd); + + close(to_cmd_pipe[0]); + close(from_cmd_pipe[1]); + + ret->to_cmd = to_cmd_pipe[1]; + ret->from_cmd = from_cmd_pipe[0]; + + if (!localproxy_by_fromfd) + localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp); + if (!localproxy_by_tofd) + localproxy_by_tofd = newtree234(localproxy_tofd_cmp); + + add234(localproxy_by_fromfd, ret); + add234(localproxy_by_tofd, ret); + + uxsel_set(ret->from_cmd, 1, localproxy_select_result); + + /* We are responsible for this and don't need it any more */ + sk_addr_free(addr); + + return (Socket) ret; +} diff --git a/putty/UNIX/UXPTERM.C b/putty/UNIX/UXPTERM.C new file mode 100644 index 0000000..73670e8 --- /dev/null +++ b/putty/UNIX/UXPTERM.C @@ -0,0 +1,57 @@ +/* + * pterm main program. + */ + +#include +#include + +#include "putty.h" + +const char *const appname = "pterm"; +const int use_event_log = 0; /* pterm doesn't need it */ +const int new_session = 0, saved_sessions = 0; /* or these */ +const int use_pty_argv = TRUE; + +Backend *select_backend(Config *cfg) +{ + return &pty_backend; +} + +int cfgbox(Config *cfg) +{ + /* + * 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. + */ + cfg->protocol = -1; + return 1; +} + +void cleanup_exit(int code) +{ + exit(code); +} + +int process_nonoption_arg(char *arg, Config *cfg, int *allow_launch) +{ + return 0; /* pterm doesn't have any. */ +} + +char *make_default_wintitle(char *hostname) +{ + return dupstr("pterm"); +} + +int main(int argc, char **argv) +{ + extern int pt_main(int argc, char **argv); + extern void pty_pre_init(void); /* declared in pty.c */ + + cmdline_tooltype = TOOLTYPE_NONNETWORK; + default_protocol = -1; + + pty_pre_init(); + + return pt_main(argc, argv); +} diff --git a/putty/UNIX/UXPTY.C b/putty/UNIX/UXPTY.C new file mode 100644 index 0000000..b1e240e --- /dev/null +++ b/putty/UNIX/UXPTY.C @@ -0,0 +1,1096 @@ +/* + * Pseudo-tty backend for pterm. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" + +#ifndef OMIT_UTMP +#include +#endif + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/* updwtmpx() needs the name of the wtmp file. Try to find it. */ +#ifndef WTMPX_FILE +#ifdef _PATH_WTMPX +#define WTMPX_FILE _PATH_WTMPX +#else +#define WTMPX_FILE "/var/log/wtmpx" +#endif +#endif + +#ifndef LASTLOG_FILE +#ifdef _PATH_LASTLOG +#define LASTLOG_FILE _PATH_LASTLOG +#else +#define LASTLOG_FILE "/var/log/lastlog" +#endif +#endif + +/* + * Set up a default for vaguely sane systems. The idea is that if + * OMIT_UTMP is not defined, then at least one of the symbols which + * enable particular forms of utmp processing should be, if only so + * that a link error can warn you that you should have defined + * OMIT_UTMP if you didn't want any. Currently HAVE_PUTUTLINE is + * the only such symbol. + */ +#ifndef OMIT_UTMP +#if !defined HAVE_PUTUTLINE +#define HAVE_PUTUTLINE +#endif +#endif + +typedef struct pty_tag *Pty; + +/* + * The pty_signal_pipe, along with the SIGCHLD handler, must be + * process-global rather than session-specific. + */ +static int pty_signal_pipe[2] = { -1, -1 }; /* obviously bogus initial val */ + +struct pty_tag { + Config cfg; + int master_fd, slave_fd; + void *frontend; + char name[FILENAME_MAX]; + pid_t child_pid; + int term_width, term_height; + int child_dead, finished; + int exit_code; + bufchain output_data; +}; + +/* + * We store our pty backends in a tree sorted by master fd, so that + * when we get an uxsel notification we know which backend instance + * is the owner of the pty that caused it. + */ +static int pty_compare_by_fd(void *av, void *bv) +{ + Pty a = (Pty)av; + Pty b = (Pty)bv; + + if (a->master_fd < b->master_fd) + return -1; + else if (a->master_fd > b->master_fd) + return +1; + return 0; +} + +static int pty_find_by_fd(void *av, void *bv) +{ + int a = *(int *)av; + Pty b = (Pty)bv; + + if (a < b->master_fd) + return -1; + else if (a > b->master_fd) + return +1; + return 0; +} + +static tree234 *ptys_by_fd = NULL; + +/* + * We also have a tree sorted by child pid, so that when we wait() + * in response to the signal we know which backend instance is the + * owner of the process that caused the signal. + */ +static int pty_compare_by_pid(void *av, void *bv) +{ + Pty a = (Pty)av; + Pty b = (Pty)bv; + + if (a->child_pid < b->child_pid) + return -1; + else if (a->child_pid > b->child_pid) + return +1; + return 0; +} + +static int pty_find_by_pid(void *av, void *bv) +{ + pid_t a = *(pid_t *)av; + Pty b = (Pty)bv; + + if (a < b->child_pid) + return -1; + else if (a > b->child_pid) + return +1; + return 0; +} + +static tree234 *ptys_by_pid = NULL; + +/* + * If we are using pty_pre_init(), it will need to have already + * allocated a pty structure, which we must then return from + * pty_init() rather than allocating a new one. Here we store that + * structure between allocation and use. + * + * Note that although most of this module is entirely capable of + * handling multiple ptys in a single process, pty_pre_init() is + * fundamentally _dependent_ on there being at most one pty per + * process, so the normal static-data constraints don't apply. + * + * Likewise, since utmp is only used via pty_pre_init, it too must + * be single-instance, so we can declare utmp-related variables + * here. + */ +static Pty single_pty = NULL; + +#ifndef OMIT_UTMP +static pid_t pty_utmp_helper_pid; +static int pty_utmp_helper_pipe; +static int pty_stamped_utmp; +static struct utmpx utmp_entry; +#endif + +/* + * pty_argv is a grievous hack to allow a proper argv to be passed + * through from the Unix command line. Again, it doesn't really + * make sense outside a one-pty-per-process setup. + */ +char **pty_argv; + +static void pty_close(Pty pty); +static void pty_try_write(Pty pty); + +#ifndef OMIT_UTMP +static void setup_utmp(char *ttyname, char *location) +{ +#ifdef HAVE_LASTLOG + struct lastlog lastlog_entry; + FILE *lastlog; +#endif + struct passwd *pw; + struct timeval tv; + + pw = getpwuid(getuid()); + memset(&utmp_entry, 0, sizeof(utmp_entry)); + utmp_entry.ut_type = USER_PROCESS; + utmp_entry.ut_pid = getpid(); + strncpy(utmp_entry.ut_line, ttyname+5, lenof(utmp_entry.ut_line)); + strncpy(utmp_entry.ut_id, ttyname+8, lenof(utmp_entry.ut_id)); + strncpy(utmp_entry.ut_user, pw->pw_name, lenof(utmp_entry.ut_user)); + strncpy(utmp_entry.ut_host, location, lenof(utmp_entry.ut_host)); + /* + * Apparently there are some architectures where (struct + * utmpx).ut_tv is not essentially struct timeval (e.g. Linux + * amd64). Hence the temporary. + */ + gettimeofday(&tv, NULL); + utmp_entry.ut_tv.tv_sec = tv.tv_sec; + utmp_entry.ut_tv.tv_usec = tv.tv_usec; + + setutxent(); + pututxline(&utmp_entry); + endutxent(); + + updwtmpx(WTMPX_FILE, &utmp_entry); + +#ifdef HAVE_LASTLOG + memset(&lastlog_entry, 0, sizeof(lastlog_entry)); + strncpy(lastlog_entry.ll_line, ttyname+5, lenof(lastlog_entry.ll_line)); + strncpy(lastlog_entry.ll_host, location, lenof(lastlog_entry.ll_host)); + time(&lastlog_entry.ll_time); + if ((lastlog = fopen(LASTLOG_FILE, "r+")) != NULL) { + fseek(lastlog, sizeof(lastlog_entry) * getuid(), SEEK_SET); + fwrite(&lastlog_entry, 1, sizeof(lastlog_entry), lastlog); + fclose(lastlog); + } +#endif + + pty_stamped_utmp = 1; + +} + +static void cleanup_utmp(void) +{ + struct timeval tv; + + if (!pty_stamped_utmp) + return; + + utmp_entry.ut_type = DEAD_PROCESS; + memset(utmp_entry.ut_user, 0, lenof(utmp_entry.ut_user)); + gettimeofday(&tv, NULL); + utmp_entry.ut_tv.tv_sec = tv.tv_sec; + utmp_entry.ut_tv.tv_usec = tv.tv_usec; + + updwtmpx(WTMPX_FILE, &utmp_entry); + + memset(utmp_entry.ut_line, 0, lenof(utmp_entry.ut_line)); + utmp_entry.ut_tv.tv_sec = 0; + utmp_entry.ut_tv.tv_usec = 0; + + setutxent(); + pututxline(&utmp_entry); + endutxent(); + + pty_stamped_utmp = 0; /* ensure we never double-cleanup */ +} +#endif + +static void sigchld_handler(int signum) +{ + if (write(pty_signal_pipe[1], "x", 1) <= 0) + /* not much we can do about it */; +} + +#ifndef OMIT_UTMP +static void fatal_sig_handler(int signum) +{ + putty_signal(signum, SIG_DFL); + cleanup_utmp(); + setuid(getuid()); + 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 + pty->master_fd = open("/dev/ptmx", O_RDWR); + + if (pty->master_fd < 0) { + perror("/dev/ptmx: open"); + exit(1); + } + + 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 + + { + /* + * Set the pty master into non-blocking mode. + */ + int fl; + fl = fcntl(pty->master_fd, F_GETFL); + if (fl != -1 && !(fl & O_NONBLOCK)) + fcntl(pty->master_fd, F_SETFL, fl | O_NONBLOCK); + } + + if (!ptys_by_fd) + ptys_by_fd = newtree234(pty_compare_by_fd); + add234(ptys_by_fd, pty); +} + +/* + * Pre-initialisation. This is here to get around the fact that GTK + * doesn't like being run in setuid/setgid programs (probably + * sensibly). So before we initialise GTK - and therefore before we + * even process the command line - we check to see if we're running + * set[ug]id. If so, we open our pty master _now_, chown it as + * necessary, and drop privileges. We can always close it again + * later. If we're potentially going to be doing utmp as well, we + * also fork off a utmp helper process and communicate with it by + * means of a pipe; the utmp helper will keep privileges in order + * to clean up utmp when we exit (i.e. when its end of our pipe + * closes). + */ +void pty_pre_init(void) +{ + Pty pty; + +#ifndef OMIT_UTMP + pid_t pid; + int pipefd[2]; +#endif + + pty = single_pty = snew(struct pty_tag); + bufchain_init(&pty->output_data); + + /* set the child signal handler straight away; it needs to be set + * before we ever fork. */ + putty_signal(SIGCHLD, sigchld_handler); + pty->master_fd = pty->slave_fd = -1; +#ifndef OMIT_UTMP + pty_stamped_utmp = FALSE; +#endif + + if (geteuid() != getuid() || getegid() != getgid()) { + pty_open_master(pty); + } + +#ifndef OMIT_UTMP + /* + * Fork off the utmp helper. + */ + if (pipe(pipefd) < 0) { + perror("pterm: pipe"); + exit(1); + } + cloexec(pipefd[0]); + cloexec(pipefd[1]); + pid = fork(); + if (pid < 0) { + perror("pterm: fork"); + exit(1); + } else if (pid == 0) { + char display[128], buffer[128]; + int dlen, ret; + + close(pipefd[1]); + /* + * Now sit here until we receive a display name from the + * other end of the pipe, and then stamp utmp. Unstamp utmp + * again, and exit, when the pipe closes. + */ + + dlen = 0; + while (1) { + + ret = read(pipefd[0], buffer, lenof(buffer)); + if (ret <= 0) { + cleanup_utmp(); + _exit(0); + } else if (!pty_stamped_utmp) { + if (dlen < lenof(display)) + memcpy(display+dlen, buffer, + min(ret, lenof(display)-dlen)); + if (buffer[ret-1] == '\0') { + /* + * Now we have a display name. NUL-terminate + * it, and stamp utmp. + */ + display[lenof(display)-1] = '\0'; + /* + * Trap as many fatal signals as we can in the + * hope of having the best possible chance to + * clean up utmp before termination. We are + * unfortunately unprotected against SIGKILL, + * but that's life. + */ + putty_signal(SIGHUP, fatal_sig_handler); + putty_signal(SIGINT, fatal_sig_handler); + putty_signal(SIGQUIT, fatal_sig_handler); + putty_signal(SIGILL, fatal_sig_handler); + putty_signal(SIGABRT, fatal_sig_handler); + putty_signal(SIGFPE, fatal_sig_handler); + putty_signal(SIGPIPE, fatal_sig_handler); + putty_signal(SIGALRM, fatal_sig_handler); + putty_signal(SIGTERM, fatal_sig_handler); + putty_signal(SIGSEGV, fatal_sig_handler); + putty_signal(SIGUSR1, fatal_sig_handler); + putty_signal(SIGUSR2, fatal_sig_handler); +#ifdef SIGBUS + putty_signal(SIGBUS, fatal_sig_handler); +#endif +#ifdef SIGPOLL + putty_signal(SIGPOLL, fatal_sig_handler); +#endif +#ifdef SIGPROF + putty_signal(SIGPROF, fatal_sig_handler); +#endif +#ifdef SIGSYS + putty_signal(SIGSYS, fatal_sig_handler); +#endif +#ifdef SIGTRAP + putty_signal(SIGTRAP, fatal_sig_handler); +#endif +#ifdef SIGVTALRM + putty_signal(SIGVTALRM, fatal_sig_handler); +#endif +#ifdef SIGXCPU + putty_signal(SIGXCPU, fatal_sig_handler); +#endif +#ifdef SIGXFSZ + putty_signal(SIGXFSZ, fatal_sig_handler); +#endif +#ifdef SIGIO + putty_signal(SIGIO, fatal_sig_handler); +#endif + setup_utmp(pty->name, display); + } + } + } + } else { + close(pipefd[0]); + pty_utmp_helper_pid = pid; + pty_utmp_helper_pipe = pipefd[1]; + } +#endif + + /* Drop privs. */ + { +#ifndef HAVE_NO_SETRESUID + int gid = getgid(), uid = getuid(); + int setresgid(gid_t, gid_t, gid_t); + int setresuid(uid_t, uid_t, uid_t); + setresgid(gid, gid, gid); + setresuid(uid, uid, uid); +#else + setgid(getgid()); + setuid(getuid()); +#endif + } +} + +int pty_real_select_result(Pty pty, int event, int status) +{ + char buf[4096]; + int ret; + int finished = FALSE; + + if (event < 0) { + /* + * We've been called because our child process did + * something. `status' tells us what. + */ + if ((WIFEXITED(status) || WIFSIGNALED(status))) { + /* + * The primary child process died. We could keep + * the terminal open for remaining subprocesses to + * output to, but conventional wisdom seems to feel + * that that's the Wrong Thing for an xterm-alike, + * so we bail out now (though we don't necessarily + * _close_ the window, depending on the state of + * Close On Exit). This would be easy enough to + * change or make configurable if necessary. + */ + pty->exit_code = status; + pty->child_dead = TRUE; + del234(ptys_by_pid, pty); + finished = TRUE; + } + } else { + if (event == 1) { + + ret = read(pty->master_fd, buf, sizeof(buf)); + + /* + * Clean termination condition is that either ret == 0, or ret + * < 0 and errno == EIO. Not sure why the latter, but it seems + * to happen. Boo. + */ + if (ret == 0 || (ret < 0 && errno == EIO)) { + /* + * We assume a clean exit if the pty has closed but the + * actual child process hasn't. The only way I can + * imagine this happening is if it detaches itself from + * the pty and goes daemonic - in which case the + * expected usage model would precisely _not_ be for + * the pterm window to hang around! + */ + finished = TRUE; + if (!pty->child_dead) + pty->exit_code = 0; + } else if (ret < 0) { + perror("read pty master"); + exit(1); + } else if (ret > 0) { + from_backend(pty->frontend, 0, buf, ret); + } + } else if (event == 2) { + /* + * Attempt to send data down the pty. + */ + pty_try_write(pty); + } + } + + if (finished && !pty->finished) { + uxsel_del(pty->master_fd); + pty_close(pty); + pty->master_fd = -1; + + pty->finished = TRUE; + + /* + * This is a slight layering-violation sort of hack: only + * if we're not closing on exit (COE is set to Never, or to + * Only On Clean and it wasn't a clean exit) do we output a + * `terminated' message. + */ + if (pty->cfg.close_on_exit == FORCE_OFF || + (pty->cfg.close_on_exit == AUTO && pty->exit_code != 0)) { + char message[512]; + if (WIFEXITED(pty->exit_code)) + sprintf(message, "\r\n[pterm: process terminated with exit" + " code %d]\r\n", WEXITSTATUS(pty->exit_code)); + else if (WIFSIGNALED(pty->exit_code)) +#ifdef HAVE_NO_STRSIGNAL + sprintf(message, "\r\n[pterm: process terminated on signal" + " %d]\r\n", WTERMSIG(pty->exit_code)); +#else + sprintf(message, "\r\n[pterm: process terminated on signal" + " %d (%.400s)]\r\n", WTERMSIG(pty->exit_code), + strsignal(WTERMSIG(pty->exit_code))); +#endif + from_backend(pty->frontend, 0, message, strlen(message)); + } + + notify_remote_exit(pty->frontend); + } + + return !finished; +} + +int pty_select_result(int fd, int event) +{ + int ret = TRUE; + Pty pty; + + if (fd == pty_signal_pipe[0]) { + pid_t pid; + int status; + char c[1]; + + if (read(pty_signal_pipe[0], c, 1) <= 0) + /* ignore error */; + /* ignore its value; it'll be `x' */ + + do { + pid = waitpid(-1, &status, WNOHANG); + + pty = find234(ptys_by_pid, &pid, pty_find_by_pid); + + if (pty) + ret = ret && pty_real_select_result(pty, -1, status); + } while (pid > 0); + } else { + pty = find234(ptys_by_fd, &fd, pty_find_by_fd); + + if (pty) + ret = ret && pty_real_select_result(pty, event, 0); + } + + return ret; +} + +static void pty_uxsel_setup(Pty pty) +{ + int rwx; + + rwx = 1; /* always want to read from pty */ + if (bufchain_size(&pty->output_data)) + rwx |= 2; /* might also want to write to it */ + uxsel_set(pty->master_fd, rwx, pty_select_result); + + /* + * In principle this only needs calling once for all pty + * backend instances, but it's simplest just to call it every + * time; uxsel won't mind. + */ + uxsel_set(pty_signal_pipe[0], 1, pty_select_result); +} + +/* + * Called to set up the pty. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, + char *host, int port, char **realhost, int nodelay, + int keepalive) +{ + int slavefd; + pid_t pid, pgrp; +#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ + long windowid; +#endif + Pty pty; + + if (single_pty) { + pty = single_pty; + } else { + pty = snew(struct pty_tag); + pty->master_fd = pty->slave_fd = -1; +#ifndef OMIT_UTMP + pty_stamped_utmp = FALSE; +#endif + } + + pty->frontend = frontend; + *backend_handle = NULL; /* we can't sensibly use this, sadly */ + + pty->cfg = *cfg; /* structure copy */ + pty->term_width = cfg->width; + pty->term_height = cfg->height; + + if (pty->master_fd < 0) + pty_open_master(pty); + + /* + * Set the backspace character to be whichever of ^H and ^? is + * specified by bksp_is_delete. + */ + { + struct termios attrs; + tcgetattr(pty->master_fd, &attrs); + attrs.c_cc[VERASE] = cfg->bksp_is_delete ? '\177' : '\010'; + tcsetattr(pty->master_fd, TCSANOW, &attrs); + } + +#ifndef OMIT_UTMP + /* + * Stamp utmp (that is, tell the utmp helper process to do so), + * or not. + */ + if (!cfg->stamp_utmp) { + close(pty_utmp_helper_pipe); /* just let the child process die */ + pty_utmp_helper_pipe = -1; + } else { + char *location = get_x_display(pty->frontend); + int len = strlen(location)+1, pos = 0; /* +1 to include NUL */ + while (pos < len) { + int ret = write(pty_utmp_helper_pipe, location+pos, len - pos); + if (ret < 0) { + perror("pterm: writing to utmp helper process"); + close(pty_utmp_helper_pipe); /* arrgh, just give up */ + pty_utmp_helper_pipe = -1; + break; + } + pos += ret; + } + } +#endif + +#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ + windowid = get_windowid(pty->frontend); +#endif + + /* + * Fork and execute the command. + */ + pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + + if (pid == 0) { + /* + * We are the child. + */ + + slavefd = pty_open_slave(pty); + if (slavefd < 0) { + perror("slave pty: open"); + _exit(1); + } + + close(pty->master_fd); + fcntl(slavefd, F_SETFD, 0); /* don't close on exec */ + dup2(slavefd, 0); + dup2(slavefd, 1); + dup2(slavefd, 2); + close(slavefd); + setsid(); +#ifdef TIOCSCTTY + ioctl(0, TIOCSCTTY, 1); +#endif + pgrp = getpid(); + tcsetpgrp(0, pgrp); + setpgid(pgrp, pgrp); + close(open(pty->name, O_WRONLY, 0)); + setpgid(pgrp, pgrp); + { + char *term_env_var = dupprintf("TERM=%s", cfg->termtype); + putenv(term_env_var); + /* We mustn't free term_env_var, as putenv links it into the + * environment in place. + */ + } +#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ + { + char *windowid_env_var = dupprintf("WINDOWID=%ld", windowid); + putenv(windowid_env_var); + /* We mustn't free windowid_env_var, as putenv links it into the + * environment in place. + */ + } +#endif + { + char *e = cfg->environmt; + char *var, *varend, *val, *varval; + while (*e) { + var = e; + while (*e && *e != '\t') e++; + varend = e; + if (*e == '\t') e++; + val = e; + while (*e) e++; + e++; + + varval = dupprintf("%.*s=%s", varend-var, var, 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. + */ + } + } + + /* + * SIGINT, SIGQUIT and SIGPIPE may have been set to ignored by + * our parent, particularly by things like sh -c 'pterm &' and + * some window or session managers. SIGCHLD, meanwhile, was + * blocked during pt_main() startup. Reverse all this for our + * child process. + */ + putty_signal(SIGINT, SIG_DFL); + putty_signal(SIGQUIT, SIG_DFL); + putty_signal(SIGPIPE, SIG_DFL); + block_signal(SIGCHLD, 0); + if (pty_argv) + execvp(pty_argv[0], pty_argv); + else { + char *shell = getenv("SHELL"); + char *shellname; + if (cfg->login_shell) { + char *p = strrchr(shell, '/'); + shellname = snewn(2+strlen(shell), char); + p = p ? p+1 : shell; + sprintf(shellname, "-%s", p); + } else + shellname = shell; + execl(getenv("SHELL"), shellname, (void *)NULL); + } + + /* + * If we're here, exec has gone badly foom. + */ + perror("exec"); + _exit(127); + } else { + pty->child_pid = pid; + pty->child_dead = FALSE; + pty->finished = FALSE; + if (pty->slave_fd > 0) + close(pty->slave_fd); + if (!ptys_by_pid) + ptys_by_pid = newtree234(pty_compare_by_pid); + add234(ptys_by_pid, pty); + } + + if (pty_signal_pipe[0] < 0) { + if (pipe(pty_signal_pipe) < 0) { + perror("pipe"); + exit(1); + } + cloexec(pty_signal_pipe[0]); + cloexec(pty_signal_pipe[1]); + } + pty_uxsel_setup(pty); + + *backend_handle = pty; + + *realhost = dupprintf("\0"); + + return NULL; +} + +static void pty_reconfig(void *handle, Config *cfg) +{ + Pty pty = (Pty)handle; + /* + * We don't have much need to reconfigure this backend, but + * unfortunately we do need to pick up the setting of Close On + * Exit so we know whether to give a `terminated' message. + */ + pty->cfg = *cfg; /* structure copy */ +} + +/* + * Stub routine (never called in pterm). + */ +static void pty_free(void *handle) +{ + Pty pty = (Pty)handle; + + /* Either of these may fail `not found'. That's fine with us. */ + del234(ptys_by_pid, pty); + del234(ptys_by_fd, pty); + + sfree(pty); +} + +static void pty_try_write(Pty pty) +{ + void *data; + int len, ret; + + assert(pty->master_fd >= 0); + + while (bufchain_size(&pty->output_data) > 0) { + bufchain_prefix(&pty->output_data, &data, &len); + ret = write(pty->master_fd, data, len); + + if (ret < 0 && (errno == EWOULDBLOCK)) { + /* + * We've sent all we can for the moment. + */ + break; + } + if (ret < 0) { + perror("write pty master"); + exit(1); + } + bufchain_consume(&pty->output_data, ret); + } + + pty_uxsel_setup(pty); +} + +/* + * Called to send data down the pty. + */ +static int pty_send(void *handle, char *buf, int len) +{ + Pty pty = (Pty)handle; + + if (pty->master_fd < 0) + return 0; /* ignore all writes if fd closed */ + + bufchain_add(&pty->output_data, buf, len); + pty_try_write(pty); + + return bufchain_size(&pty->output_data); +} + +static void pty_close(Pty pty) +{ + if (pty->master_fd >= 0) { + close(pty->master_fd); + pty->master_fd = -1; + } +#ifndef OMIT_UTMP + if (pty_utmp_helper_pipe >= 0) { + close(pty_utmp_helper_pipe); /* this causes utmp to be cleaned up */ + pty_utmp_helper_pipe = -1; + } +#endif +} + +/* + * Called to query the current socket sendability status. + */ +static int pty_sendbuffer(void *handle) +{ + /* Pty pty = (Pty)handle; */ + return 0; +} + +/* + * Called to set the size of the window + */ +static void pty_size(void *handle, int width, int height) +{ + Pty pty = (Pty)handle; + struct winsize size; + + pty->term_width = width; + pty->term_height = height; + + size.ws_row = (unsigned short)pty->term_height; + size.ws_col = (unsigned short)pty->term_width; + size.ws_xpixel = (unsigned short) pty->term_width * + font_dimension(pty->frontend, 0); + size.ws_ypixel = (unsigned short) pty->term_height * + font_dimension(pty->frontend, 1); + ioctl(pty->master_fd, TIOCSWINSZ, (void *)&size); + return; +} + +/* + * Send special codes. + */ +static void pty_special(void *handle, Telnet_Special code) +{ + /* Pty pty = (Pty)handle; */ + /* Do nothing! */ + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *pty_get_specials(void *handle) +{ + /* Pty pty = (Pty)handle; */ + /* + * Hmm. When I get round to having this actually usable, it + * might be quite nice to have the ability to deliver a few + * well chosen signals to the child process - SIGINT, SIGTERM, + * SIGKILL at least. + */ + return NULL; +} + +static int pty_connected(void *handle) +{ + /* Pty pty = (Pty)handle; */ + return TRUE; +} + +static int pty_sendok(void *handle) +{ + /* Pty pty = (Pty)handle; */ + return 1; +} + +static void pty_unthrottle(void *handle, int backlog) +{ + /* Pty pty = (Pty)handle; */ + /* do nothing */ +} + +static int pty_ldisc(void *handle, int option) +{ + /* Pty pty = (Pty)handle; */ + return 0; /* neither editing nor echoing */ +} + +static void pty_provide_ldisc(void *handle, void *ldisc) +{ + /* Pty pty = (Pty)handle; */ + /* This is a stub. */ +} + +static void pty_provide_logctx(void *handle, void *logctx) +{ + /* Pty pty = (Pty)handle; */ + /* This is a stub. */ +} + +static int pty_exitcode(void *handle) +{ + Pty pty = (Pty)handle; + if (!pty->finished) + return -1; /* not dead yet */ + else + return pty->exit_code; +} + +static int pty_cfg_info(void *handle) +{ + /* Pty pty = (Pty)handle; */ + return 0; +} + +Backend pty_backend = { + pty_init, + pty_free, + pty_reconfig, + pty_send, + pty_sendbuffer, + pty_size, + pty_special, + pty_get_specials, + pty_connected, + pty_exitcode, + pty_sendok, + pty_ldisc, + pty_provide_ldisc, + pty_provide_logctx, + pty_unthrottle, + pty_cfg_info, + "pty", + -1, + 0 +}; diff --git a/putty/UNIX/UXPUTTY.C b/putty/UNIX/UXPUTTY.C new file mode 100644 index 0000000..83eae33 --- /dev/null +++ b/putty/UNIX/UXPUTTY.C @@ -0,0 +1,141 @@ +/* + * Unix PuTTY main program. + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "storage.h" + +/* + * Stubs to avoid uxpty.c needing to be linked in. + */ +const int use_pty_argv = FALSE; +char **pty_argv; /* never used */ + +/* + * Clean up and exit. + */ +void cleanup_exit(int code) +{ + /* + * Clean up. + */ + sk_cleanup(); + random_save_seed(); + exit(code); +} + +Backend *select_backend(Config *cfg) +{ + Backend *back = backend_from_proto(cfg->protocol); + assert(back != NULL); + return back; +} + +int cfgbox(Config *cfg) +{ + char *title = dupcat(appname, " Configuration", NULL); + int ret = do_config_box(title, cfg, 0, 0); + sfree(title); + return ret; +} + +static int got_host = 0; + +const int use_event_log = 1, new_session = 1, saved_sessions = 1; + +int process_nonoption_arg(char *arg, Config *cfg, int *allow_launch) +{ + char *p, *q = arg; + + if (got_host) { + /* + * If we already have a host name, treat this argument as a + * port number. NB we have to treat this as a saved -P + * argument, so that it will be deferred until it's a good + * moment to run it. + */ + int ret = cmdline_process_param("-P", arg, 1, cfg); + assert(ret == 2); + } else if (!strncmp(q, "telnet:", 7)) { + /* + * If the hostname starts with "telnet:", + * set the protocol to Telnet and process + * the string as a Telnet URL. + */ + char c; + + q += 7; + if (q[0] == '/' && q[1] == '/') + q += 2; + cfg->protocol = PROT_TELNET; + p = q; + while (*p && *p != ':' && *p != '/') + p++; + c = *p; + if (*p) + *p++ = '\0'; + if (c == ':') + cfg->port = atoi(p); + else + cfg->port = -1; + strncpy(cfg->host, q, sizeof(cfg->host) - 1); + cfg->host[sizeof(cfg->host) - 1] = '\0'; + got_host = 1; + } else { + /* + * Otherwise, treat this argument as a host name. + */ + p = arg; + while (*p && !isspace((unsigned char)*p)) + p++; + if (*p) + *p++ = '\0'; + strncpy(cfg->host, q, sizeof(cfg->host) - 1); + cfg->host[sizeof(cfg->host) - 1] = '\0'; + got_host = 1; + } + if (got_host) + *allow_launch = TRUE; + return 1; +} + +char *make_default_wintitle(char *hostname) +{ + return dupcat(hostname, " - ", appname, NULL); +} + +/* + * X11-forwarding-related things suitable for Gtk app. + */ + +char *platform_get_x_display(void) { + const char *display; + /* Try to take account of --display and what have you. */ + if (!(display = gdk_get_display())) + /* fall back to traditional method */ + display = getenv("DISPLAY"); + return dupstr(display); +} + +int main(int argc, char **argv) +{ + extern int pt_main(int argc, char **argv); + sk_init(); + flags = FLAG_VERBOSE | FLAG_INTERACTIVE; + default_protocol = be_default_protocol; + /* Find the appropriate default port. */ + { + Backend *b = backend_from_proto(default_protocol); + default_port = 0; /* illegal */ + if (b) + default_port = b->default_port; + } + return pt_main(argc, argv); +} diff --git a/putty/UNIX/UXSEL.C b/putty/UNIX/UXSEL.C new file mode 100644 index 0000000..41d5b4d --- /dev/null +++ b/putty/UNIX/UXSEL.C @@ -0,0 +1,123 @@ +/* + * uxsel.c + * + * This module is a sort of all-purpose interchange for file + * descriptors. At one end it talks to uxnet.c and pty.c and + * anything else which might have one or more fds that need + * select()-type things doing to them during an extended program + * run; at the other end it talks to pterm.c or uxplink.c or + * anything else which might have its own means of actually doing + * those select()-type things. + */ + +#include + +#include "putty.h" +#include "tree234.h" + +struct fd { + int fd; + int rwx; /* 4=except 2=write 1=read */ + uxsel_callback_fn callback; + int id; /* for uxsel_input_remove */ +}; + +static tree234 *fds; + +static int uxsel_fd_cmp(void *av, void *bv) +{ + struct fd *a = (struct fd *)av; + struct fd *b = (struct fd *)bv; + if (a->fd < b->fd) + return -1; + if (a->fd > b->fd) + return +1; + return 0; +} +static int uxsel_fd_findcmp(void *av, void *bv) +{ + int *a = (int *)av; + struct fd *b = (struct fd *)bv; + if (*a < b->fd) + return -1; + if (*a > b->fd) + return +1; + return 0; +} + +void uxsel_init(void) +{ + fds = newtree234(uxsel_fd_cmp); +} + +/* + * Here is the interface to fd-supplying modules. They supply an + * fd, a set of read/write/execute states, and a callback function + * for when the fd satisfies one of those states. Repeated calls to + * uxsel_set on the same fd are perfectly legal and serve to change + * the rwx state (typically you only want to select an fd for + * writing when you actually have pending data you want to write to + * it!). + */ + +void uxsel_set(int fd, int rwx, uxsel_callback_fn callback) +{ + struct fd *newfd; + + uxsel_del(fd); + + if (rwx) { + newfd = snew(struct fd); + newfd->fd = fd; + newfd->rwx = rwx; + newfd->callback = callback; + newfd->id = uxsel_input_add(fd, rwx); + add234(fds, newfd); + } +} + +void uxsel_del(int fd) +{ + struct fd *oldfd = find234(fds, &fd, uxsel_fd_findcmp); + if (oldfd) { + uxsel_input_remove(oldfd->id); + del234(fds, oldfd); + sfree(oldfd); + } +} + +/* + * And here is the interface to select-functionality-supplying + * modules. + */ + +int next_fd(int *state, int *rwx) +{ + struct fd *fd; + fd = index234(fds, (*state)++); + if (fd) { + *rwx = fd->rwx; + return fd->fd; + } else + return -1; +} + +int first_fd(int *state, int *rwx) +{ + *state = 0; + return next_fd(state, rwx); +} + +int select_result(int fd, int event) +{ + struct fd *fdstruct = find234(fds, &fd, uxsel_fd_findcmp); + /* + * Apparently this can sometimes be NULL. Can't see how, but I + * assume it means I need to ignore the event since it's on an + * fd I've stopped being interested in. Sigh. + */ + if (fdstruct) + return fdstruct->callback(fd, event); + else + return 1; +} diff --git a/putty/UNIX/UXSER.C b/putty/UNIX/UXSER.C new file mode 100644 index 0000000..de47877 --- /dev/null +++ b/putty/UNIX/UXSER.C @@ -0,0 +1,590 @@ +/* + * Serial back end (Unix-specific). + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" + +#define SERIAL_MAX_BACKLOG 4096 + +typedef struct serial_backend_data { + void *frontend; + int fd; + int finished; + int inbufsize; + bufchain output_data; +} *Serial; + +/* + * We store our serial backends in a tree sorted by fd, so that + * when we get an uxsel notification we know which backend instance + * is the owner of the serial port that caused it. + */ +static int serial_compare_by_fd(void *av, void *bv) +{ + Serial a = (Serial)av; + Serial b = (Serial)bv; + + if (a->fd < b->fd) + return -1; + else if (a->fd > b->fd) + return +1; + return 0; +} + +static int serial_find_by_fd(void *av, void *bv) +{ + int a = *(int *)av; + Serial b = (Serial)bv; + + if (a < b->fd) + return -1; + else if (a > b->fd) + return +1; + return 0; +} + +static tree234 *serial_by_fd = NULL; + +static int serial_select_result(int fd, int event); +static void serial_uxsel_setup(Serial serial); +static void serial_try_write(Serial serial); + +static const char *serial_configure(Serial serial, Config *cfg) +{ + struct termios options; + int bflag, bval; + const char *str; + char *msg; + + if (serial->fd < 0) + return "Unable to reconfigure already-closed serial connection"; + + tcgetattr(serial->fd, &options); + + /* + * Find the appropriate baud rate flag. + */ +#define SETBAUD(x) (bflag = B ## x, bval = x) +#define CHECKBAUD(x) do { if (cfg->serspeed >= x) SETBAUD(x); } while (0) + SETBAUD(50); +#ifdef B75 + CHECKBAUD(75); +#endif +#ifdef B110 + CHECKBAUD(110); +#endif +#ifdef B134 + CHECKBAUD(134); +#endif +#ifdef B150 + CHECKBAUD(150); +#endif +#ifdef B200 + CHECKBAUD(200); +#endif +#ifdef B300 + CHECKBAUD(300); +#endif +#ifdef B600 + CHECKBAUD(600); +#endif +#ifdef B1200 + CHECKBAUD(1200); +#endif +#ifdef B1800 + CHECKBAUD(1800); +#endif +#ifdef B2400 + CHECKBAUD(2400); +#endif +#ifdef B4800 + CHECKBAUD(4800); +#endif +#ifdef B9600 + CHECKBAUD(9600); +#endif +#ifdef B19200 + CHECKBAUD(19200); +#endif +#ifdef B38400 + CHECKBAUD(38400); +#endif +#ifdef B57600 + CHECKBAUD(57600); +#endif +#ifdef B76800 + CHECKBAUD(76800); +#endif +#ifdef B115200 + CHECKBAUD(115200); +#endif +#ifdef B153600 + CHECKBAUD(153600); +#endif +#ifdef B230400 + CHECKBAUD(230400); +#endif +#ifdef B307200 + CHECKBAUD(307200); +#endif +#ifdef B460800 + CHECKBAUD(460800); +#endif +#ifdef B500000 + CHECKBAUD(500000); +#endif +#ifdef B576000 + CHECKBAUD(576000); +#endif +#ifdef B921600 + CHECKBAUD(921600); +#endif +#ifdef B1000000 + CHECKBAUD(1000000); +#endif +#ifdef B1152000 + CHECKBAUD(1152000); +#endif +#ifdef B1500000 + CHECKBAUD(1500000); +#endif +#ifdef B2000000 + CHECKBAUD(2000000); +#endif +#ifdef B2500000 + CHECKBAUD(2500000); +#endif +#ifdef B3000000 + CHECKBAUD(3000000); +#endif +#ifdef B3500000 + CHECKBAUD(3500000); +#endif +#ifdef B4000000 + CHECKBAUD(4000000); +#endif +#undef CHECKBAUD +#undef SETBAUD + cfsetispeed(&options, bflag); + cfsetospeed(&options, bflag); + msg = dupprintf("Configuring baud rate %d", bval); + logevent(serial->frontend, msg); + sfree(msg); + + options.c_cflag &= ~CSIZE; + switch (cfg->serdatabits) { + case 5: options.c_cflag |= CS5; break; + case 6: options.c_cflag |= CS6; break; + case 7: options.c_cflag |= CS7; break; + case 8: options.c_cflag |= CS8; break; + default: return "Invalid number of data bits (need 5, 6, 7 or 8)"; + } + msg = dupprintf("Configuring %d data bits", cfg->serdatabits); + logevent(serial->frontend, msg); + sfree(msg); + + if (cfg->serstopbits >= 4) { + options.c_cflag |= CSTOPB; + } else { + options.c_cflag &= ~CSTOPB; + } + msg = dupprintf("Configuring %d stop bits", + (options.c_cflag & CSTOPB ? 2 : 1)); + logevent(serial->frontend, msg); + sfree(msg); + + options.c_iflag &= ~(IXON|IXOFF); +#ifdef CRTSCTS + options.c_cflag &= ~CRTSCTS; +#endif +#ifdef CNEW_RTSCTS + options.c_cflag &= ~CNEW_RTSCTS; +#endif + if (cfg->serflow == SER_FLOW_XONXOFF) { + options.c_iflag |= IXON | IXOFF; + str = "XON/XOFF"; + } else if (cfg->serflow == SER_FLOW_RTSCTS) { +#ifdef CRTSCTS + options.c_cflag |= CRTSCTS; +#endif +#ifdef CNEW_RTSCTS + options.c_cflag |= CNEW_RTSCTS; +#endif + str = "RTS/CTS"; + } else + str = "no"; + msg = dupprintf("Configuring %s flow control", str); + logevent(serial->frontend, msg); + sfree(msg); + + /* Parity */ + if (cfg->serparity == SER_PAR_ODD) { + options.c_cflag |= PARENB; + options.c_cflag |= PARODD; + str = "odd"; + } else if (cfg->serparity == SER_PAR_EVEN) { + options.c_cflag |= PARENB; + options.c_cflag &= ~PARODD; + str = "even"; + } else { + options.c_cflag &= ~PARENB; + str = "no"; + } + msg = dupprintf("Configuring %s parity", str); + logevent(serial->frontend, msg); + sfree(msg); + + options.c_cflag |= CLOCAL | CREAD; + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_iflag &= ~(ISTRIP | IGNCR | INLCR | ICRNL +#ifdef IUCLC + | IUCLC +#endif + ); + options.c_oflag &= ~(OPOST +#ifdef ONLCR + | ONLCR +#endif +#ifdef OCRNL + | OCRNL +#endif +#ifdef ONOCR + | ONOCR +#endif +#ifdef ONLRET + | ONLRET +#endif + ); + options.c_cc[VMIN] = 1; + options.c_cc[VTIME] = 0; + + if (tcsetattr(serial->fd, TCSANOW, &options) < 0) + return "Unable to configure serial port"; + + return NULL; +} + +/* + * Called to set up the serial connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *serial_init(void *frontend_handle, void **backend_handle, + Config *cfg, + char *host, int port, char **realhost, int nodelay, + int keepalive) +{ + Serial serial; + const char *err; + + serial = snew(struct serial_backend_data); + *backend_handle = serial; + + serial->frontend = frontend_handle; + serial->finished = FALSE; + serial->inbufsize = 0; + bufchain_init(&serial->output_data); + + { + char *msg = dupprintf("Opening serial device %s", cfg->serline); + logevent(serial->frontend, msg); + } + + serial->fd = open(cfg->serline, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + if (serial->fd < 0) + return "Unable to open serial port"; + + cloexec(serial->fd); + + err = serial_configure(serial, cfg); + if (err) + return err; + + *realhost = dupstr(cfg->serline); + + if (!serial_by_fd) + serial_by_fd = newtree234(serial_compare_by_fd); + add234(serial_by_fd, serial); + + serial_uxsel_setup(serial); + + /* + * Specials are always available. + */ + update_specials_menu(serial->frontend); + + return NULL; +} + +static void serial_close(Serial serial) +{ + if (serial->fd >= 0) { + close(serial->fd); + serial->fd = -1; + } +} + +static void serial_free(void *handle) +{ + Serial serial = (Serial) handle; + + serial_close(serial); + + bufchain_clear(&serial->output_data); + + sfree(serial); +} + +static void serial_reconfig(void *handle, Config *cfg) +{ + Serial serial = (Serial) handle; + + /* + * FIXME: what should we do if this returns an error? + */ + serial_configure(serial, cfg); +} + +static int serial_select_result(int fd, int event) +{ + Serial serial; + char buf[4096]; + int ret; + int finished = FALSE; + + serial = find234(serial_by_fd, &fd, serial_find_by_fd); + + if (!serial) + return 1; /* spurious event; keep going */ + + if (event == 1) { + ret = read(serial->fd, buf, sizeof(buf)); + + if (ret == 0) { + /* + * Shouldn't happen on a real serial port, but I'm open + * to the idea that there might be two-way devices we + * can treat _like_ serial ports which can return EOF. + */ + finished = TRUE; + } else if (ret < 0) { +#ifdef EAGAIN + if (errno == EAGAIN) + return 1; /* spurious */ +#endif +#ifdef EWOULDBLOCK + if (errno == EWOULDBLOCK) + return 1; /* spurious */ +#endif + perror("read serial port"); + exit(1); + } else if (ret > 0) { + serial->inbufsize = from_backend(serial->frontend, 0, buf, ret); + serial_uxsel_setup(serial); /* might acquire backlog and freeze */ + } + } else if (event == 2) { + /* + * Attempt to send data down the pty. + */ + serial_try_write(serial); + } + + if (finished) { + serial_close(serial); + + serial->finished = TRUE; + + notify_remote_exit(serial->frontend); + } + + return !finished; +} + +static void serial_uxsel_setup(Serial serial) +{ + int rwx = 0; + + if (serial->inbufsize <= SERIAL_MAX_BACKLOG) + rwx |= 1; + if (bufchain_size(&serial->output_data)) + rwx |= 2; /* might also want to write to it */ + uxsel_set(serial->fd, rwx, serial_select_result); +} + +static void serial_try_write(Serial serial) +{ + void *data; + int len, ret; + + assert(serial->fd >= 0); + + while (bufchain_size(&serial->output_data) > 0) { + bufchain_prefix(&serial->output_data, &data, &len); + ret = write(serial->fd, data, len); + + if (ret < 0 && (errno == EWOULDBLOCK)) { + /* + * We've sent all we can for the moment. + */ + break; + } + if (ret < 0) { + perror("write serial port"); + exit(1); + } + bufchain_consume(&serial->output_data, ret); + } + + serial_uxsel_setup(serial); +} + +/* + * Called to send data down the serial connection. + */ +static int serial_send(void *handle, char *buf, int len) +{ + Serial serial = (Serial) handle; + + if (serial->fd < 0) + return 0; + + bufchain_add(&serial->output_data, buf, len); + serial_try_write(serial); + + return bufchain_size(&serial->output_data); +} + +/* + * Called to query the current sendability status. + */ +static int serial_sendbuffer(void *handle) +{ + Serial serial = (Serial) handle; + return bufchain_size(&serial->output_data); +} + +/* + * Called to set the size of the window + */ +static void serial_size(void *handle, int width, int height) +{ + /* Do nothing! */ + return; +} + +/* + * Send serial special codes. + */ +static void serial_special(void *handle, Telnet_Special code) +{ + Serial serial = (Serial) handle; + + if (serial->fd >= 0 && code == TS_BRK) { + tcsendbreak(serial->fd, 0); + logevent(serial->frontend, "Sending serial break at user request"); + } + + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const struct telnet_special *serial_get_specials(void *handle) +{ + static const struct telnet_special specials[] = { + {"Break", TS_BRK}, + {NULL, TS_EXITMENU} + }; + return specials; +} + +static int serial_connected(void *handle) +{ + return 1; /* always connected */ +} + +static int serial_sendok(void *handle) +{ + return 1; +} + +static void serial_unthrottle(void *handle, int backlog) +{ + Serial serial = (Serial) handle; + serial->inbufsize = backlog; + serial_uxsel_setup(serial); +} + +static int serial_ldisc(void *handle, int option) +{ + /* + * Local editing and local echo are off by default. + */ + return 0; +} + +static void serial_provide_ldisc(void *handle, void *ldisc) +{ + /* This is a stub. */ +} + +static void serial_provide_logctx(void *handle, void *logctx) +{ + /* This is a stub. */ +} + +static int serial_exitcode(void *handle) +{ + Serial serial = (Serial) handle; + if (serial->fd >= 0) + return -1; /* still connected */ + else + /* Exit codes are a meaningless concept with serial ports */ + return INT_MAX; +} + +/* + * cfg_info for Serial does nothing at all. + */ +static int serial_cfg_info(void *handle) +{ + return 0; +} + +Backend serial_backend = { + serial_init, + serial_free, + serial_reconfig, + serial_send, + serial_sendbuffer, + serial_size, + serial_special, + serial_get_specials, + serial_connected, + serial_exitcode, + serial_sendok, + serial_ldisc, + serial_provide_ldisc, + serial_provide_logctx, + serial_unthrottle, + serial_cfg_info, + "serial", + PROT_SERIAL, + 0 +}; diff --git a/putty/UNIX/UXSFTP.C b/putty/UNIX/UXSFTP.C new file mode 100644 index 0000000..a0ae9fb --- /dev/null +++ b/putty/UNIX/UXSFTP.C @@ -0,0 +1,615 @@ +/* + * uxsftp.c: the Unix-specific parts of PSFTP and PSCP. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef HAVE_NO_SYS_SELECT_H +#include +#endif + +#include "putty.h" +#include "ssh.h" +#include "psftp.h" +#include "int64.h" + +/* + * In PSFTP our selects are synchronous, so these functions are + * empty stubs. + */ +int uxsel_input_add(int fd, int rwx) { return 0; } +void uxsel_input_remove(int id) { } + +char *x_get_default(const char *key) +{ + return NULL; /* this is a stub */ +} + +void platform_get_x11_auth(struct X11Display *display, const Config *cfg) +{ + /* Do nothing, therefore no auth. */ +} +const int platform_uses_x11_unix_by_default = TRUE; + +/* + * Default settings that are specific to PSFTP. + */ +char *platform_default_s(const char *name) +{ + return NULL; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} + +FontSpec platform_default_fontspec(const char *name) +{ + FontSpec ret; + *ret.name = '\0'; + return ret; +} + +Filename platform_default_filename(const char *name) +{ + Filename ret; + if (!strcmp(name, "LogFileName")) + strcpy(ret.path, "putty.log"); + else + *ret.path = '\0'; + return ret; +} + +char *get_ttymode(void *frontend, const char *mode) { return NULL; } + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = console_get_userpass_input(p, in, inlen); + return ret; +} + +/* + * Set local current directory. Returns NULL on success, or else an + * error message which must be freed after printing. + */ +char *psftp_lcd(char *dir) +{ + if (chdir(dir) < 0) + return dupprintf("%s: chdir: %s", dir, strerror(errno)); + else + return NULL; +} + +/* + * Get local current directory. Returns a string which must be + * freed. + */ +char *psftp_getcwd(void) +{ + char *buffer, *ret; + int size = 256; + + buffer = snewn(size, char); + while (1) { + ret = getcwd(buffer, size); + if (ret != NULL) + return ret; + if (errno != ERANGE) { + sfree(buffer); + return dupprintf("[cwd unavailable: %s]", strerror(errno)); + } + /* + * Otherwise, ERANGE was returned, meaning the buffer + * wasn't big enough. + */ + size = size * 3 / 2; + buffer = sresize(buffer, size, char); + } +} + +struct RFile { + int fd; +}; + +RFile *open_existing_file(char *name, uint64 *size, + unsigned long *mtime, unsigned long *atime) +{ + int fd; + RFile *ret; + + fd = open(name, O_RDONLY); + if (fd < 0) + return NULL; + + ret = snew(RFile); + ret->fd = fd; + + if (size || mtime || atime) { + struct stat statbuf; + if (fstat(fd, &statbuf) < 0) { + fprintf(stderr, "%s: stat: %s\n", name, strerror(errno)); + memset(&statbuf, 0, sizeof(statbuf)); + } + + if (size) + *size = uint64_make((statbuf.st_size >> 16) >> 16, + statbuf.st_size); + + if (mtime) + *mtime = statbuf.st_mtime; + + if (atime) + *atime = statbuf.st_atime; + } + + return ret; +} + +int read_from_file(RFile *f, void *buffer, int length) +{ + return read(f->fd, buffer, length); +} + +void close_rfile(RFile *f) +{ + close(f->fd); + sfree(f); +} + +struct WFile { + int fd; + char *name; +}; + +WFile *open_new_file(char *name) +{ + int fd; + WFile *ret; + + fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, 0666); + if (fd < 0) + return NULL; + + ret = snew(WFile); + ret->fd = fd; + ret->name = dupstr(name); + + return ret; +} + + +WFile *open_existing_wfile(char *name, uint64 *size) +{ + int fd; + WFile *ret; + + fd = open(name, O_APPEND | O_WRONLY); + if (fd < 0) + return NULL; + + ret = snew(WFile); + ret->fd = fd; + ret->name = dupstr(name); + + if (size) { + struct stat statbuf; + if (fstat(fd, &statbuf) < 0) { + fprintf(stderr, "%s: stat: %s\n", name, strerror(errno)); + memset(&statbuf, 0, sizeof(statbuf)); + } + + *size = uint64_make((statbuf.st_size >> 16) >> 16, + statbuf.st_size); + } + + return ret; +} + +int write_to_file(WFile *f, void *buffer, int length) +{ + char *p = (char *)buffer; + int so_far = 0; + + /* Keep trying until we've really written as much as we can. */ + while (length > 0) { + int ret = write(f->fd, p, length); + + if (ret < 0) + return ret; + + if (ret == 0) + break; + + p += ret; + length -= ret; + so_far += ret; + } + + return so_far; +} + +void set_file_times(WFile *f, unsigned long mtime, unsigned long atime) +{ + struct utimbuf ut; + + ut.actime = atime; + ut.modtime = mtime; + + utime(f->name, &ut); +} + +/* Closes and frees the WFile */ +void close_wfile(WFile *f) +{ + close(f->fd); + sfree(f->name); + sfree(f); +} + +/* Seek offset bytes through file, from whence, where whence is + FROM_START, FROM_CURRENT, or FROM_END */ +int seek_file(WFile *f, uint64 offset, int whence) +{ + off_t fileofft; + int lseek_whence; + + fileofft = (((off_t) offset.hi << 16) << 16) + offset.lo; + + switch (whence) { + case FROM_START: + lseek_whence = SEEK_SET; + break; + case FROM_CURRENT: + lseek_whence = SEEK_CUR; + break; + case FROM_END: + lseek_whence = SEEK_END; + break; + default: + return -1; + } + + return lseek(f->fd, fileofft, lseek_whence) >= 0 ? 0 : -1; +} + +uint64 get_file_posn(WFile *f) +{ + off_t fileofft; + uint64 ret; + + fileofft = lseek(f->fd, (off_t) 0, SEEK_CUR); + + ret = uint64_make((fileofft >> 16) >> 16, fileofft); + + return ret; +} + +int file_type(char *name) +{ + struct stat statbuf; + + if (stat(name, &statbuf) < 0) { + if (errno != ENOENT) + fprintf(stderr, "%s: stat: %s\n", name, strerror(errno)); + return FILE_TYPE_NONEXISTENT; + } + + if (S_ISREG(statbuf.st_mode)) + return FILE_TYPE_FILE; + + if (S_ISDIR(statbuf.st_mode)) + return FILE_TYPE_DIRECTORY; + + return FILE_TYPE_WEIRD; +} + +struct DirHandle { + DIR *dir; +}; + +DirHandle *open_directory(char *name) +{ + DIR *dir; + DirHandle *ret; + + dir = opendir(name); + if (!dir) + return NULL; + + ret = snew(DirHandle); + ret->dir = dir; + return ret; +} + +char *read_filename(DirHandle *dir) +{ + struct dirent *de; + + do { + de = readdir(dir->dir); + if (de == NULL) + return NULL; + } while ((de->d_name[0] == '.' && + (de->d_name[1] == '\0' || + (de->d_name[1] == '.' && de->d_name[2] == '\0')))); + + return dupstr(de->d_name); +} + +void close_directory(DirHandle *dir) +{ + closedir(dir->dir); + sfree(dir); +} + +int test_wildcard(char *name, int cmdline) +{ + struct stat statbuf; + + if (stat(name, &statbuf) == 0) { + return WCTYPE_FILENAME; + } else if (cmdline) { + /* + * On Unix, we never need to parse wildcards coming from + * the command line, because the shell will have expanded + * them into a filename list already. + */ + return WCTYPE_NONEXISTENT; + } else { + glob_t globbed; + int ret = WCTYPE_NONEXISTENT; + + if (glob(name, GLOB_ERR, NULL, &globbed) == 0) { + if (globbed.gl_pathc > 0) + ret = WCTYPE_WILDCARD; + globfree(&globbed); + } + + return ret; + } +} + +/* + * Actually return matching file names for a local wildcard. + */ +struct WildcardMatcher { + glob_t globbed; + int i; +}; +WildcardMatcher *begin_wildcard_matching(char *name) { + WildcardMatcher *ret = snew(WildcardMatcher); + + if (glob(name, 0, NULL, &ret->globbed) < 0) { + sfree(ret); + return NULL; + } + + ret->i = 0; + + return ret; +} +char *wildcard_get_filename(WildcardMatcher *dir) { + if (dir->i < dir->globbed.gl_pathc) { + return dupstr(dir->globbed.gl_pathv[dir->i++]); + } else + return NULL; +} +void finish_wildcard_matching(WildcardMatcher *dir) { + globfree(&dir->globbed); + sfree(dir); +} + +int vet_filename(char *name) +{ + if (strchr(name, '/')) + return FALSE; + + if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]))) + return FALSE; + + return TRUE; +} + +int create_directory(char *name) +{ + return mkdir(name, 0777) == 0; +} + +char *dir_file_cat(char *dir, char *file) +{ + return dupcat(dir, "/", file, NULL); +} + +/* + * Do a select() between all currently active network fds and + * optionally stdin. + */ +static int ssh_sftp_do_select(int include_stdin, int no_fds_ok) +{ + fd_set rset, wset, xset; + int i, fdcount, fdsize, *fdlist; + int fd, fdstate, rwx, ret, maxfd; + long now = GETTICKCOUNT(); + + fdlist = NULL; + fdcount = fdsize = 0; + + do { + + /* Count the currently active fds. */ + i = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) i++; + + if (i < 1 && !no_fds_ok) + return -1; /* doom */ + + /* Expand the fdlist buffer if necessary. */ + if (i > fdsize) { + fdsize = i + 16; + fdlist = sresize(fdlist, fdsize, int); + } + + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&xset); + maxfd = 0; + + /* + * Add all currently open fds to the select sets, and store + * them in fdlist as well. + */ + fdcount = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) { + fdlist[fdcount++] = fd; + if (rwx & 1) + FD_SET_MAX(fd, maxfd, rset); + if (rwx & 2) + FD_SET_MAX(fd, maxfd, wset); + if (rwx & 4) + FD_SET_MAX(fd, maxfd, xset); + } + + if (include_stdin) + FD_SET_MAX(0, maxfd, rset); + + do { + long next, ticks; + struct timeval tv, *ptv; + + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + if (ticks <= 0) + ticks = 1; /* just in case */ + tv.tv_sec = ticks / 1000; + tv.tv_usec = ticks % 1000 * 1000; + ptv = &tv; + } else { + ptv = NULL; + } + ret = select(maxfd, &rset, &wset, &xset, ptv); + if (ret == 0) + now = next; + else { + long newnow = GETTICKCOUNT(); + /* + * Check to see whether the system clock has + * changed massively during the select. + */ + if (newnow - now < 0 || newnow - now > next - now) { + /* + * If so, look at the elapsed time in the + * select and use it to compute a new + * tickcount_offset. + */ + long othernow = now + tv.tv_sec * 1000 + tv.tv_usec / 1000; + /* So we'd like GETTICKCOUNT to have returned othernow, + * but instead it return newnow. Hence ... */ + tickcount_offset += othernow - newnow; + now = othernow; + } else { + now = newnow; + } + } + } while (ret < 0 && errno != EINTR); + } while (ret == 0); + + if (ret < 0) { + perror("select"); + exit(1); + } + + for (i = 0; i < fdcount; i++) { + fd = fdlist[i]; + /* + * We must process exceptional notifications before + * ordinary readability ones, or we may go straight + * past the urgent marker. + */ + if (FD_ISSET(fd, &xset)) + select_result(fd, 4); + if (FD_ISSET(fd, &rset)) + select_result(fd, 1); + if (FD_ISSET(fd, &wset)) + select_result(fd, 2); + } + + sfree(fdlist); + + return FD_ISSET(0, &rset) ? 1 : 0; +} + +/* + * Wait for some network data and process it. + */ +int ssh_sftp_loop_iteration(void) +{ + return ssh_sftp_do_select(FALSE, FALSE); +} + +/* + * Read a PSFTP command line from stdin. + */ +char *ssh_sftp_get_cmdline(char *prompt, int no_fds_ok) +{ + char *buf; + int buflen, bufsize, ret; + + fputs(prompt, stdout); + fflush(stdout); + + buf = NULL; + buflen = bufsize = 0; + + while (1) { + ret = ssh_sftp_do_select(TRUE, no_fds_ok); + if (ret < 0) { + printf("connection died\n"); + return NULL; /* woop woop */ + } + if (ret > 0) { + if (buflen >= bufsize) { + bufsize = buflen + 512; + buf = sresize(buf, bufsize, char); + } + ret = read(0, buf+buflen, 1); + if (ret < 0) { + perror("read"); + return NULL; + } + if (ret == 0) { + /* eof on stdin; no error, but no answer either */ + return NULL; + } + + if (buf[buflen++] == '\n') { + /* we have a full line */ + return buf; + } + } + } +} + +/* + * 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/putty/UNIX/UXSIGNAL.C b/putty/UNIX/UXSIGNAL.C new file mode 100644 index 0000000..7153903 --- /dev/null +++ b/putty/UNIX/UXSIGNAL.C @@ -0,0 +1,45 @@ +#include +#include +#include + +/* + * Calling signal() is non-portable, as it varies in meaning + * between platforms and depending on feature macros, and has + * stupid semantics at least some of the time. + * + * This function provides the same interface as the libc function, + * but provides consistent semantics. It assumes POSIX semantics + * for sigaction() (so you might need to do some more work if you + * port to something ancient like SunOS 4) + */ +void (*putty_signal(int sig, void (*func)(int)))(int) { + struct sigaction sa; + struct sigaction old; + + sa.sa_handler = func; + if(sigemptyset(&sa.sa_mask) < 0) + return SIG_ERR; + sa.sa_flags = SA_RESTART; + if(sigaction(sig, &sa, &old) < 0) + return SIG_ERR; + return old.sa_handler; +} + +void block_signal(int sig, int block_it) +{ + sigset_t ss; + + sigemptyset(&ss); + sigaddset(&ss, sig); + if(sigprocmask(block_it ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0) < 0) { + perror("sigprocmask"); + exit(1); + } +} + +/* +Local Variables: +c-basic-offset:4 +comment-column:40 +End: +*/ diff --git a/putty/UNIX/UXSTORE.C b/putty/UNIX/UXSTORE.C new file mode 100644 index 0000000..29cfa34 --- /dev/null +++ b/putty/UNIX/UXSTORE.C @@ -0,0 +1,685 @@ +/* + * uxstore.c: Unix-specific implementation of the interface defined + * in storage.h. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "putty.h" +#include "storage.h" +#include "tree234.h" + +#ifdef PATH_MAX +#define FNLEN PATH_MAX +#else +#define FNLEN 1024 /* XXX */ +#endif + +enum { + INDEX_DIR, INDEX_HOSTKEYS, INDEX_HOSTKEYS_TMP, INDEX_RANDSEED, + INDEX_SESSIONDIR, INDEX_SESSION, +}; + +static const char hex[16] = "0123456789ABCDEF"; + +static char *mungestr(const char *in) +{ + char *out, *ret; + + if (!in || !*in) + in = "Default Settings"; + + ret = out = snewn(3*strlen(in)+1, char); + + while (*in) { + /* + * There are remarkably few punctuation characters that + * aren't shell-special in some way or likely to be used as + * separators in some file format or another! Hence we use + * opt-in for safe characters rather than opt-out for + * specific unsafe ones... + */ + if (*in!='+' && *in!='-' && *in!='.' && *in!='@' && *in!='_' && + !(*in >= '0' && *in <= '9') && + !(*in >= 'A' && *in <= 'Z') && + !(*in >= 'a' && *in <= 'z')) { + *out++ = '%'; + *out++ = hex[((unsigned char) *in) >> 4]; + *out++ = hex[((unsigned char) *in) & 15]; + } else + *out++ = *in; + in++; + } + *out = '\0'; + return ret; +} + +static char *unmungestr(const char *in) +{ + char *out, *ret; + out = ret = snewn(strlen(in)+1, char); + while (*in) { + if (*in == '%' && in[1] && in[2]) { + int i, j; + + i = in[1] - '0'; + i -= (i > 9 ? 7 : 0); + j = in[2] - '0'; + j -= (j > 9 ? 7 : 0); + + *out++ = (i << 4) + j; + in += 3; + } else { + *out++ = *in++; + } + } + *out = '\0'; + return ret; +} + +static char *make_filename(int index, const char *subname) +{ + char *env, *tmp, *ret; + + /* + * Allow override of the PuTTY configuration location, and of + * specific subparts of it, by means of environment variables. + */ + if (index == INDEX_DIR) { + struct passwd *pwd; + + env = getenv("PUTTYDIR"); + if (env) + return dupstr(env); + env = getenv("HOME"); + if (env) + return dupprintf("%s/.putty", env); + pwd = getpwuid(getuid()); + if (pwd && pwd->pw_dir) + return dupprintf("%s/.putty", pwd->pw_dir); + return dupstr("/.putty"); + } + if (index == INDEX_SESSIONDIR) { + env = getenv("PUTTYSESSIONS"); + if (env) + return dupstr(env); + tmp = make_filename(INDEX_DIR, NULL); + ret = dupprintf("%s/sessions", tmp); + sfree(tmp); + return ret; + } + if (index == INDEX_SESSION) { + char *munged = mungestr(subname); + tmp = make_filename(INDEX_SESSIONDIR, NULL); + ret = dupprintf("%s/%s", tmp, munged); + sfree(tmp); + sfree(munged); + return ret; + } + if (index == INDEX_HOSTKEYS) { + env = getenv("PUTTYSSHHOSTKEYS"); + if (env) + return dupstr(env); + tmp = make_filename(INDEX_DIR, NULL); + ret = dupprintf("%s/sshhostkeys", tmp); + sfree(tmp); + return ret; + } + if (index == INDEX_HOSTKEYS_TMP) { + tmp = make_filename(INDEX_HOSTKEYS, NULL); + ret = dupprintf("%s.tmp", tmp); + sfree(tmp); + return ret; + } + if (index == INDEX_RANDSEED) { + env = getenv("PUTTYRANDOMSEED"); + if (env) + return dupstr(env); + tmp = make_filename(INDEX_DIR, NULL); + ret = dupprintf("%s/randomseed", tmp); + sfree(tmp); + return ret; + } + tmp = make_filename(INDEX_DIR, NULL); + ret = dupprintf("%s/ERROR", tmp); + sfree(tmp); + return ret; +} + +void *open_settings_w(const char *sessionname, char **errmsg) +{ + char *filename; + FILE *fp; + + *errmsg = NULL; + + /* + * Start by making sure the .putty directory and its sessions + * subdir actually exist. Ignore error returns from mkdir since + * they're perfectly likely to be `already exists', and any + * other error will trip us up later on so there's no real need + * to catch it now. + */ + filename = make_filename(INDEX_SESSIONDIR, NULL); + if (mkdir(filename, 0700) != 0) { + char *filename2 = make_filename(INDEX_DIR, NULL); + mkdir(filename2, 0700); + sfree(filename2); + mkdir(filename, 0700); + } + sfree(filename); + + filename = make_filename(INDEX_SESSION, sessionname); + fp = fopen(filename, "w"); + if (!fp) { + *errmsg = dupprintf("Unable to create %s: %s", + filename, strerror(errno)); + sfree(filename); + return NULL; /* can't open */ + } + sfree(filename); + return fp; +} + +void write_setting_s(void *handle, const char *key, const char *value) +{ + FILE *fp = (FILE *)handle; + fprintf(fp, "%s=%s\n", key, value); +} + +void write_setting_i(void *handle, const char *key, int value) +{ + FILE *fp = (FILE *)handle; + fprintf(fp, "%s=%d\n", key, value); +} + +void close_settings_w(void *handle) +{ + FILE *fp = (FILE *)handle; + fclose(fp); +} + +/* + * Reading settings, for the moment, is done by retrieving X + * resources from the X display. When we introduce disk files, I + * think what will happen is that the X resources will override + * PuTTY's inbuilt defaults, but that the disk files will then + * override those. This isn't optimal, but it's the best I can + * immediately work out. + * FIXME: the above comment is a bit out of date. Did it happen? + */ + +struct skeyval { + const char *key; + const char *value; +}; + +static tree234 *xrmtree = NULL; + +int keycmp(void *av, void *bv) +{ + struct skeyval *a = (struct skeyval *)av; + struct skeyval *b = (struct skeyval *)bv; + return strcmp(a->key, b->key); +} + +void provide_xrm_string(char *string) +{ + char *p, *q, *key; + struct skeyval *xrms, *ret; + + p = q = strchr(string, ':'); + if (!q) { + fprintf(stderr, "pterm: expected a colon in resource string" + " \"%s\"\n", string); + return; + } + q++; + while (p > string && p[-1] != '.' && p[-1] != '*') + p--; + xrms = snew(struct skeyval); + key = snewn(q-p, char); + memcpy(key, p, q-p); + key[q-p-1] = '\0'; + xrms->key = key; + while (*q && isspace((unsigned char)*q)) + q++; + xrms->value = dupstr(q); + + if (!xrmtree) + xrmtree = newtree234(keycmp); + + ret = add234(xrmtree, xrms); + if (ret) { + /* Override an existing string. */ + del234(xrmtree, ret); + add234(xrmtree, xrms); + } +} + +const char *get_setting(const char *key) +{ + struct skeyval tmp, *ret; + tmp.key = key; + if (xrmtree) { + ret = find234(xrmtree, &tmp, NULL); + if (ret) + return ret->value; + } + return x_get_default(key); +} + +void *open_settings_r(const char *sessionname) +{ + char *filename; + FILE *fp; + char *line; + tree234 *ret; + + filename = make_filename(INDEX_SESSION, sessionname); + fp = fopen(filename, "r"); + sfree(filename); + if (!fp) + return NULL; /* can't open */ + + ret = newtree234(keycmp); + + while ( (line = fgetline(fp)) ) { + char *value = strchr(line, '='); + struct skeyval *kv; + + if (!value) + continue; + *value++ = '\0'; + value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */ + + kv = snew(struct skeyval); + kv->key = dupstr(line); + kv->value = dupstr(value); + add234(ret, kv); + + sfree(line); + } + + fclose(fp); + + return ret; +} + +char *read_setting_s(void *handle, const char *key, char *buffer, int buflen) +{ + tree234 *tree = (tree234 *)handle; + const char *val; + struct skeyval tmp, *kv; + + tmp.key = key; + if (tree != NULL && + (kv = find234(tree, &tmp, NULL)) != NULL) { + val = kv->value; + assert(val != NULL); + } else + val = get_setting(key); + + if (!val) + return NULL; + else { + strncpy(buffer, val, buflen); + buffer[buflen-1] = '\0'; + return buffer; + } +} + +int read_setting_i(void *handle, const char *key, int defvalue) +{ + tree234 *tree = (tree234 *)handle; + const char *val; + struct skeyval tmp, *kv; + + tmp.key = key; + if (tree != NULL && + (kv = find234(tree, &tmp, NULL)) != NULL) { + val = kv->value; + assert(val != NULL); + } else + val = get_setting(key); + + if (!val) + return defvalue; + else + return atoi(val); +} + +int read_setting_fontspec(void *handle, const char *name, FontSpec *result) +{ + /* + * In GTK1-only PuTTY, we used to store font names simply as a + * valid X font description string (logical or alias), under a + * bare key such as "Font". + * + * In GTK2 PuTTY, we have a prefix system where "client:" + * indicates a Pango font and "server:" an X one; existing + * configuration needs to be reinterpreted as having the + * "server:" prefix, so we change the storage key from the + * provided name string (e.g. "Font") to a suffixed one + * ("FontName"). + */ + char *suffname = dupcat(name, "Name", NULL); + if (read_setting_s(handle, suffname, result->name, sizeof(result->name))) { + sfree(suffname); + return TRUE; /* got new-style name */ + } + sfree(suffname); + + /* Fall back to old-style name. */ + memcpy(result->name, "server:", 7); + if (!read_setting_s(handle, name, + result->name + 7, sizeof(result->name) - 7) || + !result->name[7]) { + result->name[0] = '\0'; + return FALSE; + } else { + return TRUE; + } +} +int read_setting_filename(void *handle, const char *name, Filename *result) +{ + return !!read_setting_s(handle, name, result->path, sizeof(result->path)); +} + +void write_setting_fontspec(void *handle, const char *name, FontSpec result) +{ + /* + * read_setting_fontspec had to handle two cases, but when + * writing our settings back out we simply always generate the + * new-style name. + */ + char *suffname = dupcat(name, "Name", NULL); + write_setting_s(handle, suffname, result.name); + sfree(suffname); +} +void write_setting_filename(void *handle, const char *name, Filename result) +{ + write_setting_s(handle, name, result.path); +} + +void close_settings_r(void *handle) +{ + tree234 *tree = (tree234 *)handle; + struct skeyval *kv; + + if (!tree) + return; + + while ( (kv = index234(tree, 0)) != NULL) { + del234(tree, kv); + sfree((char *)kv->key); + sfree((char *)kv->value); + sfree(kv); + } + + freetree234(tree); +} + +void del_settings(const char *sessionname) +{ + char *filename; + filename = make_filename(INDEX_SESSION, sessionname); + unlink(filename); + sfree(filename); +} + +void *enum_settings_start(void) +{ + DIR *dp; + char *filename; + + filename = make_filename(INDEX_SESSIONDIR, NULL); + dp = opendir(filename); + sfree(filename); + + return dp; +} + +char *enum_settings_next(void *handle, char *buffer, int buflen) +{ + DIR *dp = (DIR *)handle; + struct dirent *de; + struct stat st; + char *fullpath; + int maxlen, thislen, len; + char *unmunged; + + fullpath = make_filename(INDEX_SESSIONDIR, NULL); + maxlen = len = strlen(fullpath); + + while ( (de = readdir(dp)) != NULL ) { + thislen = len + 1 + strlen(de->d_name); + if (maxlen < thislen) { + maxlen = thislen; + fullpath = sresize(fullpath, maxlen+1, char); + } + fullpath[len] = '/'; + strncpy(fullpath+len+1, de->d_name, thislen - (len+1)); + fullpath[thislen] = '\0'; + + if (stat(fullpath, &st) < 0 || !S_ISREG(st.st_mode)) + continue; /* try another one */ + + unmunged = unmungestr(de->d_name); + strncpy(buffer, unmunged, buflen); + buffer[buflen-1] = '\0'; + sfree(unmunged); + sfree(fullpath); + return buffer; + } + + sfree(fullpath); + return NULL; +} + +void enum_settings_finish(void *handle) +{ + DIR *dp = (DIR *)handle; + closedir(dp); +} + +/* + * Lines in the host keys file are of the form + * + * type@port:hostname keydata + * + * e.g. + * + * rsa@22:foovax.example.org 0x23,0x293487364395345345....2343 + */ +int verify_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + FILE *fp; + char *filename; + char *line; + int ret; + + filename = make_filename(INDEX_HOSTKEYS, NULL); + fp = fopen(filename, "r"); + sfree(filename); + if (!fp) + return 1; /* key does not exist */ + + ret = 1; + while ( (line = fgetline(fp)) ) { + int i; + char *p = line; + char porttext[20]; + + line[strcspn(line, "\n")] = '\0'; /* strip trailing newline */ + + i = strlen(keytype); + if (strncmp(p, keytype, i)) + goto done; + p += i; + + if (*p != '@') + goto done; + p++; + + sprintf(porttext, "%d", port); + i = strlen(porttext); + if (strncmp(p, porttext, i)) + goto done; + p += i; + + if (*p != ':') + goto done; + p++; + + i = strlen(hostname); + if (strncmp(p, hostname, i)) + goto done; + p += i; + + if (*p != ' ') + goto done; + p++; + + /* + * Found the key. Now just work out whether it's the right + * one or not. + */ + if (!strcmp(p, key)) + ret = 0; /* key matched OK */ + else + ret = 2; /* key mismatch */ + + done: + sfree(line); + if (ret != 1) + break; + } + + fclose(fp); + return ret; +} + +void store_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + FILE *rfp, *wfp; + char *newtext, *line; + int headerlen; + char *filename, *tmpfilename; + + newtext = dupprintf("%s@%d:%s %s\n", keytype, port, hostname, key); + headerlen = 1 + strcspn(newtext, " "); /* count the space too */ + + /* + * Open both the old file and a new file. + */ + tmpfilename = make_filename(INDEX_HOSTKEYS_TMP, NULL); + wfp = fopen(tmpfilename, "w"); + if (!wfp) { + char *dir; + + dir = make_filename(INDEX_DIR, NULL); + mkdir(dir, 0700); + sfree(dir); + + wfp = fopen(tmpfilename, "w"); + } + if (!wfp) { + sfree(tmpfilename); + return; + } + filename = make_filename(INDEX_HOSTKEYS, NULL); + rfp = fopen(filename, "r"); + + /* + * 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); + } + fclose(rfp); + } + + /* + * Now add the new line at the end. + */ + fputs(newtext, wfp); + + fclose(wfp); + + rename(tmpfilename, filename); + + 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) { + char *dir; + + dir = make_filename(INDEX_DIR, NULL); + mkdir(dir, 0700); + sfree(dir); + + fd = open(fname, O_CREAT | O_WRONLY, 0600); + } + + while (len > 0) { + int ret = write(fd, data, len); + if (ret <= 0) break; + len -= ret; + data = (char *)data + len; + } + + close(fd); + sfree(fname); +} + +void cleanup_all(void) +{ +} diff --git a/putty/UNIX/UXUCS.C b/putty/UNIX/UXUCS.C new file mode 100644 index 0000000..d2d6522 --- /dev/null +++ b/putty/UNIX/UXUCS.C @@ -0,0 +1,273 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "putty.h" +#include "charset.h" +#include "terminal.h" +#include "misc.h" + +/* + * Unix Unicode-handling routines. + */ + +int is_dbcs_leadbyte(int codepage, char byte) +{ + return 0; /* we don't do DBCS */ +} + +int mb_to_wc(int codepage, int flags, char *mbstr, int mblen, + wchar_t *wcstr, int wclen) +{ + if (codepage == DEFAULT_CODEPAGE) { + int n = 0; + mbstate_t state; + + memset(&state, 0, sizeof state); + setlocale(LC_CTYPE, ""); + + 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; + } + + setlocale(LC_CTYPE, "C"); + + 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, wchar_t *wcstr, int wclen, + char *mbstr, int mblen, char *defchr, int *defused, + struct unicode_data *ucsdata) +{ + /* FIXME: we should remove the defused param completely... */ + if (defused) + *defused = 0; + + if (codepage == DEFAULT_CODEPAGE) { + char output[MB_LEN_MAX]; + mbstate_t state; + int n = 0; + + memset(&state, 0, sizeof state); + setlocale(LC_CTYPE, ""); + + while (wclen > 0) { + int i = wcrtomb(output, wcstr[0], &state); + if (i == (size_t)-1 || i > n - mblen) + break; + memcpy(mbstr+n, output, i); + n += i; + wcstr++; + wclen--; + } + + setlocale(LC_CTYPE, "C"); + + return n; + } else if (codepage == CS_NONE) { + int n = 0; + while (wclen > 0 && n < mblen) { + if (*wcstr >= 0xD800 && *wcstr < 0xD900) + mbstr[n++] = (*wcstr & 0xFF); + else if (defchr) + mbstr[n++] = *defchr; + wcstr++; + wclen--; + } + return n; + } else { + return charset_from_unicode(&wcstr, &wclen, mbstr, mblen, codepage, + NULL, defchr?defchr:NULL, defchr?1:0); + } +} + +/* + * Return value is TRUE if pterm is to run in direct-to-font mode. + */ +int init_ucs(struct unicode_data *ucsdata, char *linecharset, + int utf8_override, int font_charset, int vtmode) +{ + int i, ret = 0; + + /* + * In the platform-independent parts of the code, font_codepage + * is used only for system DBCS support - which we don't + * support at all. So we set this to something which will never + * be used. + */ + ucsdata->font_codepage = -1; + + /* + * If utf8_override is set and the POSIX locale settings + * dictate a UTF-8 character set, then just go straight for + * UTF-8. + */ + ucsdata->line_codepage = CS_NONE; + if (utf8_override) { + const char *s; + if (((s = getenv("LC_ALL")) && *s) || + ((s = getenv("LC_CTYPE")) && *s) || + ((s = getenv("LANG")) && *s)) { + if (strstr(s, "UTF-8")) + ucsdata->line_codepage = CS_UTF8; + } + } + + /* + * Failing that, line_codepage should be decoded from the + * specification in cfg. + */ + if (ucsdata->line_codepage == CS_NONE) + ucsdata->line_codepage = decode_codepage(linecharset); + + /* + * If line_codepage is _still_ CS_NONE, we assume we're using + * the font's own encoding. This has been passed in to us, so + * we use that. If it's still CS_NONE after _that_ - i.e. the + * font we were given had an incomprehensible charset - then we + * fall back to using the D800 page. + */ + if (ucsdata->line_codepage == CS_NONE) + ucsdata->line_codepage = font_charset; + + if (ucsdata->line_codepage == CS_NONE) + ret = 1; + + /* + * Set up unitab_line, by translating each individual character + * in the line codepage into Unicode. + */ + for (i = 0; i < 256; i++) { + char c[1], *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], *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; + if (index == 0) + return "Use font encoding"; + charset = charset_localenc_nth(index-1); + if (charset == CS_NONE) + return NULL; + return charset_to_localenc(charset); +} + +int decode_codepage(char *cp_name) +{ + if (!*cp_name) + return CS_NONE; /* use font encoding */ + return charset_from_localenc(cp_name); +} diff --git a/putty/UNIX/UX_X11.C b/putty/UNIX/UX_X11.C new file mode 100644 index 0000000..7dd17df --- /dev/null +++ b/putty/UNIX/UX_X11.C @@ -0,0 +1,40 @@ +/* + * ux_x11.c: fetch local auth data for X forwarding. + */ + +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "network.h" + +void platform_get_x11_auth(struct X11Display *disp, const Config *cfg) +{ + char *xauthfile; + int needs_free; + + /* + * Find the .Xauthority file. + */ + needs_free = FALSE; + xauthfile = getenv("XAUTHORITY"); + if (!xauthfile) { + xauthfile = getenv("HOME"); + if (xauthfile) { + xauthfile = dupcat(xauthfile, "/.Xauthority", NULL); + needs_free = TRUE; + } + } + + if (xauthfile) { + x11_get_auth_from_authfile(disp, xauthfile); + if (needs_free) + sfree(xauthfile); + } +} + +const int platform_uses_x11_unix_by_default = TRUE; diff --git a/putty/UNIX/XKEYSYM.C b/putty/UNIX/XKEYSYM.C new file mode 100644 index 0000000..cdad7de --- /dev/null +++ b/putty/UNIX/XKEYSYM.C @@ -0,0 +1,1011 @@ +/* + * xkeysym.c: mapping from X keysyms to Unicode values + * + * The basic idea of this is shamelessly cribbed from xterm. The + * actual character data is generated from Markus Kuhn's proposed + * redraft of the X11 keysym mapping table, using the following + * piece of Perl/sh code: + +wget -q -O - http://www.cl.cam.ac.uk/~mgk25/ucs/X11.keysyms | \ +perl -ne '/^(\d+)\s+(\d+)\s+[\d\/]+\s+U\+([\dA-Fa-f]+)/ and' \ + -e ' do { $a{$1 * 256+ $2} = hex $3; };' \ + -e 'END { foreach $i (sort {$a <=> $b} keys %a) {' \ + -e ' printf " {0x%x, 0x%x},\n", $i, $a{$i} } }' \ + -e 'BEGIN { $a{0x13a4} = 0x20ac }' + + * (The BEGIN clause inserts a mapping for the Euro sign which for + * some reason isn't in the list but xterm supports. *shrug*.) + */ + +#include "misc.h" + +struct keysym { + /* + * Currently nothing in here is above 0xFFFF, so I'll use + * `unsigned short' to save space. + */ + unsigned short keysym; + unsigned short unicode; +}; + +static struct keysym keysyms[] = { + {0x20, 0x20}, + {0x21, 0x21}, + {0x22, 0x22}, + {0x23, 0x23}, + {0x24, 0x24}, + {0x25, 0x25}, + {0x26, 0x26}, + {0x27, 0x27}, + {0x28, 0x28}, + {0x29, 0x29}, + {0x2a, 0x2a}, + {0x2b, 0x2b}, + {0x2c, 0x2c}, + {0x2d, 0x2d}, + {0x2e, 0x2e}, + {0x2f, 0x2f}, + {0x30, 0x30}, + {0x31, 0x31}, + {0x32, 0x32}, + {0x33, 0x33}, + {0x34, 0x34}, + {0x35, 0x35}, + {0x36, 0x36}, + {0x37, 0x37}, + {0x38, 0x38}, + {0x39, 0x39}, + {0x3a, 0x3a}, + {0x3b, 0x3b}, + {0x3c, 0x3c}, + {0x3d, 0x3d}, + {0x3e, 0x3e}, + {0x3f, 0x3f}, + {0x40, 0x40}, + {0x41, 0x41}, + {0x42, 0x42}, + {0x43, 0x43}, + {0x44, 0x44}, + {0x45, 0x45}, + {0x46, 0x46}, + {0x47, 0x47}, + {0x48, 0x48}, + {0x49, 0x49}, + {0x4a, 0x4a}, + {0x4b, 0x4b}, + {0x4c, 0x4c}, + {0x4d, 0x4d}, + {0x4e, 0x4e}, + {0x4f, 0x4f}, + {0x50, 0x50}, + {0x51, 0x51}, + {0x52, 0x52}, + {0x53, 0x53}, + {0x54, 0x54}, + {0x55, 0x55}, + {0x56, 0x56}, + {0x57, 0x57}, + {0x58, 0x58}, + {0x59, 0x59}, + {0x5a, 0x5a}, + {0x5b, 0x5b}, + {0x5c, 0x5c}, + {0x5d, 0x5d}, + {0x5e, 0x5e}, + {0x5f, 0x5f}, + {0x60, 0x60}, + {0x61, 0x61}, + {0x62, 0x62}, + {0x63, 0x63}, + {0x64, 0x64}, + {0x65, 0x65}, + {0x66, 0x66}, + {0x67, 0x67}, + {0x68, 0x68}, + {0x69, 0x69}, + {0x6a, 0x6a}, + {0x6b, 0x6b}, + {0x6c, 0x6c}, + {0x6d, 0x6d}, + {0x6e, 0x6e}, + {0x6f, 0x6f}, + {0x70, 0x70}, + {0x71, 0x71}, + {0x72, 0x72}, + {0x73, 0x73}, + {0x74, 0x74}, + {0x75, 0x75}, + {0x76, 0x76}, + {0x77, 0x77}, + {0x78, 0x78}, + {0x79, 0x79}, + {0x7a, 0x7a}, + {0x7b, 0x7b}, + {0x7c, 0x7c}, + {0x7d, 0x7d}, + {0x7e, 0x7e}, + {0xa0, 0xa0}, + {0xa1, 0xa1}, + {0xa2, 0xa2}, + {0xa3, 0xa3}, + {0xa4, 0xa4}, + {0xa5, 0xa5}, + {0xa6, 0xa6}, + {0xa7, 0xa7}, + {0xa8, 0xa8}, + {0xa9, 0xa9}, + {0xaa, 0xaa}, + {0xab, 0xab}, + {0xac, 0xac}, + {0xad, 0xad}, + {0xae, 0xae}, + {0xaf, 0xaf}, + {0xb0, 0xb0}, + {0xb1, 0xb1}, + {0xb2, 0xb2}, + {0xb3, 0xb3}, + {0xb4, 0xb4}, + {0xb5, 0xb5}, + {0xb6, 0xb6}, + {0xb7, 0xb7}, + {0xb8, 0xb8}, + {0xb9, 0xb9}, + {0xba, 0xba}, + {0xbb, 0xbb}, + {0xbc, 0xbc}, + {0xbd, 0xbd}, + {0xbe, 0xbe}, + {0xbf, 0xbf}, + {0xc0, 0xc0}, + {0xc1, 0xc1}, + {0xc2, 0xc2}, + {0xc3, 0xc3}, + {0xc4, 0xc4}, + {0xc5, 0xc5}, + {0xc6, 0xc6}, + {0xc7, 0xc7}, + {0xc8, 0xc8}, + {0xc9, 0xc9}, + {0xca, 0xca}, + {0xcb, 0xcb}, + {0xcc, 0xcc}, + {0xcd, 0xcd}, + {0xce, 0xce}, + {0xcf, 0xcf}, + {0xd0, 0xd0}, + {0xd1, 0xd1}, + {0xd2, 0xd2}, + {0xd3, 0xd3}, + {0xd4, 0xd4}, + {0xd5, 0xd5}, + {0xd6, 0xd6}, + {0xd7, 0xd7}, + {0xd8, 0xd8}, + {0xd9, 0xd9}, + {0xda, 0xda}, + {0xdb, 0xdb}, + {0xdc, 0xdc}, + {0xdd, 0xdd}, + {0xde, 0xde}, + {0xdf, 0xdf}, + {0xe0, 0xe0}, + {0xe1, 0xe1}, + {0xe2, 0xe2}, + {0xe3, 0xe3}, + {0xe4, 0xe4}, + {0xe5, 0xe5}, + {0xe6, 0xe6}, + {0xe7, 0xe7}, + {0xe8, 0xe8}, + {0xe9, 0xe9}, + {0xea, 0xea}, + {0xeb, 0xeb}, + {0xec, 0xec}, + {0xed, 0xed}, + {0xee, 0xee}, + {0xef, 0xef}, + {0xf0, 0xf0}, + {0xf1, 0xf1}, + {0xf2, 0xf2}, + {0xf3, 0xf3}, + {0xf4, 0xf4}, + {0xf5, 0xf5}, + {0xf6, 0xf6}, + {0xf7, 0xf7}, + {0xf8, 0xf8}, + {0xf9, 0xf9}, + {0xfa, 0xfa}, + {0xfb, 0xfb}, + {0xfc, 0xfc}, + {0xfd, 0xfd}, + {0xfe, 0xfe}, + {0xff, 0xff}, + {0x1a1, 0x104}, + {0x1a2, 0x2d8}, + {0x1a3, 0x141}, + {0x1a5, 0x13d}, + {0x1a6, 0x15a}, + {0x1a9, 0x160}, + {0x1aa, 0x15e}, + {0x1ab, 0x164}, + {0x1ac, 0x179}, + {0x1ae, 0x17d}, + {0x1af, 0x17b}, + {0x1b1, 0x105}, + {0x1b2, 0x2db}, + {0x1b3, 0x142}, + {0x1b5, 0x13e}, + {0x1b6, 0x15b}, + {0x1b7, 0x2c7}, + {0x1b9, 0x161}, + {0x1ba, 0x15f}, + {0x1bb, 0x165}, + {0x1bc, 0x17a}, + {0x1bd, 0x2dd}, + {0x1be, 0x17e}, + {0x1bf, 0x17c}, + {0x1c0, 0x154}, + {0x1c3, 0x102}, + {0x1c5, 0x139}, + {0x1c6, 0x106}, + {0x1c8, 0x10c}, + {0x1ca, 0x118}, + {0x1cc, 0x11a}, + {0x1cf, 0x10e}, + {0x1d0, 0x110}, + {0x1d1, 0x143}, + {0x1d2, 0x147}, + {0x1d5, 0x150}, + {0x1d8, 0x158}, + {0x1d9, 0x16e}, + {0x1db, 0x170}, + {0x1de, 0x162}, + {0x1e0, 0x155}, + {0x1e3, 0x103}, + {0x1e5, 0x13a}, + {0x1e6, 0x107}, + {0x1e8, 0x10d}, + {0x1ea, 0x119}, + {0x1ec, 0x11b}, + {0x1ef, 0x10f}, + {0x1f0, 0x111}, + {0x1f1, 0x144}, + {0x1f2, 0x148}, + {0x1f5, 0x151}, + {0x1f8, 0x159}, + {0x1f9, 0x16f}, + {0x1fb, 0x171}, + {0x1fe, 0x163}, + {0x1ff, 0x2d9}, + {0x2a1, 0x126}, + {0x2a6, 0x124}, + {0x2a9, 0x130}, + {0x2ab, 0x11e}, + {0x2ac, 0x134}, + {0x2b1, 0x127}, + {0x2b6, 0x125}, + {0x2b9, 0x131}, + {0x2bb, 0x11f}, + {0x2bc, 0x135}, + {0x2c5, 0x10a}, + {0x2c6, 0x108}, + {0x2d5, 0x120}, + {0x2d8, 0x11c}, + {0x2dd, 0x16c}, + {0x2de, 0x15c}, + {0x2e5, 0x10b}, + {0x2e6, 0x109}, + {0x2f5, 0x121}, + {0x2f8, 0x11d}, + {0x2fd, 0x16d}, + {0x2fe, 0x15d}, + {0x3a2, 0x138}, + {0x3a3, 0x156}, + {0x3a5, 0x128}, + {0x3a6, 0x13b}, + {0x3aa, 0x112}, + {0x3ab, 0x122}, + {0x3ac, 0x166}, + {0x3b3, 0x157}, + {0x3b5, 0x129}, + {0x3b6, 0x13c}, + {0x3ba, 0x113}, + {0x3bb, 0x123}, + {0x3bc, 0x167}, + {0x3bd, 0x14a}, + {0x3bf, 0x14b}, + {0x3c0, 0x100}, + {0x3c7, 0x12e}, + {0x3cc, 0x116}, + {0x3cf, 0x12a}, + {0x3d1, 0x145}, + {0x3d2, 0x14c}, + {0x3d3, 0x136}, + {0x3d9, 0x172}, + {0x3dd, 0x168}, + {0x3de, 0x16a}, + {0x3e0, 0x101}, + {0x3e7, 0x12f}, + {0x3ec, 0x117}, + {0x3ef, 0x12b}, + {0x3f1, 0x146}, + {0x3f2, 0x14d}, + {0x3f3, 0x137}, + {0x3f9, 0x173}, + {0x3fd, 0x169}, + {0x3fe, 0x16b}, + {0x47e, 0x203e}, + {0x4a1, 0x3002}, + {0x4a2, 0x300c}, + {0x4a3, 0x300d}, + {0x4a4, 0x3001}, + {0x4a5, 0x30fb}, + {0x4a6, 0x30f2}, + {0x4a7, 0x30a1}, + {0x4a8, 0x30a3}, + {0x4a9, 0x30a5}, + {0x4aa, 0x30a7}, + {0x4ab, 0x30a9}, + {0x4ac, 0x30e3}, + {0x4ad, 0x30e5}, + {0x4ae, 0x30e7}, + {0x4af, 0x30c3}, + {0x4b0, 0x30fc}, + {0x4b1, 0x30a2}, + {0x4b2, 0x30a4}, + {0x4b3, 0x30a6}, + {0x4b4, 0x30a8}, + {0x4b5, 0x30aa}, + {0x4b6, 0x30ab}, + {0x4b7, 0x30ad}, + {0x4b8, 0x30af}, + {0x4b9, 0x30b1}, + {0x4ba, 0x30b3}, + {0x4bb, 0x30b5}, + {0x4bc, 0x30b7}, + {0x4bd, 0x30b9}, + {0x4be, 0x30bb}, + {0x4bf, 0x30bd}, + {0x4c0, 0x30bf}, + {0x4c1, 0x30c1}, + {0x4c2, 0x30c4}, + {0x4c3, 0x30c6}, + {0x4c4, 0x30c8}, + {0x4c5, 0x30ca}, + {0x4c6, 0x30cb}, + {0x4c7, 0x30cc}, + {0x4c8, 0x30cd}, + {0x4c9, 0x30ce}, + {0x4ca, 0x30cf}, + {0x4cb, 0x30d2}, + {0x4cc, 0x30d5}, + {0x4cd, 0x30d8}, + {0x4ce, 0x30db}, + {0x4cf, 0x30de}, + {0x4d0, 0x30df}, + {0x4d1, 0x30e0}, + {0x4d2, 0x30e1}, + {0x4d3, 0x30e2}, + {0x4d4, 0x30e4}, + {0x4d5, 0x30e6}, + {0x4d6, 0x30e8}, + {0x4d7, 0x30e9}, + {0x4d8, 0x30ea}, + {0x4d9, 0x30eb}, + {0x4da, 0x30ec}, + {0x4db, 0x30ed}, + {0x4dc, 0x30ef}, + {0x4dd, 0x30f3}, + {0x4de, 0x309b}, + {0x4df, 0x309c}, + {0x5ac, 0x60c}, + {0x5bb, 0x61b}, + {0x5bf, 0x61f}, + {0x5c1, 0x621}, + {0x5c2, 0x622}, + {0x5c3, 0x623}, + {0x5c4, 0x624}, + {0x5c5, 0x625}, + {0x5c6, 0x626}, + {0x5c7, 0x627}, + {0x5c8, 0x628}, + {0x5c9, 0x629}, + {0x5ca, 0x62a}, + {0x5cb, 0x62b}, + {0x5cc, 0x62c}, + {0x5cd, 0x62d}, + {0x5ce, 0x62e}, + {0x5cf, 0x62f}, + {0x5d0, 0x630}, + {0x5d1, 0x631}, + {0x5d2, 0x632}, + {0x5d3, 0x633}, + {0x5d4, 0x634}, + {0x5d5, 0x635}, + {0x5d6, 0x636}, + {0x5d7, 0x637}, + {0x5d8, 0x638}, + {0x5d9, 0x639}, + {0x5da, 0x63a}, + {0x5e0, 0x640}, + {0x5e1, 0x641}, + {0x5e2, 0x642}, + {0x5e3, 0x643}, + {0x5e4, 0x644}, + {0x5e5, 0x645}, + {0x5e6, 0x646}, + {0x5e7, 0x647}, + {0x5e8, 0x648}, + {0x5e9, 0x649}, + {0x5ea, 0x64a}, + {0x5eb, 0x64b}, + {0x5ec, 0x64c}, + {0x5ed, 0x64d}, + {0x5ee, 0x64e}, + {0x5ef, 0x64f}, + {0x5f0, 0x650}, + {0x5f1, 0x651}, + {0x5f2, 0x652}, + {0x6a1, 0x452}, + {0x6a2, 0x453}, + {0x6a3, 0x451}, + {0x6a4, 0x454}, + {0x6a5, 0x455}, + {0x6a6, 0x456}, + {0x6a7, 0x457}, + {0x6a8, 0x458}, + {0x6a9, 0x459}, + {0x6aa, 0x45a}, + {0x6ab, 0x45b}, + {0x6ac, 0x45c}, + {0x6ae, 0x45e}, + {0x6af, 0x45f}, + {0x6b0, 0x2116}, + {0x6b1, 0x402}, + {0x6b2, 0x403}, + {0x6b3, 0x401}, + {0x6b4, 0x404}, + {0x6b5, 0x405}, + {0x6b6, 0x406}, + {0x6b7, 0x407}, + {0x6b8, 0x408}, + {0x6b9, 0x409}, + {0x6ba, 0x40a}, + {0x6bb, 0x40b}, + {0x6bc, 0x40c}, + {0x6be, 0x40e}, + {0x6bf, 0x40f}, + {0x6c0, 0x44e}, + {0x6c1, 0x430}, + {0x6c2, 0x431}, + {0x6c3, 0x446}, + {0x6c4, 0x434}, + {0x6c5, 0x435}, + {0x6c6, 0x444}, + {0x6c7, 0x433}, + {0x6c8, 0x445}, + {0x6c9, 0x438}, + {0x6ca, 0x439}, + {0x6cb, 0x43a}, + {0x6cc, 0x43b}, + {0x6cd, 0x43c}, + {0x6ce, 0x43d}, + {0x6cf, 0x43e}, + {0x6d0, 0x43f}, + {0x6d1, 0x44f}, + {0x6d2, 0x440}, + {0x6d3, 0x441}, + {0x6d4, 0x442}, + {0x6d5, 0x443}, + {0x6d6, 0x436}, + {0x6d7, 0x432}, + {0x6d8, 0x44c}, + {0x6d9, 0x44b}, + {0x6da, 0x437}, + {0x6db, 0x448}, + {0x6dc, 0x44d}, + {0x6dd, 0x449}, + {0x6de, 0x447}, + {0x6df, 0x44a}, + {0x6e0, 0x42e}, + {0x6e1, 0x410}, + {0x6e2, 0x411}, + {0x6e3, 0x426}, + {0x6e4, 0x414}, + {0x6e5, 0x415}, + {0x6e6, 0x424}, + {0x6e7, 0x413}, + {0x6e8, 0x425}, + {0x6e9, 0x418}, + {0x6ea, 0x419}, + {0x6eb, 0x41a}, + {0x6ec, 0x41b}, + {0x6ed, 0x41c}, + {0x6ee, 0x41d}, + {0x6ef, 0x41e}, + {0x6f0, 0x41f}, + {0x6f1, 0x42f}, + {0x6f2, 0x420}, + {0x6f3, 0x421}, + {0x6f4, 0x422}, + {0x6f5, 0x423}, + {0x6f6, 0x416}, + {0x6f7, 0x412}, + {0x6f8, 0x42c}, + {0x6f9, 0x42b}, + {0x6fa, 0x417}, + {0x6fb, 0x428}, + {0x6fc, 0x42d}, + {0x6fd, 0x429}, + {0x6fe, 0x427}, + {0x6ff, 0x42a}, + {0x7a1, 0x386}, + {0x7a2, 0x388}, + {0x7a3, 0x389}, + {0x7a4, 0x38a}, + {0x7a5, 0x3aa}, + {0x7a7, 0x38c}, + {0x7a8, 0x38e}, + {0x7a9, 0x3ab}, + {0x7ab, 0x38f}, + {0x7ae, 0x385}, + {0x7af, 0x2015}, + {0x7b1, 0x3ac}, + {0x7b2, 0x3ad}, + {0x7b3, 0x3ae}, + {0x7b4, 0x3af}, + {0x7b5, 0x3ca}, + {0x7b6, 0x390}, + {0x7b7, 0x3cc}, + {0x7b8, 0x3cd}, + {0x7b9, 0x3cb}, + {0x7ba, 0x3b0}, + {0x7bb, 0x3ce}, + {0x7c1, 0x391}, + {0x7c2, 0x392}, + {0x7c3, 0x393}, + {0x7c4, 0x394}, + {0x7c5, 0x395}, + {0x7c6, 0x396}, + {0x7c7, 0x397}, + {0x7c8, 0x398}, + {0x7c9, 0x399}, + {0x7ca, 0x39a}, + {0x7cb, 0x39b}, + {0x7cc, 0x39c}, + {0x7cd, 0x39d}, + {0x7ce, 0x39e}, + {0x7cf, 0x39f}, + {0x7d0, 0x3a0}, + {0x7d1, 0x3a1}, + {0x7d2, 0x3a3}, + {0x7d4, 0x3a4}, + {0x7d5, 0x3a5}, + {0x7d6, 0x3a6}, + {0x7d7, 0x3a7}, + {0x7d8, 0x3a8}, + {0x7d9, 0x3a9}, + {0x7e1, 0x3b1}, + {0x7e2, 0x3b2}, + {0x7e3, 0x3b3}, + {0x7e4, 0x3b4}, + {0x7e5, 0x3b5}, + {0x7e6, 0x3b6}, + {0x7e7, 0x3b7}, + {0x7e8, 0x3b8}, + {0x7e9, 0x3b9}, + {0x7ea, 0x3ba}, + {0x7eb, 0x3bb}, + {0x7ec, 0x3bc}, + {0x7ed, 0x3bd}, + {0x7ee, 0x3be}, + {0x7ef, 0x3bf}, + {0x7f0, 0x3c0}, + {0x7f1, 0x3c1}, + {0x7f2, 0x3c3}, + {0x7f3, 0x3c2}, + {0x7f4, 0x3c4}, + {0x7f5, 0x3c5}, + {0x7f6, 0x3c6}, + {0x7f7, 0x3c7}, + {0x7f8, 0x3c8}, + {0x7f9, 0x3c9}, + {0x8a1, 0x23b7}, + {0x8a2, 0x250c}, + {0x8a3, 0x2500}, + {0x8a4, 0x2320}, + {0x8a5, 0x2321}, + {0x8a6, 0x2502}, + {0x8a7, 0x23a1}, + {0x8a8, 0x23a3}, + {0x8a9, 0x23a4}, + {0x8aa, 0x23a6}, + {0x8ab, 0x239b}, + {0x8ac, 0x239d}, + {0x8ad, 0x239e}, + {0x8ae, 0x23a0}, + {0x8af, 0x23a8}, + {0x8b0, 0x23ac}, + {0x8bc, 0x2264}, + {0x8bd, 0x2260}, + {0x8be, 0x2265}, + {0x8bf, 0x222b}, + {0x8c0, 0x2234}, + {0x8c1, 0x221d}, + {0x8c2, 0x221e}, + {0x8c5, 0x2207}, + {0x8c8, 0x223c}, + {0x8c9, 0x2243}, + {0x8cd, 0x21d4}, + {0x8ce, 0x21d2}, + {0x8cf, 0x2261}, + {0x8d6, 0x221a}, + {0x8da, 0x2282}, + {0x8db, 0x2283}, + {0x8dc, 0x2229}, + {0x8dd, 0x222a}, + {0x8de, 0x2227}, + {0x8df, 0x2228}, + {0x8ef, 0x2202}, + {0x8f6, 0x192}, + {0x8fb, 0x2190}, + {0x8fc, 0x2191}, + {0x8fd, 0x2192}, + {0x8fe, 0x2193}, + {0x9e0, 0x25c6}, + {0x9e1, 0x2592}, + {0x9e2, 0x2409}, + {0x9e3, 0x240c}, + {0x9e4, 0x240d}, + {0x9e5, 0x240a}, + {0x9e8, 0x2424}, + {0x9e9, 0x240b}, + {0x9ea, 0x2518}, + {0x9eb, 0x2510}, + {0x9ec, 0x250c}, + {0x9ed, 0x2514}, + {0x9ee, 0x253c}, + {0x9ef, 0x23ba}, + {0x9f0, 0x23bb}, + {0x9f1, 0x2500}, + {0x9f2, 0x23bc}, + {0x9f3, 0x23bd}, + {0x9f4, 0x251c}, + {0x9f5, 0x2524}, + {0x9f6, 0x2534}, + {0x9f7, 0x252c}, + {0x9f8, 0x2502}, + {0xaa1, 0x2003}, + {0xaa2, 0x2002}, + {0xaa3, 0x2004}, + {0xaa4, 0x2005}, + {0xaa5, 0x2007}, + {0xaa6, 0x2008}, + {0xaa7, 0x2009}, + {0xaa8, 0x200a}, + {0xaa9, 0x2014}, + {0xaaa, 0x2013}, + {0xaae, 0x2026}, + {0xaaf, 0x2025}, + {0xab0, 0x2153}, + {0xab1, 0x2154}, + {0xab2, 0x2155}, + {0xab3, 0x2156}, + {0xab4, 0x2157}, + {0xab5, 0x2158}, + {0xab6, 0x2159}, + {0xab7, 0x215a}, + {0xab8, 0x2105}, + {0xabb, 0x2012}, + {0xabc, 0x2329}, + {0xabe, 0x232a}, + {0xac3, 0x215b}, + {0xac4, 0x215c}, + {0xac5, 0x215d}, + {0xac6, 0x215e}, + {0xac9, 0x2122}, + {0xaca, 0x2613}, + {0xacc, 0x25c1}, + {0xacd, 0x25b7}, + {0xace, 0x25cb}, + {0xacf, 0x25af}, + {0xad0, 0x2018}, + {0xad1, 0x2019}, + {0xad2, 0x201c}, + {0xad3, 0x201d}, + {0xad4, 0x211e}, + {0xad6, 0x2032}, + {0xad7, 0x2033}, + {0xad9, 0x271d}, + {0xadb, 0x25ac}, + {0xadc, 0x25c0}, + {0xadd, 0x25b6}, + {0xade, 0x25cf}, + {0xadf, 0x25ae}, + {0xae0, 0x25e6}, + {0xae1, 0x25ab}, + {0xae2, 0x25ad}, + {0xae3, 0x25b3}, + {0xae4, 0x25bd}, + {0xae5, 0x2606}, + {0xae6, 0x2022}, + {0xae7, 0x25aa}, + {0xae8, 0x25b2}, + {0xae9, 0x25bc}, + {0xaea, 0x261c}, + {0xaeb, 0x261e}, + {0xaec, 0x2663}, + {0xaed, 0x2666}, + {0xaee, 0x2665}, + {0xaf0, 0x2720}, + {0xaf1, 0x2020}, + {0xaf2, 0x2021}, + {0xaf3, 0x2713}, + {0xaf4, 0x2717}, + {0xaf5, 0x266f}, + {0xaf6, 0x266d}, + {0xaf7, 0x2642}, + {0xaf8, 0x2640}, + {0xaf9, 0x260e}, + {0xafa, 0x2315}, + {0xafb, 0x2117}, + {0xafc, 0x2038}, + {0xafd, 0x201a}, + {0xafe, 0x201e}, + {0xba3, 0x3c}, + {0xba6, 0x3e}, + {0xba8, 0x2228}, + {0xba9, 0x2227}, + {0xbc0, 0xaf}, + {0xbc2, 0x22a5}, + {0xbc3, 0x2229}, + {0xbc4, 0x230a}, + {0xbc6, 0x5f}, + {0xbca, 0x2218}, + {0xbcc, 0x2395}, + {0xbce, 0x22a4}, + {0xbcf, 0x25cb}, + {0xbd3, 0x2308}, + {0xbd6, 0x222a}, + {0xbd8, 0x2283}, + {0xbda, 0x2282}, + {0xbdc, 0x22a2}, + {0xbfc, 0x22a3}, + {0xcdf, 0x2017}, + {0xce0, 0x5d0}, + {0xce1, 0x5d1}, + {0xce2, 0x5d2}, + {0xce3, 0x5d3}, + {0xce4, 0x5d4}, + {0xce5, 0x5d5}, + {0xce6, 0x5d6}, + {0xce7, 0x5d7}, + {0xce8, 0x5d8}, + {0xce9, 0x5d9}, + {0xcea, 0x5da}, + {0xceb, 0x5db}, + {0xcec, 0x5dc}, + {0xced, 0x5dd}, + {0xcee, 0x5de}, + {0xcef, 0x5df}, + {0xcf0, 0x5e0}, + {0xcf1, 0x5e1}, + {0xcf2, 0x5e2}, + {0xcf3, 0x5e3}, + {0xcf4, 0x5e4}, + {0xcf5, 0x5e5}, + {0xcf6, 0x5e6}, + {0xcf7, 0x5e7}, + {0xcf8, 0x5e8}, + {0xcf9, 0x5e9}, + {0xcfa, 0x5ea}, + {0xda1, 0xe01}, + {0xda2, 0xe02}, + {0xda3, 0xe03}, + {0xda4, 0xe04}, + {0xda5, 0xe05}, + {0xda6, 0xe06}, + {0xda7, 0xe07}, + {0xda8, 0xe08}, + {0xda9, 0xe09}, + {0xdaa, 0xe0a}, + {0xdab, 0xe0b}, + {0xdac, 0xe0c}, + {0xdad, 0xe0d}, + {0xdae, 0xe0e}, + {0xdaf, 0xe0f}, + {0xdb0, 0xe10}, + {0xdb1, 0xe11}, + {0xdb2, 0xe12}, + {0xdb3, 0xe13}, + {0xdb4, 0xe14}, + {0xdb5, 0xe15}, + {0xdb6, 0xe16}, + {0xdb7, 0xe17}, + {0xdb8, 0xe18}, + {0xdb9, 0xe19}, + {0xdba, 0xe1a}, + {0xdbb, 0xe1b}, + {0xdbc, 0xe1c}, + {0xdbd, 0xe1d}, + {0xdbe, 0xe1e}, + {0xdbf, 0xe1f}, + {0xdc0, 0xe20}, + {0xdc1, 0xe21}, + {0xdc2, 0xe22}, + {0xdc3, 0xe23}, + {0xdc4, 0xe24}, + {0xdc5, 0xe25}, + {0xdc6, 0xe26}, + {0xdc7, 0xe27}, + {0xdc8, 0xe28}, + {0xdc9, 0xe29}, + {0xdca, 0xe2a}, + {0xdcb, 0xe2b}, + {0xdcc, 0xe2c}, + {0xdcd, 0xe2d}, + {0xdce, 0xe2e}, + {0xdcf, 0xe2f}, + {0xdd0, 0xe30}, + {0xdd1, 0xe31}, + {0xdd2, 0xe32}, + {0xdd3, 0xe33}, + {0xdd4, 0xe34}, + {0xdd5, 0xe35}, + {0xdd6, 0xe36}, + {0xdd7, 0xe37}, + {0xdd8, 0xe38}, + {0xdd9, 0xe39}, + {0xdda, 0xe3a}, + {0xddf, 0xe3f}, + {0xde0, 0xe40}, + {0xde1, 0xe41}, + {0xde2, 0xe42}, + {0xde3, 0xe43}, + {0xde4, 0xe44}, + {0xde5, 0xe45}, + {0xde6, 0xe46}, + {0xde7, 0xe47}, + {0xde8, 0xe48}, + {0xde9, 0xe49}, + {0xdea, 0xe4a}, + {0xdeb, 0xe4b}, + {0xdec, 0xe4c}, + {0xded, 0xe4d}, + {0xdf0, 0xe50}, + {0xdf1, 0xe51}, + {0xdf2, 0xe52}, + {0xdf3, 0xe53}, + {0xdf4, 0xe54}, + {0xdf5, 0xe55}, + {0xdf6, 0xe56}, + {0xdf7, 0xe57}, + {0xdf8, 0xe58}, + {0xdf9, 0xe59}, + {0xea1, 0x3131}, + {0xea2, 0x3132}, + {0xea3, 0x3133}, + {0xea4, 0x3134}, + {0xea5, 0x3135}, + {0xea6, 0x3136}, + {0xea7, 0x3137}, + {0xea8, 0x3138}, + {0xea9, 0x3139}, + {0xeaa, 0x313a}, + {0xeab, 0x313b}, + {0xeac, 0x313c}, + {0xead, 0x313d}, + {0xeae, 0x313e}, + {0xeaf, 0x313f}, + {0xeb0, 0x3140}, + {0xeb1, 0x3141}, + {0xeb2, 0x3142}, + {0xeb3, 0x3143}, + {0xeb4, 0x3144}, + {0xeb5, 0x3145}, + {0xeb6, 0x3146}, + {0xeb7, 0x3147}, + {0xeb8, 0x3148}, + {0xeb9, 0x3149}, + {0xeba, 0x314a}, + {0xebb, 0x314b}, + {0xebc, 0x314c}, + {0xebd, 0x314d}, + {0xebe, 0x314e}, + {0xebf, 0x314f}, + {0xec0, 0x3150}, + {0xec1, 0x3151}, + {0xec2, 0x3152}, + {0xec3, 0x3153}, + {0xec4, 0x3154}, + {0xec5, 0x3155}, + {0xec6, 0x3156}, + {0xec7, 0x3157}, + {0xec8, 0x3158}, + {0xec9, 0x3159}, + {0xeca, 0x315a}, + {0xecb, 0x315b}, + {0xecc, 0x315c}, + {0xecd, 0x315d}, + {0xece, 0x315e}, + {0xecf, 0x315f}, + {0xed0, 0x3160}, + {0xed1, 0x3161}, + {0xed2, 0x3162}, + {0xed3, 0x3163}, + {0xed4, 0x11a8}, + {0xed5, 0x11a9}, + {0xed6, 0x11aa}, + {0xed7, 0x11ab}, + {0xed8, 0x11ac}, + {0xed9, 0x11ad}, + {0xeda, 0x11ae}, + {0xedb, 0x11af}, + {0xedc, 0x11b0}, + {0xedd, 0x11b1}, + {0xede, 0x11b2}, + {0xedf, 0x11b3}, + {0xee0, 0x11b4}, + {0xee1, 0x11b5}, + {0xee2, 0x11b6}, + {0xee3, 0x11b7}, + {0xee4, 0x11b8}, + {0xee5, 0x11b9}, + {0xee6, 0x11ba}, + {0xee7, 0x11bb}, + {0xee8, 0x11bc}, + {0xee9, 0x11bd}, + {0xeea, 0x11be}, + {0xeeb, 0x11bf}, + {0xeec, 0x11c0}, + {0xeed, 0x11c1}, + {0xeee, 0x11c2}, + {0xeef, 0x316d}, + {0xef0, 0x3171}, + {0xef1, 0x3178}, + {0xef2, 0x317f}, + {0xef3, 0x3181}, + {0xef4, 0x3184}, + {0xef5, 0x3186}, + {0xef6, 0x318d}, + {0xef7, 0x318e}, + {0xef8, 0x11eb}, + {0xef9, 0x11f0}, + {0xefa, 0x11f9}, + {0xeff, 0x20a9}, + {0x13a4, 0x20ac}, + {0x13bc, 0x152}, + {0x13bd, 0x153}, + {0x13be, 0x178}, + {0x20a0, 0x20a0}, + {0x20a1, 0x20a1}, + {0x20a2, 0x20a2}, + {0x20a3, 0x20a3}, + {0x20a4, 0x20a4}, + {0x20a5, 0x20a5}, + {0x20a6, 0x20a6}, + {0x20a7, 0x20a7}, + {0x20a8, 0x20a8}, + {0x20aa, 0x20aa}, + {0x20ab, 0x20ab}, + {0x20ac, 0x20ac}, +}; + +int keysym_to_unicode(int keysym) +{ + int i, j, k; + + i = -1; + j = lenof(keysyms); + + while (j - i >= 2) { + k = (j + i) / 2; + if (keysyms[k].keysym == keysym) + return keysyms[k].unicode; + else if (keysyms[k].keysym < keysym) + i = k; + else + j = k; + } + return -1; +} diff --git a/putty/UNIX/XPMPTCFG.C b/putty/UNIX/XPMPTCFG.C new file mode 100644 index 0000000..af3d7fa --- /dev/null +++ b/putty/UNIX/XPMPTCFG.C @@ -0,0 +1,150 @@ +/* XPM */ +static const char *const cfg_icon_0[] = { +/* columns rows colors chars-per-pixel */ +"16 16 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$ $$$$$$$$$$$", +"$$ OO $$$$", +"$ +oO+###@+ $$$", +" o #.oO.XX@+ $$$", +" oO+.OO.XX@+ $$$", +"$ oOOOO.XX@+ $$$", +"$$ oooOO.X@+ $$$", +"$$ +..oOO.@+ $$$", +"$$ @@@+oOO++ $$", +"$ +++++ oOO #+ $", +" #######+oOO++ $", +" #@@@@@++ oOO $", +" @++++++++ oOO $", +"$ oOO ", +"$$$$$$$$$$$$ oO ", +"$$$$$$$$$$$$$ $" +}; + +/* XPM */ +static const char *const cfg_icon_1[] = { +/* columns rows colors chars-per-pixel */ +"32 32 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$ OO $$$$$$$$$$$$$$$$$$$$$$", +"$$$$$ ooOO $$$$$$$$$$$$$$$$$$$$$", +"$$$$$$ ooOO $$$$$$", +"$$ $$$ oOO @@@@@@@@@@@@@+ $$$$$", +"$ oO $$ oOOO @@@@@@@@@@@++ $$$$$", +"$ oOO oOOOO #########@+++ $$$$$", +"$$ oOOOOOOO ..........@+++ $$$$$", +"$$ ooOOOOOOO XXXXXXXXX@+++ $$$$$", +"$$$ ooooooOOO XXXXXXXX@+++ $$$$$", +"$$$$ oo ooOOO XXXXXXX@+++ $$$$$", +"$$$$$$ . ooOOO XXXXXX@+++ $$$$$", +"$$$$$$ #.X ooOOO XXXXX@+++ $$$$$", +"$$$$$$ #.XX ooOOO XXXX@+++ $$$$$", +"$$$$$$ #.XXX ooOOO XXX@+++ $$$$$", +"$$$$$$ #.XXXX ooOOO XX@+++ $$$$$", +"$$$$$$ ####### ooOOO #@+++ $$$", +"$$$$$ #@@@@@@@ ooOOO +++ @#+ $$", +"$$$$ @ @++++++++ ooOOO + @#++ $$", +"$$$ @@ ooOOO @#+++ $$", +"$$ ############### ooOOO @+++ $$", +"$$ #@@@@@@@@@@@@@@@ ooOOO +++ $$", +"$$ #@@@@@@@@@@@@@@@@ ooOOO + $$$", +"$$ #@@@@@@@@@@@@+ ooOOO $$$$", +"$$ @++++++++++++++++++ ooOOO $$$", +"$$$ ooOOO $$", +"$$$$$$$$$$$$$$$$$$$$$$$$ ooO $$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$ o $$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" +}; + +/* XPM */ +static const char *const cfg_icon_2[] = { +/* columns rows colors chars-per-pixel */ +"48 48 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$ OO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$ oOOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$ ooOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$ ooOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$$ oOOO $$$$$$$$$$", +"$$$ $$$$$$ oOOO @@@@@@@@@@@@@@@@@@@@+ $$$$$$$$$", +"$$ oO $$$$$ oOOOO @@@@@@@@@@@@@@@@@@++ $$$$$$$$$", +"$$ ooO $$$ oOOOO @@@@@@@@@@@@@@@@@+++ $$$$$$$$$", +"$$$ oOO OOOOO ################@++++ $$$$$$$$$", +"$$$ ooOOOOOOOOOOO ++++++++++++++@+++++ $$$$$$$$$", +"$$$ ooOOOOOOOOOOOO .............#+++++ $$$$$$$$$", +"$$$$ oooOOOOoOOOOOO XXXXXXXXXXXX#+++++ $$$$$$$$$", +"$$$$$ oooooooOOOOOOO XXXXXXXXXXX#+++++ $$$$$$$$$", +"$$$$$$ oo ooOOOOOOO XXXXXXXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ + ooOOOOOOO XXXXXXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+. ooOOOOOOO XXXXXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.X ooOOOOOOO XXXXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XX ooOOOOOOO XXXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXX ooOOOOOOO XXXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXXX ooOOOOOOO XXXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXXXX ooOOOOOOO XXX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXXXXX ooOOOOOOO XX#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXXXXXX ooOOOOOOO X#+++++ $$$$$$$$$", +"$$$$$$$$$ #+.XXXXXXXX ooOOOOOOO #+++++ $$$$$$$$$", +"$$$$$$$$ #@########## ooOOOOOOO +++++ $$$$$", +"$$$$$$$ @ #@@@@@@@@@@@@ ooOOOOOOO +++ @@##+ $$$$", +"$$$$$$ @@ #@@@@@@@@@@@@@ ooOOOOOOO + @@##++ $$$$", +"$$$$$ @@@ @++++++++++++++ ooOOOOOOO @@##+++ $$$$", +"$$$$ @@@@ ooOOOOOOO ##++++ $$$$", +"$$$ ####################### ooOOOOOOO @++++ $$$$", +"$$$ ######################## ooOOOOOOO ++++ $$$$", +"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO +++ $$$$", +"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO ++ $$$$", +"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO $$$$$", +"$$$ ##@@@@@@@@@@@@@@@@@@ ooOOOOOOO $$$$$", +"$$$ @@+++++++++++++++++++++++++++ ooOOOOOOO $$$$", +"$$$ @@++++++++++++++++++++++++++++ ooOOOOOOO $$$", +"$$$$ ooOOOOO $$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ooOOO $$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ooO $$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ o $$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" +}; + +const char *const *const cfg_icon[] = { + cfg_icon_0, + cfg_icon_1, + cfg_icon_2, +}; +const int n_cfg_icon = 3; diff --git a/putty/UNIX/XPMPTERM.C b/putty/UNIX/XPMPTERM.C new file mode 100644 index 0000000..5cb379f --- /dev/null +++ b/putty/UNIX/XPMPTERM.C @@ -0,0 +1,143 @@ +/* XPM */ +static const char *const main_icon_0[] = { +/* columns rows colors chars-per-pixel */ +"16 16 6 1", +" c black", +". c blue", +"X c #808080", +"o c #C0C0C0", +"O c gray100", +"+ c None", +/* pixels */ +"++++++++++++++++", +"+++ ++++", +"++ OOOOOOOoX +++", +"++ O......oX +++", +"++ O......oX +++", +"++ O......oX +++", +"++ O......oX +++", +"++ O......oX +++", +"++ ooooooooX ++", +"+ XXXXXXXXXXOX +", +" OOOOOOOOOOOoX +", +" OoooooXXXXoXX +", +" oXXXXXXXXXXX ++", +"+ +++", +"++++++++++++++++", +"++++++++++++++++" +}; + +/* XPM */ +static const char *const main_icon_1[] = { +/* columns rows colors chars-per-pixel */ +"32 32 7 1", +" c black", +". c navy", +"X c blue", +"o c #808080", +"O c #C0C0C0", +"+ c gray100", +"@ c None", +/* pixels */ +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@ @@@@@@", +"@@@@@@@@ OOOOOOOOOOOOOOOOo @@@@@", +"@@@@@@@ OOOOOOOOOOOOOOOOoo @@@@@", +"@@@@@@ +++++++++++++++Oooo @@@@@", +"@@@@@@ +..............Oooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@", +"@@@@@@ +++++++++++++++Oooo @@@", +"@@@@@ +OOOOOOOOOOOOOOooo O+o @@", +"@@@@ O Ooooooooooooooooo O+oo @@", +"@@@ OO O+ooo @@", +"@@ ++++++++++++++++++++++Oooo @@", +"@@ +OOOOOOOOOOOOOOOOOOOOOoooo @@", +"@@ +OOOOOOOOOOOOOOOOOOOOOooo @@@", +"@@ +OOOOOOOOOOOOo oOoo @@@@", +"@@ Ooooooooooooooooooooooo @@@@@", +"@@@ @@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" +}; + +/* XPM */ +static const char *const main_icon_2[] = { +/* columns rows colors chars-per-pixel */ +"48 48 7 1", +" c black", +". c navy", +"X c blue", +"o c #808080", +"O c #C0C0C0", +"+ c gray100", +"@ c None", +/* pixels */ +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@ @@@@@@@@@@", +"@@@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOo @@@@@@@@@", +"@@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOoo @@@@@@@@@", +"@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOooo @@@@@@@@@", +"@@@@@@@@@ +++++++++++++++++++++++Ooooo @@@@@@@@@", +"@@@@@@@@@ +oooooooooooooooooooooOooooo @@@@@@@@@", +"@@@@@@@@@ +o....................+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@", +"@@@@@@@@ +O+++++++++++++++++++++ooooo @@@@@", +"@@@@@@@ O +OOOOOOOOOOOOOOOOOOOOOOoooo OO++o @@@@", +"@@@@@@ OO +OOOOOOOOOOOOOOOOOOOOOOooo OO++oo @@@@", +"@@@@@ OOO Ooooooooooooooooooooooooo OO++ooo @@@@", +"@@@@ OOOO OO++oooo @@@@", +"@@@ ++++++++++++++++++++++++++++++++++Ooooo @@@@", +"@@@ +++++++++++++++++++++++++++++++++Oooooo @@@@", +"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOoooooo @@@@", +"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOoooooo @@@@", +"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOooooo @@@@@", +"@@@ ++OOOOOOOOOOOOOOOOOO oOOoooo @@@@@@", +"@@@ OOoooooooooooooooooooooooooooooooooo @@@@@@@", +"@@@ OOooooooooooooooooooooooooooooooooo @@@@@@@@", +"@@@@ @@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", +"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" +}; + +const char *const *const main_icon[] = { + main_icon_0, + main_icon_1, + main_icon_2, +}; +const int n_main_icon = 3; diff --git a/putty/UNIX/XPMPUCFG.C b/putty/UNIX/XPMPUCFG.C new file mode 100644 index 0000000..e6858e4 --- /dev/null +++ b/putty/UNIX/XPMPUCFG.C @@ -0,0 +1,150 @@ +/* XPM */ +static const char *const cfg_icon_0[] = { +/* columns rows colors chars-per-pixel */ +"16 16 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$ $$ $$", +"$$ OO #####@+ $", +"$ $ oO #XX..@+ $", +" o $ oO+X.O.@+ $", +" oO OO .O.X@+ $", +"$ oOOOOoO++@@+ $", +"$$ oooOOoOO +++ ", +"$ # oooOO +++++ ", +"$ #X..ooOO +++ $", +"$ #X.O. oOO $$", +"$ #.O.X@ oOO $$$", +"$ @++@@@+ oOO $$", +"$ ++++++++ oOO $", +" #####++++ oOO ", +" @+++++++ $$ oO ", +"$ $$$$ $" +}; + +/* XPM */ +static const char *const cfg_icon_1[] = { +/* columns rows colors chars-per-pixel */ +"32 32 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$$$$$$$$$$$$$$ $$$$", +"$$$$$$ $$$$$$$ @@@@@@@@@@@+ $$$", +"$$$$$ OO $$$$ ##########@++ $$$", +"$$$$$ ooOO $$$ #.........@++ $$$", +"$$$$$$ ooOO $$ #.XXXXXXXX@++ $$$", +"$$ $$$ oOO $$ #.XXXX XX@++ $$$", +"$ oO $$ oOOO $ #.XXX O XX@++ $$$", +"$ oOO oOOOO $ #.X O XXX@++ $$$", +"$$ oOOOOOOO $$ #. OO XXXX@++ $$$", +"$$ ooOOOOOOO $ # OO XXXXX@++ $$$", +"$$$ ooooooOOO OO ######@++ $", +"$$$$ oo ooOOO OO +++++++++ @#+ ", +"$$$$$$ $ ooOOO @#++ ", +"$$$$$$$$$$ ooOOO OOOO ######@++ ", +"$$$$$ O ooOOO O @@@@@@@+++ ", +"$$$$ @@@@@ ooOOO @@+ +@++ $", +"$$$ ######### ooOOO +++++++++ $$", +"$$$ #....... O ooOOO $$$", +"$$$ #.XXXXX OO ooOOO $$$$$$$$$$", +"$$$ #.XXXX OO @+ ooOOO $$$$$$$$$", +"$$$ #.XXX O X@++ ooOOO $$$$$$$$", +"$$$ #.XX O XXX@++ ooOOO $$$$$$$", +"$$$ #.XX XXXX@++ $ ooOOO $$$$$$", +"$$$ #.XXXXXXXX@++ $$ ooOOO $$$$$", +"$$$ ##########@++ $ ooOOO $$$$", +"$$ @+++++++++++ @#+ $ ooOOO $$$", +"$ @ @#++ $$ ooOOO $$", +" ################@++ $$$ ooO $$$", +" #@@@@@@@@@@@@@@@+++ $$$$ o $$$$", +" #@@@@@@@@+ +@++ $$$$$$ $$$$$", +" @++++++++++++++++ $$$$$$$$$$$$$", +"$ $$$$$$$$$$$$$$" +}; + +/* XPM */ +static const char *const cfg_icon_2[] = { +/* columns rows colors chars-per-pixel */ +"48 48 9 1", +" c black", +". c navy", +"X c blue", +"o c #808000", +"O c yellow", +"+ c #808080", +"@ c #C0C0C0", +"# c gray100", +"$ c None", +/* pixels */ +"$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$", +"$$$$$$$$$$$$$$$$$$$$$$$$ @@@@@@@@@@@@@@@@@+ $$$$", +"$$$$$$$$$ $$$$$$$$$$$$ @@@@@@@@@@@@@@@@@++ $$$$", +"$$$$$$$$ OO $$$$$$$$ ################@+++ $$$$", +"$$$$$$$$ oOOOO $$$$$$$ #++++++++++++++@++++ $$$$", +"$$$$$$$$$ ooOOO $$$$$$ #+.............#++++ $$$$", +"$$$$$$$$$$ ooOOO $$$$$ #+.XXXXXXXXXXXX#++++ $$$$", +"$$$$$$$$$$$ oOOO $$$$$ #+.XXXXXXXXXXXX#++++ $$$$", +"$$$ $$$$$$ oOOO $$$$$ #+.XXXXXXX XXX#++++ $$$$", +"$$ oO $$$$$ oOOOO $$$$ #+.XXXXXX O XXX#++++ $$$$", +"$$ ooO $$$$ oOOOO $$$$ #+.XXXXX O XXXX#++++ $$$$", +"$$$ oOO OOOOO $$$$$ #+.XXX O XXXXX#++++ $$$$", +"$$$ ooOOOOOOOOOOO $$$$ #+.XX OO XXXXXX#++++ $$$$", +"$$$ ooOOOOOOOOOOOO $$$ #+.X OO XXXXXXX#++++ $$$$", +"$$$$ oooOOOOoOOOOOO $$ #@ OO #########++++ $", +"$$$$$ oooooooOOOOOOO # OOO @@@@@@@@@@+++ @##+ ", +"$$$$$$ oo ooOOOOOOO OO +++++++++++++ @##++ ", +"$$$$$$$$$ $ ooOOOOOOO OO @##+++ ", +"$$$$$$$$$$$$$ ooOOOOOOO ############@+++ ", +"$$$$$$$$$$$$$$ ooOOOOOOO OOOOOO ##########@++++ ", +"$$$$$$$$$$$$$$$ ooOOOOOOO OOO @@+ @++++ $", +"$$$$$$$$$$$$$$$$ ooOOOOOOO O ++++++++++++++++ $$", +"$$$$$$$$$$$$$$$ O ooOOOOOOO ++++++++++++++++ $$$", +"$$$$$$$$$$$$$$$$ ooOOOOOOO $$$$", +"$$$$$$$ ooOOOOOOO $$$$$$$$$$$$$$$$$$", +"$$$$$$ @@@@@@@@@@@@ ooOOOOOOO $$$$$$$$$$$$$$$$$", +"$$$$$ @@@@@@@@@@@@ OO ooOOOOOOO $$$$$$$$$$$$$$$$", +"$$$$ ############ OO ooOOOOOOO $$$$$$$$$$$$$$$", +"$$$$ #++++++++++ OO @++ ooOOOOOOO $$$$$$$$$$$$$$", +"$$$$ #+........ OO .#+++ ooOOOOOOO $$$$$$$$$$$$$", +"$$$$ #+.XXXXXX O XX#++++ ooOOOOOOO $$$$$$$$$$$$", +"$$$$ #+.XXXXX O XXXX#++++ ooOOOOOOO $$$$$$$$$$$", +"$$$$ #+.XXXX O XXXXX#++++ $ ooOOOOOOO $$$$$$$$$$", +"$$$$ #+.XXXX XXXXXX#++++ $$ ooOOOOOOO $$$$$$$$$", +"$$$$ #+.XXXXXXXXXXXX#++++ $$$ ooOOOOOOO $$$$$$$$", +"$$$$ #+.XXXXXXXXXXXX#++++ $$$$ ooOOOOOOO $$$$$$$", +"$$$$ #+.XXXXXXXXXXXX#++++ $$$$$ ooOOOOOOO $$$$$$", +"$$$$ #+.XXXXXXXXXXXX#++++ $$$$$$ ooOOOOOOO $$$$$", +"$$$$ #@##############++++ $$$$ ooOOOOOOO $$$$", +"$$$ #@@@@@@@@@@@@@@@+++ @##+ $$$$ ooOOOOOOO $$$", +"$$ @ @+++++++++++++++++ @##++ $$$$$ ooOOOOO $$$$", +"$ @@ @##+++ $$$$$$ ooOOO $$$$$", +" ########################@+++ $$$$$$$ ooO $$$$$$", +" #######################@++++ $$$$$$$$ o $$$$$$$", +" ##@@@@@@@@@@@@+ @++++ $$$$$$$$$$ $$$$$$$$", +" @@++++++++++++++++++++++++ $$$$$$$$$$$$$$$$$$$$", +" @@+++++++++++++++++++++++ $$$$$$$$$$$$$$$$$$$$$", +"$ $$$$$$$$$$$$$$$$$$$$$$" +}; + +const char *const *const cfg_icon[] = { + cfg_icon_0, + cfg_icon_1, + cfg_icon_2, +}; +const int n_cfg_icon = 3; diff --git a/putty/UNIX/XPMPUTTY.C b/putty/UNIX/XPMPUTTY.C new file mode 100644 index 0000000..e51b011 --- /dev/null +++ b/putty/UNIX/XPMPUTTY.C @@ -0,0 +1,147 @@ +/* XPM */ +static const char *const main_icon_0[] = { +/* columns rows colors chars-per-pixel */ +"16 16 8 1", +" c black", +". c navy", +"X c blue", +"o c yellow", +"O c #808080", +"+ c #C0C0C0", +"@ c gray100", +"# c None", +/* pixels */ +"####### ##", +"###### @@@@@+O #", +"###### @XX..+O #", +"###### @X.o.+O #", +"###### O.o.X+O #", +"###### ooOO++O #", +"## ooooo OOO ", +"# @Oooooo OOOOO ", +"# @X..oo OOOO #", +"# @X.o.OO ##", +"# @.o.X+O ######", +"# +OO+++O ######", +"# OOOOOOOO #####", +" @@@@@OOOO #####", +" +OOOOOOO ######", +"# #######" +}; + +/* XPM */ +static const char *const main_icon_1[] = { +/* columns rows colors chars-per-pixel */ +"32 32 8 1", +" c black", +". c navy", +"X c blue", +"o c yellow", +"O c #808080", +"+ c #C0C0C0", +"@ c gray100", +"# c None", +/* pixels */ +"################ ####", +"############### +++++++++++O ###", +"############## @@@@@@@@@@+OO ###", +"############## @.........+OO ###", +"############## @.XXXXXXXX+OO ###", +"############## @.XXXX XX+OO ###", +"############## @.XXX o XX+OO ###", +"############## @.X o XXX+OO ###", +"############## @. oo XXXX+OO ###", +"############## @ oo XXXXX+OO ###", +"############## oo @@@@@@+OO #", +"############# ooo OOOOOOOOO +@O ", +"############ ooo +@OO ", +"########## ooooooooo @@@@@@+OO ", +"##### ooooooooo +++++++OOO ", +"#### +++++ ooo ++O O+OO #", +"### @@@@@@@@@ ooo OOOOOOOOOOO ##", +"### @....... oo ###", +"### @.XXXXX oo OO ##############", +"### @.XXXX oo +OO ##############", +"### @.XXX o X+OO ##############", +"### @.XX o XXX+OO ##############", +"### @.XX XXXX+OO ##############", +"### @.XXXXXXXX+OO ##############", +"### @@@@@@@@@@+OO ############", +"## +OOOOOOOOOOO +@O ###########", +"# + +@OO ###########", +" @@@@@@@@@@@@@@@@+OO ###########", +" @+++++++++++++++OOO ###########", +" @++++++++O O+OO ############", +" +OOOOOOOOOOOOOOOO #############", +"# ##############" +}; + +/* XPM */ +static const char *const main_icon_2[] = { +/* columns rows colors chars-per-pixel */ +"48 48 8 1", +" c black", +". c navy", +"X c blue", +"o c yellow", +"O c #808080", +"+ c #C0C0C0", +"@ c gray100", +"# c None", +/* pixels */ +"######################### #####", +"######################## +++++++++++++++++O ####", +"####################### +++++++++++++++++OO ####", +"###################### @@@@@@@@@@@@@@@@+OOO ####", +"###################### @OOOOOOOOOOOOOO+OOOO ####", +"###################### @O.............@OOOO ####", +"###################### @O.XXXXXXXXXXXX@OOOO ####", +"###################### @O.XXXXXXXXXXXX@OOOO ####", +"###################### @O.XXXXXXX XXX@OOOO ####", +"###################### @O.XXXXXX o XXX@OOOO ####", +"###################### @O.XXXXX o XXXX@OOOO ####", +"###################### @O.XXX o XXXXX@OOOO ####", +"###################### @O.XX oo XXXXXX@OOOO ####", +"###################### @O.X oo XXXXXXX@OOOO ####", +"###################### @+ oo @@@@@@@@@OOOO #", +"##################### @ ooo ++++++++++OOO +@@O ", +"#################### + oo OOOOOOOOOOOOO +@@OO ", +"################### + oo +@@OOO ", +"################## @ ooo @@@@@@@@@@@@+OOO ", +"################## ooooooooooo @@@@@@@@@@+OOOO ", +"################## oooooooooo ++O +OOOO #", +"################ oooooooooo OOOOOOOOOOOOOOOO ##", +"############### ooooooooooo OOOOOOOOOOOOOOOO ###", +"################ ooo ####", +"####### oo ######################", +"###### ++++++++++++ oo O ######################", +"##### ++++++++++++ ooo OO ######################", +"#### @@@@@@@@@@@@ oo OOO ######################", +"#### @OOOOOOOOOO oo +OOOO ######################", +"#### @O........ oo .@OOOO ######################", +"#### @O.XXXXXX o XX@OOOO ######################", +"#### @O.XXXXX o XXXX@OOOO ######################", +"#### @O.XXXX o XXXXX@OOOO ######################", +"#### @O.XXXX XXXXXX@OOOO ######################", +"#### @O.XXXXXXXXXXXX@OOOO ######################", +"#### @O.XXXXXXXXXXXX@OOOO ######################", +"#### @O.XXXXXXXXXXXX@OOOO ######################", +"#### @O.XXXXXXXXXXXX@OOOO ######################", +"#### @+@@@@@@@@@@@@@@OOOO ###################", +"### @+++++++++++++++OOO +@@O ##################", +"## + +OOOOOOOOOOOOOOOOO +@@OO ##################", +"# ++ +@@OOO ##################", +" @@@@@@@@@@@@@@@@@@@@@@@@+OOO ##################", +" @@@@@@@@@@@@@@@@@@@@@@@+OOOO ##################", +" @@++++++++++++O +OOOO ###################", +" ++OOOOOOOOOOOOOOOOOOOOOOOO ####################", +" ++OOOOOOOOOOOOOOOOOOOOOOO #####################", +"# ######################" +}; + +const char *const *const main_icon[] = { + main_icon_0, + main_icon_1, + main_icon_2, +}; +const int n_main_icon = 3; diff --git a/putty/VERSION.C b/putty/VERSION.C new file mode 100644 index 0000000..ece99fb --- /dev/null +++ b/putty/VERSION.C @@ -0,0 +1,42 @@ +/* + * PuTTY version numbering + */ + +#define STR1(x) #x +#define STR(x) STR1(x) + +#if defined SNAPSHOT + +#if defined SVN_REV +#define SNAPSHOT_TEXT STR(SNAPSHOT) ":r" STR(SVN_REV) +#else +#define SNAPSHOT_TEXT STR(SNAPSHOT) +#endif + +char ver[] = "Development snapshot " SNAPSHOT_TEXT; +char sshver[] = "PuTTY-Snapshot-" SNAPSHOT_TEXT; + +#undef SNAPSHOT_TEXT + +#elif defined RELEASE + +char ver[] = "Release " STR(RELEASE); +char sshver[] = "PuTTY-Release-" STR(RELEASE); + +#elif defined SVN_REV + +char ver[] = "Custom build r" STR(SVN_REV) ", " __DATE__ " " __TIME__; +char sshver[] = "PuTTY-Custom-r" STR(SVN_REV); + +#else + +char ver[] = "Unidentified build, " __DATE__ " " __TIME__; +char sshver[] = "PuTTY-Local: " __DATE__ " " __TIME__; + +#endif + +/* + * 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/putty/WCWIDTH.C b/putty/WCWIDTH.C new file mode 100644 index 0000000..bcd153d --- /dev/null +++ b/putty/WCWIDTH.C @@ -0,0 +1,303 @@ +/* + * 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 -- 2003-05-20 (Unicode 4.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +#include + +#include "putty.h" /* for prototypes */ + +struct interval { + int first; + int last; +}; + +/* auxiliary function for binary search in interval table */ +static int bisearch(wchar_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that wchar_t characters are encoded + * in ISO 10646. + */ + +int mk_wcwidth(wchar_t 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, 0x0357 }, { 0x035D, 0x036F }, { 0x0483, 0x0486 }, + { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 }, + { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C4 }, { 0x0600, 0x0603 }, { 0x0610, 0x0615 }, + { 0x064B, 0x0658 }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 }, + { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F }, + { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 }, + { 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 }, { 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 }, { 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 }, { 0x200B, 0x200F }, + { 0x202A, 0x202E }, { 0x2060, 0x2063 }, { 0x206A, 0x206F }, + { 0x20D0, 0x20EA }, { 0x302A, 0x302F }, { 0x3099, 0x309A }, + { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE23 }, + { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, + { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF } + }; + + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + + +int mk_wcswidth(const wchar_t *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_wcwidth_cjk(), 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(wchar_t ucs) +{ + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ + static const struct interval ambiguous[] = { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, + { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, + { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, + { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, + { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, + { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, + { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, + { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, + { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, + { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, + { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, + { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, + { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, + { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, + { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, + { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, + { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, + { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, + { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, + { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, + { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, + { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, + { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, + { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, + { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, + { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, + { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, + { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, + { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, + { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, + { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, + { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, + { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } + }; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, + sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return mk_wcwidth(ucs); +} + + +int mk_wcswidth_cjk(const wchar_t *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/putty/WILDCARD.C b/putty/WILDCARD.C new file mode 100644 index 0000000..75a7573 --- /dev/null +++ b/putty/WILDCARD.C @@ -0,0 +1,472 @@ +/* + * Wildcard matching engine for use with SFTP-based file transfer + * programs (PSFTP, new-look PSCP): since SFTP has no notion of + * getting the remote side to do globbing (and rightly so) we have + * to do it locally, by retrieving all the filenames in a directory + * and checking each against the wildcard pattern. + */ + +#include +#include +#include + +#include "putty.h" + +/* + * Definition of wildcard syntax: + * + * - * matches any sequence of characters, including zero. + * - ? matches exactly one character which can be anything. + * - [abc] matches exactly one character which is a, b or c. + * - [a-f] matches anything from a through f. + * - [^a-f] matches anything _except_ a through f. + * - [-_] matches - or _; [^-_] matches anything else. (The - is + * non-special if it occurs immediately after the opening + * bracket or ^.) + * - [a^] matches an a or a ^. (The ^ is non-special if it does + * _not_ occur immediately after the opening bracket.) + * - \*, \?, \[, \], \\ match the single characters *, ?, [, ], \. + * - All other characters are non-special and match themselves. + */ + +/* + * Some notes on differences from POSIX globs (IEEE Std 1003.1, 2003 ed.): + * - backslashes act as escapes even within [] bracket expressions + * - does not support [!...] for non-matching list (POSIX are weird); + * NB POSIX allows [^...] as well via "A bracket expression starting + * with an unquoted circumflex character produces unspecified + * results". If we wanted to allow [!...] we might want to define + * [^!] as having its literal meaning (match '^' or '!'). + * - none of the scary [[:class:]] stuff, etc + */ + +/* + * The wildcard matching technique we use is very simple and + * potentially O(N^2) in running time, but I don't anticipate it + * being that bad in reality (particularly since N will be the size + * of a filename, which isn't all that much). Perhaps one day, once + * PuTTY has grown a regexp matcher for some other reason, I might + * come back and reimplement wildcards by translating them into + * regexps or directly into NFAs; but for the moment, in the + * absence of any other need for the NFA->DFA translation engine, + * anything more than the simplest possible wildcard matcher is + * vast code-size overkill. + * + * Essentially, these wildcards are much simpler than regexps in + * that they consist of a sequence of rigid fragments (? and [...] + * can never match more or less than one character) separated by + * asterisks. It is therefore extremely simple to look at a rigid + * fragment and determine whether or not it begins at a particular + * point in the test string; so we can search along the string + * until we find each fragment, then search for the next. As long + * as we find each fragment in the _first_ place it occurs, there + * will never be a danger of having to backpedal and try to find it + * again somewhere else. + */ + +enum { + WC_TRAILINGBACKSLASH = 1, + WC_UNCLOSEDCLASS, + WC_INVALIDRANGE +}; + +/* + * Error reporting is done by returning various negative values + * from the wildcard routines. Passing any such value to wc_error + * will give a human-readable message. + */ +const char *wc_error(int value) +{ + value = abs(value); + switch (value) { + case WC_TRAILINGBACKSLASH: + return "'\' occurred at end of string (expected another character)"; + case WC_UNCLOSEDCLASS: + return "expected ']' to close character class"; + case WC_INVALIDRANGE: + return "character range was not terminated (']' just after '-')"; + } + return "INTERNAL ERROR: unrecognised wildcard error number"; +} + +/* + * This is the routine that tests a target string to see if an + * initial substring of it matches a fragment. If successful, it + * returns 1, and advances both `fragment' and `target' past the + * fragment and matching substring respectively. If unsuccessful it + * returns zero. If the wildcard fragment suffers a syntax error, + * it returns <0 and the precise value indexes into wc_error. + */ +static int wc_match_fragment(const char **fragment, const char **target) +{ + const char *f, *t; + + f = *fragment; + t = *target; + /* + * The fragment terminates at either the end of the string, or + * the first (unescaped) *. + */ + while (*f && *f != '*' && *t) { + /* + * Extract one character from t, and one character's worth + * of pattern from f, and step along both. Return 0 if they + * fail to match. + */ + if (*f == '\\') { + /* + * Backslash, which means f[1] is to be treated as a + * literal character no matter what it is. It may not + * be the end of the string. + */ + if (!f[1]) + return -WC_TRAILINGBACKSLASH; /* error */ + if (f[1] != *t) + return 0; /* failed to match */ + f += 2; + } else if (*f == '?') { + /* + * Question mark matches anything. + */ + f++; + } else if (*f == '[') { + int invert = 0; + int matched = 0; + /* + * Open bracket introduces a character class. + */ + f++; + if (*f == '^') { + invert = 1; + f++; + } + while (*f != ']') { + if (*f == '\\') + f++; /* backslashes still work */ + if (!*f) + return -WC_UNCLOSEDCLASS; /* error again */ + if (f[1] == '-') { + int lower, upper, ourchr; + lower = (unsigned char) *f++; + f++; /* eat the minus */ + if (*f == ']') + return -WC_INVALIDRANGE; /* different error! */ + if (*f == '\\') + f++; /* backslashes _still_ work */ + if (!*f) + return -WC_UNCLOSEDCLASS; /* error again */ + upper = (unsigned char) *f++; + ourchr = (unsigned char) *t; + if (lower > upper) { + int t = lower; lower = upper; upper = t; + } + if (ourchr >= lower && ourchr <= upper) + matched = 1; + } else { + matched |= (*t == *f++); + } + } + if (invert == matched) + return 0; /* failed to match character class */ + f++; /* eat the ] */ + } else { + /* + * Non-special character matches itself. + */ + if (*f != *t) + return 0; + f++; + } + /* + * Now we've done that, increment t past the character we + * matched. + */ + t++; + } + if (!*f || *f == '*') { + /* + * We have reached the end of f without finding a mismatch; + * so we're done. Update the caller pointers and return 1. + */ + *fragment = f; + *target = t; + return 1; + } + /* + * Otherwise, we must have reached the end of t before we + * reached the end of f; so we've failed. Return 0. + */ + return 0; +} + +/* + * This is the real wildcard matching routine. It returns 1 for a + * successful match, 0 for an unsuccessful match, and <0 for a + * syntax error in the wildcard. + */ +int wc_match(const char *wildcard, const char *target) +{ + int ret; + + /* + * Every time we see a '*' _followed_ by a fragment, we just + * search along the string for a location at which the fragment + * matches. The only special case is when we see a fragment + * right at the start, in which case we just call the matching + * routine once and give up if it fails. + */ + if (*wildcard != '*') { + ret = wc_match_fragment(&wildcard, &target); + if (ret <= 0) + return ret; /* pass back failure or error alike */ + } + + while (*wildcard) { + assert(*wildcard == '*'); + while (*wildcard == '*') + wildcard++; + + /* + * It's possible we've just hit the end of the wildcard + * after seeing a *, in which case there's no need to + * bother searching any more because we've won. + */ + if (!*wildcard) + return 1; + + /* + * Now `wildcard' points at the next fragment. So we + * attempt to match it against `target', and if that fails + * we increment `target' and try again, and so on. When we + * find we're about to try matching against the empty + * string, we give up and return 0. + */ + ret = 0; + while (*target) { + const char *save_w = wildcard, *save_t = target; + + ret = wc_match_fragment(&wildcard, &target); + + if (ret < 0) + return ret; /* syntax error */ + + if (ret > 0 && !*wildcard && *target) { + /* + * Final special case - literally. + * + * This situation arises when we are matching a + * _terminal_ fragment of the wildcard (that is, + * there is nothing after it, e.g. "*a"), and it + * has matched _too early_. For example, matching + * "*a" against "parka" will match the "a" fragment + * against the _first_ a, and then (if it weren't + * for this special case) matching would fail + * because we're at the end of the wildcard but not + * at the end of the target string. + * + * In this case what we must do is measure the + * length of the fragment in the target (which is + * why we saved `target'), jump straight to that + * distance from the end of the string using + * strlen, and match the same fragment again there + * (which is why we saved `wildcard'). Then we + * return whatever that operation returns. + */ + target = save_t + strlen(save_t) - (target - save_t); + wildcard = save_w; + return wc_match_fragment(&wildcard, &target); + } + + if (ret > 0) + break; + target++; + } + if (ret > 0) + continue; + return 0; + } + + /* + * If we reach here, it must be because we successfully matched + * a fragment and then found ourselves right at the end of the + * wildcard. Hence, we return 1 if and only if we are also + * right at the end of the target. + */ + return (*target ? 0 : 1); +} + +/* + * Another utility routine that translates a non-wildcard string + * into its raw equivalent by removing any escaping backslashes. + * Expects a target string buffer of anything up to the length of + * the original wildcard. You can also pass NULL as the output + * buffer if you're only interested in the return value. + * + * Returns 1 on success, or 0 if a wildcard character was + * encountered. In the latter case the output string MAY not be + * zero-terminated and you should not use it for anything! + */ +int wc_unescape(char *output, const char *wildcard) +{ + while (*wildcard) { + if (*wildcard == '\\') { + wildcard++; + /* We are lenient about trailing backslashes in non-wildcards. */ + if (*wildcard) { + if (output) + *output++ = *wildcard; + wildcard++; + } + } else if (*wildcard == '*' || *wildcard == '?' || + *wildcard == '[' || *wildcard == ']') { + return 0; /* it's a wildcard! */ + } else { + if (output) + *output++ = *wildcard; + wildcard++; + } + } + *output = '\0'; + return 1; /* it's clean */ +} + +#ifdef TESTMODE + +struct test { + const char *wildcard; + const char *target; + int expected_result; +}; + +const struct test fragment_tests[] = { + /* + * We exhaustively unit-test the fragment matching routine + * itself, which should save us the need to test all its + * intricacies during the full wildcard tests. + */ + {"abc", "abc", 1}, + {"abc", "abd", 0}, + {"abc", "abcd", 1}, + {"abcd", "abc", 0}, + {"ab[cd]", "abc", 1}, + {"ab[cd]", "abd", 1}, + {"ab[cd]", "abe", 0}, + {"ab[^cd]", "abc", 0}, + {"ab[^cd]", "abd", 0}, + {"ab[^cd]", "abe", 1}, + {"ab\\", "abc", -WC_TRAILINGBACKSLASH}, + {"ab\\*", "ab*", 1}, + {"ab\\?", "ab*", 0}, + {"ab?", "abc", 1}, + {"ab?", "ab", 0}, + {"ab[", "abc", -WC_UNCLOSEDCLASS}, + {"ab[c-", "abb", -WC_UNCLOSEDCLASS}, + {"ab[c-]", "abb", -WC_INVALIDRANGE}, + {"ab[c-e]", "abb", 0}, + {"ab[c-e]", "abc", 1}, + {"ab[c-e]", "abd", 1}, + {"ab[c-e]", "abe", 1}, + {"ab[c-e]", "abf", 0}, + {"ab[e-c]", "abb", 0}, + {"ab[e-c]", "abc", 1}, + {"ab[e-c]", "abd", 1}, + {"ab[e-c]", "abe", 1}, + {"ab[e-c]", "abf", 0}, + {"ab[^c-e]", "abb", 1}, + {"ab[^c-e]", "abc", 0}, + {"ab[^c-e]", "abd", 0}, + {"ab[^c-e]", "abe", 0}, + {"ab[^c-e]", "abf", 1}, + {"ab[^e-c]", "abb", 1}, + {"ab[^e-c]", "abc", 0}, + {"ab[^e-c]", "abd", 0}, + {"ab[^e-c]", "abe", 0}, + {"ab[^e-c]", "abf", 1}, + {"ab[a^]", "aba", 1}, + {"ab[a^]", "ab^", 1}, + {"ab[a^]", "abb", 0}, + {"ab[^a^]", "aba", 0}, + {"ab[^a^]", "ab^", 0}, + {"ab[^a^]", "abb", 1}, + {"ab[-c]", "ab-", 1}, + {"ab[-c]", "abc", 1}, + {"ab[-c]", "abd", 0}, + {"ab[^-c]", "ab-", 0}, + {"ab[^-c]", "abc", 0}, + {"ab[^-c]", "abd", 1}, + {"ab[\\[-\\]]", "abZ", 0}, + {"ab[\\[-\\]]", "ab[", 1}, + {"ab[\\[-\\]]", "ab\\", 1}, + {"ab[\\[-\\]]", "ab]", 1}, + {"ab[\\[-\\]]", "ab^", 0}, + {"ab[^\\[-\\]]", "abZ", 1}, + {"ab[^\\[-\\]]", "ab[", 0}, + {"ab[^\\[-\\]]", "ab\\", 0}, + {"ab[^\\[-\\]]", "ab]", 0}, + {"ab[^\\[-\\]]", "ab^", 1}, + {"ab[a-fA-F]", "aba", 1}, + {"ab[a-fA-F]", "abF", 1}, + {"ab[a-fA-F]", "abZ", 0}, +}; + +const struct test full_tests[] = { + {"a", "argh", 0}, + {"a", "ba", 0}, + {"a", "a", 1}, + {"a*", "aardvark", 1}, + {"a*", "badger", 0}, + {"*a", "park", 0}, + {"*a", "pArka", 1}, + {"*a", "parka", 1}, + {"*a*", "park", 1}, + {"*a*", "perk", 0}, + {"?b*r?", "abracadabra", 1}, + {"?b*r?", "abracadabr", 0}, + {"?b*r?", "abracadabzr", 0}, +}; + +int main(void) +{ + int i; + int fails, passes; + + fails = passes = 0; + + for (i = 0; i < sizeof(fragment_tests)/sizeof(*fragment_tests); i++) { + const char *f, *t; + int eret, aret; + f = fragment_tests[i].wildcard; + t = fragment_tests[i].target; + eret = fragment_tests[i].expected_result; + aret = wc_match_fragment(&f, &t); + if (aret != eret) { + printf("failed test: /%s/ against /%s/ returned %d not %d\n", + fragment_tests[i].wildcard, fragment_tests[i].target, + aret, eret); + fails++; + } else + passes++; + } + + for (i = 0; i < sizeof(full_tests)/sizeof(*full_tests); i++) { + const char *f, *t; + int eret, aret; + f = full_tests[i].wildcard; + t = full_tests[i].target; + eret = full_tests[i].expected_result; + aret = wc_match(f, t); + if (aret != eret) { + printf("failed test: /%s/ against /%s/ returned %d not %d\n", + full_tests[i].wildcard, full_tests[i].target, + aret, eret); + fails++; + } else + passes++; + } + + printf("passed %d, failed %d\n", passes, fails); + + return 0; +} + +#endif diff --git a/putty/WINDOWS/DEVCPP/PAGEANT/PAGEANT.DEV b/putty/WINDOWS/DEVCPP/PAGEANT/PAGEANT.DEV new file mode 100644 index 0000000..215755a --- /dev/null +++ b/putty/WINDOWS/DEVCPP/PAGEANT/PAGEANT.DEV @@ -0,0 +1,401 @@ +# DEV-C++ 5 Project File - pageant.dev +# ** DO NOT EDIT ** + +[Project] +FileName=pageant.dev +Name=pageant +Ver=1 +IsCpp=1 +Type=0 +Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix;..\..\..\macosx +Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ +Libs= +UnitCount=35 +Folders="Header Files","Resource Files","Source Files" +ObjFiles= +PrivateResource=pageant_private.rc +ResourceIncludes=..\..\..\WINDOWS +MakeIncludes= +Icon= +ExeOutput= +ObjectOutput= +OverrideOutput=0 +OverrideOutputName=pageant.exe +HostApplication= +CommandLine= +UseCustomMakefile=0 +CustomMakefile= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000000 + +[Unit1] +FileName=..\..\..\misc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit2] +FileName=..\..\..\sshaes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit3] +FileName=..\..\..\sshbn.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit4] +FileName=..\..\..\sshdes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit5] +FileName=..\..\..\sshdss.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit6] +FileName=..\..\..\sshmd5.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit7] +FileName=..\..\..\sshpubk.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit8] +FileName=..\..\..\sshrsa.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit9] +FileName=..\..\..\sshsh256.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit10] +FileName=..\..\..\sshsh512.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit11] +FileName=..\..\..\sshsha.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit12] +FileName=..\..\..\tree234.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit13] +FileName=..\..\..\version.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit14] +FileName=..\..\..\windows\winhelp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit15] +FileName=..\..\..\windows\winmisc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit16] +FileName=..\..\..\windows\winpgnt.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit17] +FileName=..\..\..\windows\winpgntc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit18] +FileName=..\..\..\windows\winutils.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit19] +FileName=..\..\..\charset\charset.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit20] +FileName=..\..\..\int64.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit21] +FileName=..\..\..\macosx\osx.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit22] +FileName=..\..\..\misc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit23] +FileName=..\..\..\network.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit24] +FileName=..\..\..\putty.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit25] +FileName=..\..\..\puttymem.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit26] +FileName=..\..\..\puttyps.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit27] +FileName=..\..\..\ssh.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit28] +FileName=..\..\..\tree234.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit29] +FileName=..\..\..\unix\unix.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit30] +FileName=..\..\..\windows\rcstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit31] +FileName=..\..\..\windows\winhelp.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit32] +FileName=..\..\..\windows\winstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit33] +FileName=..\..\..\windows\pageant.ico +Folder=Resource Files +Compile=0 +CompileCpp=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit34] +FileName=..\..\..\windows\pageant.rc +Folder=Resource Files +Compile=1 +CompileCpp=1 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit35] +FileName=..\..\..\windows\pageants.ico +Folder=Resource Files +Compile=0 +CompileCpp=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[VersionInfo] +Major=0 +Minor=0 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion=0.1 +FileDescription= +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename=pageant.exe +ProductName=pageant +ProductVersion=0.1 +AutoIncBuildNr=0 diff --git a/putty/WINDOWS/DEVCPP/PLINK/PLINK.DEV b/putty/WINDOWS/DEVCPP/PLINK/PLINK.DEV new file mode 100644 index 0000000..a7f5a5b --- /dev/null +++ b/putty/WINDOWS/DEVCPP/PLINK/PLINK.DEV @@ -0,0 +1,811 @@ +# DEV-C++ 5 Project File - plink.dev +# ** DO NOT EDIT ** + +[Project] +FileName=plink.dev +Name=plink +Ver=1 +IsCpp=1 +Type=1 +Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix;..\..\..\macosx +Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ +Libs= +UnitCount=76 +Folders="Header Files","Resource Files","Source Files" +ObjFiles= +PrivateResource=plink_private.rc +ResourceIncludes=..\..\..\WINDOWS +MakeIncludes= +Icon= +ExeOutput= +ObjectOutput= +OverrideOutput=0 +OverrideOutputName=plink.exe +HostApplication= +CommandLine= +UseCustomMakefile=0 +CustomMakefile= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000000 + +[Unit1] +FileName=..\..\..\be_all_s.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit2] +FileName=..\..\..\cmdline.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit3] +FileName=..\..\..\cproxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit4] +FileName=..\..\..\ldisc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit5] +FileName=..\..\..\logging.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit6] +FileName=..\..\..\misc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit7] +FileName=..\..\..\pgssapi.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit8] +FileName=..\..\..\pinger.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit9] +FileName=..\..\..\portfwd.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit10] +FileName=..\..\..\proxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit11] +FileName=..\..\..\raw.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit12] +FileName=..\..\..\rlogin.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit13] +FileName=..\..\..\settings.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit14] +FileName=..\..\..\ssh.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit15] +FileName=..\..\..\sshaes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit16] +FileName=..\..\..\ssharcf.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit17] +FileName=..\..\..\sshblowf.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit18] +FileName=..\..\..\sshbn.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit19] +FileName=..\..\..\sshcrc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit20] +FileName=..\..\..\sshcrcda.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit21] +FileName=..\..\..\sshdes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit22] +FileName=..\..\..\sshdh.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit23] +FileName=..\..\..\sshdss.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit24] +FileName=..\..\..\sshgssc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit25] +FileName=..\..\..\sshmd5.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit26] +FileName=..\..\..\sshpubk.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit27] +FileName=..\..\..\sshrand.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit28] +FileName=..\..\..\sshrsa.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit29] +FileName=..\..\..\sshsh256.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit30] +FileName=..\..\..\sshsh512.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit31] +FileName=..\..\..\sshsha.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit32] +FileName=..\..\..\sshzlib.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit33] +FileName=..\..\..\telnet.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit34] +FileName=..\..\..\timing.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit35] +FileName=..\..\..\tree234.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit36] +FileName=..\..\..\version.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit37] +FileName=..\..\..\wildcard.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit38] +FileName=..\..\..\windows\wincons.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit39] +FileName=..\..\..\windows\windefs.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit40] +FileName=..\..\..\windows\wingss.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit41] +FileName=..\..\..\windows\winhandl.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit42] +FileName=..\..\..\windows\winmisc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit43] +FileName=..\..\..\windows\winnet.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit44] +FileName=..\..\..\windows\winnoise.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit45] +FileName=..\..\..\windows\winnojmp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit46] +FileName=..\..\..\windows\winpgntc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit47] +FileName=..\..\..\windows\winplink.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit48] +FileName=..\..\..\windows\winproxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit49] +FileName=..\..\..\windows\winser.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit50] +FileName=..\..\..\windows\winstore.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit51] +FileName=..\..\..\windows\wintime.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit52] +FileName=..\..\..\windows\winx11.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit53] +FileName=..\..\..\x11fwd.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit54] +FileName=..\..\..\charset\charset.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit55] +FileName=..\..\..\int64.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit56] +FileName=..\..\..\ldisc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit57] +FileName=..\..\..\macosx\osx.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit58] +FileName=..\..\..\misc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit59] +FileName=..\..\..\network.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit60] +FileName=..\..\..\pgssapi.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit61] +FileName=..\..\..\proxy.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit62] +FileName=..\..\..\putty.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit63] +FileName=..\..\..\puttymem.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit64] +FileName=..\..\..\puttyps.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit65] +FileName=..\..\..\ssh.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit66] +FileName=..\..\..\sshgss.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit67] +FileName=..\..\..\sshgssc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit68] +FileName=..\..\..\storage.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit69] +FileName=..\..\..\terminal.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit70] +FileName=..\..\..\tree234.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit71] +FileName=..\..\..\unix\unix.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit72] +FileName=..\..\..\windows\rcstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit73] +FileName=..\..\..\windows\winhelp.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit74] +FileName=..\..\..\windows\winstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit75] +FileName=..\..\..\windows\plink.rc +Folder=Resource Files +Compile=1 +CompileCpp=1 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit76] +FileName=..\..\..\windows\putty.ico +Folder=Resource Files +Compile=0 +CompileCpp=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[VersionInfo] +Major=0 +Minor=0 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion=0.1 +FileDescription= +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename=plink.exe +ProductName=plink +ProductVersion=0.1 +AutoIncBuildNr=0 diff --git a/putty/WINDOWS/DEVCPP/PSCP/PSCP.DEV b/putty/WINDOWS/DEVCPP/PSCP/PSCP.DEV new file mode 100644 index 0000000..8ae4c6a --- /dev/null +++ b/putty/WINDOWS/DEVCPP/PSCP/PSCP.DEV @@ -0,0 +1,781 @@ +# DEV-C++ 5 Project File - pscp.dev +# ** DO NOT EDIT ** + +[Project] +FileName=pscp.dev +Name=pscp +Ver=1 +IsCpp=1 +Type=1 +Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix;..\..\..\macosx +Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ +Libs= +UnitCount=73 +Folders="Header Files","Resource Files","Source Files" +ObjFiles= +PrivateResource=pscp_private.rc +ResourceIncludes=..\..\..\WINDOWS +MakeIncludes= +Icon= +ExeOutput= +ObjectOutput= +OverrideOutput=0 +OverrideOutputName=pscp.exe +HostApplication= +CommandLine= +UseCustomMakefile=0 +CustomMakefile= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000000 + +[Unit1] +FileName=..\..\..\be_none.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit2] +FileName=..\..\..\cmdline.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit3] +FileName=..\..\..\cproxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit4] +FileName=..\..\..\int64.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit5] +FileName=..\..\..\logging.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit6] +FileName=..\..\..\misc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit7] +FileName=..\..\..\pgssapi.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit8] +FileName=..\..\..\pinger.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit9] +FileName=..\..\..\portfwd.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit10] +FileName=..\..\..\proxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit11] +FileName=..\..\..\pscp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit12] +FileName=..\..\..\settings.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit13] +FileName=..\..\..\sftp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit14] +FileName=..\..\..\ssh.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit15] +FileName=..\..\..\sshaes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit16] +FileName=..\..\..\ssharcf.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit17] +FileName=..\..\..\sshblowf.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit18] +FileName=..\..\..\sshbn.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit19] +FileName=..\..\..\sshcrc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit20] +FileName=..\..\..\sshcrcda.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit21] +FileName=..\..\..\sshdes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit22] +FileName=..\..\..\sshdh.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit23] +FileName=..\..\..\sshdss.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit24] +FileName=..\..\..\sshgssc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit25] +FileName=..\..\..\sshmd5.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit26] +FileName=..\..\..\sshpubk.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit27] +FileName=..\..\..\sshrand.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit28] +FileName=..\..\..\sshrsa.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit29] +FileName=..\..\..\sshsh256.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit30] +FileName=..\..\..\sshsh512.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit31] +FileName=..\..\..\sshsha.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit32] +FileName=..\..\..\sshzlib.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit33] +FileName=..\..\..\timing.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit34] +FileName=..\..\..\tree234.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit35] +FileName=..\..\..\version.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit36] +FileName=..\..\..\wildcard.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit37] +FileName=..\..\..\windows\wincons.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit38] +FileName=..\..\..\windows\windefs.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit39] +FileName=..\..\..\windows\wingss.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit40] +FileName=..\..\..\windows\winhandl.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit41] +FileName=..\..\..\windows\winmisc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit42] +FileName=..\..\..\windows\winnet.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit43] +FileName=..\..\..\windows\winnoise.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit44] +FileName=..\..\..\windows\winnojmp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit45] +FileName=..\..\..\windows\winpgntc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit46] +FileName=..\..\..\windows\winproxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit47] +FileName=..\..\..\windows\winsftp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit48] +FileName=..\..\..\windows\winstore.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit49] +FileName=..\..\..\windows\wintime.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit50] +FileName=..\..\..\x11fwd.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit51] +FileName=..\..\..\charset\charset.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit52] +FileName=..\..\..\int64.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit53] +FileName=..\..\..\macosx\osx.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit54] +FileName=..\..\..\misc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit55] +FileName=..\..\..\network.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit56] +FileName=..\..\..\pgssapi.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit57] +FileName=..\..\..\proxy.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit58] +FileName=..\..\..\psftp.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit59] +FileName=..\..\..\putty.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit60] +FileName=..\..\..\puttymem.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit61] +FileName=..\..\..\puttyps.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit62] +FileName=..\..\..\sftp.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit63] +FileName=..\..\..\ssh.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit64] +FileName=..\..\..\sshgss.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit65] +FileName=..\..\..\sshgssc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit66] +FileName=..\..\..\storage.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit67] +FileName=..\..\..\tree234.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit68] +FileName=..\..\..\unix\unix.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit69] +FileName=..\..\..\windows\rcstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit70] +FileName=..\..\..\windows\winhelp.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit71] +FileName=..\..\..\windows\winstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit72] +FileName=..\..\..\windows\pscp.ico +Folder=Resource Files +Compile=0 +CompileCpp=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit73] +FileName=..\..\..\windows\pscp.rc +Folder=Resource Files +Compile=1 +CompileCpp=1 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[VersionInfo] +Major=0 +Minor=0 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion=0.1 +FileDescription= +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename=pscp.exe +ProductName=pscp +ProductVersion=0.1 +AutoIncBuildNr=0 diff --git a/putty/WINDOWS/DEVCPP/PSFTP/PSFTP.DEV b/putty/WINDOWS/DEVCPP/PSFTP/PSFTP.DEV new file mode 100644 index 0000000..a4ab2d3 --- /dev/null +++ b/putty/WINDOWS/DEVCPP/PSFTP/PSFTP.DEV @@ -0,0 +1,781 @@ +# DEV-C++ 5 Project File - psftp.dev +# ** DO NOT EDIT ** + +[Project] +FileName=psftp.dev +Name=psftp +Ver=1 +IsCpp=1 +Type=1 +Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix;..\..\..\macosx +Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ +Libs= +UnitCount=73 +Folders="Header Files","Resource Files","Source Files" +ObjFiles= +PrivateResource=psftp_private.rc +ResourceIncludes=..\..\..\WINDOWS +MakeIncludes= +Icon= +ExeOutput= +ObjectOutput= +OverrideOutput=0 +OverrideOutputName=psftp.exe +HostApplication= +CommandLine= +UseCustomMakefile=0 +CustomMakefile= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000000 + +[Unit1] +FileName=..\..\..\be_none.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit2] +FileName=..\..\..\cmdline.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit3] +FileName=..\..\..\cproxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit4] +FileName=..\..\..\int64.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit5] +FileName=..\..\..\logging.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit6] +FileName=..\..\..\misc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit7] +FileName=..\..\..\pgssapi.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit8] +FileName=..\..\..\pinger.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit9] +FileName=..\..\..\portfwd.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit10] +FileName=..\..\..\proxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit11] +FileName=..\..\..\psftp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit12] +FileName=..\..\..\settings.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit13] +FileName=..\..\..\sftp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit14] +FileName=..\..\..\ssh.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit15] +FileName=..\..\..\sshaes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit16] +FileName=..\..\..\ssharcf.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit17] +FileName=..\..\..\sshblowf.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit18] +FileName=..\..\..\sshbn.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit19] +FileName=..\..\..\sshcrc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit20] +FileName=..\..\..\sshcrcda.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit21] +FileName=..\..\..\sshdes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit22] +FileName=..\..\..\sshdh.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit23] +FileName=..\..\..\sshdss.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit24] +FileName=..\..\..\sshgssc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit25] +FileName=..\..\..\sshmd5.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit26] +FileName=..\..\..\sshpubk.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit27] +FileName=..\..\..\sshrand.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit28] +FileName=..\..\..\sshrsa.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit29] +FileName=..\..\..\sshsh256.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit30] +FileName=..\..\..\sshsh512.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit31] +FileName=..\..\..\sshsha.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit32] +FileName=..\..\..\sshzlib.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit33] +FileName=..\..\..\timing.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit34] +FileName=..\..\..\tree234.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit35] +FileName=..\..\..\version.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit36] +FileName=..\..\..\wildcard.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit37] +FileName=..\..\..\windows\wincons.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit38] +FileName=..\..\..\windows\windefs.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit39] +FileName=..\..\..\windows\wingss.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit40] +FileName=..\..\..\windows\winhandl.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit41] +FileName=..\..\..\windows\winmisc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit42] +FileName=..\..\..\windows\winnet.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit43] +FileName=..\..\..\windows\winnoise.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit44] +FileName=..\..\..\windows\winnojmp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit45] +FileName=..\..\..\windows\winpgntc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit46] +FileName=..\..\..\windows\winproxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit47] +FileName=..\..\..\windows\winsftp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit48] +FileName=..\..\..\windows\winstore.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit49] +FileName=..\..\..\windows\wintime.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit50] +FileName=..\..\..\x11fwd.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit51] +FileName=..\..\..\charset\charset.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit52] +FileName=..\..\..\int64.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit53] +FileName=..\..\..\macosx\osx.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit54] +FileName=..\..\..\misc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit55] +FileName=..\..\..\network.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit56] +FileName=..\..\..\pgssapi.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit57] +FileName=..\..\..\proxy.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit58] +FileName=..\..\..\psftp.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit59] +FileName=..\..\..\putty.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit60] +FileName=..\..\..\puttymem.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit61] +FileName=..\..\..\puttyps.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit62] +FileName=..\..\..\sftp.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit63] +FileName=..\..\..\ssh.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit64] +FileName=..\..\..\sshgss.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit65] +FileName=..\..\..\sshgssc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit66] +FileName=..\..\..\storage.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit67] +FileName=..\..\..\tree234.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit68] +FileName=..\..\..\unix\unix.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit69] +FileName=..\..\..\windows\rcstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit70] +FileName=..\..\..\windows\winhelp.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit71] +FileName=..\..\..\windows\winstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit72] +FileName=..\..\..\windows\pscp.ico +Folder=Resource Files +Compile=0 +CompileCpp=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit73] +FileName=..\..\..\windows\psftp.rc +Folder=Resource Files +Compile=1 +CompileCpp=1 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[VersionInfo] +Major=0 +Minor=0 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion=0.1 +FileDescription= +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename=psftp.exe +ProductName=psftp +ProductVersion=0.1 +AutoIncBuildNr=0 diff --git a/putty/WINDOWS/DEVCPP/PUTTY/PUTTY.DEV b/putty/WINDOWS/DEVCPP/PUTTY/PUTTY.DEV new file mode 100644 index 0000000..a8cad26 --- /dev/null +++ b/putty/WINDOWS/DEVCPP/PUTTY/PUTTY.DEV @@ -0,0 +1,981 @@ +# DEV-C++ 5 Project File - putty.dev +# ** DO NOT EDIT ** + +[Project] +FileName=putty.dev +Name=putty +Ver=1 +IsCpp=1 +Type=0 +Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix;..\..\..\macosx +Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ +Libs= +UnitCount=93 +Folders="Header Files","Resource Files","Source Files" +ObjFiles= +PrivateResource=putty_private.rc +ResourceIncludes=..\..\..\WINDOWS +MakeIncludes= +Icon= +ExeOutput= +ObjectOutput= +OverrideOutput=0 +OverrideOutputName=putty.exe +HostApplication= +CommandLine= +UseCustomMakefile=0 +CustomMakefile= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000000 + +[Unit1] +FileName=..\..\..\be_all_s.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit2] +FileName=..\..\..\cmdline.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit3] +FileName=..\..\..\config.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit4] +FileName=..\..\..\cproxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit5] +FileName=..\..\..\dialog.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit6] +FileName=..\..\..\ldisc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit7] +FileName=..\..\..\ldiscucs.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit8] +FileName=..\..\..\logging.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit9] +FileName=..\..\..\minibidi.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit10] +FileName=..\..\..\misc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit11] +FileName=..\..\..\pgssapi.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit12] +FileName=..\..\..\pinger.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit13] +FileName=..\..\..\portfwd.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit14] +FileName=..\..\..\proxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit15] +FileName=..\..\..\raw.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit16] +FileName=..\..\..\rlogin.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit17] +FileName=..\..\..\sercfg.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit18] +FileName=..\..\..\settings.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit19] +FileName=..\..\..\ssh.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit20] +FileName=..\..\..\sshaes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit21] +FileName=..\..\..\ssharcf.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit22] +FileName=..\..\..\sshblowf.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit23] +FileName=..\..\..\sshbn.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit24] +FileName=..\..\..\sshcrc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit25] +FileName=..\..\..\sshcrcda.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit26] +FileName=..\..\..\sshdes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit27] +FileName=..\..\..\sshdh.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit28] +FileName=..\..\..\sshdss.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit29] +FileName=..\..\..\sshgssc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit30] +FileName=..\..\..\sshmd5.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit31] +FileName=..\..\..\sshpubk.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit32] +FileName=..\..\..\sshrand.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit33] +FileName=..\..\..\sshrsa.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit34] +FileName=..\..\..\sshsh256.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit35] +FileName=..\..\..\sshsh512.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit36] +FileName=..\..\..\sshsha.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit37] +FileName=..\..\..\sshzlib.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit38] +FileName=..\..\..\telnet.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit39] +FileName=..\..\..\terminal.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit40] +FileName=..\..\..\timing.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit41] +FileName=..\..\..\tree234.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit42] +FileName=..\..\..\version.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit43] +FileName=..\..\..\wcwidth.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit44] +FileName=..\..\..\wildcard.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit45] +FileName=..\..\..\windows\sizetip.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit46] +FileName=..\..\..\windows\wincfg.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit47] +FileName=..\..\..\windows\winctrls.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit48] +FileName=..\..\..\windows\windefs.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit49] +FileName=..\..\..\windows\windlg.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit50] +FileName=..\..\..\windows\window.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit51] +FileName=..\..\..\windows\wingss.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit52] +FileName=..\..\..\windows\winhandl.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit53] +FileName=..\..\..\windows\winhelp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit54] +FileName=..\..\..\windows\winjump.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit55] +FileName=..\..\..\windows\winmisc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit56] +FileName=..\..\..\windows\winnet.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit57] +FileName=..\..\..\windows\winnoise.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit58] +FileName=..\..\..\windows\winpgntc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit59] +FileName=..\..\..\windows\winprint.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit60] +FileName=..\..\..\windows\winproxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit61] +FileName=..\..\..\windows\winser.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit62] +FileName=..\..\..\windows\winstore.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit63] +FileName=..\..\..\windows\wintime.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit64] +FileName=..\..\..\windows\winucs.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit65] +FileName=..\..\..\windows\winutils.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit66] +FileName=..\..\..\windows\winx11.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit67] +FileName=..\..\..\x11fwd.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit68] +FileName=..\..\..\charset\charset.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit69] +FileName=..\..\..\dialog.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit70] +FileName=..\..\..\int64.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit71] +FileName=..\..\..\ldisc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit72] +FileName=..\..\..\macosx\osx.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit73] +FileName=..\..\..\misc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit74] +FileName=..\..\..\network.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit75] +FileName=..\..\..\pgssapi.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit76] +FileName=..\..\..\proxy.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit77] +FileName=..\..\..\putty.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit78] +FileName=..\..\..\puttymem.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit79] +FileName=..\..\..\puttyps.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit80] +FileName=..\..\..\ssh.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit81] +FileName=..\..\..\sshgss.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit82] +FileName=..\..\..\sshgssc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit83] +FileName=..\..\..\storage.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit84] +FileName=..\..\..\terminal.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit85] +FileName=..\..\..\tree234.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit86] +FileName=..\..\..\unix\unix.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit87] +FileName=..\..\..\windows\rcstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit88] +FileName=..\..\..\windows\win_res.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit89] +FileName=..\..\..\windows\winhelp.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit90] +FileName=..\..\..\windows\winstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit91] +FileName=..\..\..\windows\putty.ico +Folder=Resource Files +Compile=0 +CompileCpp=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit92] +FileName=..\..\..\windows\putty.rc +Folder=Resource Files +Compile=1 +CompileCpp=1 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit93] +FileName=..\..\..\windows\puttycfg.ico +Folder=Resource Files +Compile=0 +CompileCpp=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[VersionInfo] +Major=0 +Minor=0 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion=0.1 +FileDescription= +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename=putty.exe +ProductName=putty +ProductVersion=0.1 +AutoIncBuildNr=0 diff --git a/putty/WINDOWS/DEVCPP/PUTTYGEN/PUTTYGEN.DEV b/putty/WINDOWS/DEVCPP/PUTTYGEN/PUTTYGEN.DEV new file mode 100644 index 0000000..7d1ec3c --- /dev/null +++ b/putty/WINDOWS/DEVCPP/PUTTYGEN/PUTTYGEN.DEV @@ -0,0 +1,511 @@ +# DEV-C++ 5 Project File - puttygen.dev +# ** DO NOT EDIT ** + +[Project] +FileName=puttygen.dev +Name=puttygen +Ver=1 +IsCpp=1 +Type=0 +Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix;..\..\..\macosx +Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ +Libs= +UnitCount=46 +Folders="Header Files","Resource Files","Source Files" +ObjFiles= +PrivateResource=puttygen_private.rc +ResourceIncludes=..\..\..\WINDOWS +MakeIncludes= +Icon= +ExeOutput= +ObjectOutput= +OverrideOutput=0 +OverrideOutputName=puttygen.exe +HostApplication= +CommandLine= +UseCustomMakefile=0 +CustomMakefile= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000000 + +[Unit1] +FileName=..\..\..\import.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit2] +FileName=..\..\..\misc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit3] +FileName=..\..\..\notiming.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit4] +FileName=..\..\..\sshaes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit5] +FileName=..\..\..\sshbn.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit6] +FileName=..\..\..\sshdes.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit7] +FileName=..\..\..\sshdss.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit8] +FileName=..\..\..\sshdssg.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit9] +FileName=..\..\..\sshmd5.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit10] +FileName=..\..\..\sshprime.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit11] +FileName=..\..\..\sshpubk.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit12] +FileName=..\..\..\sshrand.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit13] +FileName=..\..\..\sshrsa.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit14] +FileName=..\..\..\sshrsag.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit15] +FileName=..\..\..\sshsh256.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit16] +FileName=..\..\..\sshsh512.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit17] +FileName=..\..\..\sshsha.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit18] +FileName=..\..\..\tree234.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit19] +FileName=..\..\..\version.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit20] +FileName=..\..\..\windows\winctrls.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit21] +FileName=..\..\..\windows\winhelp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit22] +FileName=..\..\..\windows\winmisc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit23] +FileName=..\..\..\windows\winnoise.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit24] +FileName=..\..\..\windows\winnojmp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit25] +FileName=..\..\..\windows\winpgen.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit26] +FileName=..\..\..\windows\winstore.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit27] +FileName=..\..\..\windows\wintime.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit28] +FileName=..\..\..\windows\winutils.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit29] +FileName=..\..\..\charset\charset.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit30] +FileName=..\..\..\dialog.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit31] +FileName=..\..\..\int64.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit32] +FileName=..\..\..\macosx\osx.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit33] +FileName=..\..\..\misc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit34] +FileName=..\..\..\network.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit35] +FileName=..\..\..\putty.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit36] +FileName=..\..\..\puttymem.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit37] +FileName=..\..\..\puttyps.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit38] +FileName=..\..\..\ssh.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit39] +FileName=..\..\..\storage.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit40] +FileName=..\..\..\tree234.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit41] +FileName=..\..\..\unix\unix.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit42] +FileName=..\..\..\windows\rcstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit43] +FileName=..\..\..\windows\winhelp.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit44] +FileName=..\..\..\windows\winstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit45] +FileName=..\..\..\windows\puttygen.ico +Folder=Resource Files +Compile=0 +CompileCpp=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit46] +FileName=..\..\..\windows\puttygen.rc +Folder=Resource Files +Compile=1 +CompileCpp=1 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[VersionInfo] +Major=0 +Minor=0 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion=0.1 +FileDescription= +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename=puttygen.exe +ProductName=puttygen +ProductVersion=0.1 +AutoIncBuildNr=0 diff --git a/putty/WINDOWS/DEVCPP/PUTTYTEL/PUTTYTEL.DEV b/putty/WINDOWS/DEVCPP/PUTTYTEL/PUTTYTEL.DEV new file mode 100644 index 0000000..6da9f45 --- /dev/null +++ b/putty/WINDOWS/DEVCPP/PUTTYTEL/PUTTYTEL.DEV @@ -0,0 +1,691 @@ +# DEV-C++ 5 Project File - puttytel.dev +# ** DO NOT EDIT ** + +[Project] +FileName=puttytel.dev +Name=puttytel +Ver=1 +IsCpp=1 +Type=0 +Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ +Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix;..\..\..\macosx +Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ +Libs= +UnitCount=64 +Folders="Header Files","Resource Files","Source Files" +ObjFiles= +PrivateResource=puttytel_private.rc +ResourceIncludes=..\..\..\WINDOWS +MakeIncludes= +Icon= +ExeOutput= +ObjectOutput= +OverrideOutput=0 +OverrideOutputName=puttytel.exe +HostApplication= +CommandLine= +UseCustomMakefile=0 +CustomMakefile= +IncludeVersionInfo=0 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000000 + +[Unit1] +FileName=..\..\..\be_nos_s.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit2] +FileName=..\..\..\cmdline.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit3] +FileName=..\..\..\config.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit4] +FileName=..\..\..\dialog.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit5] +FileName=..\..\..\ldisc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit6] +FileName=..\..\..\ldiscucs.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit7] +FileName=..\..\..\logging.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit8] +FileName=..\..\..\minibidi.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit9] +FileName=..\..\..\misc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit10] +FileName=..\..\..\nocproxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit11] +FileName=..\..\..\nogss.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit12] +FileName=..\..\..\pinger.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit13] +FileName=..\..\..\proxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit14] +FileName=..\..\..\raw.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit15] +FileName=..\..\..\rlogin.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit16] +FileName=..\..\..\sercfg.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit17] +FileName=..\..\..\settings.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit18] +FileName=..\..\..\telnet.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit19] +FileName=..\..\..\terminal.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit20] +FileName=..\..\..\timing.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit21] +FileName=..\..\..\tree234.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit22] +FileName=..\..\..\version.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit23] +FileName=..\..\..\wcwidth.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit24] +FileName=..\..\..\windows\sizetip.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit25] +FileName=..\..\..\windows\wincfg.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit26] +FileName=..\..\..\windows\winctrls.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit27] +FileName=..\..\..\windows\windefs.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit28] +FileName=..\..\..\windows\windlg.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit29] +FileName=..\..\..\windows\window.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit30] +FileName=..\..\..\windows\winhandl.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit31] +FileName=..\..\..\windows\winhelp.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit32] +FileName=..\..\..\windows\winjump.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit33] +FileName=..\..\..\windows\winmisc.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit34] +FileName=..\..\..\windows\winnet.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit35] +FileName=..\..\..\windows\winprint.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit36] +FileName=..\..\..\windows\winproxy.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit37] +FileName=..\..\..\windows\winser.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit38] +FileName=..\..\..\windows\winstore.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit39] +FileName=..\..\..\windows\wintime.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit40] +FileName=..\..\..\windows\winucs.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit41] +FileName=..\..\..\windows\winutils.c +Folder=Source Files +Compile=1 +CompileCpp=0 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit42] +FileName=..\..\..\charset\charset.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit43] +FileName=..\..\..\dialog.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit44] +FileName=..\..\..\int64.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit45] +FileName=..\..\..\ldisc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit46] +FileName=..\..\..\macosx\osx.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit47] +FileName=..\..\..\misc.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit48] +FileName=..\..\..\network.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit49] +FileName=..\..\..\proxy.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit50] +FileName=..\..\..\putty.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit51] +FileName=..\..\..\puttymem.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit52] +FileName=..\..\..\puttyps.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit53] +FileName=..\..\..\ssh.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit54] +FileName=..\..\..\storage.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit55] +FileName=..\..\..\terminal.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit56] +FileName=..\..\..\tree234.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit57] +FileName=..\..\..\unix\unix.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit58] +FileName=..\..\..\windows\rcstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit59] +FileName=..\..\..\windows\win_res.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit60] +FileName=..\..\..\windows\winhelp.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit61] +FileName=..\..\..\windows\winstuff.h +Folder=Header Files +Compile=1 +CompileCpp=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit62] +FileName=..\..\..\windows\putty.ico +Folder=Resource Files +Compile=0 +CompileCpp=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit63] +FileName=..\..\..\windows\puttycfg.ico +Folder=Resource Files +Compile=0 +CompileCpp=0 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit64] +FileName=..\..\..\windows\puttytel.rc +Folder=Resource Files +Compile=1 +CompileCpp=1 +Link=0 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[VersionInfo] +Major=0 +Minor=0 +Release=1 +Build=1 +LanguageID=1033 +CharsetID=1252 +CompanyName= +FileVersion=0.1 +FileDescription= +InternalName= +LegalCopyright= +LegalTrademarks= +OriginalFilename=puttytel.exe +ProductName=puttytel +ProductVersion=0.1 +AutoIncBuildNr=0 diff --git a/putty/WINDOWS/MAKEFILE.BOR b/putty/WINDOWS/MAKEFILE.BOR new file mode 100644 index 0000000..0e6e1f6 --- /dev/null +++ b/putty/WINDOWS/MAKEFILE.BOR @@ -0,0 +1,804 @@ +# Makefile for putty under Borland C. +# +# This file was created by `mkfiles.pl' from the `Recipe' file. +# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead. +# +# Extra options you can set: +# +# - VER="-DSNAPSHOT=1999-01-25 -DSVN_REV=1234" +# Generates executables whose About box report them as being a +# development snapshot. SVN_REV is a Subversion revision number. +# +# - VER=-DRELEASE=0.43 +# Generates executables whose About box report them as being a +# release version. +# +# - COMPAT=-DAUTO_WINSOCK (Windows only) +# Causes PuTTY to assume that includes its own WinSock +# header file, so that it won't try to include . +# +# - COMPAT=-DWINSOCK_TWO (Windows only) +# Causes the PuTTY utilities to include instead of +# , except Plink which _needs_ WinSock 2 so it already +# does this. +# +# - COMPAT=-DNO_SECURITY (Windows only) +# Disables Pageant's use of , which is not available +# with some development environments (such as older versions of +# the Cygwin/mingw GNU toolchain). This means that 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). +# +# - COMPAT=-DNO_MULTIMON (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. This means that PuTTY's +# full-screen mode (configurable to work on Alt-Enter) will +# not behave usefully in a multi-monitor environment. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin. +# +# - COMPAT=-DNO_HTMLHELP (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. The resulting binary +# will only look for an old-style WinHelp file (.HLP/.CNT), and +# will ignore any .CHM file. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin (although you can use +# the htmlhelp.h 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. +# +# - RCFL=-DASCIICTLS (Windows only) +# Uses ASCII rather than Unicode to specify the tab control in +# the resource file. Probably most useful when compiling with +# Cygnus/mingw32, whose resource compiler may have less of a +# problem with it. +# +# - XFLAGS=-DTELNET_DEFAULT +# Causes PuTTY to default to the Telnet protocol (in the absence +# of Default Settings and so on to the contrary). Normally PuTTY +# will default to SSH. +# +# - 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. +# + +# If you rename this file to `Makefile', you should change this line, +# so that the .rsp files still depend on the correct makefile. +MAKEFILE = Makefile.bor + +# C compilation flags +CFLAGS = -D_WINDOWS -DWINVER=0x0500 +# Resource compilation flags +RCFLAGS = -DNO_WINRESRC_H -DWIN32 -D_WIN32 -DWINVER=0x0401 + +# Get include directory for resource compiler +!if !$d(BCB) +BCB = $(MAKEDIR)\.. +!endif + +# Borland doesn't support +=. This probably shouldn't work, but seems to. +RCFLAGS = $(RCFLAGS) $(VER) + +.c.obj: + bcc32 -w-aus -w-ccc -w-par -w-pia $(COMPAT) $(CFLAGS) $(XFLAGS) \ + -I..\./ -I..\charset/ -I..\windows/ -I..\unix/ -I..\macosx/ \ + /c $*.c +.rc.res: + brcc32 $(RCFL) -i $(BCB)\include -r $(RCFLAGS) $*.rc + +all: pageant.exe plink.exe pscp.exe psftp.exe putty.exe puttygen.exe \ + puttytel.exe + +pageant.exe: misc.obj pageant.res sshaes.obj sshbn.obj sshdes.obj sshdss.obj \ + sshmd5.obj sshpubk.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj tree234.obj version.obj winhelp.obj winmisc.obj \ + winpgnt.obj winpgntc.obj winutils.obj pageant.rsp + ilink32 -aa -Gn -L$(BCB)\lib @pageant.rsp + +plink.exe: be_all_s.obj cmdline.obj cproxy.obj ldisc.obj logging.obj \ + misc.obj pgssapi.obj pinger.obj plink.res portfwd.obj \ + proxy.obj raw.obj rlogin.obj settings.obj ssh.obj sshaes.obj \ + ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj \ + sshdes.obj sshdh.obj sshdss.obj sshgssc.obj sshmd5.obj \ + sshpubk.obj sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj sshzlib.obj telnet.obj timing.obj tree234.obj \ + version.obj wildcard.obj wincons.obj windefs.obj wingss.obj \ + winhandl.obj winmisc.obj winnet.obj winnoise.obj \ + winnojmp.obj winpgntc.obj winplink.obj winproxy.obj \ + winser.obj winstore.obj wintime.obj winx11.obj x11fwd.obj \ + plink.rsp + ilink32 -ap -Gn -L$(BCB)\lib @plink.rsp + +pscp.exe: be_none.obj cmdline.obj cproxy.obj int64.obj logging.obj misc.obj \ + pgssapi.obj pinger.obj portfwd.obj proxy.obj pscp.obj \ + pscp.res settings.obj sftp.obj ssh.obj sshaes.obj \ + ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj \ + sshdes.obj sshdh.obj sshdss.obj sshgssc.obj sshmd5.obj \ + sshpubk.obj sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj sshzlib.obj timing.obj tree234.obj version.obj \ + wildcard.obj wincons.obj windefs.obj wingss.obj winhandl.obj \ + winmisc.obj winnet.obj winnoise.obj winnojmp.obj \ + winpgntc.obj winproxy.obj winsftp.obj winstore.obj \ + wintime.obj x11fwd.obj pscp.rsp + ilink32 -ap -Gn -L$(BCB)\lib @pscp.rsp + +psftp.exe: be_none.obj cmdline.obj cproxy.obj int64.obj logging.obj misc.obj \ + pgssapi.obj pinger.obj portfwd.obj proxy.obj psftp.obj \ + psftp.res settings.obj sftp.obj ssh.obj sshaes.obj \ + ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj \ + sshdes.obj sshdh.obj sshdss.obj sshgssc.obj sshmd5.obj \ + sshpubk.obj sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj sshzlib.obj timing.obj tree234.obj version.obj \ + wildcard.obj wincons.obj windefs.obj wingss.obj winhandl.obj \ + winmisc.obj winnet.obj winnoise.obj winnojmp.obj \ + winpgntc.obj winproxy.obj winsftp.obj winstore.obj \ + wintime.obj x11fwd.obj psftp.rsp + ilink32 -ap -Gn -L$(BCB)\lib @psftp.rsp + +putty.exe: be_all_s.obj cmdline.obj config.obj cproxy.obj dialog.obj \ + ldisc.obj ldiscucs.obj logging.obj minibidi.obj misc.obj \ + pgssapi.obj pinger.obj portfwd.obj proxy.obj putty.res \ + raw.obj rlogin.obj sercfg.obj settings.obj sizetip.obj \ + ssh.obj sshaes.obj ssharcf.obj sshblowf.obj sshbn.obj \ + sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj \ + sshgssc.obj sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj \ + sshsh256.obj sshsh512.obj sshsha.obj sshzlib.obj telnet.obj \ + terminal.obj timing.obj tree234.obj version.obj wcwidth.obj \ + wildcard.obj wincfg.obj winctrls.obj windefs.obj windlg.obj \ + window.obj wingss.obj winhandl.obj winhelp.obj winjump.obj \ + winmisc.obj winnet.obj winnoise.obj winpgntc.obj \ + winprint.obj winproxy.obj winser.obj winstore.obj \ + wintime.obj winucs.obj winutils.obj winx11.obj x11fwd.obj \ + putty.rsp + ilink32 -aa -Gn -L$(BCB)\lib @putty.rsp + +puttygen.exe: import.obj misc.obj notiming.obj puttygen.res sshaes.obj \ + sshbn.obj sshdes.obj sshdss.obj sshdssg.obj sshmd5.obj \ + sshprime.obj sshpubk.obj sshrand.obj sshrsa.obj sshrsag.obj \ + sshsh256.obj sshsh512.obj sshsha.obj tree234.obj version.obj \ + winctrls.obj winhelp.obj winmisc.obj winnoise.obj \ + winnojmp.obj winpgen.obj winstore.obj wintime.obj \ + winutils.obj puttygen.rsp + ilink32 -aa -Gn -L$(BCB)\lib @puttygen.rsp + +puttytel.exe: be_nos_s.obj cmdline.obj config.obj dialog.obj ldisc.obj \ + ldiscucs.obj logging.obj minibidi.obj misc.obj nocproxy.obj \ + nogss.obj pinger.obj proxy.obj puttytel.res raw.obj \ + rlogin.obj sercfg.obj settings.obj sizetip.obj telnet.obj \ + terminal.obj timing.obj tree234.obj version.obj wcwidth.obj \ + wincfg.obj winctrls.obj windefs.obj windlg.obj window.obj \ + winhandl.obj winhelp.obj winjump.obj winmisc.obj winnet.obj \ + winprint.obj winproxy.obj winser.obj winstore.obj \ + wintime.obj winucs.obj winutils.obj puttytel.rsp + ilink32 -aa -Gn -L$(BCB)\lib @puttytel.rsp + +pageant.rsp: $(MAKEFILE) + echo c0w32 + > pageant.rsp + echo misc.obj sshaes.obj sshbn.obj sshdes.obj + >> pageant.rsp + echo sshdss.obj sshmd5.obj sshpubk.obj sshrsa.obj + >> pageant.rsp + echo sshsh256.obj sshsh512.obj sshsha.obj tree234.obj + >> pageant.rsp + echo version.obj winhelp.obj winmisc.obj winpgnt.obj + >> pageant.rsp + echo winpgntc.obj winutils.obj >> pageant.rsp + echo pageant.exe >> pageant.rsp + echo nul,cw32 import32 ole32, >> pageant.rsp + echo pageant.res >> pageant.rsp + +plink.rsp: $(MAKEFILE) + echo c0x32 + > plink.rsp + echo be_all_s.obj cmdline.obj cproxy.obj ldisc.obj + >> plink.rsp + echo logging.obj misc.obj pgssapi.obj pinger.obj + >> plink.rsp + echo portfwd.obj proxy.obj raw.obj rlogin.obj + >> plink.rsp + echo settings.obj ssh.obj sshaes.obj ssharcf.obj + >> plink.rsp + echo sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj + >> plink.rsp + echo sshdes.obj sshdh.obj sshdss.obj sshgssc.obj + >> plink.rsp + echo sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj + >> plink.rsp + echo sshsh256.obj sshsh512.obj sshsha.obj sshzlib.obj + >> plink.rsp + echo telnet.obj timing.obj tree234.obj version.obj + >> plink.rsp + echo wildcard.obj wincons.obj windefs.obj wingss.obj + >> plink.rsp + echo winhandl.obj winmisc.obj winnet.obj winnoise.obj + >> plink.rsp + echo winnojmp.obj winpgntc.obj winplink.obj + >> plink.rsp + echo winproxy.obj winser.obj winstore.obj wintime.obj + >> plink.rsp + echo winx11.obj x11fwd.obj >> plink.rsp + echo plink.exe >> plink.rsp + echo nul,cw32 import32 ole32, >> plink.rsp + echo plink.res >> plink.rsp + +pscp.rsp: $(MAKEFILE) + echo c0x32 + > pscp.rsp + echo be_none.obj cmdline.obj cproxy.obj int64.obj + >> pscp.rsp + echo logging.obj misc.obj pgssapi.obj pinger.obj + >> pscp.rsp + echo portfwd.obj proxy.obj pscp.obj settings.obj + >> pscp.rsp + echo sftp.obj ssh.obj sshaes.obj ssharcf.obj + >> pscp.rsp + echo sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj + >> pscp.rsp + echo sshdes.obj sshdh.obj sshdss.obj sshgssc.obj + >> pscp.rsp + echo sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj + >> pscp.rsp + echo sshsh256.obj sshsh512.obj sshsha.obj sshzlib.obj + >> pscp.rsp + echo timing.obj tree234.obj version.obj wildcard.obj + >> pscp.rsp + echo wincons.obj windefs.obj wingss.obj winhandl.obj + >> pscp.rsp + echo winmisc.obj winnet.obj winnoise.obj winnojmp.obj + >> pscp.rsp + echo winpgntc.obj winproxy.obj winsftp.obj + >> pscp.rsp + echo winstore.obj wintime.obj x11fwd.obj >> pscp.rsp + echo pscp.exe >> pscp.rsp + echo nul,cw32 import32 ole32, >> pscp.rsp + echo pscp.res >> pscp.rsp + +psftp.rsp: $(MAKEFILE) + echo c0x32 + > psftp.rsp + echo be_none.obj cmdline.obj cproxy.obj int64.obj + >> psftp.rsp + echo logging.obj misc.obj pgssapi.obj pinger.obj + >> psftp.rsp + echo portfwd.obj proxy.obj psftp.obj settings.obj + >> psftp.rsp + echo sftp.obj ssh.obj sshaes.obj ssharcf.obj + >> psftp.rsp + echo sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj + >> psftp.rsp + echo sshdes.obj sshdh.obj sshdss.obj sshgssc.obj + >> psftp.rsp + echo sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj + >> psftp.rsp + echo sshsh256.obj sshsh512.obj sshsha.obj sshzlib.obj + >> psftp.rsp + echo timing.obj tree234.obj version.obj wildcard.obj + >> psftp.rsp + echo wincons.obj windefs.obj wingss.obj winhandl.obj + >> psftp.rsp + echo winmisc.obj winnet.obj winnoise.obj winnojmp.obj + >> psftp.rsp + echo winpgntc.obj winproxy.obj winsftp.obj + >> psftp.rsp + echo winstore.obj wintime.obj x11fwd.obj >> psftp.rsp + echo psftp.exe >> psftp.rsp + echo nul,cw32 import32 ole32, >> psftp.rsp + echo psftp.res >> psftp.rsp + +putty.rsp: $(MAKEFILE) + echo c0w32 + > putty.rsp + echo be_all_s.obj cmdline.obj config.obj cproxy.obj + >> putty.rsp + echo dialog.obj ldisc.obj ldiscucs.obj logging.obj + >> putty.rsp + echo minibidi.obj misc.obj pgssapi.obj pinger.obj + >> putty.rsp + echo portfwd.obj proxy.obj raw.obj rlogin.obj + >> putty.rsp + echo sercfg.obj settings.obj sizetip.obj ssh.obj + >> putty.rsp + echo sshaes.obj ssharcf.obj sshblowf.obj sshbn.obj + >> putty.rsp + echo sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj + >> putty.rsp + echo sshdss.obj sshgssc.obj sshmd5.obj sshpubk.obj + >> putty.rsp + echo sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj + >> putty.rsp + echo sshsha.obj sshzlib.obj telnet.obj terminal.obj + >> putty.rsp + echo timing.obj tree234.obj version.obj wcwidth.obj + >> putty.rsp + echo wildcard.obj wincfg.obj winctrls.obj windefs.obj + >> putty.rsp + echo windlg.obj window.obj wingss.obj winhandl.obj + >> putty.rsp + echo winhelp.obj winjump.obj winmisc.obj winnet.obj + >> putty.rsp + echo winnoise.obj winpgntc.obj winprint.obj + >> putty.rsp + echo winproxy.obj winser.obj winstore.obj wintime.obj + >> putty.rsp + echo winucs.obj winutils.obj winx11.obj x11fwd.obj >> putty.rsp + echo putty.exe >> putty.rsp + echo nul,cw32 import32 ole32, >> putty.rsp + echo putty.res >> putty.rsp + +puttygen.rsp: $(MAKEFILE) + echo c0w32 + > puttygen.rsp + echo import.obj misc.obj notiming.obj sshaes.obj + >> puttygen.rsp + echo sshbn.obj sshdes.obj sshdss.obj sshdssg.obj + >> puttygen.rsp + echo sshmd5.obj sshprime.obj sshpubk.obj sshrand.obj + >> puttygen.rsp + echo sshrsa.obj sshrsag.obj sshsh256.obj sshsh512.obj + >> puttygen.rsp + echo sshsha.obj tree234.obj version.obj winctrls.obj + >> puttygen.rsp + echo winhelp.obj winmisc.obj winnoise.obj winnojmp.obj + >> puttygen.rsp + echo winpgen.obj winstore.obj wintime.obj winutils.obj >> puttygen.rsp + echo puttygen.exe >> puttygen.rsp + echo nul,cw32 import32 ole32, >> puttygen.rsp + echo puttygen.res >> puttygen.rsp + +puttytel.rsp: $(MAKEFILE) + echo c0w32 + > puttytel.rsp + echo be_nos_s.obj cmdline.obj config.obj dialog.obj + >> puttytel.rsp + echo ldisc.obj ldiscucs.obj logging.obj minibidi.obj + >> puttytel.rsp + echo misc.obj nocproxy.obj nogss.obj pinger.obj + >> puttytel.rsp + echo proxy.obj raw.obj rlogin.obj sercfg.obj + >> puttytel.rsp + echo settings.obj sizetip.obj telnet.obj terminal.obj + >> puttytel.rsp + echo timing.obj tree234.obj version.obj wcwidth.obj + >> puttytel.rsp + echo wincfg.obj winctrls.obj windefs.obj windlg.obj + >> puttytel.rsp + echo window.obj winhandl.obj winhelp.obj winjump.obj + >> puttytel.rsp + echo winmisc.obj winnet.obj winprint.obj winproxy.obj + >> puttytel.rsp + echo winser.obj winstore.obj wintime.obj winucs.obj + >> puttytel.rsp + echo winutils.obj >> puttytel.rsp + echo puttytel.exe >> puttytel.rsp + echo nul,cw32 import32 ole32, >> puttytel.rsp + echo puttytel.res >> puttytel.rsp + +be_all_s.obj: ..\be_all_s.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +be_none.obj: ..\be_none.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +be_nos_s.obj: ..\be_nos_s.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +cmdgen.obj: ..\cmdgen.c ..\putty.h ..\ssh.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +cmdline.obj: ..\cmdline.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +config.obj: ..\config.c ..\putty.h ..\dialog.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +cproxy.obj: ..\cproxy.c ..\putty.h ..\ssh.h ..\network.h ..\proxy.h \ + ..\puttyps.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +dialog.obj: ..\dialog.c ..\putty.h ..\dialog.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +fromucs.obj: ..\charset\fromucs.c ..\charset\charset.h ..\charset\internal.h +gtkcfg.obj: ..\unix\gtkcfg.c ..\putty.h ..\dialog.h ..\storage.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +gtkcols.obj: ..\unix\gtkcols.c ..\unix\gtkcols.h +gtkdlg.obj: ..\unix\gtkdlg.c ..\unix\gtkcols.h ..\unix\gtkfont.h ..\putty.h \ + ..\storage.h ..\dialog.h ..\tree234.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h +gtkfont.obj: ..\unix\gtkfont.c ..\putty.h ..\unix\gtkfont.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h +gtkwin.obj: ..\unix\gtkwin.c ..\putty.h ..\terminal.h ..\unix\gtkfont.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\tree234.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h +import.obj: ..\import.c ..\putty.h ..\ssh.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +int64.obj: ..\int64.c ..\int64.h +ldisc.obj: ..\ldisc.c ..\putty.h ..\terminal.h ..\ldisc.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\tree234.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h +ldiscucs.obj: ..\ldiscucs.c ..\putty.h ..\terminal.h ..\ldisc.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\tree234.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h +localenc.obj: ..\charset\localenc.c ..\charset\charset.h \ + ..\charset\internal.h +logging.obj: ..\logging.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +macenc.obj: ..\charset\macenc.c ..\charset\charset.h ..\charset\internal.h +mimeenc.obj: ..\charset\mimeenc.c ..\charset\charset.h ..\charset\internal.h +minibidi.obj: ..\minibidi.c ..\misc.h ..\puttymem.h +misc.obj: ..\misc.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +nocproxy.obj: ..\nocproxy.c ..\putty.h ..\network.h ..\proxy.h ..\puttyps.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +nogss.obj: ..\nogss.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +notiming.obj: ..\notiming.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +osxctrls.obj: ..\macosx\osxctrls.m ..\putty.h ..\dialog.h \ + ..\macosx\osxclass.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h +osxdlg.obj: ..\macosx\osxdlg.m ..\putty.h ..\storage.h ..\dialog.h \ + ..\macosx\osxclass.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +osxmain.obj: ..\macosx\osxmain.m ..\putty.h ..\macosx\osxclass.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +osxsel.obj: ..\macosx\osxsel.m ..\putty.h ..\macosx\osxclass.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +osxwin.obj: ..\macosx\osxwin.m ..\putty.h ..\terminal.h ..\macosx\osxclass.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\tree234.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h +pageant.res: FORCE +pgssapi.obj: ..\pgssapi.c ..\putty.h ..\pgssapi.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +pinger.obj: ..\pinger.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +plink.res: FORCE +portfwd.obj: ..\portfwd.c ..\putty.h ..\ssh.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +proxy.obj: ..\proxy.c ..\putty.h ..\network.h ..\proxy.h ..\puttyps.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +pscp.obj: ..\pscp.c ..\putty.h ..\psftp.h ..\ssh.h ..\sftp.h ..\storage.h \ + ..\int64.h ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h +pscp.res: FORCE +psftp.obj: ..\psftp.c ..\putty.h ..\psftp.h ..\storage.h ..\ssh.h ..\sftp.h \ + ..\int64.h ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h +psftp.res: FORCE +putty.res: FORCE +puttygen.res: FORCE +puttytel.res: FORCE +raw.obj: ..\raw.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +rlogin.obj: ..\rlogin.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +sbcs.obj: ..\charset\sbcs.c ..\charset\charset.h ..\charset\internal.h +sbcsdat.obj: ..\charset\sbcsdat.c ..\charset\charset.h ..\charset\internal.h +sercfg.obj: ..\sercfg.c ..\putty.h ..\dialog.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +settings.obj: ..\settings.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +sftp.obj: ..\sftp.c ..\misc.h ..\int64.h ..\tree234.h ..\sftp.h \ + ..\puttymem.h +sizetip.obj: ..\windows\sizetip.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +slookup.obj: ..\charset\slookup.c ..\charset\charset.h ..\charset\internal.h \ + ..\charset\enum.c ..\charset\sbcsdat.c ..\charset\utf8.c +ssh.obj: ..\ssh.c ..\putty.h ..\tree234.h ..\ssh.h ..\sshgssc.h ..\sshgss.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h ..\int64.h \ + ..\pgssapi.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h +sshaes.obj: ..\sshaes.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +ssharcf.obj: ..\ssharcf.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +sshblowf.obj: ..\sshblowf.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +sshbn.obj: ..\sshbn.c ..\misc.h ..\ssh.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h +sshcrc.obj: ..\sshcrc.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +sshcrcda.obj: ..\sshcrcda.c ..\misc.h ..\ssh.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h +sshdes.obj: ..\sshdes.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +sshdh.obj: ..\sshdh.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +sshdss.obj: ..\sshdss.c ..\ssh.h ..\misc.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h +sshdssg.obj: ..\sshdssg.c ..\misc.h ..\ssh.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h +sshgssc.obj: ..\sshgssc.c ..\putty.h ..\sshgssc.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\pgssapi.h ..\sshgss.h ..\puttymem.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h +sshmd5.obj: ..\sshmd5.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +sshprime.obj: ..\sshprime.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +sshpubk.obj: ..\sshpubk.c ..\putty.h ..\ssh.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +sshrand.obj: ..\sshrand.c ..\putty.h ..\ssh.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +sshrsa.obj: ..\sshrsa.c ..\ssh.h ..\misc.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h +sshrsag.obj: ..\sshrsag.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +sshsh256.obj: ..\sshsh256.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +sshsh512.obj: ..\sshsh512.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +sshsha.obj: ..\sshsha.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +sshzlib.obj: ..\sshzlib.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h +telnet.obj: ..\telnet.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +terminal.obj: ..\terminal.c ..\putty.h ..\terminal.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\tree234.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h +testback.obj: ..\testback.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +time.obj: ..\time.c +timing.obj: ..\timing.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h +toucs.obj: ..\charset\toucs.c ..\charset\charset.h ..\charset\internal.h +tree234.obj: ..\tree234.c ..\puttymem.h ..\tree234.h +utf8.obj: ..\charset\utf8.c ..\charset\charset.h ..\charset\internal.h +ux_x11.obj: ..\unix\ux_x11.c ..\putty.h ..\ssh.h ..\network.h ..\puttyps.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxagentc.obj: ..\unix\uxagentc.c ..\putty.h ..\misc.h ..\tree234.h \ + ..\puttymem.h ..\puttyps.h ..\network.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxcfg.obj: ..\unix\uxcfg.c ..\putty.h ..\dialog.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxcons.obj: ..\unix\uxcons.c ..\putty.h ..\storage.h ..\ssh.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxgen.obj: ..\unix\uxgen.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +uxgss.obj: ..\unix\uxgss.c ..\putty.h ..\pgssapi.h ..\sshgss.h ..\sshgssc.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxmisc.obj: ..\unix\uxmisc.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +uxnet.obj: ..\unix\uxnet.c ..\putty.h ..\network.h ..\tree234.h ..\puttyps.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h +uxnoise.obj: ..\unix\uxnoise.c ..\putty.h ..\ssh.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxplink.obj: ..\unix\uxplink.c ..\putty.h ..\storage.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxprint.obj: ..\unix\uxprint.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxproxy.obj: ..\unix\uxproxy.c ..\tree234.h ..\putty.h ..\network.h \ + ..\proxy.h ..\puttyps.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxpterm.obj: ..\unix\uxpterm.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxpty.obj: ..\unix\uxpty.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h +uxputty.obj: ..\unix\uxputty.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxsel.obj: ..\unix\uxsel.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h +uxser.obj: ..\unix\uxser.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h +uxsftp.obj: ..\unix\uxsftp.c ..\putty.h ..\ssh.h ..\psftp.h ..\int64.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h +uxsignal.obj: ..\unix\uxsignal.c +uxstore.obj: ..\unix\uxstore.c ..\putty.h ..\storage.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h +uxucs.obj: ..\unix\uxucs.c ..\putty.h ..\charset\charset.h ..\terminal.h \ + ..\misc.h ..\puttyps.h ..\network.h ..\tree234.h \ + ..\puttymem.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h +wcwidth.obj: ..\wcwidth.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +wildcard.obj: ..\wildcard.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +wincfg.obj: ..\windows\wincfg.c ..\putty.h ..\dialog.h ..\storage.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +wincons.obj: ..\windows\wincons.c ..\putty.h ..\storage.h ..\ssh.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\int64.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\windows\winhelp.h \ + ..\charset\charset.h +winctrls.obj: ..\windows\winctrls.c ..\putty.h ..\misc.h ..\dialog.h \ + ..\puttyps.h ..\network.h ..\puttymem.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h +windefs.obj: ..\windows\windefs.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +windlg.obj: ..\windows\windlg.c ..\putty.h ..\ssh.h ..\windows\win_res.h \ + ..\storage.h ..\dialog.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\puttymem.h ..\tree234.h ..\int64.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\windows\winhelp.h \ + ..\charset\charset.h +window.obj: ..\windows\window.c ..\putty.h ..\terminal.h ..\storage.h \ + ..\windows\win_res.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h +wingss.obj: ..\windows\wingss.c ..\putty.h ..\pgssapi.h ..\sshgss.h \ + ..\sshgssc.h ..\misc.h ..\puttyps.h ..\network.h \ + ..\puttymem.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h +winhandl.obj: ..\windows\winhandl.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +winhelp.obj: ..\windows\winhelp.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +winjump.obj: ..\windows\winjump.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +winmisc.obj: ..\windows\winmisc.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +winnet.obj: ..\windows\winnet.c ..\putty.h ..\network.h ..\tree234.h \ + ..\puttyps.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h +winnoise.obj: ..\windows\winnoise.c ..\putty.h ..\ssh.h ..\storage.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\int64.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\windows\winhelp.h \ + ..\charset\charset.h +winnojmp.obj: ..\windows\winnojmp.c +winpgen.obj: ..\windows\winpgen.c ..\putty.h ..\ssh.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +winpgnt.obj: ..\windows\winpgnt.c ..\putty.h ..\ssh.h ..\misc.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\puttymem.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +winpgntc.obj: ..\windows\winpgntc.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +winplink.obj: ..\windows\winplink.c ..\putty.h ..\storage.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h +winprint.obj: ..\windows\winprint.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +winproxy.obj: ..\windows\winproxy.c ..\tree234.h ..\putty.h ..\network.h \ + ..\proxy.h ..\puttyps.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h +winser.obj: ..\windows\winser.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +winsftp.obj: ..\windows\winsftp.c ..\putty.h ..\psftp.h ..\ssh.h ..\int64.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h +winstore.obj: ..\windows\winstore.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +wintime.obj: ..\windows\wintime.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +winucs.obj: ..\windows\winucs.c ..\putty.h ..\terminal.h ..\misc.h \ + ..\puttyps.h ..\network.h ..\tree234.h ..\puttymem.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +winutils.obj: ..\windows\winutils.c ..\putty.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\puttymem.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h +winx11.obj: ..\windows\winx11.c ..\putty.h ..\ssh.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +x11fwd.obj: ..\x11fwd.c ..\putty.h ..\ssh.h ..\tree234.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h +xenc.obj: ..\charset\xenc.c ..\charset\charset.h ..\charset\internal.h +xkeysym.obj: ..\unix\xkeysym.c ..\misc.h ..\puttymem.h +xpmptcfg.obj: ..\unix\xpmptcfg.c +xpmpterm.obj: ..\unix\xpmpterm.c +xpmpucfg.obj: ..\unix\xpmpucfg.c +xpmputty.obj: ..\unix\xpmputty.c + +version.obj: FORCE + bcc32 $(VER) $(CFLAGS) /c ..\version.c + +clean: + -del *.obj + -del *.exe + -del *.res + -del *.pch + -del *.aps + -del *.il* + -del *.pdb + -del *.rsp + -del *.tds + -del *.$$$$$$ + +FORCE: + -rem dummy command diff --git a/putty/WINDOWS/MAKEFILE.CYG b/putty/WINDOWS/MAKEFILE.CYG new file mode 100644 index 0000000..9735a4e --- /dev/null +++ b/putty/WINDOWS/MAKEFILE.CYG @@ -0,0 +1,1006 @@ +# Makefile for putty under cygwin. +# +# This file was created by `mkfiles.pl' from the `Recipe' file. +# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead. +# +# Extra options you can set: +# +# - VER="-DSNAPSHOT=1999-01-25 -DSVN_REV=1234" +# Generates executables whose About box report them as being a +# development snapshot. SVN_REV is a Subversion revision number. +# +# - VER=-DRELEASE=0.43 +# Generates executables whose About box report them as being a +# release version. +# +# - COMPAT=-DAUTO_WINSOCK (Windows only) +# Causes PuTTY to assume that includes its own WinSock +# header file, so that it won't try to include . +# +# - COMPAT=-DWINSOCK_TWO (Windows only) +# Causes the PuTTY utilities to include instead of +# , except Plink which _needs_ WinSock 2 so it already +# does this. +# +# - COMPAT=-DNO_SECURITY (Windows only) +# Disables Pageant's use of , which is not available +# with some development environments (such as older versions of +# the Cygwin/mingw GNU toolchain). This means that 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). +# +# - COMPAT=-DNO_MULTIMON (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. This means that PuTTY's +# full-screen mode (configurable to work on Alt-Enter) will +# not behave usefully in a multi-monitor environment. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin. +# +# - COMPAT=-DNO_HTMLHELP (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. The resulting binary +# will only look for an old-style WinHelp file (.HLP/.CNT), and +# will ignore any .CHM file. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin (although you can use +# the htmlhelp.h 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. +# +# - RCFL=-DASCIICTLS (Windows only) +# Uses ASCII rather than Unicode to specify the tab control in +# the resource file. Probably most useful when compiling with +# Cygnus/mingw32, whose resource compiler may have less of a +# problem with it. +# +# - XFLAGS=-DTELNET_DEFAULT +# Causes PuTTY to default to the Telnet protocol (in the absence +# of Default Settings and so on to the contrary). Normally PuTTY +# will default to SSH. +# +# - 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. +# + +# You can define this path to point at your tools if you need to +# TOOLPATH = c:\cygwin\bin\ # or similar, if you're running Windows +# TOOLPATH = /pkg/mingw32msvc/i386-mingw32msvc/bin/ +CC = $(TOOLPATH)gcc +RC = $(TOOLPATH)windres +# Uncomment the following two lines to compile under Winelib +# CC = winegcc +# RC = wrc +# You may also need to tell windres where to find include files: +# RCINC = --include-dir c:\cygwin\include\ + +CFLAGS = -mno-cygwin -Wall -O2 -D_WINDOWS -DDEBUG -DWIN32S_COMPAT \ + -D_NO_OLDNAMES -DNO_MULTIMON -DNO_HTMLHELP -I.././ \ + -I../charset/ -I../windows/ -I../unix/ -I../macosx/ +LDFLAGS = -mno-cygwin -s +RCFLAGS = $(RCINC) --define WIN32=1 --define _WIN32=1 --define WINVER=0x0400 + +# XXX GNU-ism, but it's probably all right for a Cygwin/MinGW Makefile. +RCFLAGS += $(patsubst -D%,--define %,$(VER)) +# _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 + +.SUFFIXES: + +all: pageant.exe plink.exe pscp.exe psftp.exe putty.exe puttygen.exe \ + puttytel.exe + +pageant.exe: misc.o pageant.res.o sshaes.o sshbn.o sshdes.o sshdss.o \ + sshmd5.o sshpubk.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + tree234.o version.o winhelp.o winmisc.o winpgnt.o winpgntc.o \ + winutils.o + $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,pageant.map misc.o \ + pageant.res.o sshaes.o sshbn.o sshdes.o sshdss.o sshmd5.o \ + sshpubk.o sshrsa.o sshsh256.o sshsh512.o sshsha.o tree234.o \ + version.o winhelp.o winmisc.o winpgnt.o winpgntc.o \ + winutils.o -ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 \ + -lole32 -lshell32 -luser32 -lwinmm -lwinspool + +plink.exe: be_all_s.o cmdline.o cproxy.o ldisc.o logging.o misc.o pgssapi.o \ + pinger.o plink.res.o portfwd.o proxy.o raw.o rlogin.o \ + settings.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o \ + sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o \ + sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o \ + sshsha.o sshzlib.o telnet.o timing.o tree234.o version.o \ + wildcard.o wincons.o windefs.o wingss.o winhandl.o winmisc.o \ + winnet.o winnoise.o winnojmp.o winpgntc.o winplink.o \ + winproxy.o winser.o winstore.o wintime.o winx11.o x11fwd.o + $(CC) $(LDFLAGS) -o $@ -Wl,-Map,plink.map be_all_s.o cmdline.o \ + cproxy.o ldisc.o logging.o misc.o pgssapi.o pinger.o \ + plink.res.o portfwd.o proxy.o raw.o rlogin.o settings.o \ + ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o telnet.o timing.o tree234.o version.o wildcard.o \ + wincons.o windefs.o wingss.o winhandl.o winmisc.o winnet.o \ + winnoise.o winnojmp.o winpgntc.o winplink.o winproxy.o \ + winser.o winstore.o wintime.o winx11.o x11fwd.o -ladvapi32 \ + -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lole32 -lshell32 \ + -luser32 -lwinmm -lwinspool + +pscp.exe: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o pscp.o pscp.res.o settings.o \ + sftp.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o timing.o tree234.o version.o wildcard.o wincons.o \ + windefs.o wingss.o winhandl.o winmisc.o winnet.o winnoise.o \ + winnojmp.o winpgntc.o winproxy.o winsftp.o winstore.o \ + wintime.o x11fwd.o + $(CC) $(LDFLAGS) -o $@ -Wl,-Map,pscp.map be_none.o cmdline.o \ + cproxy.o int64.o logging.o misc.o pgssapi.o pinger.o \ + portfwd.o proxy.o pscp.o pscp.res.o settings.o sftp.o ssh.o \ + sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \ + sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshzlib.o \ + timing.o tree234.o version.o wildcard.o wincons.o windefs.o \ + wingss.o winhandl.o winmisc.o winnet.o winnoise.o winnojmp.o \ + winpgntc.o winproxy.o winsftp.o winstore.o wintime.o \ + x11fwd.o -ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 \ + -lole32 -lshell32 -luser32 -lwinmm -lwinspool + +psftp.exe: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pgssapi.o \ + pinger.o portfwd.o proxy.o psftp.o psftp.res.o settings.o \ + sftp.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o timing.o tree234.o version.o wildcard.o wincons.o \ + windefs.o wingss.o winhandl.o winmisc.o winnet.o winnoise.o \ + winnojmp.o winpgntc.o winproxy.o winsftp.o winstore.o \ + wintime.o x11fwd.o + $(CC) $(LDFLAGS) -o $@ -Wl,-Map,psftp.map be_none.o cmdline.o \ + cproxy.o int64.o logging.o misc.o pgssapi.o pinger.o \ + portfwd.o proxy.o psftp.o psftp.res.o settings.o sftp.o \ + ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o sshmd5.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshzlib.o timing.o tree234.o version.o wildcard.o wincons.o \ + windefs.o wingss.o winhandl.o winmisc.o winnet.o winnoise.o \ + winnojmp.o winpgntc.o winproxy.o winsftp.o winstore.o \ + wintime.o x11fwd.o -ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 \ + -limm32 -lole32 -lshell32 -luser32 -lwinmm -lwinspool + +putty.exe: be_all_s.o cmdline.o config.o cproxy.o dialog.o ldisc.o \ + ldiscucs.o logging.o minibidi.o misc.o pgssapi.o pinger.o \ + portfwd.o proxy.o putty.res.o raw.o rlogin.o sercfg.o \ + settings.o sizetip.o ssh.o sshaes.o ssharcf.o sshblowf.o \ + sshbn.o sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o \ + sshgssc.o sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh256.o \ + sshsh512.o sshsha.o sshzlib.o telnet.o terminal.o timing.o \ + tree234.o version.o wcwidth.o wildcard.o wincfg.o winctrls.o \ + windefs.o windlg.o window.o wingss.o winhandl.o winhelp.o \ + winjump.o winmisc.o winnet.o winnoise.o winpgntc.o \ + winprint.o winproxy.o winser.o winstore.o wintime.o winucs.o \ + winutils.o winx11.o x11fwd.o + $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,putty.map be_all_s.o \ + cmdline.o config.o cproxy.o dialog.o ldisc.o ldiscucs.o \ + logging.o minibidi.o misc.o pgssapi.o pinger.o portfwd.o \ + proxy.o putty.res.o raw.o rlogin.o sercfg.o settings.o \ + sizetip.o ssh.o sshaes.o ssharcf.o sshblowf.o sshbn.o \ + sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o sshgssc.o \ + sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o \ + sshsha.o sshzlib.o telnet.o terminal.o timing.o tree234.o \ + version.o wcwidth.o wildcard.o wincfg.o winctrls.o windefs.o \ + windlg.o window.o wingss.o winhandl.o winhelp.o winjump.o \ + winmisc.o winnet.o winnoise.o winpgntc.o winprint.o \ + winproxy.o winser.o winstore.o wintime.o winucs.o winutils.o \ + winx11.o x11fwd.o -ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 \ + -limm32 -lole32 -lshell32 -luser32 -lwinmm -lwinspool + +puttygen.exe: import.o misc.o notiming.o puttygen.res.o sshaes.o sshbn.o \ + sshdes.o sshdss.o sshdssg.o sshmd5.o sshprime.o sshpubk.o \ + sshrand.o sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o \ + tree234.o version.o winctrls.o winhelp.o winmisc.o \ + winnoise.o winnojmp.o winpgen.o winstore.o wintime.o \ + winutils.o + $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,puttygen.map import.o \ + misc.o notiming.o puttygen.res.o sshaes.o sshbn.o sshdes.o \ + sshdss.o sshdssg.o sshmd5.o sshprime.o sshpubk.o sshrand.o \ + sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o tree234.o \ + version.o winctrls.o winhelp.o winmisc.o winnoise.o \ + winnojmp.o winpgen.o winstore.o wintime.o winutils.o \ + -ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lole32 \ + -lshell32 -luser32 -lwinmm -lwinspool + +puttytel.exe: be_nos_s.o cmdline.o config.o dialog.o ldisc.o ldiscucs.o \ + logging.o minibidi.o misc.o nocproxy.o nogss.o pinger.o \ + proxy.o puttytel.res.o raw.o rlogin.o sercfg.o settings.o \ + sizetip.o telnet.o terminal.o timing.o tree234.o version.o \ + wcwidth.o wincfg.o winctrls.o windefs.o windlg.o window.o \ + winhandl.o winhelp.o winjump.o winmisc.o winnet.o winprint.o \ + winproxy.o winser.o winstore.o wintime.o winucs.o winutils.o + $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,puttytel.map be_nos_s.o \ + cmdline.o config.o dialog.o ldisc.o ldiscucs.o logging.o \ + minibidi.o misc.o nocproxy.o nogss.o pinger.o proxy.o \ + puttytel.res.o raw.o rlogin.o sercfg.o settings.o sizetip.o \ + telnet.o terminal.o timing.o tree234.o version.o wcwidth.o \ + wincfg.o winctrls.o windefs.o windlg.o window.o winhandl.o \ + winhelp.o winjump.o winmisc.o winnet.o winprint.o winproxy.o \ + winser.o winstore.o wintime.o winucs.o winutils.o -ladvapi32 \ + -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lole32 -lshell32 \ + -luser32 -lwinmm -lwinspool + +be_all_s.o: ../be_all_s.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_all_s.c + +be_none.o: ../be_none.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_none.c + +be_nos_s.o: ../be_nos_s.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_nos_s.c + +cmdgen.o: ../cmdgen.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cmdgen.c + +cmdline.o: ../cmdline.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cmdline.c + +config.o: ../config.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../config.c + +cproxy.o: ../cproxy.c ../putty.h ../ssh.h ../network.h ../proxy.h \ + ../puttyps.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cproxy.c + +dialog.o: ../dialog.c ../putty.h ../dialog.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../dialog.c + +fromucs.o: ../charset/fromucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/fromucs.c + +gtkcfg.o: ../unix/gtkcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkcfg.c + +gtkcols.o: ../unix/gtkcols.c ../unix/gtkcols.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkcols.c + +gtkdlg.o: ../unix/gtkdlg.c ../unix/gtkcols.h ../unix/gtkfont.h ../putty.h \ + ../storage.h ../dialog.h ../tree234.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkdlg.c + +gtkfont.o: ../unix/gtkfont.c ../putty.h ../unix/gtkfont.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkfont.c + +gtkwin.o: ../unix/gtkwin.c ../putty.h ../terminal.h ../unix/gtkfont.h \ + ../puttyps.h ../network.h ../misc.h ../tree234.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkwin.c + +import.o: ../import.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../import.c + +int64.o: ../int64.c ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../int64.c + +ldisc.o: ../ldisc.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \ + ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ldisc.c + +ldiscucs.o: ../ldiscucs.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \ + ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ldiscucs.c + +localenc.o: ../charset/localenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/localenc.c + +logging.o: ../logging.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../logging.c + +macenc.o: ../charset/macenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/macenc.c + +mimeenc.o: ../charset/mimeenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/mimeenc.c + +minibidi.o: ../minibidi.c ../misc.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../minibidi.c + +misc.o: ../misc.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../misc.c + +nocproxy.o: ../nocproxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nocproxy.c + +nogss.o: ../nogss.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nogss.c + +notiming.o: ../notiming.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../notiming.c + +osxctrls.o: ../macosx/osxctrls.m ../putty.h ../dialog.h ../macosx/osxclass.h \ + ../tree234.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxctrls.m + +osxdlg.o: ../macosx/osxdlg.m ../putty.h ../storage.h ../dialog.h \ + ../macosx/osxclass.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxdlg.m + +osxmain.o: ../macosx/osxmain.m ../putty.h ../macosx/osxclass.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxmain.m + +osxsel.o: ../macosx/osxsel.m ../putty.h ../macosx/osxclass.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxsel.m + +osxwin.o: ../macosx/osxwin.m ../putty.h ../terminal.h ../macosx/osxclass.h \ + ../puttyps.h ../network.h ../misc.h ../tree234.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../macosx/osxwin.m + +pageant.res.o: FORCE + $(RC) $(RCFL) $(RCFLAGS) ../windows/pageant.rc pageant.res.o + +pgssapi.o: ../pgssapi.c ../putty.h ../pgssapi.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pgssapi.c + +pinger.o: ../pinger.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pinger.c + +plink.res.o: FORCE + $(RC) $(RCFL) $(RCFLAGS) ../windows/plink.rc plink.res.o + +portfwd.o: ../portfwd.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../portfwd.c + +proxy.o: ../proxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../proxy.c + +pscp.o: ../pscp.c ../putty.h ../psftp.h ../ssh.h ../sftp.h ../storage.h \ + ../int64.h ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pscp.c + +pscp.res.o: FORCE + $(RC) $(RCFL) $(RCFLAGS) ../windows/pscp.rc pscp.res.o + +psftp.o: ../psftp.c ../putty.h ../psftp.h ../storage.h ../ssh.h ../sftp.h \ + ../int64.h ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../psftp.c + +psftp.res.o: FORCE + $(RC) $(RCFL) $(RCFLAGS) ../windows/psftp.rc psftp.res.o + +putty.res.o: FORCE + $(RC) $(RCFL) $(RCFLAGS) ../windows/putty.rc putty.res.o + +puttygen.res.o: FORCE + $(RC) $(RCFL) $(RCFLAGS) ../windows/puttygen.rc puttygen.res.o + +puttytel.res.o: FORCE + $(RC) $(RCFL) $(RCFLAGS) ../windows/puttytel.rc puttytel.res.o + +raw.o: ../raw.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../raw.c + +rlogin.o: ../rlogin.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../rlogin.c + +sbcs.o: ../charset/sbcs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/sbcs.c + +sbcsdat.o: ../charset/sbcsdat.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/sbcsdat.c + +sercfg.o: ../sercfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sercfg.c + +settings.o: ../settings.c ../putty.h ../storage.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../settings.c + +sftp.o: ../sftp.c ../misc.h ../int64.h ../tree234.h ../sftp.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sftp.c + +sizetip.o: ../windows/sizetip.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/sizetip.c + +slookup.o: ../charset/slookup.c ../charset/charset.h ../charset/internal.h \ + ../charset/enum.c ../charset/sbcsdat.c ../charset/utf8.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/slookup.c + +ssh.o: ../ssh.c ../putty.h ../tree234.h ../ssh.h ../sshgssc.h ../sshgss.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h ../int64.h \ + ../pgssapi.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh.c + +sshaes.o: ../sshaes.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshaes.c + +ssharcf.o: ../ssharcf.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssharcf.c + +sshblowf.o: ../sshblowf.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshblowf.c + +sshbn.o: ../sshbn.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshbn.c + +sshcrc.o: ../sshcrc.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshcrc.c + +sshcrcda.o: ../sshcrcda.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshcrcda.c + +sshdes.o: ../sshdes.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdes.c + +sshdh.o: ../sshdh.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdh.c + +sshdss.o: ../sshdss.c ../ssh.h ../misc.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdss.c + +sshdssg.o: ../sshdssg.c ../misc.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdssg.c + +sshgssc.o: ../sshgssc.c ../putty.h ../sshgssc.h ../misc.h ../puttyps.h \ + ../network.h ../pgssapi.h ../sshgss.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../tree234.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshgssc.c + +sshmd5.o: ../sshmd5.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshmd5.c + +sshprime.o: ../sshprime.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshprime.c + +sshpubk.o: ../sshpubk.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshpubk.c + +sshrand.o: ../sshrand.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrand.c + +sshrsa.o: ../sshrsa.c ../ssh.h ../misc.h ../puttymem.h ../tree234.h \ + ../network.h ../int64.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrsa.c + +sshrsag.o: ../sshrsag.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrsag.c + +sshsh256.o: ../sshsh256.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsh256.c + +sshsh512.o: ../sshsh512.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsh512.c + +sshsha.o: ../sshsha.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsha.c + +sshzlib.o: ../sshzlib.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../int64.h ../misc.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshzlib.c + +telnet.o: ../telnet.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../telnet.c + +terminal.o: ../terminal.c ../putty.h ../terminal.h ../puttyps.h ../network.h \ + ../misc.h ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../terminal.c + +testback.o: ../testback.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../testback.c + +time.o: ../time.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../time.c + +timing.o: ../timing.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../timing.c + +toucs.o: ../charset/toucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/toucs.c + +tree234.o: ../tree234.c ../puttymem.h ../tree234.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../tree234.c + +utf8.o: ../charset/utf8.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/utf8.c + +ux_x11.o: ../unix/ux_x11.c ../putty.h ../ssh.h ../network.h ../puttyps.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/ux_x11.c + +uxagentc.o: ../unix/uxagentc.c ../putty.h ../misc.h ../tree234.h \ + ../puttymem.h ../puttyps.h ../network.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxagentc.c + +uxcfg.o: ../unix/uxcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxcfg.c + +uxcons.o: ../unix/uxcons.c ../putty.h ../storage.h ../ssh.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxcons.c + +uxgen.o: ../unix/uxgen.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxgen.c + +uxgss.o: ../unix/uxgss.c ../putty.h ../pgssapi.h ../sshgss.h ../sshgssc.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxgss.c + +uxmisc.o: ../unix/uxmisc.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxmisc.c + +uxnet.o: ../unix/uxnet.c ../putty.h ../network.h ../tree234.h ../puttyps.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxnet.c + +uxnoise.o: ../unix/uxnoise.c ../putty.h ../ssh.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxnoise.c + +uxplink.o: ../unix/uxplink.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxplink.c + +uxprint.o: ../unix/uxprint.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxprint.c + +uxproxy.o: ../unix/uxproxy.c ../tree234.h ../putty.h ../network.h ../proxy.h \ + ../puttyps.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxproxy.c + +uxpterm.o: ../unix/uxpterm.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpterm.c + +uxpty.o: ../unix/uxpty.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpty.c + +uxputty.o: ../unix/uxputty.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxputty.c + +uxsel.o: ../unix/uxsel.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsel.c + +uxser.o: ../unix/uxser.c ../putty.h ../tree234.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxser.c + +uxsftp.o: ../unix/uxsftp.c ../putty.h ../ssh.h ../psftp.h ../int64.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsftp.c + +uxsignal.o: ../unix/uxsignal.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsignal.c + +uxstore.o: ../unix/uxstore.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxstore.c + +uxucs.o: ../unix/uxucs.c ../putty.h ../charset/charset.h ../terminal.h \ + ../misc.h ../puttyps.h ../network.h ../tree234.h \ + ../puttymem.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxucs.c + +wcwidth.o: ../wcwidth.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../wcwidth.c + +wildcard.o: ../wildcard.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../wildcard.c + +wincfg.o: ../windows/wincfg.c ../putty.h ../dialog.h ../storage.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincfg.c + +wincons.o: ../windows/wincons.c ../putty.h ../storage.h ../ssh.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincons.c + +winctrls.o: ../windows/winctrls.c ../putty.h ../misc.h ../dialog.h \ + ../puttyps.h ../network.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../tree234.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winctrls.c + +windefs.o: ../windows/windefs.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/windefs.c + +windlg.o: ../windows/windlg.c ../putty.h ../ssh.h ../windows/win_res.h \ + ../storage.h ../dialog.h ../puttyps.h ../network.h ../misc.h \ + ../puttymem.h ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/windlg.c + +window.o: ../windows/window.c ../putty.h ../terminal.h ../storage.h \ + ../windows/win_res.h ../puttyps.h ../network.h ../misc.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/window.c + +wingss.o: ../windows/wingss.c ../putty.h ../pgssapi.h ../sshgss.h \ + ../sshgssc.h ../misc.h ../puttyps.h ../network.h \ + ../puttymem.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wingss.c + +winhandl.o: ../windows/winhandl.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winhandl.c + +winhelp.o: ../windows/winhelp.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winhelp.c + +winjump.o: ../windows/winjump.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winjump.c + +winmisc.o: ../windows/winmisc.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winmisc.c + +winnet.o: ../windows/winnet.c ../putty.h ../network.h ../tree234.h \ + ../puttyps.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnet.c + +winnoise.o: ../windows/winnoise.c ../putty.h ../ssh.h ../storage.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../int64.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnoise.c + +winnojmp.o: ../windows/winnojmp.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnojmp.c + +winpgen.o: ../windows/winpgen.c ../putty.h ../ssh.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgen.c + +winpgnt.o: ../windows/winpgnt.c ../putty.h ../ssh.h ../misc.h ../tree234.h \ + ../puttyps.h ../network.h ../puttymem.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgnt.c + +winpgntc.o: ../windows/winpgntc.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgntc.c + +winplink.o: ../windows/winplink.c ../putty.h ../storage.h ../tree234.h \ + ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winplink.c + +winprint.o: ../windows/winprint.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winprint.c + +winproxy.o: ../windows/winproxy.c ../tree234.h ../putty.h ../network.h \ + ../proxy.h ../puttyps.h ../misc.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winproxy.c + +winser.o: ../windows/winser.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../puttymem.h ../tree234.h ../windows/winhelp.h \ + ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winser.c + +winsftp.o: ../windows/winsftp.c ../putty.h ../psftp.h ../ssh.h ../int64.h \ + ../puttyps.h ../network.h ../misc.h ../puttymem.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winsftp.c + +winstore.o: ../windows/winstore.c ../putty.h ../storage.h ../puttyps.h \ + ../network.h ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winstore.c + +wintime.o: ../windows/wintime.c ../putty.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h ../macosx/osx.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wintime.c + +winucs.o: ../windows/winucs.c ../putty.h ../terminal.h ../misc.h \ + ../puttyps.h ../network.h ../tree234.h ../puttymem.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winucs.c + +winutils.o: ../windows/winutils.c ../putty.h ../misc.h ../puttyps.h \ + ../network.h ../puttymem.h ../windows/winstuff.h \ + ../macosx/osx.h ../unix/unix.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winutils.c + +winx11.o: ../windows/winx11.c ../putty.h ../ssh.h ../puttyps.h ../network.h \ + ../misc.h ../puttymem.h ../tree234.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winx11.c + +x11fwd.o: ../x11fwd.c ../putty.h ../ssh.h ../tree234.h ../puttyps.h \ + ../network.h ../misc.h ../puttymem.h ../int64.h \ + ../windows/winstuff.h ../macosx/osx.h ../unix/unix.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../x11fwd.c + +xenc.o: ../charset/xenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/xenc.c + +xkeysym.o: ../unix/xkeysym.c ../misc.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xkeysym.c + +xpmptcfg.o: ../unix/xpmptcfg.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmptcfg.c + +xpmpterm.o: ../unix/xpmpterm.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmpterm.c + +xpmpucfg.o: ../unix/xpmpucfg.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmpucfg.c + +xpmputty.o: ../unix/xpmputty.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmputty.c + + +version.o: FORCE + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) $(VER) -c ../version.c + +clean: + rm -f *.o *.exe *.res.o *.map + +FORCE: diff --git a/putty/WINDOWS/MAKEFILE.LCC b/putty/WINDOWS/MAKEFILE.LCC new file mode 100644 index 0000000..74b5a2b --- /dev/null +++ b/putty/WINDOWS/MAKEFILE.LCC @@ -0,0 +1,873 @@ +# Makefile for putty under lcc. +# +# This file was created by `mkfiles.pl' from the `Recipe' file. +# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead. +# +# Extra options you can set: +# +# - VER="-DSNAPSHOT=1999-01-25 -DSVN_REV=1234" +# Generates executables whose About box report them as being a +# development snapshot. SVN_REV is a Subversion revision number. +# +# - VER=-DRELEASE=0.43 +# Generates executables whose About box report them as being a +# release version. +# +# - COMPAT=-DAUTO_WINSOCK (Windows only) +# Causes PuTTY to assume that includes its own WinSock +# header file, so that it won't try to include . +# +# - COMPAT=-DWINSOCK_TWO (Windows only) +# Causes the PuTTY utilities to include instead of +# , except Plink which _needs_ WinSock 2 so it already +# does this. +# +# - COMPAT=-DNO_SECURITY (Windows only) +# Disables Pageant's use of , which is not available +# with some development environments (such as older versions of +# the Cygwin/mingw GNU toolchain). This means that 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). +# +# - COMPAT=-DNO_MULTIMON (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. This means that PuTTY's +# full-screen mode (configurable to work on Alt-Enter) will +# not behave usefully in a multi-monitor environment. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin. +# +# - COMPAT=-DNO_HTMLHELP (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. The resulting binary +# will only look for an old-style WinHelp file (.HLP/.CNT), and +# will ignore any .CHM file. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin (although you can use +# the htmlhelp.h 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. +# +# - RCFL=-DASCIICTLS (Windows only) +# Uses ASCII rather than Unicode to specify the tab control in +# the resource file. Probably most useful when compiling with +# Cygnus/mingw32, whose resource compiler may have less of a +# problem with it. +# +# - XFLAGS=-DTELNET_DEFAULT +# Causes PuTTY to default to the Telnet protocol (in the absence +# of Default Settings and so on to the contrary). Normally PuTTY +# will default to SSH. +# +# - 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. +# + +# If you rename this file to `Makefile', you should change this line, +# so that the .rsp files still depend on the correct makefile. +MAKEFILE = Makefile.lcc + +# C compilation flags +CFLAGS = -D_WINDOWS -I..\./ -I..\charset/ -I..\windows/ -I..\unix/ -I..\macosx/ +# Resource compilation flags +RCFLAGS = + +# Get include directory for resource compiler + +RCFLAGS += $(VER) + +all: pageant.exe plink.exe pscp.exe psftp.exe putty.exe puttygen.exe \ + puttytel.exe + +pageant.exe: misc.obj pageant.res sshaes.obj sshbn.obj sshdes.obj sshdss.obj \ + sshmd5.obj sshpubk.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj tree234.obj version.obj winhelp.obj winmisc.obj \ + winpgnt.obj winpgntc.obj winutils.obj + lcclnk -subsystem windows -o pageant.exe misc.obj pageant.res sshaes.obj \ + sshbn.obj sshdes.obj sshdss.obj sshmd5.obj sshpubk.obj \ + sshrsa.obj sshsh256.obj sshsh512.obj sshsha.obj tree234.obj \ + version.obj winhelp.obj winmisc.obj winpgnt.obj winpgntc.obj \ + winutils.obj shell32.lib wsock32.lib ws2_32.lib winspool.lib \ + winmm.lib imm32.lib + +plink.exe: be_all_s.obj cmdline.obj cproxy.obj ldisc.obj logging.obj \ + misc.obj pgssapi.obj pinger.obj plink.res portfwd.obj \ + proxy.obj raw.obj rlogin.obj settings.obj ssh.obj sshaes.obj \ + ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj \ + sshdes.obj sshdh.obj sshdss.obj sshgssc.obj sshmd5.obj \ + sshpubk.obj sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj sshzlib.obj telnet.obj timing.obj tree234.obj \ + version.obj wildcard.obj wincons.obj windefs.obj wingss.obj \ + winhandl.obj winmisc.obj winnet.obj winnoise.obj \ + winnojmp.obj winpgntc.obj winplink.obj winproxy.obj \ + winser.obj winstore.obj wintime.obj winx11.obj x11fwd.obj + lcclnk -o plink.exe be_all_s.obj cmdline.obj cproxy.obj ldisc.obj \ + logging.obj misc.obj pgssapi.obj pinger.obj plink.res \ + portfwd.obj proxy.obj raw.obj rlogin.obj settings.obj \ + ssh.obj sshaes.obj ssharcf.obj sshblowf.obj sshbn.obj \ + sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj \ + sshgssc.obj sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj \ + sshsh256.obj sshsh512.obj sshsha.obj sshzlib.obj telnet.obj \ + timing.obj tree234.obj version.obj wildcard.obj wincons.obj \ + windefs.obj wingss.obj winhandl.obj winmisc.obj winnet.obj \ + winnoise.obj winnojmp.obj winpgntc.obj winplink.obj \ + winproxy.obj winser.obj winstore.obj wintime.obj winx11.obj \ + x11fwd.obj shell32.lib wsock32.lib ws2_32.lib winspool.lib \ + winmm.lib imm32.lib + +pscp.exe: be_none.obj cmdline.obj cproxy.obj int64.obj logging.obj misc.obj \ + pgssapi.obj pinger.obj portfwd.obj proxy.obj pscp.obj \ + pscp.res settings.obj sftp.obj ssh.obj sshaes.obj \ + ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj \ + sshdes.obj sshdh.obj sshdss.obj sshgssc.obj sshmd5.obj \ + sshpubk.obj sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj sshzlib.obj timing.obj tree234.obj version.obj \ + wildcard.obj wincons.obj windefs.obj wingss.obj winhandl.obj \ + winmisc.obj winnet.obj winnoise.obj winnojmp.obj \ + winpgntc.obj winproxy.obj winsftp.obj winstore.obj \ + wintime.obj x11fwd.obj + lcclnk -o pscp.exe be_none.obj cmdline.obj cproxy.obj int64.obj \ + logging.obj misc.obj pgssapi.obj pinger.obj portfwd.obj \ + proxy.obj pscp.obj pscp.res settings.obj sftp.obj ssh.obj \ + sshaes.obj ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj \ + sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj sshgssc.obj \ + sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj sshsh256.obj \ + sshsh512.obj sshsha.obj sshzlib.obj timing.obj tree234.obj \ + version.obj wildcard.obj wincons.obj windefs.obj wingss.obj \ + winhandl.obj winmisc.obj winnet.obj winnoise.obj \ + winnojmp.obj winpgntc.obj winproxy.obj winsftp.obj \ + winstore.obj wintime.obj x11fwd.obj shell32.lib wsock32.lib \ + ws2_32.lib winspool.lib winmm.lib imm32.lib + +psftp.exe: be_none.obj cmdline.obj cproxy.obj int64.obj logging.obj misc.obj \ + pgssapi.obj pinger.obj portfwd.obj proxy.obj psftp.obj \ + psftp.res settings.obj sftp.obj ssh.obj sshaes.obj \ + ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj \ + sshdes.obj sshdh.obj sshdss.obj sshgssc.obj sshmd5.obj \ + sshpubk.obj sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj sshzlib.obj timing.obj tree234.obj version.obj \ + wildcard.obj wincons.obj windefs.obj wingss.obj winhandl.obj \ + winmisc.obj winnet.obj winnoise.obj winnojmp.obj \ + winpgntc.obj winproxy.obj winsftp.obj winstore.obj \ + wintime.obj x11fwd.obj + lcclnk -o psftp.exe be_none.obj cmdline.obj cproxy.obj int64.obj \ + logging.obj misc.obj pgssapi.obj pinger.obj portfwd.obj \ + proxy.obj psftp.obj psftp.res settings.obj sftp.obj ssh.obj \ + sshaes.obj ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj \ + sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj sshgssc.obj \ + sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj sshsh256.obj \ + sshsh512.obj sshsha.obj sshzlib.obj timing.obj tree234.obj \ + version.obj wildcard.obj wincons.obj windefs.obj wingss.obj \ + winhandl.obj winmisc.obj winnet.obj winnoise.obj \ + winnojmp.obj winpgntc.obj winproxy.obj winsftp.obj \ + winstore.obj wintime.obj x11fwd.obj shell32.lib wsock32.lib \ + ws2_32.lib winspool.lib winmm.lib imm32.lib + +putty.exe: be_all_s.obj cmdline.obj config.obj cproxy.obj dialog.obj \ + ldisc.obj ldiscucs.obj logging.obj minibidi.obj misc.obj \ + pgssapi.obj pinger.obj portfwd.obj proxy.obj putty.res \ + raw.obj rlogin.obj sercfg.obj settings.obj sizetip.obj \ + ssh.obj sshaes.obj ssharcf.obj sshblowf.obj sshbn.obj \ + sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj \ + sshgssc.obj sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj \ + sshsh256.obj sshsh512.obj sshsha.obj sshzlib.obj telnet.obj \ + terminal.obj timing.obj tree234.obj version.obj wcwidth.obj \ + wildcard.obj wincfg.obj winctrls.obj windefs.obj windlg.obj \ + window.obj wingss.obj winhandl.obj winhelp.obj winjump.obj \ + winmisc.obj winnet.obj winnoise.obj winpgntc.obj \ + winprint.obj winproxy.obj winser.obj winstore.obj \ + wintime.obj winucs.obj winutils.obj winx11.obj x11fwd.obj + lcclnk -subsystem windows -o putty.exe be_all_s.obj cmdline.obj config.obj \ + cproxy.obj dialog.obj ldisc.obj ldiscucs.obj logging.obj \ + minibidi.obj misc.obj pgssapi.obj pinger.obj portfwd.obj \ + proxy.obj putty.res raw.obj rlogin.obj sercfg.obj \ + settings.obj sizetip.obj ssh.obj sshaes.obj ssharcf.obj \ + sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj sshdes.obj \ + sshdh.obj sshdss.obj sshgssc.obj sshmd5.obj sshpubk.obj \ + sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj sshsha.obj \ + sshzlib.obj telnet.obj terminal.obj timing.obj tree234.obj \ + version.obj wcwidth.obj wildcard.obj wincfg.obj winctrls.obj \ + windefs.obj windlg.obj window.obj wingss.obj winhandl.obj \ + winhelp.obj winjump.obj winmisc.obj winnet.obj winnoise.obj \ + winpgntc.obj winprint.obj winproxy.obj winser.obj \ + winstore.obj wintime.obj winucs.obj winutils.obj winx11.obj \ + x11fwd.obj shell32.lib wsock32.lib ws2_32.lib winspool.lib \ + winmm.lib imm32.lib + +puttygen.exe: import.obj misc.obj notiming.obj puttygen.res sshaes.obj \ + sshbn.obj sshdes.obj sshdss.obj sshdssg.obj sshmd5.obj \ + sshprime.obj sshpubk.obj sshrand.obj sshrsa.obj sshrsag.obj \ + sshsh256.obj sshsh512.obj sshsha.obj tree234.obj version.obj \ + winctrls.obj winhelp.obj winmisc.obj winnoise.obj \ + winnojmp.obj winpgen.obj winstore.obj wintime.obj \ + winutils.obj + lcclnk -subsystem windows -o puttygen.exe import.obj misc.obj notiming.obj \ + puttygen.res sshaes.obj sshbn.obj sshdes.obj sshdss.obj \ + sshdssg.obj sshmd5.obj sshprime.obj sshpubk.obj sshrand.obj \ + sshrsa.obj sshrsag.obj sshsh256.obj sshsh512.obj sshsha.obj \ + tree234.obj version.obj winctrls.obj winhelp.obj winmisc.obj \ + winnoise.obj winnojmp.obj winpgen.obj winstore.obj \ + wintime.obj winutils.obj shell32.lib wsock32.lib ws2_32.lib \ + winspool.lib winmm.lib imm32.lib + +puttytel.exe: be_nos_s.obj cmdline.obj config.obj dialog.obj ldisc.obj \ + ldiscucs.obj logging.obj minibidi.obj misc.obj nocproxy.obj \ + nogss.obj pinger.obj proxy.obj puttytel.res raw.obj \ + rlogin.obj sercfg.obj settings.obj sizetip.obj telnet.obj \ + terminal.obj timing.obj tree234.obj version.obj wcwidth.obj \ + wincfg.obj winctrls.obj windefs.obj windlg.obj window.obj \ + winhandl.obj winhelp.obj winjump.obj winmisc.obj winnet.obj \ + winprint.obj winproxy.obj winser.obj winstore.obj \ + wintime.obj winucs.obj winutils.obj + lcclnk -subsystem windows -o puttytel.exe be_nos_s.obj cmdline.obj \ + config.obj dialog.obj ldisc.obj ldiscucs.obj logging.obj \ + minibidi.obj misc.obj nocproxy.obj nogss.obj pinger.obj \ + proxy.obj puttytel.res raw.obj rlogin.obj sercfg.obj \ + settings.obj sizetip.obj telnet.obj terminal.obj timing.obj \ + tree234.obj version.obj wcwidth.obj wincfg.obj winctrls.obj \ + windefs.obj windlg.obj window.obj winhandl.obj winhelp.obj \ + winjump.obj winmisc.obj winnet.obj winprint.obj winproxy.obj \ + winser.obj winstore.obj wintime.obj winucs.obj winutils.obj \ + shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib \ + imm32.lib + +be_all_s.obj: ..\be_all_s.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\be_all_s.c +be_none.obj: ..\be_none.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\be_none.c +be_nos_s.obj: ..\be_nos_s.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\be_nos_s.c +cmdgen.obj: ..\cmdgen.c ..\putty.h ..\ssh.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\cmdgen.c +cmdline.obj: ..\cmdline.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\cmdline.c +config.obj: ..\config.c ..\putty.h ..\dialog.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\config.c +cproxy.obj: ..\cproxy.c ..\putty.h ..\ssh.h ..\network.h ..\proxy.h \ + ..\puttyps.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\cproxy.c +dialog.obj: ..\dialog.c ..\putty.h ..\dialog.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\dialog.c +fromucs.obj: ..\charset\fromucs.c ..\charset\charset.h ..\charset\internal.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\fromucs.c +gtkcfg.obj: ..\unix\gtkcfg.c ..\putty.h ..\dialog.h ..\storage.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkcfg.c +gtkcols.obj: ..\unix\gtkcols.c ..\unix\gtkcols.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkcols.c +gtkdlg.obj: ..\unix\gtkdlg.c ..\unix\gtkcols.h ..\unix\gtkfont.h ..\putty.h \ + ..\storage.h ..\dialog.h ..\tree234.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkdlg.c +gtkfont.obj: ..\unix\gtkfont.c ..\putty.h ..\unix\gtkfont.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkfont.c +gtkwin.obj: ..\unix\gtkwin.c ..\putty.h ..\terminal.h ..\unix\gtkfont.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\tree234.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkwin.c +import.obj: ..\import.c ..\putty.h ..\ssh.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\import.c +int64.obj: ..\int64.c ..\int64.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\int64.c +ldisc.obj: ..\ldisc.c ..\putty.h ..\terminal.h ..\ldisc.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\tree234.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ldisc.c +ldiscucs.obj: ..\ldiscucs.c ..\putty.h ..\terminal.h ..\ldisc.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\tree234.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ldiscucs.c +localenc.obj: ..\charset\localenc.c ..\charset\charset.h \ + ..\charset\internal.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\localenc.c +logging.obj: ..\logging.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\logging.c +macenc.obj: ..\charset\macenc.c ..\charset\charset.h ..\charset\internal.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\macenc.c +mimeenc.obj: ..\charset\mimeenc.c ..\charset\charset.h ..\charset\internal.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\mimeenc.c +minibidi.obj: ..\minibidi.c ..\misc.h ..\puttymem.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\minibidi.c +misc.obj: ..\misc.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\misc.c +nocproxy.obj: ..\nocproxy.c ..\putty.h ..\network.h ..\proxy.h ..\puttyps.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\nocproxy.c +nogss.obj: ..\nogss.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\nogss.c +notiming.obj: ..\notiming.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\notiming.c +osxctrls.obj: ..\macosx\osxctrls.m ..\putty.h ..\dialog.h \ + ..\macosx\osxclass.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\macosx\osxctrls.m +osxdlg.obj: ..\macosx\osxdlg.m ..\putty.h ..\storage.h ..\dialog.h \ + ..\macosx\osxclass.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\macosx\osxdlg.m +osxmain.obj: ..\macosx\osxmain.m ..\putty.h ..\macosx\osxclass.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\macosx\osxmain.m +osxsel.obj: ..\macosx\osxsel.m ..\putty.h ..\macosx\osxclass.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\macosx\osxsel.m +osxwin.obj: ..\macosx\osxwin.m ..\putty.h ..\terminal.h ..\macosx\osxclass.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\tree234.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\macosx\osxwin.m +pageant.res: FORCE + lrc $(RCFL) -r $(RCFLAGS) ..\windows\pageant.rc +pgssapi.obj: ..\pgssapi.c ..\putty.h ..\pgssapi.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\pgssapi.c +pinger.obj: ..\pinger.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\pinger.c +plink.res: FORCE + lrc $(RCFL) -r $(RCFLAGS) ..\windows\plink.rc +portfwd.obj: ..\portfwd.c ..\putty.h ..\ssh.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\portfwd.c +proxy.obj: ..\proxy.c ..\putty.h ..\network.h ..\proxy.h ..\puttyps.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\proxy.c +pscp.obj: ..\pscp.c ..\putty.h ..\psftp.h ..\ssh.h ..\sftp.h ..\storage.h \ + ..\int64.h ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\pscp.c +pscp.res: FORCE + lrc $(RCFL) -r $(RCFLAGS) ..\windows\pscp.rc +psftp.obj: ..\psftp.c ..\putty.h ..\psftp.h ..\storage.h ..\ssh.h ..\sftp.h \ + ..\int64.h ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\psftp.c +psftp.res: FORCE + lrc $(RCFL) -r $(RCFLAGS) ..\windows\psftp.rc +putty.res: FORCE + lrc $(RCFL) -r $(RCFLAGS) ..\windows\putty.rc +puttygen.res: FORCE + lrc $(RCFL) -r $(RCFLAGS) ..\windows\puttygen.rc +puttytel.res: FORCE + lrc $(RCFL) -r $(RCFLAGS) ..\windows\puttytel.rc +raw.obj: ..\raw.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\raw.c +rlogin.obj: ..\rlogin.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\rlogin.c +sbcs.obj: ..\charset\sbcs.c ..\charset\charset.h ..\charset\internal.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\sbcs.c +sbcsdat.obj: ..\charset\sbcsdat.c ..\charset\charset.h ..\charset\internal.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\sbcsdat.c +sercfg.obj: ..\sercfg.c ..\putty.h ..\dialog.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sercfg.c +settings.obj: ..\settings.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\settings.c +sftp.obj: ..\sftp.c ..\misc.h ..\int64.h ..\tree234.h ..\sftp.h \ + ..\puttymem.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sftp.c +sizetip.obj: ..\windows\sizetip.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\sizetip.c +slookup.obj: ..\charset\slookup.c ..\charset\charset.h ..\charset\internal.h \ + ..\charset\enum.c ..\charset\sbcsdat.c ..\charset\utf8.c + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\slookup.c +ssh.obj: ..\ssh.c ..\putty.h ..\tree234.h ..\ssh.h ..\sshgssc.h ..\sshgss.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h ..\int64.h \ + ..\pgssapi.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh.c +sshaes.obj: ..\sshaes.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshaes.c +ssharcf.obj: ..\ssharcf.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssharcf.c +sshblowf.obj: ..\sshblowf.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshblowf.c +sshbn.obj: ..\sshbn.c ..\misc.h ..\ssh.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshbn.c +sshcrc.obj: ..\sshcrc.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshcrc.c +sshcrcda.obj: ..\sshcrcda.c ..\misc.h ..\ssh.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshcrcda.c +sshdes.obj: ..\sshdes.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshdes.c +sshdh.obj: ..\sshdh.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshdh.c +sshdss.obj: ..\sshdss.c ..\ssh.h ..\misc.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshdss.c +sshdssg.obj: ..\sshdssg.c ..\misc.h ..\ssh.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshdssg.c +sshgssc.obj: ..\sshgssc.c ..\putty.h ..\sshgssc.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\pgssapi.h ..\sshgss.h ..\puttymem.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshgssc.c +sshmd5.obj: ..\sshmd5.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshmd5.c +sshprime.obj: ..\sshprime.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshprime.c +sshpubk.obj: ..\sshpubk.c ..\putty.h ..\ssh.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshpubk.c +sshrand.obj: ..\sshrand.c ..\putty.h ..\ssh.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshrand.c +sshrsa.obj: ..\sshrsa.c ..\ssh.h ..\misc.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshrsa.c +sshrsag.obj: ..\sshrsag.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshrsag.c +sshsh256.obj: ..\sshsh256.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshsh256.c +sshsh512.obj: ..\sshsh512.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshsh512.c +sshsha.obj: ..\sshsha.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshsha.c +sshzlib.obj: ..\sshzlib.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshzlib.c +telnet.obj: ..\telnet.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\telnet.c +terminal.obj: ..\terminal.c ..\putty.h ..\terminal.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\tree234.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\terminal.c +testback.obj: ..\testback.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\testback.c +time.obj: ..\time.c + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\time.c +timing.obj: ..\timing.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\timing.c +toucs.obj: ..\charset\toucs.c ..\charset\charset.h ..\charset\internal.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\toucs.c +tree234.obj: ..\tree234.c ..\puttymem.h ..\tree234.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\tree234.c +utf8.obj: ..\charset\utf8.c ..\charset\charset.h ..\charset\internal.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\utf8.c +ux_x11.obj: ..\unix\ux_x11.c ..\putty.h ..\ssh.h ..\network.h ..\puttyps.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\ux_x11.c +uxagentc.obj: ..\unix\uxagentc.c ..\putty.h ..\misc.h ..\tree234.h \ + ..\puttymem.h ..\puttyps.h ..\network.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxagentc.c +uxcfg.obj: ..\unix\uxcfg.c ..\putty.h ..\dialog.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxcfg.c +uxcons.obj: ..\unix\uxcons.c ..\putty.h ..\storage.h ..\ssh.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxcons.c +uxgen.obj: ..\unix\uxgen.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxgen.c +uxgss.obj: ..\unix\uxgss.c ..\putty.h ..\pgssapi.h ..\sshgss.h ..\sshgssc.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxgss.c +uxmisc.obj: ..\unix\uxmisc.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxmisc.c +uxnet.obj: ..\unix\uxnet.c ..\putty.h ..\network.h ..\tree234.h ..\puttyps.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxnet.c +uxnoise.obj: ..\unix\uxnoise.c ..\putty.h ..\ssh.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxnoise.c +uxplink.obj: ..\unix\uxplink.c ..\putty.h ..\storage.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxplink.c +uxprint.obj: ..\unix\uxprint.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxprint.c +uxproxy.obj: ..\unix\uxproxy.c ..\tree234.h ..\putty.h ..\network.h \ + ..\proxy.h ..\puttyps.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxproxy.c +uxpterm.obj: ..\unix\uxpterm.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxpterm.c +uxpty.obj: ..\unix\uxpty.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxpty.c +uxputty.obj: ..\unix\uxputty.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxputty.c +uxsel.obj: ..\unix\uxsel.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxsel.c +uxser.obj: ..\unix\uxser.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxser.c +uxsftp.obj: ..\unix\uxsftp.c ..\putty.h ..\ssh.h ..\psftp.h ..\int64.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxsftp.c +uxsignal.obj: ..\unix\uxsignal.c + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxsignal.c +uxstore.obj: ..\unix\uxstore.c ..\putty.h ..\storage.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxstore.c +uxucs.obj: ..\unix\uxucs.c ..\putty.h ..\charset\charset.h ..\terminal.h \ + ..\misc.h ..\puttyps.h ..\network.h ..\tree234.h \ + ..\puttymem.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxucs.c +wcwidth.obj: ..\wcwidth.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\wcwidth.c +wildcard.obj: ..\wildcard.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\wildcard.c +wincfg.obj: ..\windows\wincfg.c ..\putty.h ..\dialog.h ..\storage.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\wincfg.c +wincons.obj: ..\windows\wincons.c ..\putty.h ..\storage.h ..\ssh.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\int64.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\wincons.c +winctrls.obj: ..\windows\winctrls.c ..\putty.h ..\misc.h ..\dialog.h \ + ..\puttyps.h ..\network.h ..\puttymem.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winctrls.c +windefs.obj: ..\windows\windefs.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\windefs.c +windlg.obj: ..\windows\windlg.c ..\putty.h ..\ssh.h ..\windows\win_res.h \ + ..\storage.h ..\dialog.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\puttymem.h ..\tree234.h ..\int64.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\windlg.c +window.obj: ..\windows\window.c ..\putty.h ..\terminal.h ..\storage.h \ + ..\windows\win_res.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\window.c +wingss.obj: ..\windows\wingss.c ..\putty.h ..\pgssapi.h ..\sshgss.h \ + ..\sshgssc.h ..\misc.h ..\puttyps.h ..\network.h \ + ..\puttymem.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\wingss.c +winhandl.obj: ..\windows\winhandl.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winhandl.c +winhelp.obj: ..\windows\winhelp.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winhelp.c +winjump.obj: ..\windows\winjump.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winjump.c +winmisc.obj: ..\windows\winmisc.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winmisc.c +winnet.obj: ..\windows\winnet.c ..\putty.h ..\network.h ..\tree234.h \ + ..\puttyps.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winnet.c +winnoise.obj: ..\windows\winnoise.c ..\putty.h ..\ssh.h ..\storage.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\int64.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\windows\winhelp.h \ + ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winnoise.c +winnojmp.obj: ..\windows\winnojmp.c + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winnojmp.c +winpgen.obj: ..\windows\winpgen.c ..\putty.h ..\ssh.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winpgen.c +winpgnt.obj: ..\windows\winpgnt.c ..\putty.h ..\ssh.h ..\misc.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\puttymem.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winpgnt.c +winpgntc.obj: ..\windows\winpgntc.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winpgntc.c +winplink.obj: ..\windows\winplink.c ..\putty.h ..\storage.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winplink.c +winprint.obj: ..\windows\winprint.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winprint.c +winproxy.obj: ..\windows\winproxy.c ..\tree234.h ..\putty.h ..\network.h \ + ..\proxy.h ..\puttyps.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winproxy.c +winser.obj: ..\windows\winser.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winser.c +winsftp.obj: ..\windows\winsftp.c ..\putty.h ..\psftp.h ..\ssh.h ..\int64.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winsftp.c +winstore.obj: ..\windows\winstore.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winstore.c +wintime.obj: ..\windows\wintime.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\wintime.c +winucs.obj: ..\windows\winucs.c ..\putty.h ..\terminal.h ..\misc.h \ + ..\puttyps.h ..\network.h ..\tree234.h ..\puttymem.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winucs.c +winutils.obj: ..\windows\winutils.c ..\putty.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\puttymem.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winutils.c +winx11.obj: ..\windows\winx11.c ..\putty.h ..\ssh.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winx11.c +x11fwd.obj: ..\x11fwd.c ..\putty.h ..\ssh.h ..\tree234.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\x11fwd.c +xenc.obj: ..\charset\xenc.c ..\charset\charset.h ..\charset\internal.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\xenc.c +xkeysym.obj: ..\unix\xkeysym.c ..\misc.h ..\puttymem.h + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\xkeysym.c +xpmptcfg.obj: ..\unix\xpmptcfg.c + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\xpmptcfg.c +xpmpterm.obj: ..\unix\xpmpterm.c + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\xpmpterm.c +xpmpucfg.obj: ..\unix\xpmpucfg.c + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\xpmpucfg.c +xpmputty.obj: ..\unix\xpmputty.c + lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\xpmputty.c + +version.obj: FORCE + lcc $(VER) $(CFLAGS) /c ..\version.c + +clean: + -del *.obj + -del *.exe + -del *.res + +FORCE: diff --git a/putty/WINDOWS/MAKEFILE.VC b/putty/WINDOWS/MAKEFILE.VC new file mode 100644 index 0000000..754b149 --- /dev/null +++ b/putty/WINDOWS/MAKEFILE.VC @@ -0,0 +1,1094 @@ +# Makefile for putty under Visual C. +# +# This file was created by `mkfiles.pl' from the `Recipe' file. +# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead. +# +# Extra options you can set: +# +# - VER="/DSNAPSHOT=1999-01-25 /DSVN_REV=1234" +# Generates executables whose About box report them as being a +# development snapshot. SVN_REV is a Subversion revision number. +# +# - VER=/DRELEASE=0.43 +# Generates executables whose About box report them as being a +# release version. +# +# - COMPAT=/DAUTO_WINSOCK (Windows only) +# Causes PuTTY to assume that includes its own WinSock +# header file, so that it won't try to include . +# +# - COMPAT=/DWINSOCK_TWO (Windows only) +# Causes the PuTTY utilities to include instead of +# , except Plink which _needs_ WinSock 2 so it already +# does this. +# +# - COMPAT=/DNO_SECURITY (Windows only) +# Disables Pageant's use of , which is not available +# with some development environments (such as older versions of +# the Cygwin/mingw GNU toolchain). This means that 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). +# +# - COMPAT=/DNO_MULTIMON (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. This means that PuTTY's +# full-screen mode (configurable to work on Alt-Enter) will +# not behave usefully in a multi-monitor environment. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin. +# +# - COMPAT=/DNO_HTMLHELP (Windows only) +# Disables PuTTY's use of , which is not available +# with some development environments. The resulting binary +# will only look for an old-style WinHelp file (.HLP/.CNT), and +# will ignore any .CHM file. +# +# Note that this definition is always enabled in the Cygwin +# build, since at the time of writing this is +# known not to be available in Cygwin (although you can use +# the htmlhelp.h 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. +# +# - RCFL=/DASCIICTLS (Windows only) +# Uses ASCII rather than Unicode to specify the tab control in +# the resource file. Probably most useful when compiling with +# Cygnus/mingw32, whose resource compiler may have less of a +# problem with it. +# +# - XFLAGS=/DTELNET_DEFAULT +# Causes PuTTY to default to the Telnet protocol (in the absence +# of Default Settings and so on to the contrary). Normally PuTTY +# will default to SSH. +# +# - 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. +# + +# If you rename this file to `Makefile', you should change this line, +# so that the .rsp files still depend on the correct makefile. +MAKEFILE = Makefile.vc + +# C compilation flags +CFLAGS = /nologo /W3 /O1 -I..\./ -I..\charset/ -I..\windows/ -I..\unix/ -I..\macosx/ /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500 +LFLAGS = /incremental:no /fixed +RCFLAGS = -DWIN32 -D_WIN32 -DWINVER=0x0400 + +CFLAGS = $(CFLAGS) /DHAS_GSSAPI /DSECURITY_WIN32 +RCFLAGS = $(RCFLAGS) $(VER) + + +all: pageant.exe plink.exe pscp.exe psftp.exe putty.exe puttygen.exe \ + puttytel.exe + +pageant.exe: misc.obj pageant.res sshaes.obj sshbn.obj sshdes.obj sshdss.obj \ + sshmd5.obj sshpubk.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj tree234.obj version.obj winhelp.obj winmisc.obj \ + winpgnt.obj winpgntc.obj winutils.obj pageant.rsp + link $(LFLAGS) $(XLFLAGS) -out:pageant.exe -map:pageant.map @pageant.rsp + +plink.exe: be_all_s.obj cmdline.obj cproxy.obj ldisc.obj logging.obj \ + misc.obj pgssapi.obj pinger.obj plink.res portfwd.obj \ + proxy.obj raw.obj rlogin.obj settings.obj ssh.obj sshaes.obj \ + ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj \ + sshdes.obj sshdh.obj sshdss.obj sshgssc.obj sshmd5.obj \ + sshpubk.obj sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj sshzlib.obj telnet.obj timing.obj tree234.obj \ + version.obj wildcard.obj wincons.obj windefs.obj wingss.obj \ + winhandl.obj winmisc.obj winnet.obj winnoise.obj \ + winnojmp.obj winpgntc.obj winplink.obj winproxy.obj \ + winser.obj winstore.obj wintime.obj winx11.obj x11fwd.obj \ + plink.rsp + link $(LFLAGS) $(XLFLAGS) -out:plink.exe -map:plink.map @plink.rsp + +pscp.exe: be_none.obj cmdline.obj cproxy.obj int64.obj logging.obj misc.obj \ + pgssapi.obj pinger.obj portfwd.obj proxy.obj pscp.obj \ + pscp.res settings.obj sftp.obj ssh.obj sshaes.obj \ + ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj \ + sshdes.obj sshdh.obj sshdss.obj sshgssc.obj sshmd5.obj \ + sshpubk.obj sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj sshzlib.obj timing.obj tree234.obj version.obj \ + wildcard.obj wincons.obj windefs.obj wingss.obj winhandl.obj \ + winmisc.obj winnet.obj winnoise.obj winnojmp.obj \ + winpgntc.obj winproxy.obj winsftp.obj winstore.obj \ + wintime.obj x11fwd.obj pscp.rsp + link $(LFLAGS) $(XLFLAGS) -out:pscp.exe -map:pscp.map @pscp.rsp + +psftp.exe: be_none.obj cmdline.obj cproxy.obj int64.obj logging.obj misc.obj \ + pgssapi.obj pinger.obj portfwd.obj proxy.obj psftp.obj \ + psftp.res settings.obj sftp.obj ssh.obj sshaes.obj \ + ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj \ + sshdes.obj sshdh.obj sshdss.obj sshgssc.obj sshmd5.obj \ + sshpubk.obj sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj \ + sshsha.obj sshzlib.obj timing.obj tree234.obj version.obj \ + wildcard.obj wincons.obj windefs.obj wingss.obj winhandl.obj \ + winmisc.obj winnet.obj winnoise.obj winnojmp.obj \ + winpgntc.obj winproxy.obj winsftp.obj winstore.obj \ + wintime.obj x11fwd.obj psftp.rsp + link $(LFLAGS) $(XLFLAGS) -out:psftp.exe -map:psftp.map @psftp.rsp + +putty.exe: be_all_s.obj cmdline.obj config.obj cproxy.obj dialog.obj \ + ldisc.obj ldiscucs.obj logging.obj minibidi.obj misc.obj \ + pgssapi.obj pinger.obj portfwd.obj proxy.obj putty.res \ + raw.obj rlogin.obj sercfg.obj settings.obj sizetip.obj \ + ssh.obj sshaes.obj ssharcf.obj sshblowf.obj sshbn.obj \ + sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj \ + sshgssc.obj sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj \ + sshsh256.obj sshsh512.obj sshsha.obj sshzlib.obj telnet.obj \ + terminal.obj timing.obj tree234.obj version.obj wcwidth.obj \ + wildcard.obj wincfg.obj winctrls.obj windefs.obj windlg.obj \ + window.obj wingss.obj winhandl.obj winhelp.obj winjump.obj \ + winmisc.obj winnet.obj winnoise.obj winpgntc.obj \ + winprint.obj winproxy.obj winser.obj winstore.obj \ + wintime.obj winucs.obj winutils.obj winx11.obj x11fwd.obj \ + putty.rsp + link $(LFLAGS) $(XLFLAGS) -out:putty.exe -map:putty.map @putty.rsp + +puttygen.exe: import.obj misc.obj notiming.obj puttygen.res sshaes.obj \ + sshbn.obj sshdes.obj sshdss.obj sshdssg.obj sshmd5.obj \ + sshprime.obj sshpubk.obj sshrand.obj sshrsa.obj sshrsag.obj \ + sshsh256.obj sshsh512.obj sshsha.obj tree234.obj version.obj \ + winctrls.obj winhelp.obj winmisc.obj winnoise.obj \ + winnojmp.obj winpgen.obj winstore.obj wintime.obj \ + winutils.obj puttygen.rsp + link $(LFLAGS) $(XLFLAGS) -out:puttygen.exe -map:puttygen.map @puttygen.rsp + +puttytel.exe: be_nos_s.obj cmdline.obj config.obj dialog.obj ldisc.obj \ + ldiscucs.obj logging.obj minibidi.obj misc.obj nocproxy.obj \ + nogss.obj pinger.obj proxy.obj puttytel.res raw.obj \ + rlogin.obj sercfg.obj settings.obj sizetip.obj telnet.obj \ + terminal.obj timing.obj tree234.obj version.obj wcwidth.obj \ + wincfg.obj winctrls.obj windefs.obj windlg.obj window.obj \ + winhandl.obj winhelp.obj winjump.obj winmisc.obj winnet.obj \ + winprint.obj winproxy.obj winser.obj winstore.obj \ + wintime.obj winucs.obj winutils.obj puttytel.rsp + link $(LFLAGS) $(XLFLAGS) -out:puttytel.exe -map:puttytel.map @puttytel.rsp + +pageant.rsp: $(MAKEFILE) + echo /nologo /subsystem:windows > pageant.rsp + echo advapi32.lib comctl32.lib comdlg32.lib gdi32.lib >> pageant.rsp + echo imm32.lib misc.obj ole32.lib pageant.res >> pageant.rsp + echo shell32.lib sshaes.obj sshbn.obj sshdes.obj >> pageant.rsp + echo sshdss.obj sshmd5.obj sshpubk.obj sshrsa.obj >> pageant.rsp + echo sshsh256.obj sshsh512.obj sshsha.obj tree234.obj >> pageant.rsp + echo user32.lib version.obj winhelp.obj winmisc.obj >> pageant.rsp + echo winmm.lib winpgnt.obj winpgntc.obj winspool.lib >> pageant.rsp + echo winutils.obj >> pageant.rsp + +plink.rsp: $(MAKEFILE) + echo /nologo /subsystem:console > plink.rsp + echo advapi32.lib be_all_s.obj cmdline.obj >> plink.rsp + echo comctl32.lib comdlg32.lib cproxy.obj gdi32.lib >> plink.rsp + echo imm32.lib ldisc.obj logging.obj misc.obj >> plink.rsp + echo ole32.lib pgssapi.obj pinger.obj plink.res >> plink.rsp + echo portfwd.obj proxy.obj raw.obj rlogin.obj >> plink.rsp + echo settings.obj shell32.lib ssh.obj sshaes.obj >> plink.rsp + echo ssharcf.obj sshblowf.obj sshbn.obj sshcrc.obj >> plink.rsp + echo sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj >> plink.rsp + echo sshgssc.obj sshmd5.obj sshpubk.obj sshrand.obj >> plink.rsp + echo sshrsa.obj sshsh256.obj sshsh512.obj sshsha.obj >> plink.rsp + echo sshzlib.obj telnet.obj timing.obj tree234.obj >> plink.rsp + echo user32.lib version.obj wildcard.obj wincons.obj >> plink.rsp + echo windefs.obj wingss.obj winhandl.obj winmisc.obj >> plink.rsp + echo winmm.lib winnet.obj winnoise.obj winnojmp.obj >> plink.rsp + echo winpgntc.obj winplink.obj winproxy.obj winser.obj >> plink.rsp + echo winspool.lib winstore.obj wintime.obj winx11.obj >> plink.rsp + echo x11fwd.obj >> plink.rsp + +pscp.rsp: $(MAKEFILE) + echo /nologo /subsystem:console > pscp.rsp + echo advapi32.lib be_none.obj cmdline.obj comctl32.lib >> pscp.rsp + echo comdlg32.lib cproxy.obj gdi32.lib imm32.lib >> pscp.rsp + echo int64.obj logging.obj misc.obj ole32.lib >> pscp.rsp + echo pgssapi.obj pinger.obj portfwd.obj proxy.obj >> pscp.rsp + echo pscp.obj pscp.res settings.obj sftp.obj >> pscp.rsp + echo shell32.lib ssh.obj sshaes.obj ssharcf.obj >> pscp.rsp + echo sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj >> pscp.rsp + echo sshdes.obj sshdh.obj sshdss.obj sshgssc.obj >> pscp.rsp + echo sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj >> pscp.rsp + echo sshsh256.obj sshsh512.obj sshsha.obj sshzlib.obj >> pscp.rsp + echo timing.obj tree234.obj user32.lib version.obj >> pscp.rsp + echo wildcard.obj wincons.obj windefs.obj wingss.obj >> pscp.rsp + echo winhandl.obj winmisc.obj winmm.lib winnet.obj >> pscp.rsp + echo winnoise.obj winnojmp.obj winpgntc.obj >> pscp.rsp + echo winproxy.obj winsftp.obj winspool.lib >> pscp.rsp + echo winstore.obj wintime.obj x11fwd.obj >> pscp.rsp + +psftp.rsp: $(MAKEFILE) + echo /nologo /subsystem:console > psftp.rsp + echo advapi32.lib be_none.obj cmdline.obj comctl32.lib >> psftp.rsp + echo comdlg32.lib cproxy.obj gdi32.lib imm32.lib >> psftp.rsp + echo int64.obj logging.obj misc.obj ole32.lib >> psftp.rsp + echo pgssapi.obj pinger.obj portfwd.obj proxy.obj >> psftp.rsp + echo psftp.obj psftp.res settings.obj sftp.obj >> psftp.rsp + echo shell32.lib ssh.obj sshaes.obj ssharcf.obj >> psftp.rsp + echo sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj >> psftp.rsp + echo sshdes.obj sshdh.obj sshdss.obj sshgssc.obj >> psftp.rsp + echo sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj >> psftp.rsp + echo sshsh256.obj sshsh512.obj sshsha.obj sshzlib.obj >> psftp.rsp + echo timing.obj tree234.obj user32.lib version.obj >> psftp.rsp + echo wildcard.obj wincons.obj windefs.obj wingss.obj >> psftp.rsp + echo winhandl.obj winmisc.obj winmm.lib winnet.obj >> psftp.rsp + echo winnoise.obj winnojmp.obj winpgntc.obj >> psftp.rsp + echo winproxy.obj winsftp.obj winspool.lib >> psftp.rsp + echo winstore.obj wintime.obj x11fwd.obj >> psftp.rsp + +putty.rsp: $(MAKEFILE) + echo /nologo /subsystem:windows > putty.rsp + echo advapi32.lib be_all_s.obj cmdline.obj >> putty.rsp + echo comctl32.lib comdlg32.lib config.obj cproxy.obj >> putty.rsp + echo dialog.obj gdi32.lib imm32.lib ldisc.obj >> putty.rsp + echo ldiscucs.obj logging.obj minibidi.obj misc.obj >> putty.rsp + echo ole32.lib pgssapi.obj pinger.obj portfwd.obj >> putty.rsp + echo proxy.obj putty.res raw.obj rlogin.obj sercfg.obj >> putty.rsp + echo settings.obj shell32.lib sizetip.obj ssh.obj >> putty.rsp + echo sshaes.obj ssharcf.obj sshblowf.obj sshbn.obj >> putty.rsp + echo sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj >> putty.rsp + echo sshdss.obj sshgssc.obj sshmd5.obj sshpubk.obj >> putty.rsp + echo sshrand.obj sshrsa.obj sshsh256.obj sshsh512.obj >> putty.rsp + echo sshsha.obj sshzlib.obj telnet.obj terminal.obj >> putty.rsp + echo timing.obj tree234.obj user32.lib version.obj >> putty.rsp + echo wcwidth.obj wildcard.obj wincfg.obj winctrls.obj >> putty.rsp + echo windefs.obj windlg.obj window.obj wingss.obj >> putty.rsp + echo winhandl.obj winhelp.obj winjump.obj winmisc.obj >> putty.rsp + echo winmm.lib winnet.obj winnoise.obj winpgntc.obj >> putty.rsp + echo winprint.obj winproxy.obj winser.obj winspool.lib >> putty.rsp + echo winstore.obj wintime.obj winucs.obj winutils.obj >> putty.rsp + echo winx11.obj x11fwd.obj >> putty.rsp + +puttygen.rsp: $(MAKEFILE) + echo /nologo /subsystem:windows > puttygen.rsp + echo advapi32.lib comctl32.lib comdlg32.lib gdi32.lib >> puttygen.rsp + echo imm32.lib import.obj misc.obj notiming.obj >> puttygen.rsp + echo ole32.lib puttygen.res shell32.lib sshaes.obj >> puttygen.rsp + echo sshbn.obj sshdes.obj sshdss.obj sshdssg.obj >> puttygen.rsp + echo sshmd5.obj sshprime.obj sshpubk.obj sshrand.obj >> puttygen.rsp + echo sshrsa.obj sshrsag.obj sshsh256.obj sshsh512.obj >> puttygen.rsp + echo sshsha.obj tree234.obj user32.lib version.obj >> puttygen.rsp + echo winctrls.obj winhelp.obj winmisc.obj winmm.lib >> puttygen.rsp + echo winnoise.obj winnojmp.obj winpgen.obj >> puttygen.rsp + echo winspool.lib winstore.obj wintime.obj >> puttygen.rsp + echo winutils.obj >> puttygen.rsp + +puttytel.rsp: $(MAKEFILE) + echo /nologo /subsystem:windows > puttytel.rsp + echo advapi32.lib be_nos_s.obj cmdline.obj >> puttytel.rsp + echo comctl32.lib comdlg32.lib config.obj dialog.obj >> puttytel.rsp + echo gdi32.lib imm32.lib ldisc.obj ldiscucs.obj >> puttytel.rsp + echo logging.obj minibidi.obj misc.obj nocproxy.obj >> puttytel.rsp + echo nogss.obj ole32.lib pinger.obj proxy.obj >> puttytel.rsp + echo puttytel.res raw.obj rlogin.obj sercfg.obj >> puttytel.rsp + echo settings.obj shell32.lib sizetip.obj telnet.obj >> puttytel.rsp + echo terminal.obj timing.obj tree234.obj user32.lib >> puttytel.rsp + echo version.obj wcwidth.obj wincfg.obj winctrls.obj >> puttytel.rsp + echo windefs.obj windlg.obj window.obj winhandl.obj >> puttytel.rsp + echo winhelp.obj winjump.obj winmisc.obj winmm.lib >> puttytel.rsp + echo winnet.obj winprint.obj winproxy.obj winser.obj >> puttytel.rsp + echo winspool.lib winstore.obj wintime.obj winucs.obj >> puttytel.rsp + echo winutils.obj >> puttytel.rsp + +be_all_s.obj: ..\be_all_s.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\be_all_s.c + +be_none.obj: ..\be_none.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\be_none.c + +be_nos_s.obj: ..\be_nos_s.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\be_nos_s.c + +cmdgen.obj: ..\cmdgen.c ..\putty.h ..\ssh.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\cmdgen.c + +cmdline.obj: ..\cmdline.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\cmdline.c + +config.obj: ..\config.c ..\putty.h ..\dialog.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\config.c + +cproxy.obj: ..\cproxy.c ..\putty.h ..\ssh.h ..\network.h ..\proxy.h \ + ..\puttyps.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\cproxy.c + +dialog.obj: ..\dialog.c ..\putty.h ..\dialog.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\dialog.c + +fromucs.obj: ..\charset\fromucs.c ..\charset\charset.h ..\charset\internal.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\charset\fromucs.c + +gtkcfg.obj: ..\unix\gtkcfg.c ..\putty.h ..\dialog.h ..\storage.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\gtkcfg.c + +gtkcols.obj: ..\unix\gtkcols.c ..\unix\gtkcols.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\gtkcols.c + +gtkdlg.obj: ..\unix\gtkdlg.c ..\unix\gtkcols.h ..\unix\gtkfont.h ..\putty.h \ + ..\storage.h ..\dialog.h ..\tree234.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\gtkdlg.c + +gtkfont.obj: ..\unix\gtkfont.c ..\putty.h ..\unix\gtkfont.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\gtkfont.c + +gtkwin.obj: ..\unix\gtkwin.c ..\putty.h ..\terminal.h ..\unix\gtkfont.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\tree234.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\gtkwin.c + +import.obj: ..\import.c ..\putty.h ..\ssh.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\import.c + +int64.obj: ..\int64.c ..\int64.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\int64.c + +ldisc.obj: ..\ldisc.c ..\putty.h ..\terminal.h ..\ldisc.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\tree234.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\ldisc.c + +ldiscucs.obj: ..\ldiscucs.c ..\putty.h ..\terminal.h ..\ldisc.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\tree234.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\ldiscucs.c + +localenc.obj: ..\charset\localenc.c ..\charset\charset.h \ + ..\charset\internal.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\charset\localenc.c + +logging.obj: ..\logging.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\logging.c + +macenc.obj: ..\charset\macenc.c ..\charset\charset.h ..\charset\internal.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\charset\macenc.c + +mimeenc.obj: ..\charset\mimeenc.c ..\charset\charset.h ..\charset\internal.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\charset\mimeenc.c + +minibidi.obj: ..\minibidi.c ..\misc.h ..\puttymem.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\minibidi.c + +misc.obj: ..\misc.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\misc.c + +nocproxy.obj: ..\nocproxy.c ..\putty.h ..\network.h ..\proxy.h ..\puttyps.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\nocproxy.c + +nogss.obj: ..\nogss.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\nogss.c + +notiming.obj: ..\notiming.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\notiming.c + +osxctrls.obj: ..\macosx\osxctrls.m ..\putty.h ..\dialog.h \ + ..\macosx\osxclass.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\macosx\osxctrls.m + +osxdlg.obj: ..\macosx\osxdlg.m ..\putty.h ..\storage.h ..\dialog.h \ + ..\macosx\osxclass.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\macosx\osxdlg.m + +osxmain.obj: ..\macosx\osxmain.m ..\putty.h ..\macosx\osxclass.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\macosx\osxmain.m + +osxsel.obj: ..\macosx\osxsel.m ..\putty.h ..\macosx\osxclass.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\macosx\osxsel.m + +osxwin.obj: ..\macosx\osxwin.m ..\putty.h ..\terminal.h ..\macosx\osxclass.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\tree234.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\macosx\osxwin.m + +pageant.res: *.c *.h *.rc ..\windows\pageant.rc ..\windows\rcstuff.h \ + ..\windows\pageant.ico ..\windows\pageants.ico \ + ..\windows\version.rc2 ..\windows\pageant.mft + rc $(RCFL) -r $(RCFLAGS) ..\windows\pageant.rc + +pgssapi.obj: ..\pgssapi.c ..\putty.h ..\pgssapi.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\pgssapi.c + +pinger.obj: ..\pinger.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\pinger.c + +plink.res: *.c *.h *.rc ..\windows\plink.rc ..\windows\rcstuff.h \ + ..\windows\putty.ico ..\windows\version.rc2 + rc $(RCFL) -r $(RCFLAGS) ..\windows\plink.rc + +portfwd.obj: ..\portfwd.c ..\putty.h ..\ssh.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\portfwd.c + +proxy.obj: ..\proxy.c ..\putty.h ..\network.h ..\proxy.h ..\puttyps.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\proxy.c + +pscp.obj: ..\pscp.c ..\putty.h ..\psftp.h ..\ssh.h ..\sftp.h ..\storage.h \ + ..\int64.h ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\pscp.c + +pscp.res: *.c *.h *.rc ..\windows\pscp.rc ..\windows\rcstuff.h \ + ..\windows\pscp.ico ..\windows\version.rc2 + rc $(RCFL) -r $(RCFLAGS) ..\windows\pscp.rc + +psftp.obj: ..\psftp.c ..\putty.h ..\psftp.h ..\storage.h ..\ssh.h ..\sftp.h \ + ..\int64.h ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\psftp.c + +psftp.res: *.c *.h *.rc ..\windows\psftp.rc ..\windows\rcstuff.h \ + ..\windows\pscp.ico ..\windows\version.rc2 + rc $(RCFL) -r $(RCFLAGS) ..\windows\psftp.rc + +putty.res: *.c *.h *.rc ..\windows\putty.rc ..\windows\rcstuff.h \ + ..\windows\win_res.rc2 ..\windows\putty.mft \ + ..\windows\win_res.h ..\windows\putty.ico \ + ..\windows\puttycfg.ico ..\windows\version.rc2 + rc $(RCFL) -r $(RCFLAGS) ..\windows\putty.rc + +puttygen.res: *.c *.h *.rc ..\windows\puttygen.rc ..\windows\rcstuff.h \ + ..\windows\puttygen.ico ..\windows\version.rc2 \ + ..\windows\puttygen.mft + rc $(RCFL) -r $(RCFLAGS) ..\windows\puttygen.rc + +puttytel.res: *.c *.h *.rc ..\windows\puttytel.rc ..\windows\rcstuff.h \ + ..\windows\win_res.rc2 ..\windows\putty.mft \ + ..\windows\win_res.h ..\windows\putty.ico \ + ..\windows\puttycfg.ico ..\windows\version.rc2 + rc $(RCFL) -r $(RCFLAGS) ..\windows\puttytel.rc + +raw.obj: ..\raw.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\raw.c + +rlogin.obj: ..\rlogin.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\rlogin.c + +sbcs.obj: ..\charset\sbcs.c ..\charset\charset.h ..\charset\internal.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\charset\sbcs.c + +sbcsdat.obj: ..\charset\sbcsdat.c ..\charset\charset.h ..\charset\internal.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\charset\sbcsdat.c + +sercfg.obj: ..\sercfg.c ..\putty.h ..\dialog.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sercfg.c + +settings.obj: ..\settings.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\settings.c + +sftp.obj: ..\sftp.c ..\misc.h ..\int64.h ..\tree234.h ..\sftp.h \ + ..\puttymem.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sftp.c + +sizetip.obj: ..\windows\sizetip.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\sizetip.c + +slookup.obj: ..\charset\slookup.c ..\charset\charset.h ..\charset\internal.h \ + ..\charset\enum.c ..\charset\sbcsdat.c ..\charset\utf8.c + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\charset\slookup.c + +ssh.obj: ..\ssh.c ..\putty.h ..\tree234.h ..\ssh.h ..\sshgssc.h ..\sshgss.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h ..\int64.h \ + ..\pgssapi.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\ssh.c + +sshaes.obj: ..\sshaes.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshaes.c + +ssharcf.obj: ..\ssharcf.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\ssharcf.c + +sshblowf.obj: ..\sshblowf.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshblowf.c + +sshbn.obj: ..\sshbn.c ..\misc.h ..\ssh.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshbn.c + +sshcrc.obj: ..\sshcrc.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshcrc.c + +sshcrcda.obj: ..\sshcrcda.c ..\misc.h ..\ssh.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshcrcda.c + +sshdes.obj: ..\sshdes.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshdes.c + +sshdh.obj: ..\sshdh.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshdh.c + +sshdss.obj: ..\sshdss.c ..\ssh.h ..\misc.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshdss.c + +sshdssg.obj: ..\sshdssg.c ..\misc.h ..\ssh.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshdssg.c + +sshgssc.obj: ..\sshgssc.c ..\putty.h ..\sshgssc.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\pgssapi.h ..\sshgss.h ..\puttymem.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshgssc.c + +sshmd5.obj: ..\sshmd5.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshmd5.c + +sshprime.obj: ..\sshprime.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshprime.c + +sshpubk.obj: ..\sshpubk.c ..\putty.h ..\ssh.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshpubk.c + +sshrand.obj: ..\sshrand.c ..\putty.h ..\ssh.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshrand.c + +sshrsa.obj: ..\sshrsa.c ..\ssh.h ..\misc.h ..\puttymem.h ..\tree234.h \ + ..\network.h ..\int64.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshrsa.c + +sshrsag.obj: ..\sshrsag.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshrsag.c + +sshsh256.obj: ..\sshsh256.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshsh256.c + +sshsh512.obj: ..\sshsh512.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshsh512.c + +sshsha.obj: ..\sshsha.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshsha.c + +sshzlib.obj: ..\sshzlib.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ + ..\int64.h ..\misc.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\sshzlib.c + +telnet.obj: ..\telnet.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\telnet.c + +terminal.obj: ..\terminal.c ..\putty.h ..\terminal.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\tree234.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\terminal.c + +testback.obj: ..\testback.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\testback.c + +time.obj: ..\time.c + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\time.c + +timing.obj: ..\timing.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\timing.c + +toucs.obj: ..\charset\toucs.c ..\charset\charset.h ..\charset\internal.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\charset\toucs.c + +tree234.obj: ..\tree234.c ..\puttymem.h ..\tree234.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\tree234.c + +utf8.obj: ..\charset\utf8.c ..\charset\charset.h ..\charset\internal.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\charset\utf8.c + +ux_x11.obj: ..\unix\ux_x11.c ..\putty.h ..\ssh.h ..\network.h ..\puttyps.h \ + ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\ux_x11.c + +uxagentc.obj: ..\unix\uxagentc.c ..\putty.h ..\misc.h ..\tree234.h \ + ..\puttymem.h ..\puttyps.h ..\network.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxagentc.c + +uxcfg.obj: ..\unix\uxcfg.c ..\putty.h ..\dialog.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxcfg.c + +uxcons.obj: ..\unix\uxcons.c ..\putty.h ..\storage.h ..\ssh.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxcons.c + +uxgen.obj: ..\unix\uxgen.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxgen.c + +uxgss.obj: ..\unix\uxgss.c ..\putty.h ..\pgssapi.h ..\sshgss.h ..\sshgssc.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxgss.c + +uxmisc.obj: ..\unix\uxmisc.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxmisc.c + +uxnet.obj: ..\unix\uxnet.c ..\putty.h ..\network.h ..\tree234.h ..\puttyps.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxnet.c + +uxnoise.obj: ..\unix\uxnoise.c ..\putty.h ..\ssh.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxnoise.c + +uxplink.obj: ..\unix\uxplink.c ..\putty.h ..\storage.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxplink.c + +uxprint.obj: ..\unix\uxprint.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxprint.c + +uxproxy.obj: ..\unix\uxproxy.c ..\tree234.h ..\putty.h ..\network.h \ + ..\proxy.h ..\puttyps.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxproxy.c + +uxpterm.obj: ..\unix\uxpterm.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxpterm.c + +uxpty.obj: ..\unix\uxpty.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxpty.c + +uxputty.obj: ..\unix\uxputty.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxputty.c + +uxsel.obj: ..\unix\uxsel.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxsel.c + +uxser.obj: ..\unix\uxser.c ..\putty.h ..\tree234.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxser.c + +uxsftp.obj: ..\unix\uxsftp.c ..\putty.h ..\ssh.h ..\psftp.h ..\int64.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxsftp.c + +uxsignal.obj: ..\unix\uxsignal.c + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxsignal.c + +uxstore.obj: ..\unix\uxstore.c ..\putty.h ..\storage.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxstore.c + +uxucs.obj: ..\unix\uxucs.c ..\putty.h ..\charset\charset.h ..\terminal.h \ + ..\misc.h ..\puttyps.h ..\network.h ..\tree234.h \ + ..\puttymem.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\uxucs.c + +wcwidth.obj: ..\wcwidth.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\wcwidth.c + +wildcard.obj: ..\wildcard.c ..\putty.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\wildcard.c + +wincfg.obj: ..\windows\wincfg.c ..\putty.h ..\dialog.h ..\storage.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\wincfg.c + +wincons.obj: ..\windows\wincons.c ..\putty.h ..\storage.h ..\ssh.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\int64.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\wincons.c + +winctrls.obj: ..\windows\winctrls.c ..\putty.h ..\misc.h ..\dialog.h \ + ..\puttyps.h ..\network.h ..\puttymem.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winctrls.c + +windefs.obj: ..\windows\windefs.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\windefs.c + +windlg.obj: ..\windows\windlg.c ..\putty.h ..\ssh.h ..\windows\win_res.h \ + ..\storage.h ..\dialog.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\puttymem.h ..\tree234.h ..\int64.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\windlg.c + +window.obj: ..\windows\window.c ..\putty.h ..\terminal.h ..\storage.h \ + ..\windows\win_res.h ..\puttyps.h ..\network.h ..\misc.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\window.c + +wingss.obj: ..\windows\wingss.c ..\putty.h ..\pgssapi.h ..\sshgss.h \ + ..\sshgssc.h ..\misc.h ..\puttyps.h ..\network.h \ + ..\puttymem.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\tree234.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\wingss.c + +winhandl.obj: ..\windows\winhandl.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winhandl.c + +winhelp.obj: ..\windows\winhelp.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winhelp.c + +winjump.obj: ..\windows\winjump.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winjump.c + +winmisc.obj: ..\windows\winmisc.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winmisc.c + +winnet.obj: ..\windows\winnet.c ..\putty.h ..\network.h ..\tree234.h \ + ..\puttyps.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winnet.c + +winnoise.obj: ..\windows\winnoise.c ..\putty.h ..\ssh.h ..\storage.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\int64.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\windows\winhelp.h \ + ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winnoise.c + +winnojmp.obj: ..\windows\winnojmp.c + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winnojmp.c + +winpgen.obj: ..\windows\winpgen.c ..\putty.h ..\ssh.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winpgen.c + +winpgnt.obj: ..\windows\winpgnt.c ..\putty.h ..\ssh.h ..\misc.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\puttymem.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winpgnt.c + +winpgntc.obj: ..\windows\winpgntc.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winpgntc.c + +winplink.obj: ..\windows\winplink.c ..\putty.h ..\storage.h ..\tree234.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winplink.c + +winprint.obj: ..\windows\winprint.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winprint.c + +winproxy.obj: ..\windows\winproxy.c ..\tree234.h ..\putty.h ..\network.h \ + ..\proxy.h ..\puttyps.h ..\misc.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\puttymem.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winproxy.c + +winser.obj: ..\windows\winser.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winser.c + +winsftp.obj: ..\windows\winsftp.c ..\putty.h ..\psftp.h ..\ssh.h ..\int64.h \ + ..\puttyps.h ..\network.h ..\misc.h ..\puttymem.h \ + ..\tree234.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winsftp.c + +winstore.obj: ..\windows\winstore.c ..\putty.h ..\storage.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winstore.c + +wintime.obj: ..\windows\wintime.c ..\putty.h ..\puttyps.h ..\network.h \ + ..\misc.h ..\windows\winstuff.h ..\macosx\osx.h \ + ..\unix\unix.h ..\puttymem.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\wintime.c + +winucs.obj: ..\windows\winucs.c ..\putty.h ..\terminal.h ..\misc.h \ + ..\puttyps.h ..\network.h ..\tree234.h ..\puttymem.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winucs.c + +winutils.obj: ..\windows\winutils.c ..\putty.h ..\misc.h ..\puttyps.h \ + ..\network.h ..\puttymem.h ..\windows\winstuff.h \ + ..\macosx\osx.h ..\unix\unix.h ..\tree234.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winutils.c + +winx11.obj: ..\windows\winx11.c ..\putty.h ..\ssh.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\tree234.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\windows\winx11.c + +x11fwd.obj: ..\x11fwd.c ..\putty.h ..\ssh.h ..\tree234.h ..\puttyps.h \ + ..\network.h ..\misc.h ..\puttymem.h ..\int64.h \ + ..\windows\winstuff.h ..\macosx\osx.h ..\unix\unix.h \ + ..\windows\winhelp.h ..\charset\charset.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\x11fwd.c + +xenc.obj: ..\charset\xenc.c ..\charset\charset.h ..\charset\internal.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\charset\xenc.c + +xkeysym.obj: ..\unix\xkeysym.c ..\misc.h ..\puttymem.h + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\xkeysym.c + +xpmptcfg.obj: ..\unix\xpmptcfg.c + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\xpmptcfg.c + +xpmpterm.obj: ..\unix\xpmpterm.c + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\xpmpterm.c + +xpmpucfg.obj: ..\unix\xpmpucfg.c + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\xpmpucfg.c + +xpmputty.obj: ..\unix\xpmputty.c + cl $(COMPAT) $(CFLAGS) $(XFLAGS) /c ..\unix\xpmputty.c + + +version.obj: *.c *.h *.rc + cl $(VER) $(CFLAGS) /c ..\version.c + +clean: tidy + -del *.exe + +tidy: + -del *.obj + -del *.res + -del *.pch + -del *.aps + -del *.ilk + -del *.pdb + -del *.rsp + -del *.dsp + -del *.dsw + -del *.ncb + -del *.opt + -del *.plg + -del *.map + -del *.idb + -del debug.log diff --git a/putty/WINDOWS/MSVC/PAGEANT/PAGEANT.DSP b/putty/WINDOWS/MSVC/PAGEANT/PAGEANT.DSP new file mode 100644 index 0000000..935cedd --- /dev/null +++ b/putty/WINDOWS/MSVC/PAGEANT/PAGEANT.DSP @@ -0,0 +1,245 @@ +# Microsoft Developer Studio Project File - Name="pageant" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=pageant - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "pageant.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "pageant.mak" CFG="pageant - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "pageant - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "pageant - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "pageant - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:windows /machine:I386 +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:windows /machine:I386 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "pageant - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "pageant - Win32 Release" +# Name "pageant - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\misc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshaes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshbn.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdss.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshmd5.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshpubk.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrsa.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh256.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh512.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsha.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\version.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhelp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winmisc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winpgnt.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winpgntc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winutils.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\charset\charset.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\int64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\macosx\osx.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\network.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\putty.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttymem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttyps.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssh.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\unix\unix.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\rcstuff.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhelp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstuff.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=..\..\..\windows\pageant.ico +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\pageant.rc +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\pageants.ico +# End Source File +# End Group +# End Target +# End Project diff --git a/putty/WINDOWS/MSVC/PLINK/PLINK.DSP b/putty/WINDOWS/MSVC/PLINK/PLINK.DSP new file mode 100644 index 0000000..fc3d6eb --- /dev/null +++ b/putty/WINDOWS/MSVC/PLINK/PLINK.DSP @@ -0,0 +1,418 @@ +# Microsoft Developer Studio Project File - Name="plink" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=plink - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "plink.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "plink.mak" CFG="plink - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "plink - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "plink - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "plink - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:console /machine:I386 +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:console /machine:I386 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "plink - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "plink - Win32 Release" +# Name "plink - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\be_all_s.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\cmdline.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\cproxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ldisc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\logging.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\pgssapi.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\pinger.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\portfwd.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\proxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\raw.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\rlogin.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\settings.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssh.c + +!IF "$(CFG)" == "plink - Win32 Release" + +!ELSEIF "$(CFG)" == "plink - Win32 Debug" + +# ADD CPP /Zi + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshaes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssharcf.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshblowf.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshbn.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshcrc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshcrcda.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdh.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdss.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgssc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshmd5.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshpubk.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrand.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrsa.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh256.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh512.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsha.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshzlib.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\telnet.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\timing.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\version.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\wildcard.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wincons.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\windefs.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wingss.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhandl.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winmisc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnet.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnoise.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnojmp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winpgntc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winplink.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winproxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winser.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstore.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wintime.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winx11.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\x11fwd.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\charset\charset.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\int64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\ldisc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\macosx\osx.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\network.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\pgssapi.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\proxy.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\putty.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttymem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttyps.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssh.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgss.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgssc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\storage.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\terminal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\unix\unix.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\rcstuff.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhelp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstuff.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=..\..\..\windows\plink.rc +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\putty.ico +# End Source File +# End Group +# End Target +# End Project diff --git a/putty/WINDOWS/MSVC/PSCP/PSCP.DSP b/putty/WINDOWS/MSVC/PSCP/PSCP.DSP new file mode 100644 index 0000000..763849b --- /dev/null +++ b/putty/WINDOWS/MSVC/PSCP/PSCP.DSP @@ -0,0 +1,406 @@ +# Microsoft Developer Studio Project File - Name="pscp" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=pscp - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "pscp.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "pscp.mak" CFG="pscp - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "pscp - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "pscp - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "pscp - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:console /machine:I386 +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:console /machine:I386 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "pscp - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "pscp - Win32 Release" +# Name "pscp - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\be_none.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\cmdline.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\cproxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\int64.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\logging.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\pgssapi.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\pinger.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\portfwd.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\proxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\pscp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\settings.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sftp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssh.c + +!IF "$(CFG)" == "pscp - Win32 Release" + +!ELSEIF "$(CFG)" == "pscp - Win32 Debug" + +# ADD CPP /Zi + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshaes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssharcf.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshblowf.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshbn.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshcrc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshcrcda.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdh.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdss.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgssc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshmd5.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshpubk.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrand.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrsa.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh256.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh512.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsha.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshzlib.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\timing.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\version.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\wildcard.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wincons.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\windefs.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wingss.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhandl.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winmisc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnet.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnoise.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnojmp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winpgntc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winproxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winsftp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstore.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wintime.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\x11fwd.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\charset\charset.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\int64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\macosx\osx.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\network.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\pgssapi.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\proxy.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\psftp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\putty.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttymem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttyps.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\sftp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssh.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgss.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgssc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\storage.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\unix\unix.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\rcstuff.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhelp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstuff.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=..\..\..\windows\pscp.ico +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\pscp.rc +# End Source File +# End Group +# End Target +# End Project diff --git a/putty/WINDOWS/MSVC/PSFTP/PSFTP.DSP b/putty/WINDOWS/MSVC/PSFTP/PSFTP.DSP new file mode 100644 index 0000000..af4ce25 --- /dev/null +++ b/putty/WINDOWS/MSVC/PSFTP/PSFTP.DSP @@ -0,0 +1,406 @@ +# Microsoft Developer Studio Project File - Name="psftp" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=psftp - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "psftp.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "psftp.mak" CFG="psftp - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "psftp - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "psftp - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "psftp - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:console /machine:I386 +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:console /machine:I386 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "psftp - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "psftp - Win32 Release" +# Name "psftp - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\be_none.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\cmdline.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\cproxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\int64.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\logging.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\pgssapi.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\pinger.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\portfwd.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\proxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\psftp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\settings.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sftp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssh.c + +!IF "$(CFG)" == "psftp - Win32 Release" + +!ELSEIF "$(CFG)" == "psftp - Win32 Debug" + +# ADD CPP /Zi + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshaes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssharcf.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshblowf.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshbn.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshcrc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshcrcda.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdh.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdss.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgssc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshmd5.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshpubk.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrand.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrsa.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh256.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh512.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsha.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshzlib.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\timing.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\version.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\wildcard.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wincons.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\windefs.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wingss.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhandl.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winmisc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnet.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnoise.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnojmp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winpgntc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winproxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winsftp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstore.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wintime.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\x11fwd.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\charset\charset.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\int64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\macosx\osx.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\network.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\pgssapi.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\proxy.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\psftp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\putty.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttymem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttyps.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\sftp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssh.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgss.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgssc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\storage.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\unix\unix.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\rcstuff.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhelp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstuff.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=..\..\..\windows\pscp.ico +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\psftp.rc +# End Source File +# End Group +# End Target +# End Project diff --git a/putty/WINDOWS/MSVC/PUTTY.DSW b/putty/WINDOWS/MSVC/PUTTY.DSW new file mode 100644 index 0000000..c70ae89 --- /dev/null +++ b/putty/WINDOWS/MSVC/PUTTY.DSW @@ -0,0 +1,35 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "pageant"=".\pageant\pageant.dsp" - Package Owner=<4> +Project: "plink"=".\plink\plink.dsp" - Package Owner=<4> +Project: "pscp"=".\pscp\pscp.dsp" - Package Owner=<4> +Project: "psftp"=".\psftp\psftp.dsp" - Package Owner=<4> +Project: "putty"=".\putty\putty.dsp" - Package Owner=<4> +Project: "puttygen"=".\puttygen\puttygen.dsp" - Package Owner=<4> +Project: "puttytel"=".\puttytel\puttytel.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/putty/WINDOWS/MSVC/PUTTY/PUTTY.DSP b/putty/WINDOWS/MSVC/PUTTY/PUTTY.DSP new file mode 100644 index 0000000..69a2480 --- /dev/null +++ b/putty/WINDOWS/MSVC/PUTTY/PUTTY.DSP @@ -0,0 +1,486 @@ +# Microsoft Developer Studio Project File - Name="putty" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=putty - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "putty.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "putty.mak" CFG="putty - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "putty - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "putty - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "putty - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:windows /machine:I386 +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:windows /machine:I386 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "putty - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "putty - Win32 Release" +# Name "putty - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\be_all_s.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\cmdline.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\config.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\cproxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\dialog.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ldisc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ldiscucs.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\logging.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\minibidi.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\pgssapi.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\pinger.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\portfwd.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\proxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\raw.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\rlogin.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sercfg.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\settings.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssh.c + +!IF "$(CFG)" == "putty - Win32 Release" + +!ELSEIF "$(CFG)" == "putty - Win32 Debug" + +# ADD CPP /Zi + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshaes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssharcf.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshblowf.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshbn.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshcrc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshcrcda.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdh.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdss.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgssc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshmd5.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshpubk.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrand.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrsa.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh256.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh512.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsha.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshzlib.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\telnet.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\terminal.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\timing.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\version.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\wcwidth.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\wildcard.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\sizetip.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wincfg.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winctrls.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\windefs.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\windlg.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\window.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wingss.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhandl.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhelp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winjump.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winmisc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnet.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnoise.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winpgntc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winprint.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winproxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winser.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstore.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wintime.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winucs.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winutils.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winx11.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\x11fwd.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\charset\charset.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\dialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\int64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\ldisc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\macosx\osx.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\network.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\pgssapi.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\proxy.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\putty.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttymem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttyps.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssh.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgss.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshgssc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\storage.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\terminal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\unix\unix.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\rcstuff.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\win_res.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhelp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstuff.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=..\..\..\windows\putty.ico +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\putty.rc +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\puttycfg.ico +# End Source File +# End Group +# End Target +# End Project diff --git a/putty/WINDOWS/MSVC/PUTTYGEN/PUTTYGEN.DSP b/putty/WINDOWS/MSVC/PUTTYGEN/PUTTYGEN.DSP new file mode 100644 index 0000000..6ee999c --- /dev/null +++ b/putty/WINDOWS/MSVC/PUTTYGEN/PUTTYGEN.DSP @@ -0,0 +1,289 @@ +# Microsoft Developer Studio Project File - Name="puttygen" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=puttygen - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "puttygen.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "puttygen.mak" CFG="puttygen - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "puttygen - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "puttygen - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "puttygen - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:windows /machine:I386 +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:windows /machine:I386 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "puttygen - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "puttygen - Win32 Release" +# Name "puttygen - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\import.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\notiming.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshaes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshbn.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdes.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdss.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshdssg.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshmd5.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshprime.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshpubk.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrand.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrsa.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshrsag.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh256.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsh512.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sshsha.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\version.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winctrls.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhelp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winmisc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnoise.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnojmp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winpgen.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstore.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wintime.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winutils.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\charset\charset.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\dialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\int64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\macosx\osx.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\network.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\putty.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttymem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttyps.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssh.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\storage.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\unix\unix.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\rcstuff.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhelp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstuff.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=..\..\..\windows\puttygen.ico +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\puttygen.rc +# End Source File +# End Group +# End Target +# End Project diff --git a/putty/WINDOWS/MSVC/PUTTYTEL/PUTTYTEL.DSP b/putty/WINDOWS/MSVC/PUTTYTEL/PUTTYTEL.DSP new file mode 100644 index 0000000..cd74ae6 --- /dev/null +++ b/putty/WINDOWS/MSVC/PUTTYTEL/PUTTYTEL.DSP @@ -0,0 +1,361 @@ +# Microsoft Developer Studio Project File - Name="puttytel" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=puttytel - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "puttytel.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "puttytel.mak" CFG="puttytel - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "puttytel - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "puttytel - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "puttytel - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:windows /machine:I386 +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:windows /machine:I386 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "puttytel - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\./" /I "..\..\..\charset/" /I "..\..\..\windows/" /I "..\..\..\unix/" /I "..\..\..\macosx/" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 advapi32.lib comctl32.lib comdlg32.lib gdi32.lib imm32.lib ole32.lib shell32.lib user32.lib winmm.lib winspool.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "puttytel - Win32 Release" +# Name "puttytel - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\be_nos_s.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\cmdline.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\config.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\dialog.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ldisc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\ldiscucs.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\logging.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\minibidi.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\nocproxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\nogss.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\pinger.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\proxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\raw.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\rlogin.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\sercfg.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\settings.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\telnet.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\terminal.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\timing.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\version.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\wcwidth.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\sizetip.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wincfg.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winctrls.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\windefs.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\windlg.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\window.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhandl.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhelp.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winjump.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winmisc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winnet.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winprint.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winproxy.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winser.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstore.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\wintime.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winucs.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winutils.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\charset\charset.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\dialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\int64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\ldisc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\macosx\osx.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\misc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\network.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\proxy.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\putty.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttymem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\puttyps.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\ssh.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\storage.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\terminal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\tree234.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\unix\unix.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\rcstuff.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\win_res.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winhelp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\winstuff.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=..\..\..\windows\putty.ico +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\puttycfg.ico +# End Source File +# Begin Source File + +SOURCE=..\..\..\windows\puttytel.rc +# End Source File +# End Group +# End Target +# End Project diff --git a/putty/WINDOWS/PAGEANT.ICO b/putty/WINDOWS/PAGEANT.ICO new file mode 100644 index 0000000..93274fd Binary files /dev/null and b/putty/WINDOWS/PAGEANT.ICO differ diff --git a/putty/WINDOWS/PAGEANT.MFT b/putty/WINDOWS/PAGEANT.MFT new file mode 100644 index 0000000..3934c8d --- /dev/null +++ b/putty/WINDOWS/PAGEANT.MFT @@ -0,0 +1,31 @@ + + + + + PuTTY SSH authentication agent + + + + + + + + + + true + + + diff --git a/putty/WINDOWS/PAGEANT.RC b/putty/WINDOWS/PAGEANT.RC new file mode 100644 index 0000000..f6422f3 --- /dev/null +++ b/putty/WINDOWS/PAGEANT.RC @@ -0,0 +1,95 @@ +/* + * Windows resources for Pageant. + */ + +#include "rcstuff.h" + +#define APPNAME "Pageant" +#define APPDESC "PuTTY SSH authentication agent" + +200 ICON "pageant.ico" +201 ICON "pageants.ico" + +210 DIALOG DISCARDABLE 0, 0, 140, 60 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Pageant: Enter Passphrase" +FONT 8, "MS Shell Dlg" +BEGIN + CTEXT "Enter passphrase for key", 100, 10, 6, 120, 8 + CTEXT "", 101, 10, 16, 120, 8 + EDITTEXT 102, 10, 26, 120, 12, ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "O&K", IDOK, 20, 42, 40, 14 + PUSHBUTTON "&Cancel", IDCANCEL, 80, 42, 40, 14 +END + +211 DIALOG DISCARDABLE 0, 0, 330, 200 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Pageant Key List" +FONT 8, "MS Shell Dlg" +BEGIN + LISTBOX 100, 10, 10, 310, 155, + LBS_EXTENDEDSEL | LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "&Add Key", 101, 75, 162, 60, 14 + PUSHBUTTON "&Remove Key", 102, 195, 162, 60, 14 + PUSHBUTTON "&Help", 103, 10, 182, 50, 14 + DEFPUSHBUTTON "&Close", IDOK, 270, 182, 50, 14 +END + +/* Accelerators used: cl */ +213 DIALOG DISCARDABLE 140, 40, 136, 70 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About Pageant" +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "&Close", IDOK, 82, 52, 48, 14 + PUSHBUTTON "View &Licence", 101, 6, 52, 70, 14 + CTEXT "Pageant", 102, 10, 6, 120, 8 + CTEXT "", 100, 10, 16, 120, 16 + CTEXT "\251 1997-2011 Simon Tatham. All rights reserved.", + 103, 10, 34, 120, 16 +END + +/* No accelerators used */ +214 DIALOG DISCARDABLE 50, 50, 226, 263 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Licence" +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "OK", IDOK, 98, 243, 44, 14 + + LTEXT "Copyright \251 1997-2011 Simon Tatham", 1000, 10, 10, 206, 8 + + LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8 + LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8 + LTEXT "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa,", 1003, 10, 42, 206, 8 + LTEXT "Markus Kuhn, Colin Watson, and CORE SDI S.A.", 1004, 10, 50, 206, 8 + + LTEXT "Permission is hereby granted, free of charge, to any person", 1005, 10, 66, 206, 8 + LTEXT "obtaining a copy of this software and associated documentation", 1006, 10, 74, 206, 8 + LTEXT "files (the ""Software""), to deal in the Software without restriction,", 1007, 10, 82, 206, 8 + LTEXT "including without limitation the rights to use, copy, modify, merge,", 1008, 10, 90, 206, 8 + LTEXT "publish, distribute, sublicense, and/or sell copies of the Software,", 1009, 10, 98, 206, 8 + LTEXT "and to permit persons to whom the Software is furnished to do so,", 1010, 10, 106, 206, 8 + LTEXT "subject to the following conditions:", 1011, 10, 114, 206, 8 + + LTEXT "The above copyright notice and this permission notice shall be", 1012, 10, 130, 206, 8 + LTEXT "included in all copies or substantial portions of the Software.", 1013, 10, 138, 206, 8 + + LTEXT "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT", 1014, 10, 154, 206, 8 + LTEXT "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,", 1015, 10, 162, 206, 8 + LTEXT "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", 1016, 10, 170, 206, 8 + LTEXT "MERCHANTABILITY, FITNESS FOR A PARTICULAR", 1017, 10, 178, 206, 8 + LTEXT "PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", 1018, 10, 186, 206, 8 + LTEXT "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES", 1019, 10, 194, 206, 8 + LTEXT "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,", 1020, 10, 202, 206, 8 + LTEXT "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN", 1021, 10, 210, 206, 8 + LTEXT "CONNECTION WITH THE SOFTWARE OR THE USE OR", 1022, 10, 218, 206, 8 + LTEXT "OTHER DEALINGS IN THE SOFTWARE.", 1023, 10, 226, 206, 8 + +END + +#include "version.rc2" + +#ifndef NO_MANIFESTS +1 RT_MANIFEST "pageant.mft" +#endif /* NO_MANIFESTS */ diff --git a/putty/WINDOWS/PAGEANTS.ICO b/putty/WINDOWS/PAGEANTS.ICO new file mode 100644 index 0000000..28a6d01 Binary files /dev/null and b/putty/WINDOWS/PAGEANTS.ICO differ diff --git a/putty/WINDOWS/PLINK.RC b/putty/WINDOWS/PLINK.RC new file mode 100644 index 0000000..7dd7ce9 --- /dev/null +++ b/putty/WINDOWS/PLINK.RC @@ -0,0 +1,8 @@ +#include "rcstuff.h" + +#define APPNAME "Plink" +#define APPDESC "Command-line SSH, Telnet, and Rlogin client" + +200 ICON "putty.ico" + +#include "version.rc2" diff --git a/putty/WINDOWS/PSCP.ICO b/putty/WINDOWS/PSCP.ICO new file mode 100644 index 0000000..2b3c1be Binary files /dev/null and b/putty/WINDOWS/PSCP.ICO differ diff --git a/putty/WINDOWS/PSCP.RC b/putty/WINDOWS/PSCP.RC new file mode 100644 index 0000000..54617e1 --- /dev/null +++ b/putty/WINDOWS/PSCP.RC @@ -0,0 +1,8 @@ +#include "rcstuff.h" + +#define APPNAME "PSCP" +#define APPDESC "Command-line SCP/SFTP client" + +200 ICON "pscp.ico" + +#include "version.rc2" diff --git a/putty/WINDOWS/PSFTP.RC b/putty/WINDOWS/PSFTP.RC new file mode 100644 index 0000000..c3efc6f --- /dev/null +++ b/putty/WINDOWS/PSFTP.RC @@ -0,0 +1,8 @@ +#include "rcstuff.h" + +#define APPNAME "PSFTP" +#define APPDESC "Command-line interactive SFTP client" + +200 ICON "pscp.ico" + +#include "version.rc2" diff --git a/putty/WINDOWS/PUTTY.ICO b/putty/WINDOWS/PUTTY.ICO new file mode 100644 index 0000000..fcd641a Binary files /dev/null and b/putty/WINDOWS/PUTTY.ICO differ diff --git a/putty/WINDOWS/PUTTY.ISS b/putty/WINDOWS/PUTTY.ISS new file mode 100644 index 0000000..fb979ca --- /dev/null +++ b/putty/WINDOWS/PUTTY.ISS @@ -0,0 +1,106 @@ +; -*- no -*- +; $Id: putty.iss 9202 2011-07-12 18:26:18Z simon $ +; +; -- Inno Setup installer script for PuTTY and its related tools. +; Last tested with Inno Setup 5.0.8. +; +; TODO for future releases: +; +; - It might be nice to have an option to add PSCP, Plink and PSFTP to +; the PATH. See wish `installer-addpath'. +; +; - Maybe a "custom" installation might be useful? Hassle with +; UninstallDisplayIcon, though. + +[Setup] +AppName=PuTTY +AppVerName=PuTTY version 0.61 +VersionInfoTextVersion=Release 0.61 +AppVersion=0.61 +VersionInfoVersion=0.61.0.0 +AppPublisher=Simon Tatham +AppPublisherURL=http://www.chiark.greenend.org.uk/~sgtatham/putty/ +AppReadmeFile={app}\README.txt +DefaultDirName={pf}\PuTTY +DefaultGroupName=PuTTY +SetupIconFile=puttyins.ico +UninstallDisplayIcon={app}\putty.exe +ChangesAssociations=yes +;ChangesEnvironment=yes -- when PATH munging is sorted (probably) +Compression=zip/9 +AllowNoIcons=yes + +[Files] +; We flag all files with "restartreplace" et al primarily for the benefit +; of unattended un/installations/upgrades, when the user is running one +; of the apps at a time. Without it, the operation will fail noisily in +; this situation. +; This does mean that the user will be prompted to restart their machine +; if any of the files _were_ open during installation (or, if /VERYSILENT +; is used, the machine will be restarted automatically!). The /NORESTART +; flag avoids this. +; It might be nicer to have a "no worries, replace the file next time you +; reboot" option, but the developers have no interest in adding one. +; NB: apparently, using long (non-8.3) filenames with restartreplace is a +; bad idea. (Not that we do.) +Source: "putty.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete +Source: "pageant.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete +Source: "puttygen.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete +Source: "pscp.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete +Source: "psftp.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete +Source: "plink.exe"; DestDir: "{app}"; Flags: promptifolder replacesameversion restartreplace uninsrestartdelete +Source: "website.url"; DestDir: "{app}"; Flags: restartreplace uninsrestartdelete +Source: "..\doc\putty.chm"; DestDir: "{app}"; Flags: restartreplace uninsrestartdelete +Source: "..\doc\putty.hlp"; DestDir: "{app}"; Flags: restartreplace uninsrestartdelete +Source: "..\doc\putty.cnt"; DestDir: "{app}"; Flags: restartreplace uninsrestartdelete +Source: "..\LICENCE"; DestDir: "{app}"; Flags: restartreplace uninsrestartdelete +Source: "README.txt"; DestDir: "{app}"; Flags: isreadme restartreplace uninsrestartdelete + +[Icons] +Name: "{group}\PuTTY"; Filename: "{app}\putty.exe" +; We have to fall back from the .chm to the older .hlp file on some Windows +; versions. +Name: "{group}\PuTTY Manual"; Filename: "{app}\putty.chm"; MinVersion: 4.1,5.0 +Name: "{group}\PuTTY Manual"; Filename: "{app}\putty.hlp"; OnlyBelowVersion: 4.1,5.0 +Name: "{group}\PuTTY Web Site"; Filename: "{app}\website.url" +Name: "{group}\PSFTP"; Filename: "{app}\psftp.exe" +Name: "{group}\PuTTYgen"; Filename: "{app}\puttygen.exe" +Name: "{group}\Pageant"; Filename: "{app}\pageant.exe" +Name: "{commondesktop}\PuTTY"; Filename: "{app}\putty.exe"; Tasks: desktopicon\common +Name: "{userdesktop}\PuTTY"; Filename: "{app}\putty.exe"; Tasks: desktopicon\user +; Putting this in {commonappdata} doesn't seem to work, on 98SE at least. +Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\PuTTY"; Filename: "{app}\putty.exe"; Tasks: quicklaunchicon + +[Tasks] +Name: desktopicon; Description: "Create a &desktop icon for PuTTY"; GroupDescription: "Additional icons:"; Flags: unchecked +Name: desktopicon\common; Description: "For all users"; GroupDescription: "Additional icons:"; Flags: exclusive unchecked +Name: desktopicon\user; Description: "For the current user only"; GroupDescription: "Additional icons:"; Flags: exclusive unchecked +Name: quicklaunchicon; Description: "Create a &Quick Launch icon for PuTTY (current user only)"; GroupDescription: "Additional icons:"; Flags: unchecked +Name: associate; Description: "&Associate .PPK files (PuTTY Private Key) with Pageant and PuTTYgen"; GroupDescription: "Other tasks:" + +[Registry] +Root: HKCR; Subkey: ".ppk"; ValueType: string; ValueName: ""; ValueData: "PuTTYPrivateKey"; Flags: uninsdeletevalue; Tasks: associate +Root: HKCR; Subkey: "PuTTYPrivateKey"; ValueType: string; ValueName: ""; ValueData: "PuTTY Private Key File"; Flags: uninsdeletekey; Tasks: associate +Root: HKCR; Subkey: "PuTTYPrivateKey\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\pageant.exe,0"; Tasks: associate +Root: HKCR; Subkey: "PuTTYPrivateKey\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\pageant.exe"" ""%1"""; Tasks: associate +Root: HKCR; Subkey: "PuTTYPrivateKey\shell\edit"; ValueType: string; ValueName: ""; ValueData: "&Edit"; Tasks: associate +Root: HKCR; Subkey: "PuTTYPrivateKey\shell\edit\command"; ValueType: string; ValueName: ""; ValueData: """{app}\puttygen.exe"" ""%1"""; Tasks: associate +; Add to PATH on NT-class OS? + +[UninstallRun] +; -cleanup-during-uninstall is an undocumented option that tailors the +; message displayed. +; XXX: it would be nice if this task weren't run if a silent uninstall is +; requested, but "skipifsilent" is disallowed. +Filename: "{app}\putty.exe"; Parameters: "-cleanup-during-uninstall"; RunOnceId: "PuTTYCleanup"; StatusMsg: "Cleaning up saved sessions etc (optional)..." + +[Messages] +; Since it's possible for the user to be asked to restart their computer, +; we should override the default messages to explain exactly why, so they +; can make an informed decision. (Especially as 95% of users won't need or +; want to restart; see rant above.) +FinishedRestartLabel=One or more [name] programs are still running. Setup will not replace these program files until you restart your computer. Would you like to restart now? +; This message is popped up in a message box on a /SILENT install. +FinishedRestartMessage=One or more [name] programs are still running.%nSetup will not replace these program files until you restart your computer.%n%nWould you like to restart now? +; ...and this comes up if you try to uninstall. +UninstalledAndNeedsRestart=One or more %1 programs are still running.%nThe program files will not be removed until your computer is restarted.%n%nWould you like to restart now? diff --git a/putty/WINDOWS/PUTTY.MFT b/putty/WINDOWS/PUTTY.MFT new file mode 100644 index 0000000..7eed30c --- /dev/null +++ b/putty/WINDOWS/PUTTY.MFT @@ -0,0 +1,31 @@ + + + + + A network client and terminal emulator + + + + + + + + + + true + + + diff --git a/putty/WINDOWS/PUTTY.RC b/putty/WINDOWS/PUTTY.RC new file mode 100644 index 0000000..77c9f21 --- /dev/null +++ b/putty/WINDOWS/PUTTY.RC @@ -0,0 +1,10 @@ +#include "rcstuff.h" + +#define APPNAME "PuTTY" +#define APPDESC "SSH, Telnet and Rlogin client" + +#include "win_res.rc2" + +#ifndef NO_MANIFESTS +1 RT_MANIFEST "putty.mft" +#endif /* NO_MANIFESTS */ diff --git a/putty/WINDOWS/PUTTYCFG.ICO b/putty/WINDOWS/PUTTYCFG.ICO new file mode 100644 index 0000000..bb32be8 Binary files /dev/null and b/putty/WINDOWS/PUTTYCFG.ICO differ diff --git a/putty/WINDOWS/PUTTYGEN.ICO b/putty/WINDOWS/PUTTYGEN.ICO new file mode 100644 index 0000000..ddc8305 Binary files /dev/null and b/putty/WINDOWS/PUTTYGEN.ICO differ diff --git a/putty/WINDOWS/PUTTYGEN.MFT b/putty/WINDOWS/PUTTYGEN.MFT new file mode 100644 index 0000000..5f394a1 --- /dev/null +++ b/putty/WINDOWS/PUTTYGEN.MFT @@ -0,0 +1,31 @@ + + + + + SSH key generator for PuTTY + + + + + + + + + + true + + + diff --git a/putty/WINDOWS/PUTTYGEN.RC b/putty/WINDOWS/PUTTYGEN.RC new file mode 100644 index 0000000..1cea261 --- /dev/null +++ b/putty/WINDOWS/PUTTYGEN.RC @@ -0,0 +1,88 @@ +/* + * Windows resources for PuTTYgen. + */ + +#include "rcstuff.h" + +#define APPNAME "PuTTYgen" +#define APPDESC "PuTTY SSH key generation utility" + +200 ICON "puttygen.ico" + +201 DIALOG DISCARDABLE 0, 0, 318, 270 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Key Generator" +FONT 8, "MS Shell Dlg" +BEGIN +END + +210 DIALOG DISCARDABLE 0, 0, 140, 60 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTYgen: Enter Passphrase" +FONT 8, "MS Shell Dlg" +BEGIN + CTEXT "Enter passphrase for key", 100, 10, 6, 120, 8 + CTEXT "", 101, 10, 16, 120, 8 + EDITTEXT 102, 10, 26, 120, 12, ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "O&K", IDOK, 20, 42, 40, 14 + PUSHBUTTON "&Cancel", IDCANCEL, 80, 42, 40, 14 +END + +/* Accelerators used: cl */ +213 DIALOG DISCARDABLE 140, 40, 136, 70 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About PuTTYgen" +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "&Close", IDOK, 82, 52, 48, 14 + PUSHBUTTON "View &Licence", 101, 6, 52, 70, 14 + CTEXT "PuTTYgen", 102, 10, 6, 120, 8 + CTEXT "", 100, 10, 16, 120, 16 + CTEXT "\251 1997-2011 Simon Tatham. All rights reserved.", + 103, 10, 34, 120, 16 +END + +/* No accelerators used */ +214 DIALOG DISCARDABLE 50, 50, 226, 263 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Licence" +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "OK", IDOK, 98, 243, 44, 14 + + LTEXT "Copyright \251 1997-2011 Simon Tatham", 1000, 10, 10, 206, 8 + + LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8 + LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8 + LTEXT "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa,", 1003, 10, 42, 206, 8 + LTEXT "Markus Kuhn, Colin Watson, and CORE SDI S.A.", 1004, 10, 50, 206, 8 + + LTEXT "Permission is hereby granted, free of charge, to any person", 1005, 10, 66, 206, 8 + LTEXT "obtaining a copy of this software and associated documentation", 1006, 10, 74, 206, 8 + LTEXT "files (the ""Software""), to deal in the Software without restriction,", 1007, 10, 82, 206, 8 + LTEXT "including without limitation the rights to use, copy, modify, merge,", 1008, 10, 90, 206, 8 + LTEXT "publish, distribute, sublicense, and/or sell copies of the Software,", 1009, 10, 98, 206, 8 + LTEXT "and to permit persons to whom the Software is furnished to do so,", 1010, 10, 106, 206, 8 + LTEXT "subject to the following conditions:", 1011, 10, 114, 206, 8 + + LTEXT "The above copyright notice and this permission notice shall be", 1012, 10, 130, 206, 8 + LTEXT "included in all copies or substantial portions of the Software.", 1013, 10, 138, 206, 8 + + LTEXT "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT", 1014, 10, 154, 206, 8 + LTEXT "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,", 1015, 10, 162, 206, 8 + LTEXT "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", 1016, 10, 170, 206, 8 + LTEXT "MERCHANTABILITY, FITNESS FOR A PARTICULAR", 1017, 10, 178, 206, 8 + LTEXT "PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", 1018, 10, 186, 206, 8 + LTEXT "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES", 1019, 10, 194, 206, 8 + LTEXT "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,", 1020, 10, 202, 206, 8 + LTEXT "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN", 1021, 10, 210, 206, 8 + LTEXT "CONNECTION WITH THE SOFTWARE OR THE USE OR", 1022, 10, 218, 206, 8 + LTEXT "OTHER DEALINGS IN THE SOFTWARE.", 1023, 10, 226, 206, 8 + +END + +#include "version.rc2" + +#ifndef NO_MANIFESTS +1 RT_MANIFEST "puttygen.mft" +#endif /* NO_MANIFESTS */ diff --git a/putty/WINDOWS/PUTTYINS.ICO b/putty/WINDOWS/PUTTYINS.ICO new file mode 100644 index 0000000..2bd92c6 Binary files /dev/null and b/putty/WINDOWS/PUTTYINS.ICO differ diff --git a/putty/WINDOWS/PUTTYTEL.RC b/putty/WINDOWS/PUTTYTEL.RC new file mode 100644 index 0000000..86964b6 --- /dev/null +++ b/putty/WINDOWS/PUTTYTEL.RC @@ -0,0 +1,11 @@ +#include "rcstuff.h" + +#define APPNAME "PuTTYtel" +#define APPDESC "Telnet and Rlogin client" + +#include "win_res.rc2" + +#ifndef NO_MANIFESTS +/* FIXME */ +1 RT_MANIFEST "putty.mft" +#endif /* NO_MANIFESTS */ diff --git a/putty/WINDOWS/RCSTUFF.H b/putty/WINDOWS/RCSTUFF.H new file mode 100644 index 0000000..74280e5 --- /dev/null +++ b/putty/WINDOWS/RCSTUFF.H @@ -0,0 +1,50 @@ +/* + * Miscellaneous stuff to include in all .rc files. + */ + +#ifndef PUTTY_RCSTUFF_H +#define PUTTY_RCSTUFF_H + +#ifdef __LCC__ +#include +#else + +/* Some compilers, like Borland, don't have winresrc.h */ +#ifndef NO_WINRESRC_H +#ifndef MSVC4 +#include +#else +#include +#endif +#endif + +#endif /* end #ifdef __LCC__ */ + +/* Some systems don't define this, so I do it myself if necessary */ +#ifndef TCS_MULTILINE +#define TCS_MULTILINE 0x0200 +#endif + +/* Likewise */ +#ifndef RT_MANIFEST +#define RT_MANIFEST 24 +#endif + +/* LCC is the offender here. */ +#ifndef VS_FF_DEBUG +#define VS_FF_DEBUG 1 +#endif +#ifndef VS_FF_PRERELEASE +#define VS_FF_PRERELEASE 2 +#endif +#ifndef VS_FF_PRIVATEBUILD +#define VS_FF_PRIVATEBUILD 8 +#endif +#ifndef VOS__WINDOWS32 +#define VOS__WINDOWS32 4 +#endif +#ifndef VFT_APP +#define VFT_APP 1 +#endif + +#endif /* PUTTY_RCSTUFF_H */ diff --git a/putty/WINDOWS/README.TXT b/putty/WINDOWS/README.TXT new file mode 100644 index 0000000..7d35728 --- /dev/null +++ b/putty/WINDOWS/README.TXT @@ -0,0 +1,40 @@ +PuTTY README +============ + +This is the README file for the PuTTY installer distribution. If +you're reading this, you've probably just run our installer and +installed PuTTY on your system. + +What should I do next? +---------------------- + +If you want to use PuTTY to connect to other computers, or use PSFTP +to transfer files, you should just be able to run them from the +Start menu. + +If you want to use the command-line-only file transfer utility PSCP, +you will probably want to put the PuTTY installation directory on +your PATH. How you do this depends on your version of Windows. On +Windows NT, 2000, and XP, you can set it using Control Panel > System; +on Windows 95, 98, and Me, you will need to edit AUTOEXEC.BAT. Consult +your Windows manuals for details. + +Some versions of Windows will refuse to run HTML Help files (.CHM) +if they are installed on a network drive. If you have installed +PuTTY on a network drive, you might want to check that the help file +works properly. If not, see http://support.microsoft.com/kb/896054 +for information on how to solve this problem. + +What do I do if it doesn't work? +-------------------------------- + +The PuTTY home web site is + + http://www.chiark.greenend.org.uk/~sgtatham/putty/ + +Here you will find our list of known bugs and pending feature +requests. If your problem is not listed in there, or in the FAQ, or +in the manuals, read the Feedback page to find out how to report +bugs to us. PLEASE read the Feedback page carefully: it is there to +save you time as well as us. Do not send us one-line bug reports +telling us `it doesn't work'. diff --git a/putty/WINDOWS/SIZETIP.C b/putty/WINDOWS/SIZETIP.C new file mode 100644 index 0000000..951c569 --- /dev/null +++ b/putty/WINDOWS/SIZETIP.C @@ -0,0 +1,194 @@ +/* + * sizetip.c - resize tips for PuTTY(tel) terminal window. + */ + +#include +#include +#include + +#include "putty.h" + +static ATOM tip_class = 0; + +static HFONT tip_font; +static COLORREF tip_bg; +static COLORREF tip_text; + +static LRESULT CALLBACK SizeTipWndProc(HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam) +{ + + switch (nMsg) { + case WM_ERASEBKGND: + return TRUE; + + case WM_PAINT: + { + HBRUSH hbr; + HGDIOBJ holdbr; + RECT cr; + int wtlen; + LPTSTR wt; + HDC hdc; + + PAINTSTRUCT ps; + hdc = BeginPaint(hWnd, &ps); + + SelectObject(hdc, tip_font); + SelectObject(hdc, GetStockObject(BLACK_PEN)); + + hbr = CreateSolidBrush(tip_bg); + holdbr = SelectObject(hdc, hbr); + + GetClientRect(hWnd, &cr); + Rectangle(hdc, cr.left, cr.top, cr.right, cr.bottom); + + wtlen = GetWindowTextLength(hWnd); + wt = (LPTSTR) snewn(wtlen + 1, TCHAR); + GetWindowText(hWnd, wt, wtlen + 1); + + SetTextColor(hdc, tip_text); + SetBkColor(hdc, tip_bg); + + TextOut(hdc, cr.left + 3, cr.top + 3, wt, wtlen); + + sfree(wt); + + SelectObject(hdc, holdbr); + DeleteObject(hbr); + + EndPaint(hWnd, &ps); + } + return 0; + + case WM_NCHITTEST: + return HTTRANSPARENT; + + case WM_DESTROY: + DeleteObject(tip_font); + tip_font = NULL; + break; + + case WM_SETTEXT: + { + LPCTSTR str = (LPCTSTR) lParam; + SIZE sz; + HDC hdc = CreateCompatibleDC(NULL); + + SelectObject(hdc, tip_font); + GetTextExtentPoint32(hdc, str, _tcslen(str), &sz); + + SetWindowPos(hWnd, NULL, 0, 0, sz.cx + 6, sz.cy + 6, + SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); + InvalidateRect(hWnd, NULL, FALSE); + + DeleteDC(hdc); + } + break; + } + + return DefWindowProc(hWnd, nMsg, wParam, lParam); +} + +static HWND tip_wnd = NULL; +static int tip_enabled = 0; + +void UpdateSizeTip(HWND src, int cx, int cy) +{ + TCHAR str[32]; + + if (!tip_enabled) + return; + + if (!tip_wnd) { + NONCLIENTMETRICS nci; + + /* First make sure the window class is registered */ + + if (!tip_class) { + WNDCLASS wc; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = SizeTipWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = hinst; + wc.hIcon = NULL; + wc.hCursor = NULL; + wc.hbrBackground = NULL; + wc.lpszMenuName = NULL; + wc.lpszClassName = "SizeTipClass"; + + tip_class = RegisterClass(&wc); + } +#if 0 + /* Default values based on Windows Standard color scheme */ + + tip_font = GetStockObject(SYSTEM_FONT); + tip_bg = RGB(255, 255, 225); + tip_text = RGB(0, 0, 0); +#endif + + /* Prepare other GDI objects and drawing info */ + + tip_bg = GetSysColor(COLOR_INFOBK); + tip_text = GetSysColor(COLOR_INFOTEXT); + + memset(&nci, 0, sizeof(NONCLIENTMETRICS)); + nci.cbSize = sizeof(NONCLIENTMETRICS); + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, + sizeof(NONCLIENTMETRICS), &nci, 0); + tip_font = CreateFontIndirect(&nci.lfStatusFont); + } + + /* Generate the tip text */ + + sprintf(str, "%dx%d", cx, cy); + + if (!tip_wnd) { + HDC hdc; + SIZE sz; + RECT wr; + int ix, iy; + + /* calculate the tip's size */ + + hdc = CreateCompatibleDC(NULL); + GetTextExtentPoint32(hdc, str, _tcslen(str), &sz); + DeleteDC(hdc); + + GetWindowRect(src, &wr); + + ix = wr.left; + if (ix < 16) + ix = 16; + + iy = wr.top - sz.cy; + if (iy < 16) + iy = 16; + + /* Create the tip window */ + + tip_wnd = + CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST, + MAKEINTRESOURCE(tip_class), str, WS_POPUP, ix, + iy, sz.cx, sz.cy, NULL, NULL, hinst, NULL); + + ShowWindow(tip_wnd, SW_SHOWNOACTIVATE); + + } else { + + /* Tip already exists, just set the text */ + + SetWindowText(tip_wnd, str); + } +} + +void EnableSizeTip(int bEnable) +{ + if (tip_wnd && !bEnable) { + DestroyWindow(tip_wnd); + tip_wnd = NULL; + } + + tip_enabled = bEnable; +} diff --git a/putty/WINDOWS/VERSION.RC2 b/putty/WINDOWS/VERSION.RC2 new file mode 100644 index 0000000..6a6e591 --- /dev/null +++ b/putty/WINDOWS/VERSION.RC2 @@ -0,0 +1,134 @@ +/* + * Standard Windows version information. + * (For inclusion in other .rc files with appropriate macro definitions.) + * FIXME: This file is called '.rc2' rather than '.rc' to avoid MSVC trying + * to compile it on its own when using the project files. Nicer solutions + * welcome. + */ + +/* + * Binary versions in Windows are major.minor.build.revision. Each + * component is 16-bit. + * Here we have: + * major.minor + * PuTTY version number (e.g. 0.58). (We've made a policy decision + * that these will be numeric from now on.) + * Present in releases and snapshots (for the sake of monotonicity + * in version numbers). + * build + * In releases, always 0. + * In snapshots, nearest Subversion revision. (It shouldn't be + * assumed that only one binary will have a given build number, of + * course.) + * revision + * Reserved; always 0. + * + * Examples of these version numbers: + * Release: 0.58.0.0 (but 0.58 didn't have a VERSIONINFO resource) + * Snapshot: 0.58.6356.0 (between 0.58 and the next release) + * Local: 0.0.0.0 + */ + +/* + * Mechanics of version naming/numbering. + * (This is a ripoff of ../version.c.) + */ + +#define STR1(x) #x +#define STR(x) STR1(x) + +/* We keep this around even for snapshots, for monotonicity of version + * numbering. It needs to be kept up to date. NB _comma_-separated. */ +#define BASE_VERSION 0,61 + +#if defined SNAPSHOT + +/* Make SVN_REV mandatory for snapshots, to avoid issuing binary + * version numbers that look like full releases. */ +#ifndef SVN_REV +#error SVN_REV not defined/nonzero for snapshot build +#endif + +#define VERSION_TEXT "Development snapshot " STR(SNAPSHOT) ":r" STR(SVN_REV) +#ifdef MODIFIED +#define BINARY_VERSION 0,0,0,0 +#else +#define BINARY_VERSION BASE_VERSION,SVN_REV,0 +#endif + +#elif defined RELEASE + +#define VERSION_TEXT "Release " STR(RELEASE) +#define BINARY_VERSION BASE_VERSION,0,0 + +#elif defined SVN_REV + +#define VERSION_TEXT "Custom build r" STR(SVN_REV) +#ifdef MODIFIED +#define BINARY_VERSION 0,0,0,0 +#else +#define BINARY_VERSION BASE_VERSION,SVN_REV,0 +#endif + +#else + +/* We can't reliably get the same date and time as version.c, so + * we won't bother trying. */ +#define VERSION_TEXT "Unidentified build" +#define BINARY_VERSION 0,0,0,0 + +#endif + +/* + * The actual VERSIONINFO resource. + */ +VS_VERSION_INFO VERSIONINFO +/* (None of this "fixed" info appears to be trivially user-visible on + * Win98SE. The binary version does show up on Win2K.) */ +FILEVERSION BINARY_VERSION +PRODUCTVERSION BINARY_VERSION /* version of whole suite */ +FILEFLAGSMASK VS_FF_DEBUG | VS_FF_PRERELEASE | VS_FF_PRIVATEBUILD +FILEFLAGS 0x0L +#if defined DEBUG + | VS_FF_DEBUG +#endif +#if defined SNAPSHOT + | VS_FF_PRERELEASE +#elif !defined RELEASE + | VS_FF_PRIVATEBUILD +#endif +FILEOS VOS__WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE 0x0L /* n/a for VFT_APP */ +BEGIN + /* (On Win98SE and Win2K, we can see most of this on the Version tab + * in the file properties in Explorer.) */ + BLOCK "StringFileInfo" + BEGIN + /* "lang-charset" LLLLCCCC = (UK English, Unicode) */ + BLOCK "080904B0" + BEGIN + VALUE "CompanyName", "Simon Tatham" /* required :/ */ + VALUE "ProductName", "PuTTY suite" + VALUE "FileDescription", APPDESC + VALUE "InternalName", APPNAME + VALUE "OriginalFilename", APPNAME + VALUE "FileVersion", VERSION_TEXT + VALUE "ProductVersion", VERSION_TEXT + VALUE "LegalCopyright", "Copyright \251 1997-2011 Simon Tatham." +#if (!defined SNAPSHOT) && (!defined RELEASE) + /* Only if VS_FF_PRIVATEBUILD. */ + VALUE "PrivateBuild", VERSION_TEXT /* NBI */ +#endif + END + END + BLOCK "VarFileInfo" + BEGIN + /* Once again -- same meanings -- apparently necessary */ + VALUE "Translation", 0x809, 1200 + END +END + +#undef VERSION_TEXT +#undef BASE_VERSION +#undef BINARY_VERSION diff --git a/putty/WINDOWS/WEBSITE.URL b/putty/WINDOWS/WEBSITE.URL new file mode 100644 index 0000000..4b50369 Binary files /dev/null and b/putty/WINDOWS/WEBSITE.URL differ diff --git a/putty/WINDOWS/WINCFG.C b/putty/WINDOWS/WINCFG.C new file mode 100644 index 0000000..60694fc --- /dev/null +++ b/putty/WINDOWS/WINCFG.C @@ -0,0 +1,404 @@ +/* + * wincfg.c - the Windows-specific parts of the PuTTY configuration + * box. + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +static void about_handler(union control *ctrl, void *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, void *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, void *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, int has_help, + int midsession, int protocol) +{ + 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), + dlg_stdcheckbox_handler, + I(offsetof(Config,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 == offsetof(Config,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), + dlg_stdcheckbox_handler, I(offsetof(Config,compose_key))); + ctrl_checkbox(s, "Control-Alt is different from AltGr", 'd', + HELPCTX(keyboard_ctrlalt), + dlg_stdcheckbox_handler, I(offsetof(Config,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 == offsetof(Config, beep)) { + assert(c->generic.handler == dlg_stdradiobutton_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), + dlg_stdfilesel_handler, I(offsetof(Config, 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), + dlg_stdradiobutton_handler, + I(offsetof(Config, 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), + dlg_stdcheckbox_handler, I(offsetof(Config,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), + dlg_stdradiobutton_handler, + I(offsetof(Config, 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), + dlg_stdcheckbox_handler, + I(offsetof(Config,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 == offsetof(Config, vtmode)) { + assert(c->generic.handler == dlg_stdradiobutton_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", "format", + "Formatting of pasted characters"); + ctrl_checkbox(s, "Paste to clipboard in RTF as well as plain text", 'f', + HELPCTX(selection_rtf), + dlg_stdcheckbox_handler, I(offsetof(Config,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), + dlg_stdradiobutton_handler, + I(offsetof(Config, 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), + dlg_stdcheckbox_handler, I(offsetof(Config,try_palette))); + ctrl_checkbox(s, "Use system colours", 's', + HELPCTX(colours_system), + dlg_stdcheckbox_handler, I(offsetof(Config,system_colour))); + + + /* + * Resize-by-changing-font is a Windows insanity. + */ + s = ctrl_getset(b, "Window", "size", "Set the size of the window"); + ctrl_radiobuttons(s, "When window is resized:", 'z', 1, + HELPCTX(window_resize), + dlg_stdradiobutton_handler, + I(offsetof(Config, 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), + dlg_stdcheckbox_handler, I(offsetof(Config,alt_f4))); + ctrl_checkbox(s, "System menu appears on ALT-Space", 'y', + HELPCTX(behaviour_altspace), + dlg_stdcheckbox_handler, I(offsetof(Config,alt_space))); + ctrl_checkbox(s, "System menu appears on ALT alone", 'l', + HELPCTX(behaviour_altonly), + dlg_stdcheckbox_handler, I(offsetof(Config,alt_only))); + ctrl_checkbox(s, "Ensure window is always on top", 'e', + HELPCTX(behaviour_alwaysontop), + dlg_stdcheckbox_handler, I(offsetof(Config,alwaysontop))); + ctrl_checkbox(s, "Full screen on Alt-Enter", 'f', + HELPCTX(behaviour_altenter), + dlg_stdcheckbox_handler, + I(offsetof(Config,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 == offsetof(Config, proxy_type)) { + assert(c->generic.handler == dlg_stdradiobutton_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 == + offsetof(Config, proxy_telnet_command)) { + assert(c->generic.handler == dlg_stdeditbox_handler); + sfree(c->generic.label); + c->generic.label = dupstr("Telnet command, or local" + " proxy command"); + break; + } + } + } + + /* + * Serial back end is available on Windows. + */ + if (!midsession || (protocol == PROT_SERIAL)) + ser_setup_config_box(b, midsession, 0x1F, 0x0F); + + /* + * $XAUTHORITY is not reliable on Windows, so we provide a + * means to override it. + */ + if (!midsession && backend_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), + dlg_stdfilesel_handler, I(offsetof(Config, xauthfile))); + } +} diff --git a/putty/WINDOWS/WINCONS.C b/putty/WINDOWS/WINCONS.C new file mode 100644 index 0000000..4f984d9 --- /dev/null +++ b/putty/WINDOWS/WINCONS.C @@ -0,0 +1,409 @@ +/* + * wincons.c - various interactive-prompt routines shared between + * the Windows console PuTTY tools + */ + +#include +#include +#include + +#include "putty.h" +#include "storage.h" +#include "ssh.h" + +int console_batch_mode = FALSE; + +static void *console_logctx = NULL; + +/* + * Clean up and exit. + */ +void cleanup_exit(int code) +{ + /* + * Clean up. + */ + sk_cleanup(); + + random_save_seed(); +#ifdef MSCRYPTOAPI + crypto_wrapup(); +#endif + + exit(code); +} + +void set_busy_status(void *frontend, int status) +{ +} + +void notify_remote_exit(void *frontend) +{ +} + +void timer_change_notify(long next) +{ +} + +int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, + char *keystr, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx) +{ + int ret; + HANDLE hin; + DWORD savemode, i; + + static const char absentmsg_batch[] = + "The server's host key is not cached in the registry. You\n" + "have no guarantee that the server is the computer you\n" + "think it is.\n" + "The server's %s key fingerprint is:\n" + "%s\n" + "Connection abandoned.\n"; + static const char absentmsg[] = + "The server's host key is not cached in the registry. You\n" + "have no guarantee that the server is the computer you\n" + "think it is.\n" + "The server's %s key fingerprint is:\n" + "%s\n" + "If you trust this host, enter \"y\" to add the key to\n" + "PuTTY's cache and carry on connecting.\n" + "If you want to carry on connecting just once, without\n" + "adding the key to the cache, enter \"n\".\n" + "If you do not trust this host, press Return to abandon the\n" + "connection.\n" + "Store key in cache? (y/n) "; + + static const char wrongmsg_batch[] = + "WARNING - POTENTIAL SECURITY BREACH!\n" + "The server's host key does not match the one PuTTY has\n" + "cached in the registry. This means that either the\n" + "server administrator has changed the host key, or you\n" + "have actually connected to another computer pretending\n" + "to be the server.\n" + "The new %s key fingerprint is:\n" + "%s\n" + "Connection abandoned.\n"; + static const char wrongmsg[] = + "WARNING - POTENTIAL SECURITY BREACH!\n" + "The server's host key does not match the one PuTTY has\n" + "cached in the registry. This means that either the\n" + "server administrator has changed the host key, or you\n" + "have actually connected to another computer pretending\n" + "to be the server.\n" + "The new %s key fingerprint is:\n" + "%s\n" + "If you were expecting this change and trust the new key,\n" + "enter \"y\" to update PuTTY's cache and continue connecting.\n" + "If you want to carry on connecting but without updating\n" + "the cache, enter \"n\".\n" + "If you want to abandon the connection completely, press\n" + "Return to cancel. Pressing Return is the ONLY guaranteed\n" + "safe choice.\n" + "Update cached key? (y/n, Return cancels connection) "; + + static const char abandoned[] = "Connection abandoned.\n"; + + char line[32]; + + /* + * 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 */ + if (console_batch_mode) { + fprintf(stderr, wrongmsg_batch, keytype, fingerprint); + return 0; + } + fprintf(stderr, wrongmsg, keytype, fingerprint); + fflush(stderr); + } + if (ret == 1) { /* key was absent */ + if (console_batch_mode) { + fprintf(stderr, absentmsg_batch, keytype, fingerprint); + return 0; + } + fprintf(stderr, absentmsg, keytype, fingerprint); + fflush(stderr); + } + + 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] != '\0' && line[0] != '\r' && line[0] != '\n') { + if (line[0] == 'y' || line[0] == 'Y') + store_host_key(host, port, keytype, keystr); + return 1; + } else { + fprintf(stderr, abandoned); + return 0; + } +} + +void update_specials_menu(void *frontend) +{ +} + +/* + * Ask whether the selected algorithm is acceptable (since it was + * below the configured 'warn' threshold). + */ +int askalg(void *frontend, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + HANDLE hin; + DWORD savemode, i; + + static const char msg[] = + "The first %s supported by the server is\n" + "%s, which is below the configured warning threshold.\n" + "Continue with connection? (y/n) "; + static const char msg_batch[] = + "The first %s supported by the server is\n" + "%s, which is below the configured warning threshold.\n" + "Connection abandoned.\n"; + static const char abandoned[] = "Connection abandoned.\n"; + + char line[32]; + + if (console_batch_mode) { + fprintf(stderr, msg_batch, algtype, algname); + return 0; + } + + fprintf(stderr, msg, algtype, algname); + 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 { + fprintf(stderr, abandoned); + return 0; + } +} + +/* + * Ask whether to wipe a session log file before writing to it. + * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). + */ +int askappend(void *frontend, Filename filename, + void (*callback)(void *ctx, int result), void *ctx) +{ + 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 (RSA), 1024-bit:\n" + " " PGP_RSA_MASTER_KEY_FP "\n" + "PuTTY Master Key (DSA), 1024-bit:\n" + " " PGP_DSA_MASTER_KEY_FP "\n", stdout); +} + +void console_provide_logctx(void *logctx) +{ + console_logctx = logctx; +} + +void logevent(void *frontend, const char *string) +{ + log_eventlog(console_logctx, string); +} + +static void console_data_untrusted(HANDLE hout, const char *data, int len) +{ + DWORD dummy; + /* FIXME: control-character filtering */ + WriteFile(hout, data, len, &dummy, NULL); +} + +int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + HANDLE hin, hout; + 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++) + memset(p->prompts[i]->result, 0, p->prompts[i]->result_len); + } + + /* + * 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) { + size_t l = strlen(p->name); + console_data_untrusted(hout, p->name, l); + if (p->name[l-1] != '\n') + console_data_untrusted(hout, "\n", 1); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + size_t l = strlen(p->instruction); + console_data_untrusted(hout, p->instruction, l); + if (p->instruction[l-1] != '\n') + console_data_untrusted(hout, "\n", 1); + } + + for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { + + DWORD savemode, newmode, i = 0; + prompt_t *pr = p->prompts[curr_prompt]; + BOOL r; + + 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_data_untrusted(hout, pr->prompt, strlen(pr->prompt)); + + r = ReadFile(hin, pr->result, pr->result_len - 1, &i, NULL); + + SetConsoleMode(hin, savemode); + + if ((int) i > pr->result_len) + i = pr->result_len - 1; + else + i = i - 2; + pr->result[i] = '\0'; + + if (!pr->echo) { + DWORD dummy; + WriteFile(hout, "\r\n", 2, &dummy, NULL); + } + + } + + return 1; /* success */ + +} + +void frontend_keypress(void *handle) +{ + /* + * This is nothing but a stub, in console code. + */ + return; +} diff --git a/putty/WINDOWS/WINCTRLS.C b/putty/WINDOWS/WINCTRLS.C new file mode 100644 index 0000000..159c925 --- /dev/null +++ b/putty/WINDOWS/WINCTRLS.C @@ -0,0 +1,2623 @@ +/* + * winctrls.c: routines to self-manage the controls in a dialog + * box. + */ + +/* + * Possible TODO in new cross-platform config box stuff: + * + * - When lining up two controls alongside each other, I wonder if + * we could conveniently arrange to centre them vertically? + * Particularly ugly in the current setup is the `Add new + * forwarded port:' static next to the rather taller `Remove' + * button. + */ + +#include +#include + +#include "putty.h" +#include "misc.h" +#include "dialog.h" + +#include + +#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 + +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) 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, int 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; + + if (text) { + 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, text, id); + } + + 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 *); + int bid; + if (!btext) + break; + bid = va_arg(ap, int); + 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 *); + int bid; + if (!btext) + break; + bid = va_arg(ap, int); + } + 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 *); + int bid; + if (!btext) + break; + bid = va_arg(ap, int); + } + 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 lpx = GetDeviceCaps(hdc, LOGPIXELSX); + 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; + + 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, int 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, int 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; + ctl = doctl(cp, r, "LISTBOX", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | + WS_VSCROLL | LBS_HASSTRINGS | LBS_USETABSTOPS, + WS_EX_CLIENTEDGE, + "", listid); + 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 = LBItemFromPt(hwnd, cursor, scroll); + if (ret == -1) + return ret; + ret = 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 = LBItemFromPt(hwnd, uppoint, FALSE); + downitem = 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, + int 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 = LBItemFromPt(dlm->hWnd, dlm->ptCursor, TRUE); + hdl->dragging = 0; + /* XXX hack Q183115 */ + SetWindowLongPtr(hwnd, DWLP_MSGRESULT, TRUE); + ret |= 1; break; + case DL_CANCELDRAG: + DrawInsert(hwnd, dlm->hWnd, -1); /* Clear arrow */ + SendDlgItemMessage(hwnd, hdl->listid, + LB_DELETESTRING, hdl->dummyitem, 0); + hdl->dragging = 0; + ret |= 1; break; + case DL_DRAGGING: + hdl->dragging = 1; + dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE); + if (dest > hdl->dummyitem) dest = hdl->dummyitem; + 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; + DrawInsert (hwnd, dlm->hWnd, -1); + } + SendDlgItemMessage(hwnd, hdl->listid, + LB_DELETESTRING, hdl->dummyitem, 0); + if (hdl->dragging) { + hdl->dragging = 0; + 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); +} + +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; + 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 = 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 = 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; + + /* + * 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); + sfree(escaped); + data = snew(FontSpec); + break; + default: + assert(!"Can't happen"); + num_ids = 0; /* placate gcc */ + break; + } + + /* + * 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->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 (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, + int 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, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + return dp->focused == ctrl ? dp->lastfocused : dp->focused; +} + +/* + * The dialog-box procedure calls this function to handle Windows + * messages on a control we manage. + */ +int winctrl_handle_command(struct dlgparam *dp, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + struct winctrl *c; + union control *ctrl; + int i, id, 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 0; + + /* + * 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 0; /* 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 0; /* 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 = 0; + 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)) { + strncpy(fs.name, lf.lfFaceName, + sizeof(fs.name) - 1); + fs.name[sizeof(fs.name) - 1] = '\0'; + fs.isbold = (lf.lfWeight == FW_BOLD); + fs.charset = lf.lfCharSet; + fs.height = cf.iPointSize / 10; + dlg_fontsel_set(ctrl, dp, 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. + */ +int 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 0; /* 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 0; /* no help available for this ctrl */ + + launch_help(hwnd, c->ctrl->generic.helpctx.p); + return 1; +} + +/* + * 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; +} + +void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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; + assert(!"No radio button was checked?!"); + return 0; +} + +void dlg_checkbox_set(union control *ctrl, void *dlg, int checked) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); + CheckDlgButton(dp->hwnd, c->base_id, (checked != 0)); +} + +int dlg_checkbox_get(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg, char const *text) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + SetDlgItemText(dp->hwnd, c->base_id+1, text); +} + +void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + GetDlgItemText(dp->hwnd, c->base_id+1, buffer, length); + buffer[length-1] = '\0'; +} + +/* The `listbox' functions can also apply to combo boxes. */ +void dlg_listbox_clear(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct 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, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg, char const *text) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg, + char const *text, int id) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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; +} + +int dlg_listbox_issel(union control *ctrl, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg, int index) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg, char const *text) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg, char const *text) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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: + assert(!"Can't happen"); + break; + } + if (escaped) { + SetDlgItemText(dp->hwnd, id, escaped); + sfree(escaped); + } +} + +void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_FILESELECT); + SetDlgItemText(dp->hwnd, c->base_id+1, fn.path); +} + +void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_FILESELECT); + GetDlgItemText(dp->hwnd, c->base_id+1, fn->path, lenof(fn->path)); + fn->path[lenof(fn->path)-1] = '\0'; +} + +void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs) +{ + char *buf, *boldstr; + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); + + *(FontSpec *)c->data = fs; /* structure copy */ + + 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); +} + +void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fs) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); + *fs = *(FontSpec *)c->data; /* structure copy */ +} + +/* + * Bracketing a large set of updates in these two functions will + * cause the front end (if possible) to delay updating the screen + * until it's all complete, thus avoiding flicker. + */ +void dlg_update_start(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + int id; + HWND ctl; + 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(void *dlg) +{ + /* struct dlgparam *dp = (struct dlgparam *)dlg; */ + MessageBeep(0); +} + +void dlg_error_msg(void *dlg, char *msg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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(void *dlg, int value) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + dp->ended = TRUE; + dp->endresult = value; +} + +void dlg_refresh(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + 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, void *dlg, int r, int g, int b) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + dp->coloursel_wanted = TRUE; + dp->coloursel_result.r = r; + dp->coloursel_result.g = g; + dp->coloursel_result.b = b; +} + +int dlg_coloursel_results(union control *ctrl, void *dlg, + int *r, int *g, int *b) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + if (dp->coloursel_result.ok) { + *r = dp->coloursel_result.r; + *g = dp->coloursel_result.g; + *b = dp->coloursel_result.b; + return 1; + } else + return 0; +} + +void dlg_auto_set_fixed_pitch_flag(void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + Config *cfg = (Config *)dp->data; + HFONT font; + HDC hdc; + TEXTMETRIC tm; + int 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 'Config *' + * as dp->data. + */ + + font = CreateFont(0, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg->font_quality), + FIXED_PITCH | FF_DONTCARE, cfg->font.name); + hdc = GetDC(NULL); + if (font && hdc && SelectObject(hdc, font) && 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 (font) + DeleteObject(font); + + if (is_var) + dp->fixed_pitch_fonts = FALSE; +} + +int dlg_get_fixed_pitch_flag(void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + return dp->fixed_pitch_fonts; +} + +void dlg_set_fixed_pitch_flag(void *dlg, int flag) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + dp->fixed_pitch_fonts = flag; +} + +struct perctrl_privdata { + union control *ctrl; + void *data; + int needs_free; +}; + +static int perctrl_privdata_cmp(void *av, void *bv) +{ + struct perctrl_privdata *a = (struct perctrl_privdata *)av; + struct perctrl_privdata *b = (struct perctrl_privdata *)bv; + if (a->ctrl < b->ctrl) + return -1; + else if (a->ctrl > b->ctrl) + return +1; + return 0; +} + +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->privdata = newtree234(perctrl_privdata_cmp); + 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) +{ + struct perctrl_privdata *p; + + if (dp->privdata) { + while ( (p = index234(dp->privdata, 0)) != NULL ) { + del234(dp->privdata, p); + if (p->needs_free) + sfree(p->data); + sfree(p); + } + freetree234(dp->privdata); + dp->privdata = NULL; + } + sfree(dp->wintitle); + sfree(dp->errtitle); +} + +void *dlg_get_privdata(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct perctrl_privdata tmp, *p; + tmp.ctrl = ctrl; + p = find234(dp->privdata, &tmp, NULL); + if (p) + return p->data; + else + return NULL; +} + +void dlg_set_privdata(union control *ctrl, void *dlg, void *ptr) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct perctrl_privdata tmp, *p; + tmp.ctrl = ctrl; + p = find234(dp->privdata, &tmp, NULL); + if (!p) { + p = snew(struct perctrl_privdata); + p->ctrl = ctrl; + p->needs_free = FALSE; + add234(dp->privdata, p); + } + p->data = ptr; +} + +void *dlg_alloc_privdata(union control *ctrl, void *dlg, size_t size) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct perctrl_privdata tmp, *p; + tmp.ctrl = ctrl; + p = find234(dp->privdata, &tmp, NULL); + if (!p) { + p = snew(struct perctrl_privdata); + p->ctrl = ctrl; + p->needs_free = FALSE; + add234(dp->privdata, p); + } + assert(!p->needs_free); + p->needs_free = TRUE; + /* + * This is an internal allocation routine, so it's allowed to + * use smalloc directly. + */ + p->data = smalloc(size); + return p->data; +} diff --git a/putty/WINDOWS/WINDEFS.C b/putty/WINDOWS/WINDEFS.C new file mode 100644 index 0000000..de01daf --- /dev/null +++ b/putty/WINDOWS/WINDEFS.C @@ -0,0 +1,43 @@ +/* + * windefs.c: default settings that are specific to Windows. + */ + +#include "putty.h" + +#include + +FontSpec platform_default_fontspec(const char *name) +{ + FontSpec ret; + if (!strcmp(name, "Font")) { + strcpy(ret.name, "Courier New"); + ret.isbold = 0; + ret.charset = ANSI_CHARSET; + ret.height = 10; + } else { + ret.name[0] = '\0'; + } + return ret; +} + +Filename platform_default_filename(const char *name) +{ + Filename ret; + if (!strcmp(name, "LogFileName")) + strcpy(ret.path, "putty.log"); + else + *ret.path = '\0'; + return ret; +} + +char *platform_default_s(const char *name) +{ + if (!strcmp(name, "SerialLine")) + return dupstr("COM1"); + return NULL; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} diff --git a/putty/WINDOWS/WINDLG.C b/putty/WINDOWS/WINDLG.C new file mode 100644 index 0000000..debc510 --- /dev/null +++ b/putty/WINDOWS/WINDLG.C @@ -0,0 +1,926 @@ +/* + * windlg.c - dialogs for PuTTY(tel), including the configuration dialog. + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "win_res.h" +#include "storage.h" +#include "dialog.h" + +#include +#include +#include + +#ifdef MSVC4 +#define TVINSERTSTRUCT TV_INSERTSTRUCT +#define TVITEM TV_ITEM +#define ICON_BIG 1 +#endif + +/* + * These are the various bits of data required to handle the + * portable-dialog stuff in the config box. Having them at file + * scope in here isn't too bad a place to put them; if we were ever + * to need more than one config box per process we could always + * shift them to a per-config-box structure stored in GWL_USERDATA. + */ +static struct controlbox *ctrlbox; +/* + * ctrls_base holds the OK and Cancel buttons: the controls which + * are present in all dialog panels. ctrls_panel holds the ones + * which change from panel to panel. + */ +static struct winctrls ctrls_base, ctrls_panel; +static struct dlgparam dp; + +static char **events = NULL; +static int nevents = 0, negsize = 0; + +extern Config cfg; /* defined in window.c */ + +#define PRINTER_DISABLED_STRING "None (printing disabled)" + +void force_normal(HWND hwnd) +{ + static int recurse = 0; + + WINDOWPLACEMENT wp; + + if (recurse) + return; + recurse = 1; + + wp.length = sizeof(wp); + if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { + wp.showCmd = SW_SHOWNORMAL; + SetWindowPlacement(hwnd, &wp); + } + recurse = 0; +} + +static int 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 < nevents; i++) + SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING, + 0, (LPARAM) events[i]); + 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(events[selitems[i]]) + sizeof(sel_nl); + + clipdata = snewn(size, char); + if (clipdata) { + char *p = clipdata; + for (i = 0; i < count; i++) { + char *q = events[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(NULL, clipdata, size, TRUE); + sfree(clipdata); + } + sfree(selitems); + + for (i = 0; i < nevents; 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 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); + } + 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 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); + SetDlgItemText(hwnd, IDA_TEXT1, appname); + SetDlgItemText(hwnd, IDA_VERSION, ver); + 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", + "http://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 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 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; + + 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; + + path = s->pathname; + } + + /* + * Put the treeview selection on to the Session panel. + * This should also cause creation of the relevant + * controls. + */ + TreeView_SelectItem(treeview, hfirst); + } + + /* + * 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; + } + } + } + + 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) { + HTREEITEM i = + TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom); + TVITEM item; + char buffer[64]; + + 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); + } +} + +int do_config(void) +{ + int 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 = &cfg; + 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; +} + +int do_reconfig(HWND hwnd, int protcfginfo) +{ + Config backup_cfg; + int ret; + + backup_cfg = cfg; /* structure copy */ + + ctrlbox = ctrl_new_box(); + setup_config_box(ctrlbox, TRUE, cfg.protocol, protcfginfo); + win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), TRUE, + cfg.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 = &cfg; + 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) + cfg = backup_cfg; /* structure copy */ + + return ret; +} + +void logevent(void *frontend, const char *string) +{ + char timebuf[40]; + struct tm tm; + + log_eventlog(logctx, string); + + if (nevents >= negsize) { + negsize += 64; + events = sresize(events, negsize, char *); + } + + tm=ltime(); + strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); + + events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char); + strcpy(events[nevents], timebuf); + strcat(events[nevents], string); + if (logbox) { + int count; + SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING, + 0, (LPARAM) events[nevents]); + count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0); + SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0); + } + nevents++; +} + +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); +} + +int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, + char *keystr, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx) +{ + int ret; + + static const char absentmsg[] = + "The server's host key is not cached in the registry. You\n" + "have no guarantee that the server is the computer you\n" + "think it is.\n" + "The server's %s key fingerprint is:\n" + "%s\n" + "If you trust this host, hit Yes to add the key to\n" + "%s's cache and carry on connecting.\n" + "If you want to carry on connecting just once, without\n" + "adding the key to the cache, hit No.\n" + "If you do not trust this host, hit Cancel to abandon the\n" + "connection.\n"; + + static const char wrongmsg[] = + "WARNING - POTENTIAL SECURITY BREACH!\n" + "\n" + "The server's host key does not match the one %s has\n" + "cached in the registry. This means that either the\n" + "server administrator has changed the host key, or you\n" + "have actually connected to another computer pretending\n" + "to be the server.\n" + "The new %s key fingerprint is:\n" + "%s\n" + "If you were expecting this change and trust the new key,\n" + "hit Yes to update %s's cache and continue connecting.\n" + "If you want to carry on connecting but without updating\n" + "the cache, hit No.\n" + "If you want to abandon the connection completely, hit\n" + "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n"; + + static const char mbtitle[] = "%s Security Alert"; + + /* + * Verify the key against the registry. + */ + ret = verify_host_key(host, port, keytype, keystr); + + if (ret == 0) /* success - key matched OK */ + return 1; + else if (ret == 2) { /* key was different */ + int mbret; + char *text = dupprintf(wrongmsg, appname, keytype, fingerprint, + appname); + char *caption = dupprintf(mbtitle, appname); + mbret = message_box(text, caption, + MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3, + HELPCTXID(errors_hostkey_changed)); + assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL); + sfree(text); + sfree(caption); + if (mbret == IDYES) { + store_host_key(host, port, keytype, keystr); + return 1; + } else if (mbret == IDNO) + return 1; + } else if (ret == 1) { /* key was absent */ + int mbret; + char *text = dupprintf(absentmsg, keytype, fingerprint, appname); + char *caption = dupprintf(mbtitle, appname); + mbret = message_box(text, caption, + MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3, + HELPCTXID(errors_hostkey_absent)); + assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL); + sfree(text); + sfree(caption); + if (mbret == IDYES) { + store_host_key(host, port, keytype, keystr); + return 1; + } else if (mbret == IDNO) + return 1; + } + return 0; /* abandon the connection */ +} + +/* + * Ask whether the selected algorithm is acceptable (since it was + * below the configured 'warn' threshold). + */ +int askalg(void *frontend, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char mbtitle[] = "%s Security Alert"; + static const char msg[] = + "The first %s supported by the server\n" + "is %.64s, 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; +} + +/* + * Ask whether to wipe a session log file before writing to it. + * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). + */ +int askappend(void *frontend, Filename filename, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msgtemplate[] = + "The session log file \"%.*s\" already exists.\n" + "You can overwrite it with a new session log,\n" + "append your session log to the end of it,\n" + "or disable session logging for this session.\n" + "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; +} + +/* + * 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/putty/WINDOWS/WINDOW.C b/putty/WINDOWS/WINDOW.C new file mode 100644 index 0000000..cac7672 --- /dev/null +++ b/putty/WINDOWS/WINDOW.C @@ -0,0 +1,5547 @@ +/* + * window.c - the PuTTY(tel) main program, which runs a PuTTY terminal + * emulator and backend in a window. + */ + +#include +#include +#include +#include +#include +#include + +#ifndef NO_MULTIMON +#define COMPILE_MULTIMON_STUBS +#endif + +#define PUTTY_DO_GLOBALS /* actually _define_ globals */ +#include "putty.h" +#include "terminal.h" +#include "storage.h" +#include "win_res.h" + +#ifndef NO_MULTIMON +#include +#endif + +#include +#include +#include +#include + +/* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of + * wParam are used by Windows, and should be masked off, so we shouldn't + * attempt to store information in them. Hence all these identifiers have + * the low 4 bits clear. Also, identifiers should < 0xF000. */ + +#define IDM_SHOWLOG 0x0010 +#define IDM_NEWSESS 0x0020 +#define IDM_DUPSESS 0x0030 +#define IDM_RESTART 0x0040 +#define IDM_RECONF 0x0050 +#define IDM_CLRSB 0x0060 +#define IDM_RESET 0x0070 +#define IDM_HELP 0x0140 +#define IDM_ABOUT 0x0150 +#define IDM_SAVEDSESS 0x0160 +#define IDM_COPYALL 0x0170 +#define IDM_FULLSCREEN 0x0180 +#define IDM_PASTE 0x0190 +#define IDM_SPECIALSEP 0x0200 + +#define IDM_SPECIAL_MIN 0x0400 +#define IDM_SPECIAL_MAX 0x0800 + +#define IDM_SAVED_MIN 0x1000 +#define IDM_SAVED_MAX 0x5000 +#define MENU_SAVED_STEP 16 +/* Maximum number of sessions on saved-session submenu */ +#define MENU_SAVED_MAX ((IDM_SAVED_MAX-IDM_SAVED_MIN) / MENU_SAVED_STEP) + +#define WM_IGNORE_CLIP (WM_APP + 2) +#define WM_FULLSCR_ON_MAX (WM_APP + 3) +#define WM_AGENT_CALLBACK (WM_APP + 4) +#define WM_GOT_CLIPDATA (WM_APP + 6) + +/* Needed for Chinese support and apparently not always defined. */ +#ifndef VK_PROCESSKEY +#define VK_PROCESSKEY 0xE5 +#endif + +/* Mouse wheel support. */ +#ifndef WM_MOUSEWHEEL +#define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */ +#endif +#ifndef WHEEL_DELTA +#define WHEEL_DELTA 120 +#endif + +static Mouse_Button translate_button(Mouse_Button button); +static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, + unsigned char *output); +static void cfgtopalette(void); +static void systopalette(void); +static void init_palette(void); +static void init_fonts(int, int); +static void another_font(int); +static void deinit_fonts(void); +static void set_input_locale(HKL); +static void update_savedsess_menu(void); +static void init_flashwindow(void); + +static int is_full_screen(void); +static void make_full_screen(void); +static void clear_full_screen(void); +static void flip_full_screen(void); +static int process_clipdata(HGLOBAL clipdata, int unicode); + +/* Window layout information */ +static void reset_window(int); +static int extra_width, extra_height; +static int font_width, font_height, font_dualwidth, font_varpitch; +static int offset_width, offset_height; +static int was_zoomed = 0; +static int prev_rows, prev_cols; + +static int pending_netevent = 0; +static WPARAM pend_netevent_wParam = 0; +static LPARAM pend_netevent_lParam = 0; +static void enact_pending_netevent(void); +static void flash_window(int mode); +static void sys_cursor_update(void); +static int get_fullscreen_rect(RECT * ss); + +static int caret_x = -1, caret_y = -1; + +static int kbd_codepage; + +static void *ldisc; +static Backend *back; +static void *backhandle; + +static struct unicode_data ucsdata; +static int must_close_session, session_closed; +static int reconfiguring = FALSE; + +static const struct telnet_special *specials = NULL; +static HMENU specials_menu = NULL; +static int n_specials = 0; + +static wchar_t *clipboard_contents; +static size_t clipboard_length; + +#define TIMING_TIMER_ID 1234 +static long timing_next_time; + +static struct { + HMENU menu; +} popup_menus[2]; +enum { SYSMENU, CTXMENU }; +static HMENU savedsess_menu; + +Config cfg; /* exported to windlg.c */ + +static struct sesslist sesslist; /* for saved-session menu */ + +struct agent_callback { + void (*callback)(void *, void *, int); + void *callback_ctx; + void *data; + int len; +}; + +#define FONT_NORMAL 0 +#define FONT_BOLD 1 +#define FONT_UNDERLINE 2 +#define FONT_BOLDUND 3 +#define FONT_WIDE 0x04 +#define FONT_HIGH 0x08 +#define FONT_NARROW 0x10 + +#define FONT_OEM 0x20 +#define FONT_OEMBOLD 0x21 +#define FONT_OEMUND 0x22 +#define FONT_OEMBOLDUND 0x23 + +#define FONT_MAXNO 0x2F +#define FONT_SHIFT 5 +static HFONT fonts[FONT_MAXNO]; +static LOGFONT lfont; +static int fontflag[FONT_MAXNO]; +static enum { + BOLD_COLOURS, BOLD_SHADOW, BOLD_FONT +} bold_mode; +static enum { + UND_LINE, UND_FONT +} und_mode; +static int descent; + +#define NCFGCOLOURS 22 +#define NEXTCOLOURS 240 +#define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS) +static COLORREF colours[NALLCOLOURS]; +static HPALETTE pal; +static LPLOGPALETTE logpal; +static RGBTRIPLE defpal[NALLCOLOURS]; + +static HBITMAP caretbm; + +static int dbltime, lasttime, lastact; +static Mouse_Button lastbtn; + +/* this allows xterm-style mouse handling. */ +static int send_raw_mouse = 0; +static int wheel_accumulator = 0; + +static int busy_status = BUSY_NOT; + +static char *window_name, *icon_name; + +static int compose_state = 0; + +static UINT wm_mousewheel = WM_MOUSEWHEEL; + +/* Dummy routine, only required in plink. */ +void ldisc_update(void *frontend, int echo, int edit) +{ +} + +char *get_ttymode(void *frontend, const char *mode) +{ + return term_get_ttymode(term, mode); +} + +static void start_backend(void) +{ + const char *error; + char msg[1024], *title; + char *realhost; + int i; + + /* + * Select protocol. This is farmed out into a table in a + * separate file to enable an ssh-free variant. + */ + back = backend_from_proto(cfg.protocol); + if (back == NULL) { + char *str = dupprintf("%s Internal Error", appname); + MessageBox(NULL, "Unsupported protocol number found", + str, MB_OK | MB_ICONEXCLAMATION); + sfree(str); + cleanup_exit(1); + } + + error = back->init(NULL, &backhandle, &cfg, + cfg.host, cfg.port, &realhost, cfg.tcp_nodelay, + cfg.tcp_keepalives); + back->provide_logctx(backhandle, logctx); + if (error) { + char *str = dupprintf("%s Error", appname); + sprintf(msg, "Unable to open connection to\n" + "%.800s\n" "%s", cfg_dest(&cfg), error); + MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK); + sfree(str); + exit(0); + } + window_name = icon_name = NULL; + if (*cfg.wintitle) { + title = cfg.wintitle; + } else { + sprintf(msg, "%s - %s", realhost, appname); + title = msg; + } + sfree(realhost); + set_title(NULL, title); + set_icon(NULL, title); + + /* + * Connect the terminal to the backend for resize purposes. + */ + term_provide_resize_fn(term, back->size, backhandle); + + /* + * Set up a line discipline. + */ + ldisc = ldisc_create(&cfg, term, back, backhandle, NULL); + + /* + * Destroy the Restart Session menu item. (This will return + * failure if it's already absent, as it will be the very first + * time we call this function. We ignore that, because as long + * as the menu item ends up not being there, we don't care + * whether it was us who removed it or not!) + */ + for (i = 0; i < lenof(popup_menus); i++) { + DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); + } + + must_close_session = FALSE; + session_closed = FALSE; +} + +static void close_session(void) +{ + char morestuff[100]; + int i; + + session_closed = TRUE; + sprintf(morestuff, "%.70s (inactive)", appname); + set_icon(NULL, morestuff); + set_title(NULL, morestuff); + + if (ldisc) { + ldisc_free(ldisc); + ldisc = NULL; + } + if (back) { + back->free(backhandle); + backhandle = NULL; + back = NULL; + term_provide_resize_fn(term, NULL, NULL); + update_specials_menu(NULL); + } + + /* + * Show the Restart Session menu item. Do a precautionary + * delete first to ensure we never end up with more than one. + */ + for (i = 0; i < lenof(popup_menus); i++) { + DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); + InsertMenu(popup_menus[i].menu, IDM_DUPSESS, MF_BYCOMMAND | MF_ENABLED, + IDM_RESTART, "&Restart Session"); + } + + /* + * Unset the 'must_close_session' flag, or else we'll come + * straight back here the next time we go round the main message + * loop - which, worse still, will be immediately (without + * blocking) because we've just triggered a WM_SETTEXT by the + * window title change above. + */ + must_close_session = FALSE; +} + +int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) +{ + WNDCLASS wndclass; + MSG msg; + HRESULT hr; + int guess_width, guess_height; + + hinst = inst; + hwnd = NULL; + flags = FLAG_VERBOSE | FLAG_INTERACTIVE; + + sk_init(); + + InitCommonControls(); + + /* Ensure a Maximize setting in Explorer doesn't maximise the + * config box. */ + defuse_showwindow(); + + if (!init_winver()) + { + char *str = dupprintf("%s Fatal Error", appname); + MessageBox(NULL, "Windows refuses to report a version", + str, MB_OK | MB_ICONEXCLAMATION); + sfree(str); + return 1; + } + + /* + * If we're running a version of Windows that doesn't support + * WM_MOUSEWHEEL, find out what message number we should be + * using instead. + */ + if (osVersion.dwMajorVersion < 4 || + (osVersion.dwMajorVersion == 4 && + osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT)) + wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG"); + + init_help(); + + init_flashwindow(); + + /* + * Initialize COM. + */ + hr = CoInitialize(NULL); + if (hr != S_OK && hr != S_FALSE) { + char *str = dupprintf("%s Fatal Error", appname); + MessageBox(NULL, "Failed to initialize COM subsystem", + str, MB_OK | MB_ICONEXCLAMATION); + sfree(str); + return 1; + } + + /* + * Process the command line. + */ + { + char *p; + int got_host = 0; + /* By default, we bring up the config dialog, rather than launching + * a session. This gets set to TRUE if something happens to change + * that (e.g., a hostname is specified on the command-line). */ + int allow_launch = FALSE; + + default_protocol = be_default_protocol; + /* Find the appropriate default port. */ + { + Backend *b = backend_from_proto(default_protocol); + default_port = 0; /* illegal */ + if (b) + default_port = b->default_port; + } + cfg.logtype = LGTYP_NONE; + + do_defaults(NULL, &cfg); + + p = cmdline; + + /* + * Process a couple of command-line options which are more + * easily dealt with before the line is broken up into words. + * These are the old-fashioned but convenient @sessionname and + * the internal-use-only &sharedmemoryhandle, neither of which + * are combined with anything else. + */ + while (*p && isspace(*p)) + p++; + if (*p == '@') { + /* + * An initial @ means that the whole of the rest of the + * command line should be treated as the name of a saved + * session, with _no quoting or escaping_. This makes it a + * very convenient means of automated saved-session + * launching, via IDM_SAVEDSESS or Windows 7 jump lists. + */ + int i = strlen(p); + while (i > 1 && isspace(p[i - 1])) + i--; + p[i] = '\0'; + do_defaults(p + 1, &cfg); + if (!cfg_launchable(&cfg) && !do_config()) { + cleanup_exit(0); + } + allow_launch = TRUE; /* allow it to be launched directly */ + } else if (*p == '&') { + /* + * An initial & means we've been given a command line + * containing the hex value of a HANDLE for a file + * mapping object, which we must then extract as a + * config. + */ + HANDLE filemap; + Config *cp; + if (sscanf(p + 1, "%p", &filemap) == 1 && + (cp = MapViewOfFile(filemap, FILE_MAP_READ, + 0, 0, sizeof(Config))) != NULL) { + cfg = *cp; + UnmapViewOfFile(cp); + CloseHandle(filemap); + } else if (!do_config()) { + cleanup_exit(0); + } + allow_launch = TRUE; + } 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 r.right - r.left) + guess_width = r.right - r.left; + if (guess_height > r.bottom - r.top) + guess_height = r.bottom - r.top; + } + + { + int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL; + int exwinmode = 0; + if (!cfg.scrollbar) + winmode &= ~(WS_VSCROLL); + if (cfg.resize_action == RESIZE_DISABLED) + winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); + if (cfg.alwaysontop) + exwinmode |= WS_EX_TOPMOST; + if (cfg.sunken_edge) + exwinmode |= WS_EX_CLIENTEDGE; + hwnd = CreateWindowEx(exwinmode, appname, appname, + winmode, CW_USEDEFAULT, CW_USEDEFAULT, + guess_width, guess_height, + NULL, NULL, inst, NULL); + } + + /* + * Initialise the terminal. (We have to do this _after_ + * creating the window, since the terminal is the first thing + * which will call schedule_timer(), which will in turn call + * timer_change_notify() which will expect hwnd to exist.) + */ + term = term_init(&cfg, &ucsdata, NULL); + logctx = log_init(NULL, &cfg); + term_provide_logctx(term, logctx); + term_size(term, cfg.height, cfg.width, cfg.savelines); + + /* + * Initialise the fonts, simultaneously correcting the guesses + * for font_{width,height}. + */ + init_fonts(0,0); + + /* + * Correct the guesses for extra_{width,height}. + */ + { + RECT cr, wr; + GetWindowRect(hwnd, &wr); + GetClientRect(hwnd, &cr); + offset_width = offset_height = cfg.window_border; + extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; + extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; + } + + /* + * Resize the window, now we know what size we _really_ want it + * to be. + */ + guess_width = extra_width + font_width * term->cols; + guess_height = extra_height + font_height * term->rows; + SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); + + /* + * Set up a caret bitmap, with no content. + */ + { + char *bits; + int size = (font_width + 15) / 16 * 2 * font_height; + bits = snewn(size, char); + memset(bits, 0, size); + caretbm = CreateBitmap(font_width, font_height, 1, 1, bits); + sfree(bits); + } + CreateCaret(hwnd, caretbm, font_width, font_height); + + /* + * Initialise the scroll bar. + */ + { + SCROLLINFO si; + + si.cbSize = sizeof(si); + si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; + si.nMin = 0; + si.nMax = term->rows - 1; + si.nPage = term->rows; + si.nPos = 0; + SetScrollInfo(hwnd, SB_VERT, &si, FALSE); + } + + /* + * Prepare the mouse handler. + */ + lastact = MA_NOTHING; + lastbtn = MBT_NOTHING; + dbltime = GetDoubleClickTime(); + + /* + * Set up the session-control options on the system menu. + */ + { + HMENU m; + int j; + char *str; + + popup_menus[SYSMENU].menu = GetSystemMenu(hwnd, FALSE); + popup_menus[CTXMENU].menu = CreatePopupMenu(); + AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_PASTE, "&Paste"); + + savedsess_menu = CreateMenu(); + get_sesslist(&sesslist, TRUE); + update_savedsess_menu(); + + for (j = 0; j < lenof(popup_menus); j++) { + m = popup_menus[j].menu; + + AppendMenu(m, MF_SEPARATOR, 0, 0); + AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log"); + AppendMenu(m, MF_SEPARATOR, 0, 0); + AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session..."); + AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session"); + AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) savedsess_menu, + "Sa&ved Sessions"); + AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings..."); + AppendMenu(m, MF_SEPARATOR, 0, 0); + AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard"); + AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback"); + AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal"); + AppendMenu(m, MF_SEPARATOR, 0, 0); + AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ? + MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen"); + AppendMenu(m, MF_SEPARATOR, 0, 0); + if (has_help()) + AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help"); + str = dupprintf("&About %s", appname); + AppendMenu(m, MF_ENABLED, IDM_ABOUT, str); + sfree(str); + } + } + + start_backend(); + + /* + * Set up the initial input locale. + */ + set_input_locale(GetKeyboardLayout(0)); + + /* + * Finally show the window! + */ + ShowWindow(hwnd, show); + SetForegroundWindow(hwnd); + + /* + * Set the palette up. + */ + pal = NULL; + logpal = NULL; + init_palette(); + + term_set_focus(term, GetForegroundWindow() == hwnd); + UpdateWindow(hwnd); + + while (1) { + 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); + if (must_close_session) + close_session(); + } else + sfree(handles); + + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) + goto finished; /* two-level break */ + + if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg))) + DispatchMessage(&msg); + /* Send the paste buffer if there's anything to send */ + term_paste(term); + /* If there's nothing new in the queue then we can do everything + * we've delayed, reading the socket, writing, and repainting + * the window. + */ + if (must_close_session) + close_session(); + } + + /* The messages seem unreliable; especially if we're being tricky */ + term_set_focus(term, GetForegroundWindow() == hwnd); + + if (pending_netevent) + enact_pending_netevent(); + + net_pending_errors(); + } + + finished: + cleanup_exit(msg.wParam); /* this doesn't return... */ + return msg.wParam; /* ... but optimiser doesn't know */ +} + +/* + * Clean up and exit. + */ +void cleanup_exit(int code) +{ + /* + * Clean up. + */ + deinit_fonts(); + sfree(logpal); + if (pal) + DeleteObject(pal); + sk_cleanup(); + + if (cfg.protocol == PROT_SSH) { + random_save_seed(); +#ifdef MSCRYPTOAPI + crypto_wrapup(); +#endif + } + shutdown_help(); + + /* Clean up COM. */ + CoUninitialize(); + + exit(code); +} + +/* + * Set up, or shut down, an AsyncSelect. Called from winnet.c. + */ +char *do_select(SOCKET skt, int startup) +{ + int msg, events; + if (startup) { + msg = WM_NETEVENT; + events = (FD_CONNECT | FD_READ | FD_WRITE | + FD_OOB | FD_CLOSE | FD_ACCEPT); + } else { + msg = events = 0; + } + if (!hwnd) + return "do_select(): internal error (hwnd==NULL)"; + if (p_WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) { + switch (p_WSAGetLastError()) { + case WSAENETDOWN: + return "Network is down"; + default: + return "WSAAsyncSelect(): unknown error"; + } + } + return NULL; +} + +/* + * Refresh the saved-session submenu from `sesslist'. + */ +static void update_savedsess_menu(void) +{ + int i; + while (DeleteMenu(savedsess_menu, 0, MF_BYPOSITION)) ; + /* skip sesslist.sessions[0] == Default Settings */ + for (i = 1; + i < ((sesslist.nsessions <= MENU_SAVED_MAX+1) ? sesslist.nsessions + : MENU_SAVED_MAX+1); + i++) + AppendMenu(savedsess_menu, MF_ENABLED, + IDM_SAVED_MIN + (i-1)*MENU_SAVED_STEP, + sesslist.sessions[i]); + if (sesslist.nsessions <= 1) + AppendMenu(savedsess_menu, MF_GRAYED, IDM_SAVED_MIN, "(No sessions)"); +} + +/* + * Update the Special Commands submenu. + */ +void update_specials_menu(void *frontend) +{ + HMENU new_menu; + int i, j; + + if (back) + specials = back->get_specials(backhandle); + else + specials = NULL; + + if (specials) { + /* We can't use Windows to provide a stack for submenus, so + * here's a lame "stack" that will do for now. */ + HMENU saved_menu = NULL; + int nesting = 1; + new_menu = CreatePopupMenu(); + for (i = 0; nesting > 0; i++) { + assert(IDM_SPECIAL_MIN + 0x10 * i < IDM_SPECIAL_MAX); + switch (specials[i].code) { + case TS_SEP: + AppendMenu(new_menu, MF_SEPARATOR, 0, 0); + break; + case TS_SUBMENU: + assert(nesting < 2); + nesting++; + saved_menu = new_menu; /* XXX lame stacking */ + new_menu = CreatePopupMenu(); + AppendMenu(saved_menu, MF_POPUP | MF_ENABLED, + (UINT) new_menu, specials[i].name); + break; + case TS_EXITMENU: + nesting--; + if (nesting) { + new_menu = saved_menu; /* XXX lame stacking */ + saved_menu = NULL; + } + break; + default: + AppendMenu(new_menu, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i, + specials[i].name); + break; + } + } + /* Squirrel the highest special. */ + n_specials = i - 1; + } else { + new_menu = NULL; + n_specials = 0; + } + + for (j = 0; j < lenof(popup_menus); j++) { + if (specials_menu) { + /* XXX does this free up all submenus? */ + DeleteMenu(popup_menus[j].menu, (UINT)specials_menu, MF_BYCOMMAND); + DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND); + } + if (new_menu) { + InsertMenu(popup_menus[j].menu, IDM_SHOWLOG, + MF_BYCOMMAND | MF_POPUP | MF_ENABLED, + (UINT) new_menu, "S&pecial Command"); + InsertMenu(popup_menus[j].menu, IDM_SHOWLOG, + MF_BYCOMMAND | MF_SEPARATOR, IDM_SPECIALSEP, 0); + } + } + specials_menu = new_menu; +} + +static void update_mouse_pointer(void) +{ + LPTSTR curstype; + int force_visible = FALSE; + static int forced_visible = FALSE; + switch (busy_status) { + case BUSY_NOT: + if (send_raw_mouse) + curstype = IDC_ARROW; + else + curstype = IDC_IBEAM; + break; + case BUSY_WAITING: + curstype = IDC_APPSTARTING; /* this may be an abuse */ + force_visible = TRUE; + break; + case BUSY_CPU: + curstype = IDC_WAIT; + force_visible = TRUE; + break; + default: + assert(0); + } + { + HCURSOR cursor = LoadCursor(NULL, curstype); + SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG_PTR)cursor); + SetCursor(cursor); /* force redraw of cursor at current posn */ + } + if (force_visible != forced_visible) { + /* We want some cursor shapes to be visible always. + * Along with show_mouseptr(), this manages the ShowCursor() + * counter such that if we switch back to a non-force_visible + * cursor, the previous visibility state is restored. */ + ShowCursor(force_visible); + forced_visible = force_visible; + } +} + +void set_busy_status(void *frontend, int status) +{ + busy_status = status; + update_mouse_pointer(); +} + +/* + * set or clear the "raw mouse message" mode + */ +void set_raw_mouse_mode(void *frontend, int activate) +{ + activate = activate && !cfg.no_mouse_rep; + send_raw_mouse = activate; + update_mouse_pointer(); +} + +/* + * Print a message box and close the connection. + */ +void connection_fatal(void *frontend, char *fmt, ...) +{ + va_list ap; + char *stuff, morestuff[100]; + + va_start(ap, fmt); + stuff = dupvprintf(fmt, ap); + va_end(ap); + sprintf(morestuff, "%.70s Fatal Error", appname); + MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK); + sfree(stuff); + + if (cfg.close_on_exit == FORCE_ON) + PostQuitMessage(1); + else { + must_close_session = TRUE; + } +} + +/* + * Report an error at the command-line parsing stage. + */ +void cmdline_error(char *fmt, ...) +{ + va_list ap; + char *stuff, morestuff[100]; + + va_start(ap, fmt); + stuff = dupvprintf(fmt, ap); + va_end(ap); + sprintf(morestuff, "%.70s Command Line Error", appname); + MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK); + sfree(stuff); + exit(1); +} + +/* + * Actually do the job requested by a WM_NETEVENT + */ +static void enact_pending_netevent(void) +{ + static int reentering = 0; + extern int select_result(WPARAM, LPARAM); + + if (reentering) + return; /* don't unpend the pending */ + + pending_netevent = FALSE; + + reentering = 1; + select_result(pend_netevent_wParam, pend_netevent_lParam); + reentering = 0; +} + +/* + * Copy the colour palette from the configuration data into defpal. + * This is non-trivial because the colour indices are different. + */ +static void cfgtopalette(void) +{ + int i; + static const int ww[] = { + 256, 257, 258, 259, 260, 261, + 0, 8, 1, 9, 2, 10, 3, 11, + 4, 12, 5, 13, 6, 14, 7, 15 + }; + + for (i = 0; i < 22; i++) { + int w = ww[i]; + defpal[w].rgbtRed = cfg.colours[i][0]; + defpal[w].rgbtGreen = cfg.colours[i][1]; + defpal[w].rgbtBlue = cfg.colours[i][2]; + } + for (i = 0; i < NEXTCOLOURS; i++) { + if (i < 216) { + int r = i / 36, g = (i / 6) % 6, b = i % 6; + defpal[i+16].rgbtRed = r ? r * 40 + 55 : 0; + defpal[i+16].rgbtGreen = g ? g * 40 + 55 : 0; + defpal[i+16].rgbtBlue = b ? b * 40 + 55 : 0; + } else { + int shade = i - 216; + shade = shade * 10 + 8; + defpal[i+16].rgbtRed = defpal[i+16].rgbtGreen = + defpal[i+16].rgbtBlue = shade; + } + } + + /* Override with system colours if appropriate */ + if (cfg.system_colour) + systopalette(); +} + +/* + * Override bit of defpal with colours from the system. + * (NB that this takes a copy the system colours at the time this is called, + * so subsequent colour scheme changes don't take effect. To fix that we'd + * probably want to be using GetSysColorBrush() and the like.) + */ +static void systopalette(void) +{ + int i; + static const struct { int nIndex; int norm; int bold; } or[] = + { + { COLOR_WINDOWTEXT, 256, 257 }, /* Default Foreground */ + { COLOR_WINDOW, 258, 259 }, /* Default Background */ + { COLOR_HIGHLIGHTTEXT, 260, 260 }, /* Cursor Text */ + { COLOR_HIGHLIGHT, 261, 261 }, /* Cursor Colour */ + }; + + for (i = 0; i < (sizeof(or)/sizeof(or[0])); i++) { + COLORREF colour = GetSysColor(or[i].nIndex); + defpal[or[i].norm].rgbtRed = + defpal[or[i].bold].rgbtRed = GetRValue(colour); + defpal[or[i].norm].rgbtGreen = + defpal[or[i].bold].rgbtGreen = GetGValue(colour); + defpal[or[i].norm].rgbtBlue = + defpal[or[i].bold].rgbtBlue = GetBValue(colour); + } +} + +/* + * Set up the colour palette. + */ +static void init_palette(void) +{ + int i; + HDC hdc = GetDC(hwnd); + if (hdc) { + if (cfg.try_palette && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) { + /* + * This is a genuine case where we must use smalloc + * because the snew macros can't cope. + */ + logpal = smalloc(sizeof(*logpal) + - sizeof(logpal->palPalEntry) + + NALLCOLOURS * sizeof(PALETTEENTRY)); + logpal->palVersion = 0x300; + logpal->palNumEntries = NALLCOLOURS; + for (i = 0; i < NALLCOLOURS; i++) { + logpal->palPalEntry[i].peRed = defpal[i].rgbtRed; + logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen; + logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue; + logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE; + } + pal = CreatePalette(logpal); + if (pal) { + SelectPalette(hdc, pal, FALSE); + RealizePalette(hdc); + SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE); + } + } + ReleaseDC(hwnd, hdc); + } + if (pal) + for (i = 0; i < NALLCOLOURS; i++) + colours[i] = PALETTERGB(defpal[i].rgbtRed, + defpal[i].rgbtGreen, + defpal[i].rgbtBlue); + else + for (i = 0; i < NALLCOLOURS; i++) + colours[i] = RGB(defpal[i].rgbtRed, + defpal[i].rgbtGreen, defpal[i].rgbtBlue); +} + +/* + * This is a wrapper to ExtTextOut() to force Windows to display + * the precise glyphs we give it. Otherwise it would do its own + * bidi and Arabic shaping, and we would end up uncertain which + * characters it had put where. + */ +static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc, + unsigned short *lpString, UINT cbCount, + CONST INT *lpDx, int opaque) +{ +#ifdef __LCC__ + /* + * The LCC include files apparently don't supply the + * GCP_RESULTSW type, but we can make do with GCP_RESULTS + * proper: the differences aren't important to us (the only + * variable-width string parameter is one we don't use anyway). + */ + GCP_RESULTS gcpr; +#else + GCP_RESULTSW gcpr; +#endif + char *buffer = snewn(cbCount*2+2, char); + char *classbuffer = snewn(cbCount, char); + memset(&gcpr, 0, sizeof(gcpr)); + memset(buffer, 0, cbCount*2+2); + memset(classbuffer, GCPCLASS_NEUTRAL, cbCount); + + gcpr.lStructSize = sizeof(gcpr); + gcpr.lpGlyphs = (void *)buffer; + gcpr.lpClass = (void *)classbuffer; + gcpr.nGlyphs = cbCount; + GetCharacterPlacementW(hdc, lpString, cbCount, 0, &gcpr, + FLI_MASK | GCP_CLASSIN | GCP_DIACRITIC); + + ExtTextOut(hdc, x, y, + ETO_GLYPH_INDEX | ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), + lprc, buffer, cbCount, lpDx); +} + +/* + * The exact_textout() wrapper, unfortunately, destroys the useful + * Windows `font linking' behaviour: automatic handling of Unicode + * code points not supported in this font by falling back to a font + * which does contain them. Therefore, we adopt a multi-layered + * approach: for any potentially-bidi text, we use exact_textout(), + * and for everything else we use a simple ExtTextOut as we did + * before exact_textout() was introduced. + */ +static void general_textout(HDC hdc, int x, int y, CONST RECT *lprc, + unsigned short *lpString, UINT cbCount, + CONST INT *lpDx, int opaque) +{ + int i, j, xp, xn; + int bkmode = 0, got_bkmode = FALSE; + + xp = xn = x; + + for (i = 0; i < (int)cbCount ;) { + int rtl = is_rtl(lpString[i]); + + xn += lpDx[i]; + + for (j = i+1; j < (int)cbCount; j++) { + if (rtl != is_rtl(lpString[j])) + break; + xn += lpDx[j]; + } + + /* + * Now [i,j) indicates a maximal substring of lpString + * which should be displayed using the same textout + * function. + */ + if (rtl) { + exact_textout(hdc, xp, y, lprc, lpString+i, j-i, + font_varpitch ? NULL : lpDx+i, opaque); + } else { + ExtTextOutW(hdc, xp, y, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), + lprc, lpString+i, j-i, + font_varpitch ? NULL : lpDx+i); + } + + i = j; + xp = xn; + + bkmode = GetBkMode(hdc); + got_bkmode = TRUE; + SetBkMode(hdc, TRANSPARENT); + opaque = FALSE; + } + + if (got_bkmode) + SetBkMode(hdc, bkmode); +} + +static int get_font_width(HDC hdc, const TEXTMETRIC *tm) +{ + int ret; + /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ + if (!(tm->tmPitchAndFamily & TMPF_FIXED_PITCH)) { + ret = tm->tmAveCharWidth; + } else { +#define FIRST '0' +#define LAST '9' + ABCFLOAT widths[LAST-FIRST + 1]; + int j; + + font_varpitch = TRUE; + font_dualwidth = TRUE; + if (GetCharABCWidthsFloat(hdc, FIRST, LAST, widths)) { + ret = 0; + for (j = 0; j < lenof(widths); j++) { + int width = (int)(0.5 + widths[j].abcfA + + widths[j].abcfB + widths[j].abcfC); + if (ret < width) + ret = width; + } + } else { + ret = tm->tmMaxCharWidth; + } +#undef FIRST +#undef LAST + } + return ret; +} + +/* + * Initialise all the fonts we will need initially. There may be as many as + * three or as few as one. The other (potentially) twenty-one fonts are done + * if/when they are needed. + * + * We also: + * + * - check the font width and height, correcting our guesses if + * necessary. + * + * - verify that the bold font is the same width as the ordinary + * one, and engage shadow bolding if not. + * + * - verify that the underlined font is the same width as the + * ordinary one (manual underlining by means of line drawing can + * be done in a pinch). + */ +static void init_fonts(int pick_width, int pick_height) +{ + TEXTMETRIC tm; + CPINFO cpinfo; + int fontsize[3]; + int i; + HDC hdc; + int fw_dontcare, fw_bold; + + for (i = 0; i < FONT_MAXNO; i++) + fonts[i] = NULL; + + bold_mode = cfg.bold_colour ? BOLD_COLOURS : BOLD_FONT; + und_mode = UND_FONT; + + if (cfg.font.isbold) { + fw_dontcare = FW_BOLD; + fw_bold = FW_HEAVY; + } else { + fw_dontcare = FW_DONTCARE; + fw_bold = FW_BOLD; + } + + hdc = GetDC(hwnd); + + if (pick_height) + font_height = pick_height; + else { + font_height = cfg.font.height; + if (font_height > 0) { + font_height = + -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72); + } + } + font_width = pick_width; + +#define f(i,c,w,u) \ + fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \ + c, OUT_DEFAULT_PRECIS, \ + CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality), \ + FIXED_PITCH | FF_DONTCARE, cfg.font.name) + + f(FONT_NORMAL, cfg.font.charset, fw_dontcare, FALSE); + + SelectObject(hdc, fonts[FONT_NORMAL]); + GetTextMetrics(hdc, &tm); + + GetObject(fonts[FONT_NORMAL], sizeof(LOGFONT), &lfont); + + /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ + if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) { + font_varpitch = FALSE; + font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth); + } else { + font_varpitch = TRUE; + font_dualwidth = TRUE; + } + if (pick_width == 0 || pick_height == 0) { + font_height = tm.tmHeight; + font_width = get_font_width(hdc, &tm); + } + +#ifdef RDB_DEBUG_PATCH + debug(23, "Primary font H=%d, AW=%d, MW=%d", + tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth); +#endif + + { + CHARSETINFO info; + DWORD cset = tm.tmCharSet; + memset(&info, 0xFF, sizeof(info)); + + /* !!! Yes the next line is right */ + if (cset == OEM_CHARSET) + ucsdata.font_codepage = GetOEMCP(); + else + if (TranslateCharsetInfo ((DWORD *) cset, &info, TCI_SRCCHARSET)) + ucsdata.font_codepage = info.ciACP; + else + ucsdata.font_codepage = -1; + + GetCPInfo(ucsdata.font_codepage, &cpinfo); + ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1); + } + + f(FONT_UNDERLINE, cfg.font.charset, fw_dontcare, TRUE); + + /* + * Some fonts, e.g. 9-pt Courier, draw their underlines + * outside their character cell. We successfully prevent + * screen corruption by clipping the text output, but then + * we lose the underline completely. Here we try to work + * out whether this is such a font, and if it is, we set a + * flag that causes underlines to be drawn by hand. + * + * Having tried other more sophisticated approaches (such + * as examining the TEXTMETRIC structure or requesting the + * height of a string), I think we'll do this the brute + * force way: we create a small bitmap, draw an underlined + * space on it, and test to see whether any pixels are + * foreground-coloured. (Since we expect the underline to + * go all the way across the character cell, we only search + * down a single column of the bitmap, half way across.) + */ + { + HDC und_dc; + HBITMAP und_bm, und_oldbm; + int i, gotit; + COLORREF c; + + und_dc = CreateCompatibleDC(hdc); + und_bm = CreateCompatibleBitmap(hdc, font_width, font_height); + und_oldbm = SelectObject(und_dc, und_bm); + SelectObject(und_dc, fonts[FONT_UNDERLINE]); + SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP); + SetTextColor(und_dc, RGB(255, 255, 255)); + SetBkColor(und_dc, RGB(0, 0, 0)); + SetBkMode(und_dc, OPAQUE); + ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL); + gotit = FALSE; + for (i = 0; i < font_height; i++) { + c = GetPixel(und_dc, font_width / 2, i); + if (c != RGB(0, 0, 0)) + gotit = TRUE; + } + SelectObject(und_dc, und_oldbm); + DeleteObject(und_bm); + DeleteDC(und_dc); + if (!gotit) { + und_mode = UND_LINE; + DeleteObject(fonts[FONT_UNDERLINE]); + fonts[FONT_UNDERLINE] = 0; + } + } + + if (bold_mode == BOLD_FONT) { + f(FONT_BOLD, cfg.font.charset, fw_bold, FALSE); + } +#undef f + + descent = tm.tmAscent + 1; + if (descent >= font_height) + descent = font_height - 1; + + for (i = 0; i < 3; i++) { + if (fonts[i]) { + if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm)) + fontsize[i] = get_font_width(hdc, &tm) + 256 * tm.tmHeight; + else + fontsize[i] = -i; + } else + fontsize[i] = -i; + } + + ReleaseDC(hwnd, hdc); + + if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) { + und_mode = UND_LINE; + DeleteObject(fonts[FONT_UNDERLINE]); + fonts[FONT_UNDERLINE] = 0; + } + + if (bold_mode == BOLD_FONT && + fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) { + bold_mode = BOLD_SHADOW; + DeleteObject(fonts[FONT_BOLD]); + fonts[FONT_BOLD] = 0; + } + fontflag[0] = fontflag[1] = fontflag[2] = 1; + + init_ucs(&cfg, &ucsdata); +} + +static void another_font(int fontno) +{ + int basefont; + int fw_dontcare, fw_bold; + int c, u, w, x; + char *s; + + if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno]) + return; + + basefont = (fontno & ~(FONT_BOLDUND)); + if (basefont != fontno && !fontflag[basefont]) + another_font(basefont); + + if (cfg.font.isbold) { + fw_dontcare = FW_BOLD; + fw_bold = FW_HEAVY; + } else { + fw_dontcare = FW_DONTCARE; + fw_bold = FW_BOLD; + } + + c = cfg.font.charset; + w = fw_dontcare; + u = FALSE; + s = cfg.font.name; + x = font_width; + + if (fontno & FONT_WIDE) + x *= 2; + if (fontno & FONT_NARROW) + x = (x+1)/2; + if (fontno & FONT_OEM) + c = OEM_CHARSET; + if (fontno & FONT_BOLD) + w = fw_bold; + if (fontno & FONT_UNDERLINE) + u = TRUE; + + fonts[fontno] = + CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w, + FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality), + DEFAULT_PITCH | FF_DONTCARE, s); + + fontflag[fontno] = 1; +} + +static void deinit_fonts(void) +{ + int i; + for (i = 0; i < FONT_MAXNO; i++) { + if (fonts[i]) + DeleteObject(fonts[i]); + fonts[i] = 0; + fontflag[i] = 0; + } +} + +void request_resize(void *frontend, int w, int h) +{ + int width, height; + + /* If the window is maximized supress resizing attempts */ + if (IsZoomed(hwnd)) { + if (cfg.resize_action == RESIZE_TERM) + return; + } + + if (cfg.resize_action == RESIZE_DISABLED) return; + if (h == term->rows && w == term->cols) return; + + /* Sanity checks ... */ + { + static int first_time = 1; + static RECT ss; + + switch (first_time) { + case 1: + /* Get the size of the screen */ + if (get_fullscreen_rect(&ss)) + /* first_time = 0 */ ; + else { + first_time = 2; + break; + } + case 0: + /* Make sure the values are sane */ + width = (ss.right - ss.left - extra_width) / 4; + height = (ss.bottom - ss.top - extra_height) / 6; + + if (w > width || h > height) + return; + if (w < 15) + w = 15; + if (h < 1) + h = 1; + } + } + + term_size(term, h, w, cfg.savelines); + + if (cfg.resize_action != RESIZE_FONT && !IsZoomed(hwnd)) { + width = extra_width + font_width * w; + height = extra_height + font_height * h; + + SetWindowPos(hwnd, NULL, 0, 0, width, height, + SWP_NOACTIVATE | SWP_NOCOPYBITS | + SWP_NOMOVE | SWP_NOZORDER); + } else + reset_window(0); + + InvalidateRect(hwnd, NULL, TRUE); +} + +static void reset_window(int reinit) { + /* + * This function decides how to resize or redraw when the + * user changes something. + * + * This function doesn't like to change the terminal size but if the + * font size is locked that may be it's only soluion. + */ + int win_width, win_height; + RECT cr, wr; + +#ifdef RDB_DEBUG_PATCH + debug((27, "reset_window()")); +#endif + + /* Current window sizes ... */ + GetWindowRect(hwnd, &wr); + GetClientRect(hwnd, &cr); + + win_width = cr.right - cr.left; + win_height = cr.bottom - cr.top; + + if (cfg.resize_action == RESIZE_DISABLED) reinit = 2; + + /* Are we being forced to reload the fonts ? */ + if (reinit>1) { +#ifdef RDB_DEBUG_PATCH + debug((27, "reset_window() -- Forced deinit")); +#endif + deinit_fonts(); + init_fonts(0,0); + } + + /* Oh, looks like we're minimised */ + if (win_width == 0 || win_height == 0) + return; + + /* Is the window out of position ? */ + if ( !reinit && + (offset_width != (win_width-font_width*term->cols)/2 || + offset_height != (win_height-font_height*term->rows)/2) ){ + offset_width = (win_width-font_width*term->cols)/2; + offset_height = (win_height-font_height*term->rows)/2; + InvalidateRect(hwnd, NULL, TRUE); +#ifdef RDB_DEBUG_PATCH + debug((27, "reset_window() -> Reposition terminal")); +#endif + } + + if (IsZoomed(hwnd)) { + /* We're fullscreen, this means we must not change the size of + * the window so it's the font size or the terminal itself. + */ + + extra_width = wr.right - wr.left - cr.right + cr.left; + extra_height = wr.bottom - wr.top - cr.bottom + cr.top; + + if (cfg.resize_action != RESIZE_TERM) { + if ( font_width != win_width/term->cols || + font_height != win_height/term->rows) { + deinit_fonts(); + init_fonts(win_width/term->cols, win_height/term->rows); + offset_width = (win_width-font_width*term->cols)/2; + offset_height = (win_height-font_height*term->rows)/2; + InvalidateRect(hwnd, NULL, TRUE); +#ifdef RDB_DEBUG_PATCH + debug((25, "reset_window() -> Z font resize to (%d, %d)", + font_width, font_height)); +#endif + } + } else { + if ( font_width * term->cols != win_width || + font_height * term->rows != win_height) { + /* Our only choice at this point is to change the + * size of the terminal; Oh well. + */ + term_size(term, win_height/font_height, win_width/font_width, + cfg.savelines); + offset_width = (win_width-font_width*term->cols)/2; + offset_height = (win_height-font_height*term->rows)/2; + InvalidateRect(hwnd, NULL, TRUE); +#ifdef RDB_DEBUG_PATCH + debug((27, "reset_window() -> Zoomed term_size")); +#endif + } + } + return; + } + + /* Hmm, a force re-init means we should ignore the current window + * so we resize to the default font size. + */ + if (reinit>0) { +#ifdef RDB_DEBUG_PATCH + debug((27, "reset_window() -> Forced re-init")); +#endif + + offset_width = offset_height = cfg.window_border; + extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; + extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; + + if (win_width != font_width*term->cols + offset_width*2 || + win_height != font_height*term->rows + offset_height*2) { + + /* If this is too large windows will resize it to the maximum + * allowed window size, we will then be back in here and resize + * the font or terminal to fit. + */ + SetWindowPos(hwnd, NULL, 0, 0, + font_width*term->cols + extra_width, + font_height*term->rows + extra_height, + SWP_NOMOVE | SWP_NOZORDER); + } + + InvalidateRect(hwnd, NULL, TRUE); + return; + } + + /* Okay the user doesn't want us to change the font so we try the + * window. But that may be too big for the screen which forces us + * to change the terminal. + */ + if ((cfg.resize_action == RESIZE_TERM && reinit<=0) || + (cfg.resize_action == RESIZE_EITHER && reinit<0) || + reinit>0) { + offset_width = offset_height = cfg.window_border; + extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; + extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; + + if (win_width != font_width*term->cols + offset_width*2 || + win_height != font_height*term->rows + offset_height*2) { + + static RECT ss; + int width, height; + + get_fullscreen_rect(&ss); + + width = (ss.right - ss.left - extra_width) / font_width; + height = (ss.bottom - ss.top - extra_height) / font_height; + + /* Grrr too big */ + if ( term->rows > height || term->cols > width ) { + if (cfg.resize_action == RESIZE_EITHER) { + /* Make the font the biggest we can */ + if (term->cols > width) + font_width = (ss.right - ss.left - extra_width) + / term->cols; + if (term->rows > height) + font_height = (ss.bottom - ss.top - extra_height) + / term->rows; + + deinit_fonts(); + init_fonts(font_width, font_height); + + width = (ss.right - ss.left - extra_width) / font_width; + height = (ss.bottom - ss.top - extra_height) / font_height; + } else { + if ( height > term->rows ) height = term->rows; + if ( width > term->cols ) width = term->cols; + term_size(term, height, width, cfg.savelines); +#ifdef RDB_DEBUG_PATCH + debug((27, "reset_window() -> term resize to (%d,%d)", + height, width)); +#endif + } + } + + SetWindowPos(hwnd, NULL, 0, 0, + font_width*term->cols + extra_width, + font_height*term->rows + extra_height, + SWP_NOMOVE | SWP_NOZORDER); + + InvalidateRect(hwnd, NULL, TRUE); +#ifdef RDB_DEBUG_PATCH + debug((27, "reset_window() -> window resize to (%d,%d)", + font_width*term->cols + extra_width, + font_height*term->rows + extra_height)); +#endif + } + return; + } + + /* We're allowed to or must change the font but do we want to ? */ + + if (font_width != (win_width-cfg.window_border*2)/term->cols || + font_height != (win_height-cfg.window_border*2)/term->rows) { + + deinit_fonts(); + init_fonts((win_width-cfg.window_border*2)/term->cols, + (win_height-cfg.window_border*2)/term->rows); + offset_width = (win_width-font_width*term->cols)/2; + offset_height = (win_height-font_height*term->rows)/2; + + extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2; + extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2; + + InvalidateRect(hwnd, NULL, TRUE); +#ifdef RDB_DEBUG_PATCH + debug((25, "reset_window() -> font resize to (%d,%d)", + font_width, font_height)); +#endif + } +} + +static void set_input_locale(HKL kl) +{ + char lbuf[20]; + + GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE, + lbuf, sizeof(lbuf)); + + kbd_codepage = atoi(lbuf); +} + +static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt) +{ + int thistime = GetMessageTime(); + + if (send_raw_mouse && !(cfg.mouse_override && shift)) { + lastbtn = MBT_NOTHING; + term_mouse(term, b, translate_button(b), MA_CLICK, + x, y, shift, ctrl, alt); + return; + } + + if (lastbtn == b && thistime - lasttime < dbltime) { + lastact = (lastact == MA_CLICK ? MA_2CLK : + lastact == MA_2CLK ? MA_3CLK : + lastact == MA_3CLK ? MA_CLICK : MA_NOTHING); + } else { + lastbtn = b; + lastact = MA_CLICK; + } + if (lastact != MA_NOTHING) + term_mouse(term, b, translate_button(b), lastact, + x, y, shift, ctrl, alt); + lasttime = thistime; +} + +/* + * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT) + * into a cooked one (SELECT, EXTEND, PASTE). + */ +static Mouse_Button translate_button(Mouse_Button button) +{ + if (button == MBT_LEFT) + return MBT_SELECT; + if (button == MBT_MIDDLE) + return cfg.mouse_is_xterm == 1 ? MBT_PASTE : MBT_EXTEND; + if (button == MBT_RIGHT) + return cfg.mouse_is_xterm == 1 ? MBT_EXTEND : MBT_PASTE; + return 0; /* shouldn't happen */ +} + +static void show_mouseptr(int show) +{ + /* NB that the counter in ShowCursor() is also frobbed by + * update_mouse_pointer() */ + static int cursor_visible = 1; + if (!cfg.hide_mouseptr) /* override if this feature disabled */ + show = 1; + if (cursor_visible && !show) + ShowCursor(FALSE); + else if (!cursor_visible && show) + ShowCursor(TRUE); + cursor_visible = show; +} + +static int is_alt_pressed(void) +{ + BYTE keystate[256]; + int r = GetKeyboardState(keystate); + if (!r) + return FALSE; + if (keystate[VK_MENU] & 0x80) + return TRUE; + if (keystate[VK_RMENU] & 0x80) + return TRUE; + return FALSE; +} + +static int resizing; + +void notify_remote_exit(void *fe) +{ + int exitcode; + + if (!session_closed && + (exitcode = back->exitcode(backhandle)) >= 0) { + /* Abnormal exits will already have set session_closed and taken + * appropriate action. */ + if (cfg.close_on_exit == FORCE_ON || + (cfg.close_on_exit == AUTO && exitcode != INT_MAX)) { + PostQuitMessage(0); + } else { + must_close_session = TRUE; + session_closed = TRUE; + /* exitcode == INT_MAX indicates that the connection was closed + * by a fatal error, so an error box will be coming our way and + * we should not generate this informational one. */ + if (exitcode != INT_MAX) + MessageBox(hwnd, "Connection closed by remote host", + appname, MB_OK | MB_ICONINFORMATION); + } + } +} + +void timer_change_notify(long next) +{ + long ticks = next - GETTICKCOUNT(); + if (ticks <= 0) ticks = 1; /* just in case */ + KillTimer(hwnd, TIMING_TIMER_ID); + SetTimer(hwnd, TIMING_TIMER_ID, ticks, NULL); + timing_next_time = next; +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam) +{ + HDC hdc; + static int ignore_clip = FALSE; + static int need_backend_resize = FALSE; + static int fullscr_on_max = FALSE; + static int processed_resize = FALSE; + static UINT last_mousemove = 0; + + switch (message) { + case WM_TIMER: + if ((UINT_PTR)wParam == TIMING_TIMER_ID) { + long next; + + KillTimer(hwnd, TIMING_TIMER_ID); + if (run_timers(timing_next_time, &next)) { + timer_change_notify(next); + } else { + } + } + return 0; + case WM_CREATE: + break; + case WM_CLOSE: + { + char *str; + show_mouseptr(1); + str = dupprintf("%s Exit Confirmation", appname); + if (!cfg.warn_on_close || session_closed || + MessageBox(hwnd, + "Are you sure you want to close this session?", + str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1) + == IDOK) + DestroyWindow(hwnd); + sfree(str); + } + return 0; + case WM_DESTROY: + show_mouseptr(1); + PostQuitMessage(0); + return 0; + case WM_INITMENUPOPUP: + if ((HMENU)wParam == savedsess_menu) { + /* About to pop up Saved Sessions sub-menu. + * Refresh the session list. */ + get_sesslist(&sesslist, FALSE); /* free */ + get_sesslist(&sesslist, TRUE); + update_savedsess_menu(); + return 0; + } + break; + case WM_COMMAND: + case WM_SYSCOMMAND: + switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */ + case IDM_SHOWLOG: + showeventlog(hwnd); + break; + case IDM_NEWSESS: + case IDM_DUPSESS: + case IDM_SAVEDSESS: + { + char b[2048]; + char c[30], *cl; + int freecl = FALSE; + BOOL inherit_handles; + STARTUPINFO si; + PROCESS_INFORMATION pi; + HANDLE filemap = NULL; + + if (wParam == IDM_DUPSESS) { + /* + * Allocate a file-mapping memory chunk for the + * config structure. + */ + SECURITY_ATTRIBUTES sa; + Config *p; + + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + filemap = CreateFileMapping(INVALID_HANDLE_VALUE, + &sa, + PAGE_READWRITE, + 0, sizeof(Config), NULL); + if (filemap && filemap != INVALID_HANDLE_VALUE) { + p = (Config *) MapViewOfFile(filemap, + FILE_MAP_WRITE, + 0, 0, sizeof(Config)); + if (p) { + *p = cfg; /* structure copy */ + UnmapViewOfFile(p); + } + } + inherit_handles = TRUE; + sprintf(c, "putty &%p", filemap); + cl = c; + } else if (wParam == IDM_SAVEDSESS) { + unsigned int sessno = ((lParam - IDM_SAVED_MIN) + / MENU_SAVED_STEP) + 1; + if (sessno < (unsigned)sesslist.nsessions) { + char *session = sesslist.sessions[sessno]; + cl = dupprintf("putty @%s", session); + inherit_handles = FALSE; + freecl = TRUE; + } else + break; + } else /* IDM_NEWSESS */ { + cl = NULL; + inherit_handles = FALSE; + } + + GetModuleFileName(NULL, b, sizeof(b) - 1); + si.cb = sizeof(si); + si.lpReserved = NULL; + si.lpDesktop = NULL; + si.lpTitle = NULL; + si.dwFlags = 0; + si.cbReserved2 = 0; + si.lpReserved2 = NULL; + CreateProcess(b, cl, NULL, NULL, inherit_handles, + NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi); + + if (filemap) + CloseHandle(filemap); + if (freecl) + sfree(cl); + } + break; + case IDM_RESTART: + if (!back) { + logevent(NULL, "----- Session restarted -----"); + term_pwron(term, FALSE); + start_backend(); + } + + break; + case IDM_RECONF: + { + Config prev_cfg; + int init_lvl = 1; + int reconfig_result; + + if (reconfiguring) + break; + else + reconfiguring = TRUE; + + GetWindowText(hwnd, cfg.wintitle, sizeof(cfg.wintitle)); + prev_cfg = cfg; + + reconfig_result = + do_reconfig(hwnd, back ? back->cfg_info(backhandle) : 0); + reconfiguring = FALSE; + if (!reconfig_result) + break; + + { + /* Disable full-screen if resizing forbidden */ + int i; + for (i = 0; i < lenof(popup_menus); i++) + EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, + MF_BYCOMMAND | + (cfg.resize_action == RESIZE_DISABLED) + ? MF_GRAYED : MF_ENABLED); + /* Gracefully unzoom if necessary */ + if (IsZoomed(hwnd) && + (cfg.resize_action == RESIZE_DISABLED)) { + ShowWindow(hwnd, SW_RESTORE); + } + } + + /* Pass new config data to the logging module */ + log_reconfig(logctx, &cfg); + + sfree(logpal); + /* + * Flush the line discipline's edit buffer in the + * case where local editing has just been disabled. + */ + if (ldisc) + ldisc_send(ldisc, NULL, 0, 0); + if (pal) + DeleteObject(pal); + logpal = NULL; + pal = NULL; + cfgtopalette(); + init_palette(); + + /* Pass new config data to the terminal */ + term_reconfig(term, &cfg); + + /* Pass new config data to the back end */ + if (back) + back->reconfig(backhandle, &cfg); + + /* Screen size changed ? */ + if (cfg.height != prev_cfg.height || + cfg.width != prev_cfg.width || + cfg.savelines != prev_cfg.savelines || + cfg.resize_action == RESIZE_FONT || + (cfg.resize_action == RESIZE_EITHER && IsZoomed(hwnd)) || + cfg.resize_action == RESIZE_DISABLED) + term_size(term, cfg.height, cfg.width, cfg.savelines); + + /* Enable or disable the scroll bar, etc */ + { + LONG nflg, flag = GetWindowLongPtr(hwnd, GWL_STYLE); + LONG nexflag, exflag = + GetWindowLongPtr(hwnd, GWL_EXSTYLE); + + nexflag = exflag; + if (cfg.alwaysontop != prev_cfg.alwaysontop) { + if (cfg.alwaysontop) { + nexflag |= WS_EX_TOPMOST; + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE); + } else { + nexflag &= ~(WS_EX_TOPMOST); + SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE); + } + } + if (cfg.sunken_edge) + nexflag |= WS_EX_CLIENTEDGE; + else + nexflag &= ~(WS_EX_CLIENTEDGE); + + nflg = flag; + if (is_full_screen() ? + cfg.scrollbar_in_fullscreen : cfg.scrollbar) + nflg |= WS_VSCROLL; + else + nflg &= ~WS_VSCROLL; + + if (cfg.resize_action == RESIZE_DISABLED || + is_full_screen()) + nflg &= ~WS_THICKFRAME; + else + nflg |= WS_THICKFRAME; + + if (cfg.resize_action == RESIZE_DISABLED) + nflg &= ~WS_MAXIMIZEBOX; + else + nflg |= WS_MAXIMIZEBOX; + + if (nflg != flag || nexflag != exflag) { + if (nflg != flag) + SetWindowLongPtr(hwnd, GWL_STYLE, nflg); + if (nexflag != exflag) + SetWindowLongPtr(hwnd, GWL_EXSTYLE, nexflag); + + SetWindowPos(hwnd, NULL, 0, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOCOPYBITS | + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | + SWP_FRAMECHANGED); + + init_lvl = 2; + } + } + + /* Oops */ + if (cfg.resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) { + force_normal(hwnd); + init_lvl = 2; + } + + set_title(NULL, cfg.wintitle); + if (IsIconic(hwnd)) { + SetWindowText(hwnd, + cfg.win_name_always ? window_name : + icon_name); + } + + if (strcmp(cfg.font.name, prev_cfg.font.name) != 0 || + strcmp(cfg.line_codepage, prev_cfg.line_codepage) != 0 || + cfg.font.isbold != prev_cfg.font.isbold || + cfg.font.height != prev_cfg.font.height || + cfg.font.charset != prev_cfg.font.charset || + cfg.font_quality != prev_cfg.font_quality || + cfg.vtmode != prev_cfg.vtmode || + cfg.bold_colour != prev_cfg.bold_colour || + cfg.resize_action == RESIZE_DISABLED || + cfg.resize_action == RESIZE_EITHER || + (cfg.resize_action != prev_cfg.resize_action)) + init_lvl = 2; + + InvalidateRect(hwnd, NULL, TRUE); + reset_window(init_lvl); + net_pending_errors(); + } + break; + case IDM_COPYALL: + term_copyall(term); + break; + case IDM_PASTE: + request_paste(NULL); + break; + case IDM_CLRSB: + term_clrsb(term); + break; + case IDM_RESET: + term_pwron(term, TRUE); + if (ldisc) + ldisc_send(ldisc, NULL, 0, 0); + break; + case IDM_ABOUT: + showabout(hwnd); + break; + case IDM_HELP: + launch_help(hwnd, NULL); + break; + case SC_MOUSEMENU: + /* + * We get this if the System menu has been activated + * using the mouse. + */ + show_mouseptr(1); + break; + case SC_KEYMENU: + /* + * We get this if the System menu has been activated + * using the keyboard. This might happen from within + * TranslateKey, in which case it really wants to be + * followed by a `space' character to actually _bring + * the menu up_ rather than just sitting there in + * `ready to appear' state. + */ + show_mouseptr(1); /* make sure pointer is visible */ + if( lParam == 0 ) + PostMessage(hwnd, WM_CHAR, ' ', 0); + break; + case IDM_FULLSCREEN: + flip_full_screen(); + break; + default: + if (wParam >= IDM_SAVED_MIN && wParam < IDM_SAVED_MAX) { + SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam); + } + if (wParam >= IDM_SPECIAL_MIN && wParam <= IDM_SPECIAL_MAX) { + int i = (wParam - IDM_SPECIAL_MIN) / 0x10; + /* + * Ensure we haven't been sent a bogus SYSCOMMAND + * which would cause us to reference invalid memory + * and crash. Perhaps I'm just too paranoid here. + */ + if (i >= n_specials) + break; + if (back) + back->special(backhandle, specials[i].code); + net_pending_errors(); + } + } + break; + +#define X_POS(l) ((int)(short)LOWORD(l)) +#define Y_POS(l) ((int)(short)HIWORD(l)) + +#define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width) +#define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height) + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + if (message == WM_RBUTTONDOWN && + ((wParam & MK_CONTROL) || (cfg.mouse_is_xterm == 2))) { + POINT cursorpos; + + show_mouseptr(1); /* make sure pointer is visible */ + GetCursorPos(&cursorpos); + TrackPopupMenu(popup_menus[CTXMENU].menu, + TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON, + cursorpos.x, cursorpos.y, + 0, hwnd, NULL); + break; + } + { + int button, press; + + switch (message) { + case WM_LBUTTONDOWN: + button = MBT_LEFT; + wParam |= MK_LBUTTON; + press = 1; + break; + case WM_MBUTTONDOWN: + button = MBT_MIDDLE; + wParam |= MK_MBUTTON; + press = 1; + break; + case WM_RBUTTONDOWN: + button = MBT_RIGHT; + wParam |= MK_RBUTTON; + press = 1; + break; + case WM_LBUTTONUP: + button = MBT_LEFT; + wParam &= ~MK_LBUTTON; + press = 0; + break; + case WM_MBUTTONUP: + button = MBT_MIDDLE; + wParam &= ~MK_MBUTTON; + press = 0; + break; + case WM_RBUTTONUP: + button = MBT_RIGHT; + wParam &= ~MK_RBUTTON; + press = 0; + break; + default: + button = press = 0; /* shouldn't happen */ + } + show_mouseptr(1); + /* + * Special case: in full-screen mode, if the left + * button is clicked in the very top left corner of the + * window, we put up the System menu instead of doing + * selection. + */ + { + char mouse_on_hotspot = 0; + POINT pt; + + GetCursorPos(&pt); +#ifndef NO_MULTIMON + { + HMONITOR mon; + MONITORINFO mi; + + mon = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL); + + if (mon != NULL) { + mi.cbSize = sizeof(MONITORINFO); + GetMonitorInfo(mon, &mi); + + if (mi.rcMonitor.left == pt.x && + mi.rcMonitor.top == pt.y) { + mouse_on_hotspot = 1; + } + } + } +#else + if (pt.x == 0 && pt.y == 0) { + mouse_on_hotspot = 1; + } +#endif + if (is_full_screen() && press && + button == MBT_LEFT && mouse_on_hotspot) { + SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, + MAKELPARAM(pt.x, pt.y)); + return 0; + } + } + + if (press) { + click(button, + TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)), + wParam & MK_SHIFT, wParam & MK_CONTROL, + is_alt_pressed()); + SetCapture(hwnd); + } else { + term_mouse(term, button, translate_button(button), MA_RELEASE, + TO_CHR_X(X_POS(lParam)), + TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, + wParam & MK_CONTROL, is_alt_pressed()); + if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON))) + ReleaseCapture(); + } + } + return 0; + case WM_MOUSEMOVE: + { + /* + * Windows seems to like to occasionally send MOUSEMOVE + * events even if the mouse hasn't moved. Don't unhide + * the mouse pointer in this case. + */ + static WPARAM wp = 0; + static LPARAM lp = 0; + if (wParam != wp || lParam != lp || + last_mousemove != WM_MOUSEMOVE) { + show_mouseptr(1); + wp = wParam; lp = lParam; + last_mousemove = WM_MOUSEMOVE; + } + } + /* + * Add the mouse position and message time to the random + * number noise. + */ + noise_ultralight(lParam); + + if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) && + GetCapture() == hwnd) { + Mouse_Button b; + if (wParam & MK_LBUTTON) + b = MBT_LEFT; + else if (wParam & MK_MBUTTON) + b = MBT_MIDDLE; + else + b = MBT_RIGHT; + term_mouse(term, b, translate_button(b), MA_DRAG, + TO_CHR_X(X_POS(lParam)), + TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, + wParam & MK_CONTROL, is_alt_pressed()); + } + return 0; + case WM_NCMOUSEMOVE: + { + static WPARAM wp = 0; + static LPARAM lp = 0; + if (wParam != wp || lParam != lp || + last_mousemove != WM_NCMOUSEMOVE) { + show_mouseptr(1); + wp = wParam; lp = lParam; + last_mousemove = WM_NCMOUSEMOVE; + } + } + noise_ultralight(lParam); + break; + case WM_IGNORE_CLIP: + ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */ + break; + case WM_DESTROYCLIPBOARD: + if (!ignore_clip) + term_deselect(term); + ignore_clip = FALSE; + return 0; + case WM_PAINT: + { + PAINTSTRUCT p; + + HideCaret(hwnd); + hdc = BeginPaint(hwnd, &p); + if (pal) { + SelectPalette(hdc, pal, TRUE); + RealizePalette(hdc); + } + + /* + * We have to be careful about term_paint(). It will + * set a bunch of character cells to INVALID and then + * call do_paint(), which will redraw those cells and + * _then mark them as done_. This may not be accurate: + * when painting in WM_PAINT context we are restricted + * to the rectangle which has just been exposed - so if + * that only covers _part_ of a character cell and the + * rest of it was already visible, that remainder will + * not be redrawn at all. Accordingly, we must not + * paint any character cell in a WM_PAINT context which + * already has a pending update due to terminal output. + * The simplest solution to this - and many, many + * thanks to Hung-Te Lin for working all this out - is + * not to do any actual painting at _all_ if there's a + * pending terminal update: just mark the relevant + * character cells as INVALID and wait for the + * scheduled full update to sort it out. + * + * I have a suspicion this isn't the _right_ solution. + * An alternative approach would be to have terminal.c + * separately track what _should_ be on the terminal + * screen and what _is_ on the terminal screen, and + * have two completely different types of redraw (one + * for full updates, which syncs the former with the + * terminal itself, and one for WM_PAINT which syncs + * the latter with the former); yet another possibility + * would be to have the Windows front end do what the + * GTK one already does, and maintain a bitmap of the + * current terminal appearance so that WM_PAINT becomes + * completely trivial. However, this should do for now. + */ + term_paint(term, hdc, + (p.rcPaint.left-offset_width)/font_width, + (p.rcPaint.top-offset_height)/font_height, + (p.rcPaint.right-offset_width-1)/font_width, + (p.rcPaint.bottom-offset_height-1)/font_height, + !term->window_update_pending); + + if (p.fErase || + p.rcPaint.left < offset_width || + p.rcPaint.top < offset_height || + p.rcPaint.right >= offset_width + font_width*term->cols || + p.rcPaint.bottom>= offset_height + font_height*term->rows) + { + HBRUSH fillcolour, oldbrush; + HPEN edge, oldpen; + fillcolour = CreateSolidBrush ( + colours[ATTR_DEFBG>>ATTR_BGSHIFT]); + oldbrush = SelectObject(hdc, fillcolour); + edge = CreatePen(PS_SOLID, 0, + colours[ATTR_DEFBG>>ATTR_BGSHIFT]); + oldpen = SelectObject(hdc, edge); + + /* + * Jordan Russell reports that this apparently + * ineffectual IntersectClipRect() call masks a + * Windows NT/2K bug causing strange display + * problems when the PuTTY window is taller than + * the primary monitor. It seems harmless enough... + */ + IntersectClipRect(hdc, + p.rcPaint.left, p.rcPaint.top, + p.rcPaint.right, p.rcPaint.bottom); + + ExcludeClipRect(hdc, + offset_width, offset_height, + offset_width+font_width*term->cols, + offset_height+font_height*term->rows); + + Rectangle(hdc, p.rcPaint.left, p.rcPaint.top, + p.rcPaint.right, p.rcPaint.bottom); + + /* SelectClipRgn(hdc, NULL); */ + + SelectObject(hdc, oldbrush); + DeleteObject(fillcolour); + SelectObject(hdc, oldpen); + DeleteObject(edge); + } + SelectObject(hdc, GetStockObject(SYSTEM_FONT)); + SelectObject(hdc, GetStockObject(WHITE_PEN)); + EndPaint(hwnd, &p); + ShowCaret(hwnd); + } + return 0; + case WM_NETEVENT: + /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc + * but the only one that's likely to try to overload us is FD_READ. + * This means buffering just one is fine. + */ + if (pending_netevent) + enact_pending_netevent(); + + pending_netevent = TRUE; + pend_netevent_wParam = wParam; + pend_netevent_lParam = lParam; + if (WSAGETSELECTEVENT(lParam) != FD_READ) + enact_pending_netevent(); + + net_pending_errors(); + return 0; + case WM_SETFOCUS: + term_set_focus(term, TRUE); + CreateCaret(hwnd, caretbm, font_width, font_height); + ShowCaret(hwnd); + flash_window(0); /* stop */ + compose_state = 0; + term_update(term); + break; + case WM_KILLFOCUS: + show_mouseptr(1); + term_set_focus(term, FALSE); + DestroyCaret(); + caret_x = caret_y = -1; /* ensure caret is replaced next time */ + term_update(term); + break; + case WM_ENTERSIZEMOVE: +#ifdef RDB_DEBUG_PATCH + debug((27, "WM_ENTERSIZEMOVE")); +#endif + EnableSizeTip(1); + resizing = TRUE; + need_backend_resize = FALSE; + break; + case WM_EXITSIZEMOVE: + EnableSizeTip(0); + resizing = FALSE; +#ifdef RDB_DEBUG_PATCH + debug((27, "WM_EXITSIZEMOVE")); +#endif + if (need_backend_resize) { + term_size(term, cfg.height, cfg.width, cfg.savelines); + InvalidateRect(hwnd, NULL, TRUE); + } + break; + case WM_SIZING: + /* + * This does two jobs: + * 1) Keep the sizetip uptodate + * 2) Make sure the window size is _stepped_ in units of the font size. + */ + if (cfg.resize_action == RESIZE_TERM || + (cfg.resize_action == RESIZE_EITHER && !is_alt_pressed())) { + int width, height, w, h, ew, eh; + LPRECT r = (LPRECT) lParam; + + if ( !need_backend_resize && cfg.resize_action == RESIZE_EITHER && + (cfg.height != term->rows || cfg.width != term->cols )) { + /* + * Great! It seems that both the terminal size and the + * font size have been changed and the user is now dragging. + * + * It will now be difficult to get back to the configured + * font size! + * + * This would be easier but it seems to be too confusing. + + term_size(term, cfg.height, cfg.width, cfg.savelines); + reset_window(2); + */ + cfg.height=term->rows; cfg.width=term->cols; + + InvalidateRect(hwnd, NULL, TRUE); + need_backend_resize = TRUE; + } + + width = r->right - r->left - extra_width; + height = r->bottom - r->top - extra_height; + w = (width + font_width / 2) / font_width; + if (w < 1) + w = 1; + h = (height + font_height / 2) / font_height; + if (h < 1) + h = 1; + UpdateSizeTip(hwnd, w, h); + ew = width - w * font_width; + eh = height - h * font_height; + if (ew != 0) { + if (wParam == WMSZ_LEFT || + wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT) + r->left += ew; + else + r->right -= ew; + } + if (eh != 0) { + if (wParam == WMSZ_TOP || + wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT) + r->top += eh; + else + r->bottom -= eh; + } + if (ew || eh) + return 1; + else + return 0; + } else { + int width, height, w, h, rv = 0; + int ex_width = extra_width + (cfg.window_border - offset_width) * 2; + int ex_height = extra_height + (cfg.window_border - offset_height) * 2; + LPRECT r = (LPRECT) lParam; + + width = r->right - r->left - ex_width; + height = r->bottom - r->top - ex_height; + + w = (width + term->cols/2)/term->cols; + h = (height + term->rows/2)/term->rows; + if ( r->right != r->left + w*term->cols + ex_width) + rv = 1; + + if (wParam == WMSZ_LEFT || + wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT) + r->left = r->right - w*term->cols - ex_width; + else + r->right = r->left + w*term->cols + ex_width; + + if (r->bottom != r->top + h*term->rows + ex_height) + rv = 1; + + if (wParam == WMSZ_TOP || + wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT) + r->top = r->bottom - h*term->rows - ex_height; + else + r->bottom = r->top + h*term->rows + ex_height; + + return rv; + } + /* break; (never reached) */ + case WM_FULLSCR_ON_MAX: + fullscr_on_max = TRUE; + break; + case WM_MOVE: + sys_cursor_update(); + break; + case WM_SIZE: +#ifdef RDB_DEBUG_PATCH + debug((27, "WM_SIZE %s (%d,%d)", + (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED": + (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED": + (wParam == SIZE_RESTORED && resizing) ? "to": + (wParam == SIZE_RESTORED) ? "SIZE_RESTORED": + "...", + LOWORD(lParam), HIWORD(lParam))); +#endif + if (wParam == SIZE_MINIMIZED) + SetWindowText(hwnd, + cfg.win_name_always ? window_name : icon_name); + if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) + SetWindowText(hwnd, window_name); + if (wParam == SIZE_RESTORED) { + processed_resize = FALSE; + clear_full_screen(); + if (processed_resize) { + /* + * Inhibit normal processing of this WM_SIZE; a + * secondary one was triggered just now by + * clear_full_screen which contained the correct + * client area size. + */ + return 0; + } + } + if (wParam == SIZE_MAXIMIZED && fullscr_on_max) { + fullscr_on_max = FALSE; + processed_resize = FALSE; + make_full_screen(); + if (processed_resize) { + /* + * Inhibit normal processing of this WM_SIZE; a + * secondary one was triggered just now by + * make_full_screen which contained the correct client + * area size. + */ + return 0; + } + } + + processed_resize = TRUE; + + if (cfg.resize_action == RESIZE_DISABLED) { + /* A resize, well it better be a minimize. */ + reset_window(-1); + } else { + + int width, height, w, h; + + width = LOWORD(lParam); + height = HIWORD(lParam); + + if (wParam == SIZE_MAXIMIZED && !was_zoomed) { + was_zoomed = 1; + prev_rows = term->rows; + prev_cols = term->cols; + if (cfg.resize_action == RESIZE_TERM) { + w = width / font_width; + if (w < 1) w = 1; + h = height / font_height; + if (h < 1) h = 1; + + term_size(term, h, w, cfg.savelines); + } + reset_window(0); + } else if (wParam == SIZE_RESTORED && was_zoomed) { + was_zoomed = 0; + if (cfg.resize_action == RESIZE_TERM) { + w = (width-cfg.window_border*2) / font_width; + if (w < 1) w = 1; + h = (height-cfg.window_border*2) / font_height; + if (h < 1) h = 1; + term_size(term, h, w, cfg.savelines); + reset_window(2); + } else if (cfg.resize_action != RESIZE_FONT) + reset_window(2); + else + reset_window(0); + } else if (wParam == SIZE_MINIMIZED) { + /* do nothing */ + } else if (cfg.resize_action == RESIZE_TERM || + (cfg.resize_action == RESIZE_EITHER && + !is_alt_pressed())) { + w = (width-cfg.window_border*2) / font_width; + if (w < 1) w = 1; + h = (height-cfg.window_border*2) / font_height; + if (h < 1) h = 1; + + if (resizing) { + /* + * Don't call back->size in mid-resize. (To + * prevent massive numbers of resize events + * getting sent down the connection during an NT + * opaque drag.) + */ + need_backend_resize = TRUE; + cfg.height = h; + cfg.width = w; + } else { + term_size(term, h, w, cfg.savelines); + } + } else { + reset_window(0); + } + } + sys_cursor_update(); + return 0; + case WM_VSCROLL: + switch (LOWORD(wParam)) { + case SB_BOTTOM: + term_scroll(term, -1, 0); + break; + case SB_TOP: + term_scroll(term, +1, 0); + break; + case SB_LINEDOWN: + term_scroll(term, 0, +1); + break; + case SB_LINEUP: + term_scroll(term, 0, -1); + break; + case SB_PAGEDOWN: + term_scroll(term, 0, +term->rows / 2); + break; + case SB_PAGEUP: + term_scroll(term, 0, -term->rows / 2); + break; + case SB_THUMBPOSITION: + case SB_THUMBTRACK: + term_scroll(term, 1, HIWORD(wParam)); + break; + } + break; + case WM_PALETTECHANGED: + if ((HWND) wParam != hwnd && pal != NULL) { + HDC hdc = get_ctx(NULL); + if (hdc) { + if (RealizePalette(hdc) > 0) + UpdateColors(hdc); + free_ctx(hdc); + } + } + break; + case WM_QUERYNEWPALETTE: + if (pal != NULL) { + HDC hdc = get_ctx(NULL); + if (hdc) { + if (RealizePalette(hdc) > 0) + UpdateColors(hdc); + free_ctx(hdc); + return TRUE; + } + } + return FALSE; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + /* + * Add the scan code and keypress timing to the random + * number noise. + */ + noise_ultralight(lParam); + + /* + * We don't do TranslateMessage since it disassociates the + * resulting CHAR message from the KEYDOWN that sparked it, + * which we occasionally don't want. Instead, we process + * KEYDOWN, and call the Win32 translator functions so that + * we get the translations under _our_ control. + */ + { + unsigned char buf[20]; + int len; + + if (wParam == VK_PROCESSKEY) { /* IME PROCESS key */ + if (message == WM_KEYDOWN) { + MSG m; + m.hwnd = hwnd; + m.message = WM_KEYDOWN; + m.wParam = wParam; + m.lParam = lParam & 0xdfff; + TranslateMessage(&m); + } else break; /* pass to Windows for default processing */ + } else { + len = TranslateKey(message, wParam, lParam, buf); + if (len == -1) + return DefWindowProc(hwnd, message, wParam, lParam); + + if (len != 0) { + /* + * Interrupt an ongoing paste. I'm not sure + * this is sensible, but for the moment it's + * preferable to having to faff about buffering + * things. + */ + term_nopaste(term); + + /* + * We need not bother about stdin backlogs + * here, because in GUI PuTTY we can't do + * anything about it anyway; there's no means + * of asking Windows to hold off on KEYDOWN + * messages. We _have_ to buffer everything + * we're sent. + */ + term_seen_key_event(term); + if (ldisc) + ldisc_send(ldisc, buf, len, 1); + show_mouseptr(0); + } + } + } + net_pending_errors(); + return 0; + case WM_INPUTLANGCHANGE: + /* wParam == Font number */ + /* lParam == Locale */ + set_input_locale((HKL)lParam); + sys_cursor_update(); + break; + case WM_IME_STARTCOMPOSITION: + { + HIMC hImc = ImmGetContext(hwnd); + ImmSetCompositionFont(hImc, &lfont); + ImmReleaseContext(hwnd, hImc); + } + break; + case WM_IME_COMPOSITION: + { + HIMC hIMC; + int n; + char *buff; + + if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS || + osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */ + + if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */ + break; /* fall back to DefWindowProc */ + + hIMC = ImmGetContext(hwnd); + n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0); + + if (n > 0) { + int i; + buff = snewn(n, char); + ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n); + /* + * Jaeyoun Chung reports that Korean character + * input doesn't work correctly if we do a single + * luni_send() covering the whole of buff. So + * instead we luni_send the characters one by one. + */ + term_seen_key_event(term); + for (i = 0; i < n; i += 2) { + if (ldisc) + luni_send(ldisc, (unsigned short *)(buff+i), 1, 1); + } + free(buff); + } + ImmReleaseContext(hwnd, hIMC); + return 1; + } + + case WM_IME_CHAR: + if (wParam & 0xFF00) { + unsigned char buf[2]; + + buf[1] = wParam; + buf[0] = wParam >> 8; + term_seen_key_event(term); + if (ldisc) + lpage_send(ldisc, kbd_codepage, buf, 2, 1); + } else { + char c = (unsigned char) wParam; + term_seen_key_event(term); + if (ldisc) + lpage_send(ldisc, kbd_codepage, &c, 1, 1); + } + return (0); + case WM_CHAR: + case WM_SYSCHAR: + /* + * Nevertheless, we are prepared to deal with WM_CHAR + * messages, should they crop up. So if someone wants to + * post the things to us as part of a macro manoeuvre, + * we're ready to cope. + */ + { + char c = (unsigned char)wParam; + term_seen_key_event(term); + if (ldisc) + lpage_send(ldisc, CP_ACP, &c, 1, 1); + } + return 0; + case WM_SYSCOLORCHANGE: + if (cfg.system_colour) { + /* Refresh palette from system colours. */ + /* XXX actually this zaps the entire palette. */ + systopalette(); + init_palette(); + /* Force a repaint of the terminal window. */ + term_invalidate(term); + } + break; + case WM_AGENT_CALLBACK: + { + struct agent_callback *c = (struct agent_callback *)lParam; + c->callback(c->callback_ctx, c->data, c->len); + sfree(c); + } + return 0; + case WM_GOT_CLIPDATA: + if (process_clipdata((HGLOBAL)lParam, wParam)) + term_do_paste(term); + return 0; + default: + if (message == wm_mousewheel || message == WM_MOUSEWHEEL) { + int shift_pressed=0, control_pressed=0; + + if (message == WM_MOUSEWHEEL) { + wheel_accumulator += (short)HIWORD(wParam); + shift_pressed=LOWORD(wParam) & MK_SHIFT; + control_pressed=LOWORD(wParam) & MK_CONTROL; + } else { + BYTE keys[256]; + wheel_accumulator += (int)wParam; + if (GetKeyboardState(keys)!=0) { + shift_pressed=keys[VK_SHIFT]&0x80; + control_pressed=keys[VK_CONTROL]&0x80; + } + } + + /* process events when the threshold is reached */ + while (abs(wheel_accumulator) >= WHEEL_DELTA) { + int b; + + /* reduce amount for next time */ + if (wheel_accumulator > 0) { + b = MBT_WHEEL_UP; + wheel_accumulator -= WHEEL_DELTA; + } else if (wheel_accumulator < 0) { + b = MBT_WHEEL_DOWN; + wheel_accumulator += WHEEL_DELTA; + } else + break; + + if (send_raw_mouse && + !(cfg.mouse_override && shift_pressed)) { + /* Mouse wheel position is in screen coordinates for + * some reason */ + POINT p; + p.x = X_POS(lParam); p.y = Y_POS(lParam); + if (ScreenToClient(hwnd, &p)) { + /* send a mouse-down followed by a mouse up */ + term_mouse(term, b, translate_button(b), + MA_CLICK, + TO_CHR_X(p.x), + TO_CHR_Y(p.y), shift_pressed, + control_pressed, is_alt_pressed()); + term_mouse(term, b, translate_button(b), + MA_RELEASE, TO_CHR_X(p.x), + TO_CHR_Y(p.y), shift_pressed, + control_pressed, is_alt_pressed()); + } /* else: not sure when this can fail */ + } else { + /* trigger a scroll */ + term_scroll(term, 0, + b == MBT_WHEEL_UP ? + -term->rows / 2 : term->rows / 2); + } + } + return 0; + } + } + + /* + * Any messages we don't process completely above are passed through to + * DefWindowProc() for default processing. + */ + return DefWindowProc(hwnd, message, wParam, lParam); +} + +/* + * Move the system caret. (We maintain one, even though it's + * invisible, for the benefit of blind people: apparently some + * helper software tracks the system caret, so we should arrange to + * have one.) + */ +void sys_cursor(void *frontend, int x, int y) +{ + int cx, cy; + + if (!term->has_focus) return; + + /* + * Avoid gratuitously re-updating the cursor position and IMM + * window if there's no actual change required. + */ + cx = x * font_width + offset_width; + cy = y * font_height + offset_height; + if (cx == caret_x && cy == caret_y) + return; + caret_x = cx; + caret_y = cy; + + sys_cursor_update(); +} + +static void sys_cursor_update(void) +{ + COMPOSITIONFORM cf; + HIMC hIMC; + + if (!term->has_focus) return; + + if (caret_x < 0 || caret_y < 0) + return; + + SetCaretPos(caret_x, caret_y); + + /* IMM calls on Win98 and beyond only */ + if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */ + + if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS && + osVersion.dwMinorVersion == 0) return; /* 95 */ + + /* we should have the IMM functions */ + hIMC = ImmGetContext(hwnd); + cf.dwStyle = CFS_POINT; + cf.ptCurrentPos.x = caret_x; + cf.ptCurrentPos.y = caret_y; + ImmSetCompositionWindow(hIMC, &cf); + + ImmReleaseContext(hwnd, hIMC); +} + +/* + * Draw a line of text in the window, at given character + * coordinates, in given attributes. + * + * We are allowed to fiddle with the contents of `text'. + */ +void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr) +{ + COLORREF fg, bg, t; + int nfg, nbg, nfont; + HDC hdc = ctx; + RECT line_box; + int force_manual_underline = 0; + int fnt_width, char_width; + int text_adjust = 0; + int xoffset = 0; + int maxlen, remaining, opaque; + static int *lpDx = NULL; + static int lpDx_len = 0; + int *lpDx_maybe; + + lattr &= LATTR_MODE; + + char_width = fnt_width = font_width * (1 + (lattr != LATTR_NORM)); + + if (attr & ATTR_WIDE) + char_width *= 2; + + /* Only want the left half of double width lines */ + if (lattr != LATTR_NORM && x*2 >= term->cols) + return; + + x *= fnt_width; + y *= font_height; + x += offset_width; + y += offset_height; + + if ((attr & TATTR_ACTCURS) && (cfg.cursor_type == 0 || term->big_cursor)) { + attr &= ~(ATTR_REVERSE|ATTR_BLINK|ATTR_COLOURS); + if (bold_mode == BOLD_COLOURS) + attr &= ~ATTR_BOLD; + + /* cursor fg and bg */ + attr |= (260 << ATTR_FGSHIFT) | (261 << ATTR_BGSHIFT); + } + + nfont = 0; + if (cfg.vtmode == VT_POORMAN && lattr != LATTR_NORM) { + /* Assume a poorman font is borken in other ways too. */ + lattr = LATTR_WIDE; + } else + switch (lattr) { + case LATTR_NORM: + break; + case LATTR_WIDE: + nfont |= FONT_WIDE; + break; + default: + nfont |= FONT_WIDE + FONT_HIGH; + break; + } + if (attr & ATTR_NARROW) + nfont |= FONT_NARROW; + + /* Special hack for the VT100 linedraw glyphs. */ + if (text[0] >= 0x23BA && text[0] <= 0x23BD) { + switch ((unsigned char) (text[0])) { + case 0xBA: + text_adjust = -2 * font_height / 5; + break; + case 0xBB: + text_adjust = -1 * font_height / 5; + break; + case 0xBC: + text_adjust = font_height / 5; + break; + case 0xBD: + text_adjust = 2 * font_height / 5; + break; + } + if (lattr == LATTR_TOP || lattr == LATTR_BOT) + text_adjust *= 2; + text[0] = ucsdata.unitab_xterm['q']; + if (attr & ATTR_UNDER) { + attr &= ~ATTR_UNDER; + force_manual_underline = 1; + } + } + + /* Anything left as an original character set is unprintable. */ + if (DIRECT_CHAR(text[0])) { + int i; + for (i = 0; i < len; i++) + text[i] = 0xFFFD; + } + + /* OEM CP */ + if ((text[0] & CSET_MASK) == CSET_OEMCP) + nfont |= FONT_OEM; + + nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT); + nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT); + if (bold_mode == BOLD_FONT && (attr & ATTR_BOLD)) + nfont |= FONT_BOLD; + if (und_mode == UND_FONT && (attr & ATTR_UNDER)) + nfont |= FONT_UNDERLINE; + another_font(nfont); + if (!fonts[nfont]) { + if (nfont & FONT_UNDERLINE) + force_manual_underline = 1; + /* Don't do the same for manual bold, it could be bad news. */ + + nfont &= ~(FONT_BOLD | FONT_UNDERLINE); + } + another_font(nfont); + if (!fonts[nfont]) + nfont = FONT_NORMAL; + if (attr & ATTR_REVERSE) { + t = nfg; + nfg = nbg; + nbg = t; + } + if (bold_mode == BOLD_COLOURS && (attr & ATTR_BOLD)) { + if (nfg < 16) nfg |= 8; + else if (nfg >= 256) nfg |= 1; + } + if (bold_mode == BOLD_COLOURS && (attr & ATTR_BLINK)) { + if (nbg < 16) nbg |= 8; + else if (nbg >= 256) nbg |= 1; + } + fg = colours[nfg]; + bg = colours[nbg]; + SelectObject(hdc, fonts[nfont]); + SetTextColor(hdc, fg); + SetBkColor(hdc, bg); + if (attr & TATTR_COMBINING) + SetBkMode(hdc, TRANSPARENT); + else + SetBkMode(hdc, OPAQUE); + line_box.left = x; + line_box.top = y; + line_box.right = x + char_width * len; + line_box.bottom = y + font_height; + + /* Only want the left half of double width lines */ + if (line_box.right > font_width*term->cols+offset_width) + line_box.right = font_width*term->cols+offset_width; + + if (font_varpitch) { + /* + * If we're using a variable-pitch font, we unconditionally + * draw the glyphs one at a time and centre them in their + * character cells (which means in particular that we must + * disable the lpDx mechanism). This gives slightly odd but + * generally reasonable results. + */ + xoffset = char_width / 2; + SetTextAlign(hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP); + lpDx_maybe = NULL; + maxlen = 1; + } else { + /* + * In a fixed-pitch font, we draw the whole string in one go + * in the normal way. + */ + xoffset = 0; + SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP); + lpDx_maybe = lpDx; + maxlen = len; + } + + opaque = TRUE; /* start by erasing the rectangle */ + for (remaining = len; remaining > 0; + text += len, remaining -= len, x += char_width * len) { + len = (maxlen < remaining ? maxlen : remaining); + + if (len > lpDx_len) { + if (len > lpDx_len) { + lpDx_len = len * 9 / 8 + 16; + lpDx = sresize(lpDx, lpDx_len, int); + } + } + { + int i; + for (i = 0; i < len; i++) + lpDx[i] = char_width; + } + + /* We're using a private area for direct to font. (512 chars.) */ + if (ucsdata.dbcs_screenfont && (text[0] & CSET_MASK) == CSET_ACP) { + /* Ho Hum, dbcs fonts are a PITA! */ + /* To display on W9x I have to convert to UCS */ + static wchar_t *uni_buf = 0; + static int uni_len = 0; + int nlen, mptr; + if (len > uni_len) { + sfree(uni_buf); + uni_len = len; + uni_buf = snewn(uni_len, wchar_t); + } + + for(nlen = mptr = 0; mptr directlen) { + directlen = len; + directbuf = sresize(directbuf, directlen, char); + } + + for (i = 0; i < len; i++) + directbuf[i] = text[i] & 0xFF; + + ExtTextOut(hdc, x + xoffset, + y - font_height * (lattr == LATTR_BOT) + text_adjust, + ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), + &line_box, directbuf, len, lpDx_maybe); + if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { + SetBkMode(hdc, TRANSPARENT); + + /* GRR: This draws the character outside its box and + * can leave 'droppings' even with the clip box! I + * suppose I could loop it one character at a time ... + * yuk. + * + * Or ... I could do a test print with "W", and use +1 + * or -1 for this shift depending on if the leftmost + * column is blank... + */ + ExtTextOut(hdc, x + xoffset - 1, + y - font_height * (lattr == + LATTR_BOT) + text_adjust, + ETO_CLIPPED, &line_box, directbuf, len, lpDx_maybe); + } + } else { + /* And 'normal' unicode characters */ + static WCHAR *wbuf = NULL; + static int wlen = 0; + int i; + + if (wlen < len) { + sfree(wbuf); + wlen = len; + wbuf = snewn(wlen, WCHAR); + } + + for (i = 0; i < len; i++) + wbuf[i] = text[i]; + + /* print Glyphs as they are, without Windows' Shaping*/ + general_textout(hdc, x + xoffset, + y - font_height * (lattr==LATTR_BOT) + text_adjust, + &line_box, wbuf, len, lpDx, + opaque && !(attr & TATTR_COMBINING)); + + /* And the shadow bold hack. */ + if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { + SetBkMode(hdc, TRANSPARENT); + ExtTextOutW(hdc, x + xoffset - 1, + y - font_height * (lattr == + LATTR_BOT) + text_adjust, + ETO_CLIPPED, &line_box, wbuf, len, lpDx_maybe); + } + } + + /* + * If we're looping round again, stop erasing the background + * rectangle. + */ + SetBkMode(hdc, TRANSPARENT); + opaque = FALSE; + } + if (lattr != LATTR_TOP && (force_manual_underline || + (und_mode == UND_LINE + && (attr & ATTR_UNDER)))) { + HPEN oldpen; + int dec = descent; + if (lattr == LATTR_BOT) + dec = dec * 2 - font_height; + + oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg)); + MoveToEx(hdc, x, y + dec, NULL); + LineTo(hdc, x + len * char_width, y + dec); + oldpen = SelectObject(hdc, oldpen); + DeleteObject(oldpen); + } +} + +/* + * Wrapper that handles combining characters. + */ +void do_text(Context ctx, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr) +{ + if (attr & TATTR_COMBINING) { + unsigned long a = 0; + attr &= ~TATTR_COMBINING; + while (len--) { + do_text_internal(ctx, x, y, text, 1, attr | a, lattr); + text++; + a = TATTR_COMBINING; + } + } else + do_text_internal(ctx, x, y, text, len, attr, lattr); +} + +void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr) +{ + + int fnt_width; + int char_width; + HDC hdc = ctx; + int ctype = cfg.cursor_type; + + lattr &= LATTR_MODE; + + if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) { + if (*text != UCSWIDE) { + do_text(ctx, x, y, text, len, attr, lattr); + return; + } + ctype = 2; + attr |= TATTR_RIGHTCURS; + } + + fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM)); + if (attr & ATTR_WIDE) + char_width *= 2; + x *= fnt_width; + y *= font_height; + x += offset_width; + y += offset_height; + + if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) { + POINT pts[5]; + HPEN oldpen; + pts[0].x = pts[1].x = pts[4].x = x; + pts[2].x = pts[3].x = x + char_width - 1; + pts[0].y = pts[3].y = pts[4].y = y; + pts[1].y = pts[2].y = y + font_height - 1; + oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[261])); + Polyline(hdc, pts, 5); + oldpen = SelectObject(hdc, oldpen); + DeleteObject(oldpen); + } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) { + int startx, starty, dx, dy, length, i; + if (ctype == 1) { + startx = x; + starty = y + descent; + dx = 1; + dy = 0; + length = char_width; + } else { + int xadjust = 0; + if (attr & TATTR_RIGHTCURS) + xadjust = char_width - 1; + startx = x + xadjust; + starty = y; + dx = 0; + dy = 1; + length = font_height; + } + if (attr & TATTR_ACTCURS) { + HPEN oldpen; + oldpen = + SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[261])); + MoveToEx(hdc, startx, starty, NULL); + LineTo(hdc, startx + dx * length, starty + dy * length); + oldpen = SelectObject(hdc, oldpen); + DeleteObject(oldpen); + } else { + for (i = 0; i < length; i++) { + if (i % 2 == 0) { + SetPixel(hdc, startx, starty, colours[261]); + } + startx += dx; + starty += dy; + } + } + } +} + +/* This function gets the actual width of a character in the normal font. + */ +int char_width(Context ctx, int uc) { + HDC hdc = ctx; + int ibuf = 0; + + /* If the font max is the same as the font ave width then this + * function is a no-op. + */ + if (!font_dualwidth) return 1; + + switch (uc & CSET_MASK) { + case CSET_ASCII: + uc = ucsdata.unitab_line[uc & 0xFF]; + break; + case CSET_LINEDRW: + uc = ucsdata.unitab_xterm[uc & 0xFF]; + break; + case CSET_SCOACS: + uc = ucsdata.unitab_scoacs[uc & 0xFF]; + break; + } + if (DIRECT_FONT(uc)) { + if (ucsdata.dbcs_screenfont) return 1; + + /* Speedup, I know of no font where ascii is the wrong width */ + if ((uc&~CSET_MASK) >= ' ' && (uc&~CSET_MASK)<= '~') + return 1; + + if ( (uc & CSET_MASK) == CSET_ACP ) { + SelectObject(hdc, fonts[FONT_NORMAL]); + } else if ( (uc & CSET_MASK) == CSET_OEMCP ) { + another_font(FONT_OEM); + if (!fonts[FONT_OEM]) return 0; + + SelectObject(hdc, fonts[FONT_OEM]); + } else + return 0; + + if ( GetCharWidth32(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1 && + GetCharWidth(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1) + return 0; + } else { + /* Speedup, I know of no font where ascii is the wrong width */ + if (uc >= ' ' && uc <= '~') return 1; + + SelectObject(hdc, fonts[FONT_NORMAL]); + if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 ) + /* Okay that one worked */ ; + else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 ) + /* This should work on 9x too, but it's "less accurate" */ ; + else + return 0; + } + + ibuf += font_width / 2 -1; + ibuf /= font_width; + + return ibuf; +} + +/* + * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII + * codes. Returns number of bytes used, zero to drop the message, + * -1 to forward the message to Windows, or another negative number + * to indicate a NUL-terminated "special" string. + */ +static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, + unsigned char *output) +{ + BYTE keystate[256]; + int scan, left_alt = 0, key_down, shift_state; + int r, i, code; + unsigned char *p = output; + static int alt_sum = 0; + + HKL kbd_layout = GetKeyboardLayout(0); + + /* keys is for ToAsciiEx. There's some ick here, see below. */ + static WORD keys[3]; + static int compose_char = 0; + static WPARAM compose_key = 0; + + r = GetKeyboardState(keystate); + if (!r) + memset(keystate, 0, sizeof(keystate)); + else { +#if 0 +#define SHOW_TOASCII_RESULT + { /* Tell us all about key events */ + static BYTE oldstate[256]; + static int first = 1; + static int scan; + int ch; + if (first) + memcpy(oldstate, keystate, sizeof(oldstate)); + first = 0; + + if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) { + debug(("+")); + } else if ((HIWORD(lParam) & KF_UP) + && scan == (HIWORD(lParam) & 0xFF)) { + debug((". U")); + } else { + debug((".\n")); + if (wParam >= VK_F1 && wParam <= VK_F20) + debug(("K_F%d", wParam + 1 - VK_F1)); + else + switch (wParam) { + case VK_SHIFT: + debug(("SHIFT")); + break; + case VK_CONTROL: + debug(("CTRL")); + break; + case VK_MENU: + debug(("ALT")); + break; + default: + debug(("VK_%02x", wParam)); + } + if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP) + debug(("*")); + debug((", S%02x", scan = (HIWORD(lParam) & 0xFF))); + + ch = MapVirtualKeyEx(wParam, 2, kbd_layout); + if (ch >= ' ' && ch <= '~') + debug((", '%c'", ch)); + else if (ch) + debug((", $%02x", ch)); + + if (keys[0]) + debug((", KB0=%02x", keys[0])); + if (keys[1]) + debug((", KB1=%02x", keys[1])); + if (keys[2]) + debug((", KB2=%02x", keys[2])); + + if ((keystate[VK_SHIFT] & 0x80) != 0) + debug((", S")); + if ((keystate[VK_CONTROL] & 0x80) != 0) + debug((", C")); + if ((HIWORD(lParam) & KF_EXTENDED)) + debug((", E")); + if ((HIWORD(lParam) & KF_UP)) + debug((", U")); + } + + if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT); + else if ((HIWORD(lParam) & KF_UP)) + oldstate[wParam & 0xFF] ^= 0x80; + else + oldstate[wParam & 0xFF] ^= 0x81; + + for (ch = 0; ch < 256; ch++) + if (oldstate[ch] != keystate[ch]) + debug((", M%02x=%02x", ch, keystate[ch])); + + memcpy(oldstate, keystate, sizeof(oldstate)); + } +#endif + + if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) { + keystate[VK_RMENU] = keystate[VK_MENU]; + } + + + /* Nastyness with NUMLock - Shift-NUMLock is left alone though */ + if ((cfg.funky_type == FUNKY_VT400 || + (cfg.funky_type <= FUNKY_LINUX && term->app_keypad_keys && + !cfg.no_applic_k)) + && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) { + + wParam = VK_EXECUTE; + + /* UnToggle NUMLock */ + if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) + keystate[VK_NUMLOCK] ^= 1; + } + + /* And write back the 'adjusted' state */ + SetKeyboardState(keystate); + } + + /* Disable Auto repeat if required */ + if (term->repeat_off && + (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) + return 0; + + if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0) + left_alt = 1; + + key_down = ((HIWORD(lParam) & KF_UP) == 0); + + /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */ + if (left_alt && (keystate[VK_CONTROL] & 0x80)) { + if (cfg.ctrlaltkeys) + keystate[VK_MENU] = 0; + else { + keystate[VK_RMENU] = 0x80; + left_alt = 0; + } + } + + scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF)); + shift_state = ((keystate[VK_SHIFT] & 0x80) != 0) + + ((keystate[VK_CONTROL] & 0x80) != 0) * 2; + + /* Note if AltGr was pressed and if it was used as a compose key */ + if (!compose_state) { + compose_key = 0x100; + if (cfg.compose_key) { + if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) + compose_key = wParam; + } + if (wParam == VK_APPS) + compose_key = wParam; + } + + if (wParam == compose_key) { + if (compose_state == 0 + && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state = + 1; + else if (compose_state == 1 && (HIWORD(lParam) & KF_UP)) + compose_state = 2; + else + compose_state = 0; + } else if (compose_state == 1 && wParam != VK_CONTROL) + compose_state = 0; + + if (compose_state > 1 && left_alt) + compose_state = 0; + + /* Sanitize the number pad if not using a PC NumPad */ + if (left_alt || (term->app_keypad_keys && !cfg.no_applic_k + && cfg.funky_type != FUNKY_XTERM) + || cfg.funky_type == FUNKY_VT400 || cfg.nethack_keypad || compose_state) { + if ((HIWORD(lParam) & KF_EXTENDED) == 0) { + int nParam = 0; + switch (wParam) { + case VK_INSERT: + nParam = VK_NUMPAD0; + break; + case VK_END: + nParam = VK_NUMPAD1; + break; + case VK_DOWN: + nParam = VK_NUMPAD2; + break; + case VK_NEXT: + nParam = VK_NUMPAD3; + break; + case VK_LEFT: + nParam = VK_NUMPAD4; + break; + case VK_CLEAR: + nParam = VK_NUMPAD5; + break; + case VK_RIGHT: + nParam = VK_NUMPAD6; + break; + case VK_HOME: + nParam = VK_NUMPAD7; + break; + case VK_UP: + nParam = VK_NUMPAD8; + break; + case VK_PRIOR: + nParam = VK_NUMPAD9; + break; + case VK_DELETE: + nParam = VK_DECIMAL; + break; + } + if (nParam) { + if (keystate[VK_NUMLOCK] & 1) + shift_state |= 1; + wParam = nParam; + } + } + } + + /* If a key is pressed and AltGr is not active */ + if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) { + /* Okay, prepare for most alts then ... */ + if (left_alt) + *p++ = '\033'; + + /* Lets see if it's a pattern we know all about ... */ + if (wParam == VK_PRIOR && shift_state == 1) { + SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0); + return 0; + } + if (wParam == VK_PRIOR && shift_state == 2) { + SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0); + return 0; + } + if (wParam == VK_NEXT && shift_state == 1) { + SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0); + return 0; + } + if (wParam == VK_NEXT && shift_state == 2) { + SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0); + return 0; + } + if ((wParam == VK_PRIOR || wParam == VK_NEXT) && shift_state == 3) { + term_scroll_to_selection(term, (wParam == VK_PRIOR ? 0 : 1)); + return 0; + } + if (wParam == VK_INSERT && shift_state == 1) { + request_paste(NULL); + return 0; + } + if (left_alt && wParam == VK_F4 && cfg.alt_f4) { + return -1; + } + if (left_alt && wParam == VK_SPACE && cfg.alt_space) { + SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0); + return -1; + } + if (left_alt && wParam == VK_RETURN && cfg.fullscreenonaltenter && + (cfg.resize_action != RESIZE_DISABLED)) { + if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT) + flip_full_screen(); + return -1; + } + /* Control-Numlock for app-keypad mode switch */ + if (wParam == VK_PAUSE && shift_state == 2) { + term->app_keypad_keys ^= 1; + return 0; + } + + /* Nethack keypad */ + if (cfg.nethack_keypad && !left_alt) { + switch (wParam) { + case VK_NUMPAD1: + *p++ = "bB\002\002"[shift_state & 3]; + return p - output; + case VK_NUMPAD2: + *p++ = "jJ\012\012"[shift_state & 3]; + return p - output; + case VK_NUMPAD3: + *p++ = "nN\016\016"[shift_state & 3]; + return p - output; + case VK_NUMPAD4: + *p++ = "hH\010\010"[shift_state & 3]; + return p - output; + case VK_NUMPAD5: + *p++ = shift_state ? '.' : '.'; + return p - output; + case VK_NUMPAD6: + *p++ = "lL\014\014"[shift_state & 3]; + return p - output; + case VK_NUMPAD7: + *p++ = "yY\031\031"[shift_state & 3]; + return p - output; + case VK_NUMPAD8: + *p++ = "kK\013\013"[shift_state & 3]; + return p - output; + case VK_NUMPAD9: + *p++ = "uU\025\025"[shift_state & 3]; + return p - output; + } + } + + /* Application Keypad */ + if (!left_alt) { + int xkey = 0; + + if (cfg.funky_type == FUNKY_VT400 || + (cfg.funky_type <= FUNKY_LINUX && + term->app_keypad_keys && !cfg.no_applic_k)) switch (wParam) { + case VK_EXECUTE: + xkey = 'P'; + break; + case VK_DIVIDE: + xkey = 'Q'; + break; + case VK_MULTIPLY: + xkey = 'R'; + break; + case VK_SUBTRACT: + xkey = 'S'; + break; + } + if (term->app_keypad_keys && !cfg.no_applic_k) + switch (wParam) { + case VK_NUMPAD0: + xkey = 'p'; + break; + case VK_NUMPAD1: + xkey = 'q'; + break; + case VK_NUMPAD2: + xkey = 'r'; + break; + case VK_NUMPAD3: + xkey = 's'; + break; + case VK_NUMPAD4: + xkey = 't'; + break; + case VK_NUMPAD5: + xkey = 'u'; + break; + case VK_NUMPAD6: + xkey = 'v'; + break; + case VK_NUMPAD7: + xkey = 'w'; + break; + case VK_NUMPAD8: + xkey = 'x'; + break; + case VK_NUMPAD9: + xkey = 'y'; + break; + + case VK_DECIMAL: + xkey = 'n'; + break; + case VK_ADD: + if (cfg.funky_type == FUNKY_XTERM) { + if (shift_state) + xkey = 'l'; + else + xkey = 'k'; + } else if (shift_state) + xkey = 'm'; + else + xkey = 'l'; + break; + + case VK_DIVIDE: + if (cfg.funky_type == FUNKY_XTERM) + xkey = 'o'; + break; + case VK_MULTIPLY: + if (cfg.funky_type == FUNKY_XTERM) + xkey = 'j'; + break; + case VK_SUBTRACT: + if (cfg.funky_type == FUNKY_XTERM) + xkey = 'm'; + break; + + case VK_RETURN: + if (HIWORD(lParam) & KF_EXTENDED) + xkey = 'M'; + break; + } + if (xkey) { + if (term->vt52_mode) { + if (xkey >= 'P' && xkey <= 'S') + p += sprintf((char *) p, "\x1B%c", xkey); + else + p += sprintf((char *) p, "\x1B?%c", xkey); + } else + p += sprintf((char *) p, "\x1BO%c", xkey); + return p - output; + } + } + + if (wParam == VK_BACK && shift_state == 0) { /* Backspace */ + *p++ = (cfg.bksp_is_delete ? 0x7F : 0x08); + *p++ = 0; + return -2; + } + if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */ + /* We do the opposite of what is configured */ + *p++ = (cfg.bksp_is_delete ? 0x08 : 0x7F); + *p++ = 0; + return -2; + } + if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */ + *p++ = 0x1B; + *p++ = '['; + *p++ = 'Z'; + return p - output; + } + if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */ + *p++ = 0; + return p - output; + } + if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */ + *p++ = 160; + return p - output; + } + if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */ + if (back) + back->special(backhandle, TS_BRK); + return 0; + } + if (wParam == VK_PAUSE) { /* Break/Pause */ + *p++ = 26; + *p++ = 0; + return -2; + } + /* Control-2 to Control-8 are special */ + if (shift_state == 2 && wParam >= '2' && wParam <= '8') { + *p++ = "\000\033\034\035\036\037\177"[wParam - '2']; + return p - output; + } + if (shift_state == 2 && (wParam == 0xBD || wParam == 0xBF)) { + *p++ = 0x1F; + return p - output; + } + if (shift_state == 2 && (wParam == 0xDF || wParam == 0xDC)) { + *p++ = 0x1C; + return p - output; + } + if (shift_state == 3 && wParam == 0xDE) { + *p++ = 0x1E; /* Ctrl-~ == Ctrl-^ in xterm at least */ + return p - output; + } + if (shift_state == 0 && wParam == VK_RETURN && term->cr_lf_return) { + *p++ = '\r'; + *p++ = '\n'; + return p - output; + } + + /* + * Next, all the keys that do tilde codes. (ESC '[' nn '~', + * for integer decimal nn.) + * + * We also deal with the weird ones here. Linux VCs replace F1 + * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but + * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w + * respectively. + */ + code = 0; + switch (wParam) { + case VK_F1: + code = (keystate[VK_SHIFT] & 0x80 ? 23 : 11); + break; + case VK_F2: + code = (keystate[VK_SHIFT] & 0x80 ? 24 : 12); + break; + case VK_F3: + code = (keystate[VK_SHIFT] & 0x80 ? 25 : 13); + break; + case VK_F4: + code = (keystate[VK_SHIFT] & 0x80 ? 26 : 14); + break; + case VK_F5: + code = (keystate[VK_SHIFT] & 0x80 ? 28 : 15); + break; + case VK_F6: + code = (keystate[VK_SHIFT] & 0x80 ? 29 : 17); + break; + case VK_F7: + code = (keystate[VK_SHIFT] & 0x80 ? 31 : 18); + break; + case VK_F8: + code = (keystate[VK_SHIFT] & 0x80 ? 32 : 19); + break; + case VK_F9: + code = (keystate[VK_SHIFT] & 0x80 ? 33 : 20); + break; + case VK_F10: + code = (keystate[VK_SHIFT] & 0x80 ? 34 : 21); + break; + case VK_F11: + code = 23; + break; + case VK_F12: + code = 24; + break; + case VK_F13: + code = 25; + break; + case VK_F14: + code = 26; + break; + case VK_F15: + code = 28; + break; + case VK_F16: + code = 29; + break; + case VK_F17: + code = 31; + break; + case VK_F18: + code = 32; + break; + case VK_F19: + code = 33; + break; + case VK_F20: + code = 34; + break; + } + if ((shift_state&2) == 0) switch (wParam) { + case VK_HOME: + code = 1; + break; + case VK_INSERT: + code = 2; + break; + case VK_DELETE: + code = 3; + break; + case VK_END: + code = 4; + break; + case VK_PRIOR: + code = 5; + break; + case VK_NEXT: + code = 6; + break; + } + /* Reorder edit keys to physical order */ + if (cfg.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((char *) p, "\x1B%c", " HLMEIG"[code]); + return p - output; + } + + if (cfg.funky_type == FUNKY_SCO && /* SCO function keys */ + code >= 11 && code <= 34) { + char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{"; + int index = 0; + switch (wParam) { + case VK_F1: index = 0; break; + case VK_F2: index = 1; break; + case VK_F3: index = 2; break; + case VK_F4: index = 3; break; + case VK_F5: index = 4; break; + case VK_F6: index = 5; break; + case VK_F7: index = 6; break; + case VK_F8: index = 7; break; + case VK_F9: index = 8; break; + case VK_F10: index = 9; break; + case VK_F11: index = 10; break; + case VK_F12: index = 11; break; + } + if (keystate[VK_SHIFT] & 0x80) index += 12; + if (keystate[VK_CONTROL] & 0x80) index += 24; + p += sprintf((char *) p, "\x1B[%c", codes[index]); + return p - output; + } + if (cfg.funky_type == FUNKY_SCO && /* SCO small keypad */ + code >= 1 && code <= 6) { + char codes[] = "HL.FIG"; + if (code == 3) { + *p++ = '\x7F'; + } else { + p += sprintf((char *) p, "\x1B[%c", codes[code-1]); + } + return p - output; + } + if ((term->vt52_mode || cfg.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((char *) p, "\x1B%c", code + 'P' - 11 - offt); + else + p += + sprintf((char *) p, "\x1BO%c", code + 'P' - 11 - offt); + return p - output; + } + if (cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { + p += sprintf((char *) p, "\x1B[[%c", code + 'A' - 11); + return p - output; + } + if (cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { + if (term->vt52_mode) + p += sprintf((char *) p, "\x1B%c", code + 'P' - 11); + else + p += sprintf((char *) p, "\x1BO%c", code + 'P' - 11); + return p - output; + } + if (cfg.rxvt_homeend && (code == 1 || code == 4)) { + p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw"); + return p - output; + } + if (code) { + p += sprintf((char *) p, "\x1B[%d~", code); + return p - output; + } + + /* + * Now the remaining keys (arrows and Keypad 5. Keypad 5 for + * some reason seems to send VK_CLEAR to Windows...). + */ + { + char xkey = 0; + switch (wParam) { + case VK_UP: + xkey = 'A'; + break; + case VK_DOWN: + xkey = 'B'; + break; + case VK_RIGHT: + xkey = 'C'; + break; + case VK_LEFT: + xkey = 'D'; + break; + case VK_CLEAR: + xkey = 'G'; + break; + } + if (xkey) { + p += format_arrow_key(p, term, xkey, shift_state); + return p - output; + } + } + + /* + * Finally, deal with Return ourselves. (Win95 seems to + * foul it up when Alt is pressed, for some reason.) + */ + if (wParam == VK_RETURN) { /* Return */ + *p++ = 0x0D; + *p++ = 0; + return -2; + } + + if (left_alt && wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9) + alt_sum = alt_sum * 10 + wParam - VK_NUMPAD0; + else + alt_sum = 0; + } + + /* Okay we've done everything interesting; let windows deal with + * the boring stuff */ + { + BOOL capsOn=0; + + /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */ + if(cfg.xlat_capslockcyr && keystate[VK_CAPITAL] != 0) { + capsOn= !left_alt; + keystate[VK_CAPITAL] = 0; + } + + /* XXX how do we know what the max size of the keys array should + * be is? There's indication on MS' website of an Inquire/InquireEx + * functioning returning a KBINFO structure which tells us. */ + if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) { + /* XXX 'keys' parameter is declared in MSDN documentation as + * 'LPWORD lpChar'. + * The experience of a French user indicates that on + * Win98, WORD[] should be passed in, but on Win2K, it should + * be BYTE[]. German WinXP and my Win2K with "US International" + * driver corroborate this. + * Experimentally I've conditionalised the behaviour on the + * Win9x/NT split, but I suspect it's worse than that. + * See wishlist item `win-dead-keys' for more horrible detail + * and speculations. */ + BYTE keybs[3]; + int i; + r = ToAsciiEx(wParam, scan, keystate, (LPWORD)keybs, 0, kbd_layout); + for (i=0; i<3; i++) keys[i] = keybs[i]; + } else { + r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout); + } +#ifdef SHOW_TOASCII_RESULT + if (r == 1 && !key_down) { + if (alt_sum) { + if (in_utf(term) || ucsdata.dbcs_screenfont) + debug((", (U+%04x)", alt_sum)); + else + debug((", LCH(%d)", alt_sum)); + } else { + debug((", ACH(%d)", keys[0])); + } + } else if (r > 0) { + int r1; + debug((", ASC(")); + for (r1 = 0; r1 < r; r1++) { + debug(("%s%d", r1 ? "," : "", keys[r1])); + } + debug((")")); + } +#endif + if (r > 0) { + WCHAR keybuf; + + /* + * Interrupt an ongoing paste. I'm not sure this is + * sensible, but for the moment it's preferable to + * having to faff about buffering things. + */ + term_nopaste(term); + + p = output; + for (i = 0; i < r; i++) { + unsigned char ch = (unsigned char) keys[i]; + + if (compose_state == 2 && (ch & 0x80) == 0 && ch > ' ') { + compose_char = ch; + compose_state++; + continue; + } + if (compose_state == 3 && (ch & 0x80) == 0 && ch > ' ') { + int nc; + compose_state = 0; + + if ((nc = check_compose(compose_char, ch)) == -1) { + MessageBeep(MB_ICONHAND); + return 0; + } + keybuf = nc; + term_seen_key_event(term); + if (ldisc) + luni_send(ldisc, &keybuf, 1, 1); + continue; + } + + compose_state = 0; + + if (!key_down) { + if (alt_sum) { + if (in_utf(term) || ucsdata.dbcs_screenfont) { + keybuf = alt_sum; + term_seen_key_event(term); + if (ldisc) + luni_send(ldisc, &keybuf, 1, 1); + } else { + ch = (char) alt_sum; + /* + * We need not bother about stdin + * backlogs here, because in GUI PuTTY + * we can't do anything about it + * anyway; there's no means of asking + * Windows to hold off on KEYDOWN + * messages. We _have_ to buffer + * everything we're sent. + */ + term_seen_key_event(term); + if (ldisc) + ldisc_send(ldisc, &ch, 1, 1); + } + alt_sum = 0; + } else { + term_seen_key_event(term); + if (ldisc) + lpage_send(ldisc, kbd_codepage, &ch, 1, 1); + } + } else { + if(capsOn && ch < 0x80) { + WCHAR cbuf[2]; + cbuf[0] = 27; + cbuf[1] = xlat_uskbd2cyrllic(ch); + term_seen_key_event(term); + if (ldisc) + luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1); + } else { + char cbuf[2]; + cbuf[0] = '\033'; + cbuf[1] = ch; + term_seen_key_event(term); + if (ldisc) + lpage_send(ldisc, kbd_codepage, + cbuf+!left_alt, 1+!!left_alt, 1); + } + } + show_mouseptr(0); + } + + /* This is so the ALT-Numpad and dead keys work correctly. */ + keys[0] = 0; + + return p - output; + } + /* If we're definitly not building up an ALT-54321 then clear it */ + if (!left_alt) + keys[0] = 0; + /* If we will be using alt_sum fix the 256s */ + else if (keys[0] && (in_utf(term) || ucsdata.dbcs_screenfont)) + keys[0] = 10; + } + + /* + * ALT alone may or may not want to bring up the System menu. + * If it's not meant to, we return 0 on presses or releases of + * ALT, to show that we've swallowed the keystroke. Otherwise + * we return -1, which means Windows will give the keystroke + * its default handling (i.e. bring up the System menu). + */ + if (wParam == VK_MENU && !cfg.alt_only) + return 0; + + return -1; +} + +void set_title(void *frontend, char *title) +{ + sfree(window_name); + window_name = snewn(1 + strlen(title), char); + strcpy(window_name, title); + if (cfg.win_name_always || !IsIconic(hwnd)) + SetWindowText(hwnd, title); +} + +void set_icon(void *frontend, char *title) +{ + sfree(icon_name); + icon_name = snewn(1 + strlen(title), char); + strcpy(icon_name, title); + if (!cfg.win_name_always && IsIconic(hwnd)) + SetWindowText(hwnd, title); +} + +void set_sbar(void *frontend, int total, int start, int page) +{ + SCROLLINFO si; + + if (is_full_screen() ? !cfg.scrollbar_in_fullscreen : !cfg.scrollbar) + return; + + si.cbSize = sizeof(si); + si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; + si.nMin = 0; + si.nMax = total - 1; + si.nPage = page; + si.nPos = start; + if (hwnd) + SetScrollInfo(hwnd, SB_VERT, &si, TRUE); +} + +Context get_ctx(void *frontend) +{ + HDC hdc; + if (hwnd) { + hdc = GetDC(hwnd); + if (hdc && pal) + SelectPalette(hdc, pal, FALSE); + return hdc; + } else + return NULL; +} + +void free_ctx(Context ctx) +{ + SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE); + ReleaseDC(hwnd, ctx); +} + +static void real_palette_set(int n, int r, int g, int b) +{ + if (pal) { + logpal->palPalEntry[n].peRed = r; + logpal->palPalEntry[n].peGreen = g; + logpal->palPalEntry[n].peBlue = b; + logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE; + colours[n] = PALETTERGB(r, g, b); + SetPaletteEntries(pal, 0, NALLCOLOURS, logpal->palPalEntry); + } else + colours[n] = RGB(r, g, b); +} + +void palette_set(void *frontend, int n, int r, int g, int b) +{ + if (n >= 16) + n += 256 - 16; + if (n > NALLCOLOURS) + return; + real_palette_set(n, r, g, b); + if (pal) { + HDC hdc = get_ctx(frontend); + UnrealizeObject(pal); + RealizePalette(hdc); + free_ctx(hdc); + } else { + if (n == (ATTR_DEFBG>>ATTR_BGSHIFT)) + /* If Default Background changes, we need to ensure any + * space between the text area and the window border is + * redrawn. */ + InvalidateRect(hwnd, NULL, TRUE); + } +} + +void palette_reset(void *frontend) +{ + int i; + + /* And this */ + for (i = 0; i < NALLCOLOURS; i++) { + if (pal) { + logpal->palPalEntry[i].peRed = defpal[i].rgbtRed; + logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen; + logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue; + logpal->palPalEntry[i].peFlags = 0; + colours[i] = PALETTERGB(defpal[i].rgbtRed, + defpal[i].rgbtGreen, + defpal[i].rgbtBlue); + } else + colours[i] = RGB(defpal[i].rgbtRed, + defpal[i].rgbtGreen, defpal[i].rgbtBlue); + } + + if (pal) { + HDC hdc; + SetPaletteEntries(pal, 0, NALLCOLOURS, logpal->palPalEntry); + hdc = get_ctx(frontend); + RealizePalette(hdc); + free_ctx(hdc); + } else { + /* Default Background may have changed. Ensure any space between + * text area and window border is redrawn. */ + InvalidateRect(hwnd, NULL, TRUE); + } +} + +void write_aclip(void *frontend, char *data, int len, int must_deselect) +{ + HGLOBAL clipdata; + void *lock; + + clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1); + if (!clipdata) + return; + lock = GlobalLock(clipdata); + if (!lock) + return; + memcpy(lock, data, len); + ((unsigned char *) lock)[len] = 0; + GlobalUnlock(clipdata); + + if (!must_deselect) + SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0); + + if (OpenClipboard(hwnd)) { + EmptyClipboard(); + SetClipboardData(CF_TEXT, clipdata); + CloseClipboard(); + } else + GlobalFree(clipdata); + + if (!must_deselect) + SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0); +} + +/* + * Note: unlike write_aclip() this will not append a nul. + */ +void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect) +{ + HGLOBAL clipdata, clipdata2, clipdata3; + int len2; + void *lock, *lock2, *lock3; + + len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL); + + clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, + len * sizeof(wchar_t)); + clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2); + + if (!clipdata || !clipdata2) { + if (clipdata) + GlobalFree(clipdata); + if (clipdata2) + GlobalFree(clipdata2); + return; + } + if (!(lock = GlobalLock(clipdata))) + return; + if (!(lock2 = GlobalLock(clipdata2))) + return; + + memcpy(lock, data, len * sizeof(wchar_t)); + WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL); + + if (cfg.rtf_paste) { + wchar_t unitab[256]; + char *rtf = NULL; + unsigned char *tdata = (unsigned char *)lock2; + wchar_t *udata = (wchar_t *)lock; + int rtflen = 0, uindex = 0, tindex = 0; + int rtfsize = 0; + int multilen, blen, alen, totallen, i; + char before[16], after[4]; + int fgcolour, lastfgcolour = 0; + int bgcolour, lastbgcolour = 0; + int attrBold, lastAttrBold = 0; + int attrUnder, lastAttrUnder = 0; + int palette[NALLCOLOURS]; + int numcolours; + + get_unitab(CP_ACP, unitab, 0); + + rtfsize = 100 + strlen(cfg.font.name); + rtf = snewn(rtfsize, char); + rtflen = sprintf(rtf, "{\\rtf1\\ansi\\deff0{\\fonttbl\\f0\\fmodern %s;}\\f0\\fs%d", + cfg.font.name, cfg.font.height*2); + + /* + * Add colour palette + * {\colortbl ;\red255\green0\blue0;\red0\green0\blue128;} + */ + + /* + * First - Determine all colours in use + * o Foregound and background colours share the same palette + */ + if (attr) { + memset(palette, 0, sizeof(palette)); + for (i = 0; i < (len-1); i++) { + fgcolour = ((attr[i] & ATTR_FGMASK) >> ATTR_FGSHIFT); + bgcolour = ((attr[i] & ATTR_BGMASK) >> ATTR_BGSHIFT); + + if (attr[i] & ATTR_REVERSE) { + int tmpcolour = fgcolour; /* Swap foreground and background */ + fgcolour = bgcolour; + bgcolour = tmpcolour; + } + + if (bold_mode == BOLD_COLOURS && (attr[i] & ATTR_BOLD)) { + if (fgcolour < 8) /* ANSI colours */ + fgcolour += 8; + else if (fgcolour >= 256) /* Default colours */ + fgcolour ++; + } + + if (attr[i] & ATTR_BLINK) { + if (bgcolour < 8) /* ANSI colours */ + bgcolour += 8; + else if (bgcolour >= 256) /* Default colours */ + bgcolour ++; + } + + palette[fgcolour]++; + palette[bgcolour]++; + } + + /* + * Next - Create a reduced palette + */ + numcolours = 0; + for (i = 0; i < NALLCOLOURS; i++) { + if (palette[i] != 0) + palette[i] = ++numcolours; + } + + /* + * Finally - Write the colour table + */ + rtf = sresize(rtf, rtfsize + (numcolours * 25), char); + strcat(rtf, "{\\colortbl ;"); + rtflen = strlen(rtf); + + for (i = 0; i < NALLCOLOURS; i++) { + if (palette[i] != 0) { + rtflen += sprintf(&rtf[rtflen], "\\red%d\\green%d\\blue%d;", defpal[i].rgbtRed, defpal[i].rgbtGreen, defpal[i].rgbtBlue); + } + } + strcpy(&rtf[rtflen], "}"); + rtflen ++; + } + + /* + * We want to construct a piece of RTF that specifies the + * same Unicode text. To do this we will read back in + * parallel from the Unicode data in `udata' and the + * non-Unicode data in `tdata'. For each character in + * `tdata' which becomes the right thing in `udata' when + * looked up in `unitab', we just copy straight over from + * tdata. For each one that doesn't, we must WCToMB it + * individually and produce a \u escape sequence. + * + * It would probably be more robust to just bite the bullet + * and WCToMB each individual Unicode character one by one, + * then MBToWC each one back to see if it was an accurate + * translation; but that strikes me as a horrifying number + * of Windows API calls so I want to see if this faster way + * will work. If it screws up badly we can always revert to + * the simple and slow way. + */ + while (tindex < len2 && uindex < len && + tdata[tindex] && udata[uindex]) { + if (tindex + 1 < len2 && + tdata[tindex] == '\r' && + tdata[tindex+1] == '\n') { + tindex++; + uindex++; + } + + /* + * Set text attributes + */ + if (attr) { + if (rtfsize < rtflen + 64) { + rtfsize = rtflen + 512; + rtf = sresize(rtf, rtfsize, char); + } + + /* + * Determine foreground and background colours + */ + fgcolour = ((attr[tindex] & ATTR_FGMASK) >> ATTR_FGSHIFT); + bgcolour = ((attr[tindex] & ATTR_BGMASK) >> ATTR_BGSHIFT); + + if (attr[tindex] & ATTR_REVERSE) { + int tmpcolour = fgcolour; /* Swap foreground and background */ + fgcolour = bgcolour; + bgcolour = tmpcolour; + } + + if (bold_mode == BOLD_COLOURS && (attr[tindex] & ATTR_BOLD)) { + if (fgcolour < 8) /* ANSI colours */ + fgcolour += 8; + else if (fgcolour >= 256) /* Default colours */ + fgcolour ++; + } + + if (attr[tindex] & ATTR_BLINK) { + if (bgcolour < 8) /* ANSI colours */ + bgcolour += 8; + else if (bgcolour >= 256) /* Default colours */ + bgcolour ++; + } + + /* + * Collect other attributes + */ + if (bold_mode != BOLD_COLOURS) + attrBold = attr[tindex] & ATTR_BOLD; + else + attrBold = 0; + + attrUnder = attr[tindex] & ATTR_UNDER; + + /* + * Reverse video + * o If video isn't reversed, ignore colour attributes for default foregound + * or background. + * o Special case where bolded text is displayed using the default foregound + * and background colours - force to bolded RTF. + */ + if (!(attr[tindex] & ATTR_REVERSE)) { + if (bgcolour >= 256) /* Default color */ + bgcolour = -1; /* No coloring */ + + if (fgcolour >= 256) { /* Default colour */ + if (bold_mode == BOLD_COLOURS && (fgcolour & 1) && bgcolour == -1) + attrBold = ATTR_BOLD; /* Emphasize text with bold attribute */ + + fgcolour = -1; /* No coloring */ + } + } + + /* + * Write RTF text attributes + */ + if (lastfgcolour != fgcolour) { + lastfgcolour = fgcolour; + rtflen += sprintf(&rtf[rtflen], "\\cf%d ", (fgcolour >= 0) ? palette[fgcolour] : 0); + } + + if (lastbgcolour != bgcolour) { + lastbgcolour = bgcolour; + rtflen += sprintf(&rtf[rtflen], "\\highlight%d ", (bgcolour >= 0) ? palette[bgcolour] : 0); + } + + if (lastAttrBold != attrBold) { + lastAttrBold = attrBold; + rtflen += sprintf(&rtf[rtflen], "%s", attrBold ? "\\b " : "\\b0 "); + } + + if (lastAttrUnder != attrUnder) { + lastAttrUnder = attrUnder; + rtflen += sprintf(&rtf[rtflen], "%s", attrUnder ? "\\ul " : "\\ulnone "); + } + } + + if (unitab[tdata[tindex]] == udata[uindex]) { + multilen = 1; + before[0] = '\0'; + after[0] = '\0'; + blen = alen = 0; + } else { + multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1, + NULL, 0, NULL, NULL); + if (multilen != 1) { + blen = sprintf(before, "{\\uc%d\\u%d", multilen, + udata[uindex]); + alen = 1; strcpy(after, "}"); + } else { + blen = sprintf(before, "\\u%d", udata[uindex]); + alen = 0; after[0] = '\0'; + } + } + assert(tindex + multilen <= len2); + totallen = blen + alen; + for (i = 0; i < multilen; i++) { + if (tdata[tindex+i] == '\\' || + tdata[tindex+i] == '{' || + tdata[tindex+i] == '}') + totallen += 2; + else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) + totallen += 6; /* \par\r\n */ + else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) + totallen += 4; + else + totallen++; + } + + if (rtfsize < rtflen + totallen + 3) { + rtfsize = rtflen + totallen + 512; + rtf = sresize(rtf, rtfsize, char); + } + + strcpy(rtf + rtflen, before); rtflen += blen; + for (i = 0; i < multilen; i++) { + if (tdata[tindex+i] == '\\' || + tdata[tindex+i] == '{' || + tdata[tindex+i] == '}') { + rtf[rtflen++] = '\\'; + rtf[rtflen++] = tdata[tindex+i]; + } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) { + rtflen += sprintf(rtf+rtflen, "\\par\r\n"); + } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) { + rtflen += sprintf(rtf+rtflen, "\\'%02x", tdata[tindex+i]); + } else { + rtf[rtflen++] = tdata[tindex+i]; + } + } + strcpy(rtf + rtflen, after); rtflen += alen; + + tindex += multilen; + uindex++; + } + + rtf[rtflen++] = '}'; /* Terminate RTF stream */ + rtf[rtflen++] = '\0'; + rtf[rtflen++] = '\0'; + + clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen); + if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) { + memcpy(lock3, rtf, rtflen); + GlobalUnlock(clipdata3); + } + sfree(rtf); + } else + clipdata3 = NULL; + + GlobalUnlock(clipdata); + GlobalUnlock(clipdata2); + + if (!must_deselect) + SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0); + + if (OpenClipboard(hwnd)) { + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, clipdata); + SetClipboardData(CF_TEXT, clipdata2); + if (clipdata3) + SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3); + CloseClipboard(); + } else { + GlobalFree(clipdata); + GlobalFree(clipdata2); + } + + if (!must_deselect) + SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0); +} + +static DWORD WINAPI clipboard_read_threadfunc(void *param) +{ + HWND hwnd = (HWND)param; + HGLOBAL clipdata; + + if (OpenClipboard(NULL)) { + if ((clipdata = GetClipboardData(CF_UNICODETEXT))) { + SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)1, (LPARAM)clipdata); + } else if ((clipdata = GetClipboardData(CF_TEXT))) { + SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)0, (LPARAM)clipdata); + } + CloseClipboard(); + } + + return 0; +} + +static int process_clipdata(HGLOBAL clipdata, int unicode) +{ + sfree(clipboard_contents); + clipboard_contents = NULL; + clipboard_length = 0; + + if (unicode) { + wchar_t *p = GlobalLock(clipdata); + wchar_t *p2; + + if (p) { + /* Unwilling to rely on Windows having wcslen() */ + for (p2 = p; *p2; p2++); + clipboard_length = p2 - p; + clipboard_contents = snewn(clipboard_length + 1, wchar_t); + memcpy(clipboard_contents, p, clipboard_length * sizeof(wchar_t)); + clipboard_contents[clipboard_length] = L'\0'; + return TRUE; + } + } else { + char *s = GlobalLock(clipdata); + int i; + + if (s) { + i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0); + clipboard_contents = snewn(i, wchar_t); + MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, + clipboard_contents, i); + clipboard_length = i - 1; + clipboard_contents[clipboard_length] = L'\0'; + return TRUE; + } + } + + return FALSE; +} + +void request_paste(void *frontend) +{ + /* + * I always thought pasting was synchronous in Windows; the + * clipboard access functions certainly _look_ synchronous, + * unlike the X ones. But in fact it seems that in some + * situations the contents of the clipboard might not be + * immediately available, and the clipboard-reading functions + * may block. This leads to trouble if the application + * delivering the clipboard data has to get hold of it by - + * for example - talking over a network connection which is + * forwarded through this very PuTTY. + * + * Hence, we spawn a subthread to read the clipboard, and do + * our paste when it's finished. The thread will send a + * message back to our main window when it terminates, and + * that tells us it's OK to paste. + */ + DWORD in_threadid; /* required for Win9x */ + CreateThread(NULL, 0, clipboard_read_threadfunc, + hwnd, 0, &in_threadid); +} + +void get_clip(void *frontend, wchar_t **p, int *len) +{ + if (p) { + *p = clipboard_contents; + *len = clipboard_length; + } +} + +#if 0 +/* + * Move `lines' lines from position `from' to position `to' in the + * window. + */ +void optimised_move(void *frontend, int to, int from, int lines) +{ + RECT r; + int min, max; + + min = (to < from ? to : from); + max = to + from - min; + + r.left = offset_width; + r.right = offset_width + term->cols * font_width; + r.top = offset_height + min * font_height; + r.bottom = offset_height + (max + lines) * font_height; + ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r); +} +#endif + +/* + * Print a message box and perform a fatal exit. + */ +void fatalbox(char *fmt, ...) +{ + va_list ap; + char *stuff, morestuff[100]; + + va_start(ap, fmt); + stuff = dupvprintf(fmt, ap); + va_end(ap); + sprintf(morestuff, "%.70s Fatal Error", appname); + MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK); + sfree(stuff); + cleanup_exit(1); +} + +/* + * Print a modal (Really Bad) message box and perform a fatal exit. + */ +void modalfatalbox(char *fmt, ...) +{ + va_list ap; + char *stuff, morestuff[100]; + + va_start(ap, fmt); + stuff = dupvprintf(fmt, ap); + va_end(ap); + sprintf(morestuff, "%.70s Fatal Error", appname); + MessageBox(hwnd, stuff, morestuff, + MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); + sfree(stuff); + cleanup_exit(1); +} + +DECL_WINDOWS_FUNCTION(static, BOOL, FlashWindowEx, (PFLASHWINFO)); + +static void init_flashwindow(void) +{ + HMODULE user32_module = load_system32_dll("user32.dll"); + GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx); +} + +static BOOL flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout) +{ + if (p_FlashWindowEx) { + FLASHWINFO fi; + fi.cbSize = sizeof(fi); + fi.hwnd = hwnd; + fi.dwFlags = dwFlags; + fi.uCount = uCount; + fi.dwTimeout = dwTimeout; + return (*p_FlashWindowEx)(&fi); + } + else + return FALSE; /* shrug */ +} + +static void flash_window(int mode); +static long next_flash; +static int flashing = 0; + +/* + * Timer for platforms where we must maintain window flashing manually + * (e.g., Win95). + */ +static void flash_window_timer(void *ctx, long now) +{ + if (flashing && now - next_flash >= 0) { + flash_window(1); + } +} + +/* + * Manage window caption / taskbar flashing, if enabled. + * 0 = stop, 1 = maintain, 2 = start + */ +static void flash_window(int mode) +{ + if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) { + /* stop */ + if (flashing) { + flashing = 0; + if (p_FlashWindowEx) + flash_window_ex(FLASHW_STOP, 0, 0); + else + FlashWindow(hwnd, FALSE); + } + + } else if (mode == 2) { + /* start */ + if (!flashing) { + flashing = 1; + if (p_FlashWindowEx) { + /* For so-called "steady" mode, we use uCount=2, which + * seems to be the traditional number of flashes used + * by user notifications (e.g., by Explorer). + * uCount=0 appears to enable continuous flashing, per + * "flashing" mode, although I haven't seen this + * documented. */ + flash_window_ex(FLASHW_ALL | FLASHW_TIMER, + (cfg.beep_ind == B_IND_FLASH ? 0 : 2), + 0 /* system cursor blink rate */); + /* No need to schedule timer */ + } else { + FlashWindow(hwnd, TRUE); + next_flash = schedule_timer(450, flash_window_timer, hwnd); + } + } + + } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) { + /* maintain */ + if (flashing && !p_FlashWindowEx) { + FlashWindow(hwnd, TRUE); /* toggle */ + next_flash = schedule_timer(450, flash_window_timer, hwnd); + } + } +} + +/* + * Beep. + */ +void do_beep(void *frontend, int mode) +{ + if (mode == BELL_DEFAULT) { + /* + * For MessageBeep style bells, we want to be careful of + * timing, because they don't have the nice property of + * PlaySound bells that each one cancels the previous + * active one. So we limit the rate to one per 50ms or so. + */ + static long lastbeep = 0; + long beepdiff; + + beepdiff = GetTickCount() - lastbeep; + if (beepdiff >= 0 && beepdiff < 50) + return; + MessageBeep(MB_OK); + /* + * The above MessageBeep call takes time, so we record the + * time _after_ it finishes rather than before it starts. + */ + lastbeep = GetTickCount(); + } else if (mode == BELL_WAVEFILE) { + if (!PlaySound(cfg.bell_wavefile.path, NULL, + SND_ASYNC | SND_FILENAME)) { + char buf[sizeof(cfg.bell_wavefile.path) + 80]; + char otherbuf[100]; + sprintf(buf, "Unable to play sound file\n%s\n" + "Using default sound instead", cfg.bell_wavefile.path); + sprintf(otherbuf, "%.70s Sound Error", appname); + MessageBox(hwnd, buf, otherbuf, + MB_OK | MB_ICONEXCLAMATION); + cfg.beep = BELL_DEFAULT; + } + } else if (mode == BELL_PCSPEAKER) { + static long lastbeep = 0; + long beepdiff; + + beepdiff = GetTickCount() - lastbeep; + if (beepdiff >= 0 && beepdiff < 50) + return; + + /* + * We must beep in different ways depending on whether this + * is a 95-series or NT-series OS. + */ + if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) + Beep(800, 100); + else + MessageBeep(-1); + lastbeep = GetTickCount(); + } + /* Otherwise, either visual bell or disabled; do nothing here */ + if (!term->has_focus) { + flash_window(2); /* start */ + } +} + +/* + * Minimise or restore the window in response to a server-side + * request. + */ +void set_iconic(void *frontend, int iconic) +{ + if (IsIconic(hwnd)) { + if (!iconic) + ShowWindow(hwnd, SW_RESTORE); + } else { + if (iconic) + ShowWindow(hwnd, SW_MINIMIZE); + } +} + +/* + * Move the window in response to a server-side request. + */ +void move_window(void *frontend, int x, int y) +{ + if (cfg.resize_action == RESIZE_DISABLED || + cfg.resize_action == RESIZE_FONT || + IsZoomed(hwnd)) + return; + + SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); +} + +/* + * Move the window to the top or bottom of the z-order in response + * to a server-side request. + */ +void set_zorder(void *frontend, int top) +{ + if (cfg.alwaysontop) + return; /* ignore */ + SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE); +} + +/* + * Refresh the window in response to a server-side request. + */ +void refresh_window(void *frontend) +{ + InvalidateRect(hwnd, NULL, TRUE); +} + +/* + * Maximise or restore the window in response to a server-side + * request. + */ +void set_zoomed(void *frontend, int zoomed) +{ + if (IsZoomed(hwnd)) { + if (!zoomed) + ShowWindow(hwnd, SW_RESTORE); + } else { + if (zoomed) + ShowWindow(hwnd, SW_MAXIMIZE); + } +} + +/* + * Report whether the window is iconic, for terminal reports. + */ +int is_iconic(void *frontend) +{ + return IsIconic(hwnd); +} + +/* + * Report the window's position, for terminal reports. + */ +void get_window_pos(void *frontend, int *x, int *y) +{ + RECT r; + GetWindowRect(hwnd, &r); + *x = r.left; + *y = r.top; +} + +/* + * Report the window's pixel size, for terminal reports. + */ +void get_window_pixels(void *frontend, int *x, int *y) +{ + RECT r; + GetWindowRect(hwnd, &r); + *x = r.right - r.left; + *y = r.bottom - r.top; +} + +/* + * Return the window or icon title. + */ +char *get_window_title(void *frontend, int icon) +{ + return icon ? icon_name : window_name; +} + +/* + * See if we're in full-screen mode. + */ +static int is_full_screen() +{ + if (!IsZoomed(hwnd)) + return FALSE; + if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_CAPTION) + return FALSE; + return TRUE; +} + +/* Get the rect/size of a full screen window using the nearest available + * monitor in multimon systems; default to something sensible if only + * one monitor is present. */ +static int get_fullscreen_rect(RECT * ss) +{ +#if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON) + HMONITOR mon; + MONITORINFO mi; + mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + mi.cbSize = sizeof(mi); + GetMonitorInfo(mon, &mi); + + /* structure copy */ + *ss = mi.rcMonitor; + return TRUE; +#else +/* could also use code like this: + ss->left = ss->top = 0; + ss->right = GetSystemMetrics(SM_CXSCREEN); + ss->bottom = GetSystemMetrics(SM_CYSCREEN); +*/ + return GetClientRect(GetDesktopWindow(), ss); +#endif +} + + +/* + * Go full-screen. This should only be called when we are already + * maximised. + */ +static void make_full_screen() +{ + DWORD style; + RECT ss; + + assert(IsZoomed(hwnd)); + + if (is_full_screen()) + return; + + /* Remove the window furniture. */ + style = GetWindowLongPtr(hwnd, GWL_STYLE); + style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME); + if (cfg.scrollbar_in_fullscreen) + style |= WS_VSCROLL; + else + style &= ~WS_VSCROLL; + SetWindowLongPtr(hwnd, GWL_STYLE, style); + + /* Resize ourselves to exactly cover the nearest monitor. */ + get_fullscreen_rect(&ss); + SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top, + ss.right - ss.left, + ss.bottom - ss.top, + SWP_FRAMECHANGED); + + /* We may have changed size as a result */ + + reset_window(0); + + /* Tick the menu item in the System and context menus. */ + { + int i; + for (i = 0; i < lenof(popup_menus); i++) + CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_CHECKED); + } +} + +/* + * Clear the full-screen attributes. + */ +static void clear_full_screen() +{ + DWORD oldstyle, style; + + /* Reinstate the window furniture. */ + style = oldstyle = GetWindowLongPtr(hwnd, GWL_STYLE); + style |= WS_CAPTION | WS_BORDER; + if (cfg.resize_action == RESIZE_DISABLED) + style &= ~WS_THICKFRAME; + else + style |= WS_THICKFRAME; + if (cfg.scrollbar) + style |= WS_VSCROLL; + else + style &= ~WS_VSCROLL; + if (style != oldstyle) { + SetWindowLongPtr(hwnd, GWL_STYLE, style); + SetWindowPos(hwnd, NULL, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | + SWP_FRAMECHANGED); + } + + /* Untick the menu item in the System and context menus. */ + { + int i; + for (i = 0; i < lenof(popup_menus); i++) + CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_UNCHECKED); + } +} + +/* + * Toggle full-screen mode. + */ +static void flip_full_screen() +{ + if (is_full_screen()) { + ShowWindow(hwnd, SW_RESTORE); + } else if (IsZoomed(hwnd)) { + make_full_screen(); + } else { + SendMessage(hwnd, WM_FULLSCR_ON_MAX, 0, 0); + ShowWindow(hwnd, SW_MAXIMIZE); + } +} + +void frontend_keypress(void *handle) +{ + /* + * Keypress termination in non-Close-On-Exit mode is not + * currently supported in PuTTY proper, because the window + * always has a perfectly good Close button anyway. So we do + * nothing here. + */ + return; +} + +int from_backend(void *frontend, int is_stderr, const char *data, int len) +{ + return term_data(term, is_stderr, data, len); +} + +int from_backend_untrusted(void *frontend, const char *data, int len) +{ + return term_data_untrusted(term, data, len); +} + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = term_get_userpass_input(term, p, in, inlen); + return ret; +} + +void agent_schedule_callback(void (*callback)(void *, void *, int), + void *callback_ctx, void *data, int len) +{ + struct agent_callback *c = snew(struct agent_callback); + c->callback = callback; + c->callback_ctx = callback_ctx; + c->data = data; + c->len = len; + PostMessage(hwnd, WM_AGENT_CALLBACK, 0, (LPARAM)c); +} diff --git a/putty/WINDOWS/WINGSS.C b/putty/WINDOWS/WINGSS.C new file mode 100644 index 0000000..a2076c5 --- /dev/null +++ b/putty/WINDOWS/WINGSS.C @@ -0,0 +1,478 @@ +#ifndef NO_GSSAPI + +#include "putty.h" + +#include + +#include "pgssapi.h" +#include "sshgss.h" +#include "sshgssc.h" + +#include "misc.h" + +/* Windows code to set up the GSSAPI library list. */ + +const int ngsslibs = 3; +const char *const gsslibnames[3] = { + "MIT Kerberos GSSAPI32.DLL", + "Microsoft SSPI SECUR32.DLL", + "User-specified GSSAPI DLL", +}; +const struct keyvalwhere gsslibkeywords[] = { + { "gssapi32", 0, -1, -1 }, + { "sspi", 1, -1, -1 }, + { "custom", 2, -1, -1 }, +}; + +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + AcquireCredentialsHandleA, + (SEC_CHAR *, SEC_CHAR *, ULONG, PLUID, + PVOID, SEC_GET_KEY_FN, PVOID, PCredHandle, PTimeStamp)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + InitializeSecurityContextA, + (PCredHandle, PCtxtHandle, SEC_CHAR *, ULONG, ULONG, + ULONG, PSecBufferDesc, ULONG, PCtxtHandle, + PSecBufferDesc, PULONG, PTimeStamp)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + FreeContextBuffer, + (PVOID)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + FreeCredentialsHandle, + (PCredHandle)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + DeleteSecurityContext, + (PCtxtHandle)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + QueryContextAttributesA, + (PCtxtHandle, ULONG, PVOID)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + MakeSignature, + (PCtxtHandle, ULONG, PSecBufferDesc, ULONG)); + +typedef struct winSsh_gss_ctx { + unsigned long maj_stat; + unsigned long min_stat; + CredHandle cred_handle; + CtxtHandle context; + PCtxtHandle context_handle; + TimeStamp expiry; +} winSsh_gss_ctx; + + +const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}; + +const char *gsslogmsg = NULL; + +static void ssh_sspi_bind_fns(struct ssh_gss_library *lib); + +struct ssh_gss_liblist *ssh_gss_setup(const Config *cfg) +{ + HMODULE module; + HKEY regkey; + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + + list->libraries = snewn(3, struct ssh_gss_library); + list->nlibraries = 0; + + /* MIT Kerberos GSSAPI implementation */ + /* TODO: For 64-bit builds, check for gssapi64.dll */ + module = NULL; + if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", ®key) + == ERROR_SUCCESS) { + DWORD type, size; + LONG ret; + char *buffer; + + /* Find out the string length */ + ret = RegQueryValueEx(regkey, "InstallDir", NULL, &type, NULL, &size); + + if (ret == ERROR_SUCCESS && type == REG_SZ) { + buffer = snewn(size + 20, char); + ret = RegQueryValueEx(regkey, "InstallDir", NULL, + &type, buffer, &size); + if (ret == ERROR_SUCCESS && type == REG_SZ) { + strcat(buffer, "\\bin\\gssapi32.dll"); + module = LoadLibrary(buffer); + } + sfree(buffer); + } + RegCloseKey(regkey); + } + if (module) { + struct ssh_gss_library *lib = + &list->libraries[list->nlibraries++]; + + lib->id = 0; + lib->gsslogmsg = "Using GSSAPI from GSSAPI32.DLL"; + lib->handle = (void *)module; + +#define BIND_GSS_FN(name) \ + lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name) + + BIND_GSS_FN(delete_sec_context); + BIND_GSS_FN(display_status); + BIND_GSS_FN(get_mic); + BIND_GSS_FN(import_name); + BIND_GSS_FN(init_sec_context); + BIND_GSS_FN(release_buffer); + BIND_GSS_FN(release_cred); + BIND_GSS_FN(release_name); + +#undef BIND_GSS_FN + + ssh_gssapi_bind_fns(lib); + } + + /* Microsoft SSPI Implementation */ + module = load_system32_dll("secur32.dll"); + if (module) { + struct ssh_gss_library *lib = + &list->libraries[list->nlibraries++]; + + lib->id = 1; + lib->gsslogmsg = "Using SSPI from SECUR32.DLL"; + lib->handle = (void *)module; + + GET_WINDOWS_FUNCTION(module, AcquireCredentialsHandleA); + GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA); + GET_WINDOWS_FUNCTION(module, FreeContextBuffer); + GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle); + GET_WINDOWS_FUNCTION(module, DeleteSecurityContext); + GET_WINDOWS_FUNCTION(module, QueryContextAttributesA); + GET_WINDOWS_FUNCTION(module, MakeSignature); + + ssh_sspi_bind_fns(lib); + } + + /* + * Custom GSSAPI DLL. + */ + module = NULL; + if (cfg->ssh_gss_custom.path[0]) { + module = LoadLibrary(cfg->ssh_gss_custom.path); + } + if (module) { + struct ssh_gss_library *lib = + &list->libraries[list->nlibraries++]; + + lib->id = 2; + lib->gsslogmsg = dupprintf("Using GSSAPI from user-specified" + " library '%s'", cfg->ssh_gss_custom.path); + lib->handle = (void *)module; + +#define BIND_GSS_FN(name) \ + lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name) + + BIND_GSS_FN(delete_sec_context); + BIND_GSS_FN(display_status); + BIND_GSS_FN(get_mic); + BIND_GSS_FN(import_name); + BIND_GSS_FN(init_sec_context); + BIND_GSS_FN(release_buffer); + BIND_GSS_FN(release_cred); + BIND_GSS_FN(release_name); + +#undef BIND_GSS_FN + + ssh_gssapi_bind_fns(lib); + } + + + return list; +} + +void ssh_gss_cleanup(struct ssh_gss_liblist *list) +{ + int i; + + /* + * LoadLibrary and FreeLibrary are defined to employ reference + * counting in the case where the same library is repeatedly + * loaded, so even in a multiple-sessions-per-process context + * (not that we currently expect ever to have such a thing on + * Windows) it's safe to naively FreeLibrary everything here + * without worrying about destroying it under the feet of + * another SSH instance still using it. + */ + for (i = 0; i < list->nlibraries; i++) { + FreeLibrary((HMODULE)list->libraries[i].handle); + if (list->libraries[i].id == 2) { + /* The 'custom' id involves a dynamically allocated message. + * Note that we must cast away the 'const' to free it. */ + sfree((char *)list->libraries[i].gsslogmsg); + } + } + sfree(list->libraries); + sfree(list); +} + +static Ssh_gss_stat ssh_sspi_indicate_mech(struct ssh_gss_library *lib, + Ssh_gss_buf *mech) +{ + *mech = gss_mech_krb5; + return SSH_GSS_OK; +} + + +static Ssh_gss_stat ssh_sspi_import_name(struct ssh_gss_library *lib, + char *host, Ssh_gss_name *srv_name) +{ + char *pStr; + + /* Check hostname */ + if (host == NULL) return SSH_GSS_FAILURE; + + /* copy it into form host/FQDN */ + pStr = dupcat("host/", host, NULL); + + *srv_name = (Ssh_gss_name) pStr; + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_sspi_acquire_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx) +{ + winSsh_gss_ctx *winctx = snew(winSsh_gss_ctx); + memset(winctx, 0, sizeof(winSsh_gss_ctx)); + + /* prepare our "wrapper" structure */ + winctx->maj_stat = winctx->min_stat = SEC_E_OK; + winctx->context_handle = NULL; + + /* Specifying no principal name here means use the credentials of + the current logged-in user */ + + winctx->maj_stat = p_AcquireCredentialsHandleA(NULL, + "Kerberos", + SECPKG_CRED_OUTBOUND, + NULL, + NULL, + NULL, + NULL, + &winctx->cred_handle, + &winctx->expiry); + + if (winctx->maj_stat != SEC_E_OK) return SSH_GSS_FAILURE; + + *ctx = (Ssh_gss_ctx) winctx; + return SSH_GSS_OK; +} + + +static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx, + Ssh_gss_name srv_name, + int to_deleg, + Ssh_gss_buf *recv_tok, + Ssh_gss_buf *send_tok) +{ + winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx; + SecBuffer wsend_tok = {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; + + /* check if we have to delegate ... */ + if (to_deleg) flags |= ISC_REQ_DELEGATE; + winctx->maj_stat = p_InitializeSecurityContextA(&winctx->cred_handle, + winctx->context_handle, + (char*) srv_name, + flags, + 0, /* reserved */ + SECURITY_NATIVE_DREP, + &input_desc, + 0, /* reserved */ + &winctx->context, + &output_desc, + &ret_flags, + &winctx->expiry); + + /* prepare for the next round */ + winctx->context_handle = &winctx->context; + send_tok->value = wsend_tok.pvBuffer; + send_tok->length = wsend_tok.cbBuffer; + + /* check & return our status */ + if (winctx->maj_stat==SEC_E_OK) return SSH_GSS_S_COMPLETE; + if (winctx->maj_stat==SEC_I_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED; + + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_sspi_free_tok(struct ssh_gss_library *lib, + Ssh_gss_buf *send_tok) +{ + /* check input */ + if (send_tok == NULL) return SSH_GSS_FAILURE; + + /* free Windows buffer */ + p_FreeContextBuffer(send_tok->value); + SSH_GSS_CLEAR_BUF(send_tok); + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_sspi_release_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx) +{ + winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) *ctx; + + /* check input */ + if (winctx == NULL) return SSH_GSS_FAILURE; + + /* free Windows data */ + p_FreeCredentialsHandle(&winctx->cred_handle); + 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; + char *msg; + + if (winctx == NULL) return SSH_GSS_FAILURE; + + /* decode the error code */ + switch (winctx->maj_stat) { + case SEC_E_OK: msg="SSPI status OK"; break; + case SEC_E_INVALID_HANDLE: msg="The handle passed to the function" + " is invalid."; + break; + case SEC_E_TARGET_UNKNOWN: msg="The target was not recognized."; break; + case SEC_E_LOGON_DENIED: msg="The logon failed."; break; + case SEC_E_INTERNAL_ERROR: msg="The Local Security Authority cannot" + " be contacted."; + break; + case SEC_E_NO_CREDENTIALS: msg="No credentials are available in the" + " security package."; + break; + case SEC_E_NO_AUTHENTICATING_AUTHORITY: + msg="No authority could be contacted for authentication." + "The domain name of the authenticating party could be wrong," + " the domain could be unreachable, or there might have been" + " a trust relationship failure."; + break; + case SEC_E_INSUFFICIENT_MEMORY: + msg="One or more of the SecBufferDesc structures passed as" + " an OUT parameter has a buffer that is too small."; + break; + case SEC_E_INVALID_TOKEN: + msg="The error is due to a malformed input token, such as a" + " token corrupted in transit, a token" + " of incorrect size, or a token passed into the wrong" + " security package. Passing a token to" + " the wrong package can happen if client and server did not" + " negotiate the proper security package."; + break; + default: + msg = "Internal SSPI error"; + break; + } + + buf->value = dupstr(msg); + buf->length = strlen(buf->value); + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_sspi_get_mic(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *buf, + Ssh_gss_buf *hash) +{ + winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx; + SecPkgContext_Sizes ContextSizes; + SecBufferDesc InputBufferDescriptor; + SecBuffer InputSecurityToken[2]; + + if (winctx == NULL) return SSH_GSS_FAILURE; + + winctx->maj_stat = 0; + + memset(&ContextSizes, 0, sizeof(ContextSizes)); + + winctx->maj_stat = p_QueryContextAttributesA(&winctx->context, + SECPKG_ATTR_SIZES, + &ContextSizes); + + if (winctx->maj_stat != SEC_E_OK || + ContextSizes.cbMaxSignature == 0) + return winctx->maj_stat; + + InputBufferDescriptor.cBuffers = 2; + InputBufferDescriptor.pBuffers = InputSecurityToken; + InputBufferDescriptor.ulVersion = SECBUFFER_VERSION; + InputSecurityToken[0].BufferType = SECBUFFER_DATA; + InputSecurityToken[0].cbBuffer = buf->length; + InputSecurityToken[0].pvBuffer = buf->value; + InputSecurityToken[1].BufferType = SECBUFFER_TOKEN; + InputSecurityToken[1].cbBuffer = ContextSizes.cbMaxSignature; + InputSecurityToken[1].pvBuffer = snewn(ContextSizes.cbMaxSignature, char); + + winctx->maj_stat = p_MakeSignature(&winctx->context, + 0, + &InputBufferDescriptor, + 0); + + if (winctx->maj_stat == SEC_E_OK) { + hash->length = InputSecurityToken[1].cbBuffer; + hash->value = InputSecurityToken[1].pvBuffer; + } + + return winctx->maj_stat; +} + +static Ssh_gss_stat ssh_sspi_free_mic(struct ssh_gss_library *lib, + Ssh_gss_buf *hash) +{ + sfree(hash->value); + return SSH_GSS_OK; +} + +static void ssh_sspi_bind_fns(struct ssh_gss_library *lib) +{ + lib->indicate_mech = ssh_sspi_indicate_mech; + lib->import_name = ssh_sspi_import_name; + lib->release_name = ssh_sspi_release_name; + lib->init_sec_context = ssh_sspi_init_sec_context; + lib->free_tok = ssh_sspi_free_tok; + lib->acquire_cred = ssh_sspi_acquire_cred; + lib->release_cred = ssh_sspi_release_cred; + lib->get_mic = ssh_sspi_get_mic; + lib->free_mic = ssh_sspi_free_mic; + lib->display_status = ssh_sspi_display_status; +} + +#else + +/* Dummy function so this source file defines something if NO_GSSAPI + is defined. */ + +void ssh_gss_init(void) +{ +} + +#endif diff --git a/putty/WINDOWS/WINHANDL.C b/putty/WINDOWS/WINHANDL.C new file mode 100644 index 0000000..dbcab2b --- /dev/null +++ b/putty/WINDOWS/WINHANDL.C @@ -0,0 +1,596 @@ +/* + * winhandl.c: Module to give Windows front ends the general + * ability to deal with consoles, pipes, serial ports, or any other + * type of data stream accessed through a Windows API HANDLE rather + * than a WinSock SOCKET. + * + * We do this by spawning a subthread to continuously try to read + * from the handle. Every time a read successfully returns some + * data, the subthread sets an event object which is picked up by + * the main thread, and the main thread then sets an event in + * return to instruct the subthread to resume reading. + * + * Output works precisely the other way round, in a second + * subthread. The output subthread should not be attempting to + * write all the time, because it hasn't always got data _to_ + * write; so the output thread waits for an event object notifying + * it to _attempt_ a write, and then it sets an event in return + * when one completes. + * + * (It's terribly annoying having to spawn a subthread for each + * direction of each handle. Technically it isn't necessary for + * serial ports, since we could use overlapped I/O within the main + * thread and wait directly on the event objects in the OVERLAPPED + * structures. However, we can't use this trick for some types of + * file handle at all - for some reason Windows restricts use of + * OVERLAPPED to files which were opened with the overlapped flag - + * and so we must use threads for those. This being the case, it's + * simplest just to use threads for everything rather than trying + * to keep track of multiple completely separate mechanisms.) + */ + +#include + +#include "putty.h" + +/* ---------------------------------------------------------------------- + * Generic definitions. + */ + +/* + * Maximum amount of backlog we will allow to build up on an input + * handle before we stop reading from it. + */ +#define MAX_BACKLOG 32768 + +struct handle_generic { + /* + * Initial fields common to both handle_input and handle_output + * structures. + * + * The three HANDLEs are set up at initialisation time and are + * thereafter read-only to both main thread and subthread. + * `moribund' is only used by the main thread; `done' is + * written by the main thread before signalling to the + * subthread. `defunct' and `busy' are used only by the main + * thread. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ + void *privdata; /* for client to remember who they are */ +}; + +/* ---------------------------------------------------------------------- + * Input threads. + */ + +/* + * Data required by an input thread. + */ +struct handle_input { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ + void *privdata; /* for client to remember who they are */ + + /* + * Data set at initialisation and then read-only. + */ + int flags; + + /* + * Data set by the input thread before signalling ev_to_main, + * and read by the main thread after receiving that signal. + */ + char buffer[4096]; /* the data read from the handle */ + DWORD len; /* how much data that was */ + int readerr; /* lets us know about read errors */ + + /* + * Callback function called by this module when data arrives on + * an input handle. + */ + handle_inputfn_t gotdata; +}; + +/* + * The actual thread procedure for an input thread. + */ +static DWORD WINAPI handle_input_threadfunc(void *param) +{ + struct handle_input *ctx = (struct handle_input *) param; + OVERLAPPED ovl, *povl; + HANDLE oev; + int readret, 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; + + SetEvent(ctx->ev_to_main); + + if (!ctx->len) + break; + + WaitForSingleObject(ctx->ev_from_main, INFINITE); + if (ctx->done) + break; /* main thread told us to shut down */ + } + + if (povl) + CloseHandle(oev); + + return 0; +} + +/* + * This is called after a succcessful read, or from the + * `unthrottle' function. It decides whether or not to begin a new + * read operation. + */ +static void handle_throttle(struct handle_input *ctx, int backlog) +{ + if (ctx->defunct) + return; + + /* + * If there's a read operation already in progress, do nothing: + * when that completes, we'll come back here and be in a + * position to make a better decision. + */ + if (ctx->busy) + return; + + /* + * Otherwise, we must decide whether to start a new read based + * on the size of the backlog. + */ + if (backlog < MAX_BACKLOG) { + SetEvent(ctx->ev_from_main); + ctx->busy = TRUE; + } +} + +/* ---------------------------------------------------------------------- + * Output threads. + */ + +/* + * Data required by an output thread. + */ +struct handle_output { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ + void *privdata; /* for client to remember who they are */ + + /* + * Data set at initialisation and then read-only. + */ + int flags; + + /* + * Data set by the main thread before signalling ev_from_main, + * and read by the input thread after receiving that signal. + */ + char *buffer; /* the data to write */ + DWORD len; /* how much data there is */ + + /* + * Data set by the input thread before signalling ev_to_main, + * and read by the main thread after receiving that signal. + */ + DWORD lenwritten; /* how much data we actually wrote */ + int writeerr; /* return value from WriteFile */ + + /* + * Data only ever read or written by the main thread. + */ + bufchain queued_data; /* data still waiting to be written */ + + /* + * Callback function called when the backlog in the bufchain + * drops. + */ + handle_outputfn_t sentdata; +}; + +static DWORD WINAPI handle_output_threadfunc(void *param) +{ + struct handle_output *ctx = (struct handle_output *) param; + OVERLAPPED ovl, *povl; + HANDLE oev; + int writeret; + + if (ctx->flags & HANDLE_FLAG_OVERLAPPED) { + povl = &ovl; + oev = CreateEvent(NULL, TRUE, FALSE, NULL); + } else { + povl = NULL; + } + + while (1) { + WaitForSingleObject(ctx->ev_from_main, INFINITE); + if (ctx->done) { + 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) + break; + } + + if (povl) + CloseHandle(oev); + + return 0; +} + +static void handle_try_output(struct handle_output *ctx) +{ + void *senddata; + int sendlen; + + if (!ctx->busy && bufchain_size(&ctx->queued_data)) { + bufchain_prefix(&ctx->queued_data, &senddata, &sendlen); + ctx->buffer = senddata; + ctx->len = sendlen; + SetEvent(ctx->ev_from_main); + ctx->busy = TRUE; + } +} + +/* ---------------------------------------------------------------------- + * Unified code handling both input and output threads. + */ + +struct handle { + int output; + union { + struct handle_generic g; + struct handle_input i; + struct handle_output o; + } 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 ((unsigned)a->u.g.ev_to_main < (unsigned)b->u.g.ev_to_main) + return -1; + else if ((unsigned)a->u.g.ev_to_main > (unsigned)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 ((unsigned)*a < (unsigned)b->u.g.ev_to_main) + return -1; + else if ((unsigned)*a > (unsigned)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->output = FALSE; + h->u.i.h = handle; + h->u.i.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL); + h->u.i.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL); + h->u.i.gotdata = gotdata; + h->u.i.defunct = FALSE; + h->u.i.moribund = FALSE; + h->u.i.done = FALSE; + h->u.i.privdata = privdata; + h->u.i.flags = flags; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + CreateThread(NULL, 0, handle_input_threadfunc, + &h->u.i, 0, &in_threadid); + h->u.i.busy = TRUE; + + return h; +} + +struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, + void *privdata, int flags) +{ + struct handle *h = snew(struct handle); + DWORD out_threadid; /* required for Win9x */ + + h->output = TRUE; + 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.sentdata = sentdata; + h->u.o.flags = flags; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + CreateThread(NULL, 0, handle_output_threadfunc, + &h->u.o, 0, &out_threadid); + + return h; +} + +int handle_write(struct handle *h, const void *data, int len) +{ + assert(h->output); + bufchain_add(&h->u.o.queued_data, data, len); + handle_try_output(&h->u.o); + return bufchain_size(&h->u.o.queued_data); +} + +HANDLE *handle_get_events(int *nevents) +{ + HANDLE *ret; + struct handle *h; + int i, n, size; + + /* + * Go through our tree counting the handle objects currently + * engaged in useful activity. + */ + ret = NULL; + n = size = 0; + if (handles_by_evtomain) { + for (i = 0; (h = index234(handles_by_evtomain, i)) != NULL; i++) { + if (h->u.g.busy) { + if (n >= size) { + size += 32; + ret = sresize(ret, size, HANDLE); + } + ret[n++] = h->u.g.ev_to_main; + } + } + } + + *nevents = n; + return ret; +} + +static void handle_destroy(struct handle *h) +{ + if (h->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) +{ + /* + * If the handle is currently busy, we cannot immediately free + * it. 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. + */ + assert(h && !h->u.g.moribund); + if (h->u.g.busy) { + /* + * Just 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 already treated as dead from the + * external user's point of view, so do nothing with the + * actual event. Just signal the thread to die if + * necessary, 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; + } + + if (!h->output) { + int backlog; + + 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.gotdata(h, NULL, -h->u.i.readerr); + h->u.i.defunct = TRUE; + } else { + backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len); + handle_throttle(&h->u.i, backlog); + } + } else { + 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.sentdata(h, -h->u.o.writeerr); + h->u.o.defunct = TRUE; + } else { + bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); + h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data)); + handle_try_output(&h->u.o); + } + } +} + +void handle_unthrottle(struct handle *h, int backlog) +{ + assert(!h->output); + handle_throttle(&h->u.i, backlog); +} + +int handle_backlog(struct handle *h) +{ + assert(h->output); + return bufchain_size(&h->u.o.queued_data); +} + +void *handle_get_privdata(struct handle *h) +{ + return h->u.g.privdata; +} diff --git a/putty/WINDOWS/WINHELP.C b/putty/WINDOWS/WINHELP.C new file mode 100644 index 0000000..c072e05 --- /dev/null +++ b/putty/WINDOWS/WINHELP.C @@ -0,0 +1,138 @@ +/* + * winhelp.c: centralised functions to launch Windows help files, + * and to decide whether to use .HLP or .CHM help in any given + * situation. + */ + +#include +#include +#include +#include + +#include "putty.h" + +#ifndef NO_HTMLHELP +#include +#endif /* NO_HTMLHELP */ + +static int requested_help; +static char *help_path; +static int help_has_contents; +#ifndef NO_HTMLHELP +DECL_WINDOWS_FUNCTION(static, HWND, HtmlHelpA, (HWND, LPCSTR, UINT, DWORD)); +static char *chm_path; +#endif /* NO_HTMLHELP */ + +void init_help(void) +{ + char b[2048], *p, *q, *r; + FILE *fp; + + GetModuleFileName(NULL, b, sizeof(b) - 1); + 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_HELP_FILE); + if ( (fp = fopen(b, "r")) != NULL) { + help_path = dupstr(b); + fclose(fp); + } else + help_path = NULL; + strcpy(r, PUTTY_HELP_CONTENTS); + if ( (fp = fopen(b, "r")) != NULL) { + help_has_contents = TRUE; + fclose(fp); + } else + help_has_contents = FALSE; + +#ifndef NO_HTMLHELP + strcpy(r, PUTTY_CHM_FILE); + if ( (fp = fopen(b, "r")) != NULL) { + chm_path = dupstr(b); + fclose(fp); + } else + chm_path = NULL; + if (chm_path) { + HINSTANCE dllHH = load_system32_dll("hhctrl.ocx"); + GET_WINDOWS_FUNCTION(dllHH, HtmlHelpA); + if (!p_HtmlHelpA) { + chm_path = NULL; + if (dllHH) + FreeLibrary(dllHH); + } + } +#endif /* NO_HTMLHELP */ +} + +void shutdown_help(void) +{ + /* Nothing to do currently. + * (If we were running HTML Help single-threaded, this is where we'd + * call HH_UNINITIALIZE.) */ +} + +int has_help(void) +{ + /* + * FIXME: it would be nice here to disregard help_path on + * platforms that didn't have WINHLP32. But that's probably + * unrealistic, since even Vista will have it if the user + * specifically downloads it. + */ + return (help_path +#ifndef NO_HTMLHELP + || chm_path +#endif /* NO_HTMLHELP */ + ); +} + +void launch_help(HWND hwnd, const char *topic) +{ + if (topic) { + int colonpos = strcspn(topic, ":"); + +#ifndef NO_HTMLHELP + if (chm_path) { + char *fname; + assert(topic[colonpos] != '\0'); + fname = dupprintf("%s::/%s.html>main", chm_path, + topic + colonpos + 1); + p_HtmlHelpA(hwnd, fname, HH_DISPLAY_TOPIC, 0); + sfree(fname); + } else +#endif /* NO_HTMLHELP */ + if (help_path) { + char *cmd = dupprintf("JI(`',`%.*s')", colonpos, topic); + WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd); + sfree(cmd); + } + } else { +#ifndef NO_HTMLHELP + if (chm_path) { + p_HtmlHelpA(hwnd, chm_path, HH_DISPLAY_TOPIC, 0); + } else +#endif /* NO_HTMLHELP */ + if (help_path) { + WinHelp(hwnd, help_path, + help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0); + } + } + requested_help = TRUE; +} + +void quit_help(HWND hwnd) +{ + if (requested_help) { +#ifndef NO_HTMLHELP + if (chm_path) { + p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0); + } else +#endif /* NO_HTMLHELP */ + if (help_path) { + WinHelp(hwnd, help_path, HELP_QUIT, 0); + } + requested_help = FALSE; + } +} diff --git a/putty/WINDOWS/WINHELP.H b/putty/WINDOWS/WINHELP.H new file mode 100644 index 0000000..d670c0c --- /dev/null +++ b/putty/WINDOWS/WINHELP.H @@ -0,0 +1,187 @@ +/* + * winhelp.h - define Windows Help context names. + * Each definition has the form "winhelp-topic:halibut-topic", where: + * - "winhelp-topic" matches up with the \cfg{winhelp-topic} directives + * in the Halibut source, and is used for WinHelp; + * - "halibut-topic" matches up with the Halibut keywords in the 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 "session.hostname:config-hostname" +#define WINHELP_CTX_session_saved "session.saved:config-saving" +#define WINHELP_CTX_session_coe "session.coe:config-closeonexit" +#define WINHELP_CTX_logging_main "logging.main:config-logging" +#define WINHELP_CTX_logging_filename "logging.filename:config-logfilename" +#define WINHELP_CTX_logging_exists "logging.exists:config-logfileexists" +#define WINHELP_CTX_logging_flush "logging.flush:config-logflush" +#define WINHELP_CTX_logging_ssh_omit_password "logging.ssh.omitpassword:config-logssh" +#define WINHELP_CTX_logging_ssh_omit_data "logging.ssh.omitdata:config-logssh" +#define WINHELP_CTX_keyboard_backspace "keyboard.backspace:config-backspace" +#define WINHELP_CTX_keyboard_homeend "keyboard.homeend:config-homeend" +#define WINHELP_CTX_keyboard_funkeys "keyboard.funkeys:config-funkeys" +#define WINHELP_CTX_keyboard_appkeypad "keyboard.appkeypad:config-appkeypad" +#define WINHELP_CTX_keyboard_appcursor "keyboard.appcursor:config-appcursor" +#define WINHELP_CTX_keyboard_nethack "keyboard.nethack:config-nethack" +#define WINHELP_CTX_keyboard_compose "keyboard.compose:config-compose" +#define WINHELP_CTX_keyboard_ctrlalt "keyboard.ctrlalt:config-ctrlalt" +#define WINHELP_CTX_features_application "features.application:config-features-application" +#define WINHELP_CTX_features_mouse "features.mouse:config-features-mouse" +#define WINHELP_CTX_features_resize "features.resize:config-features-resize" +#define WINHELP_CTX_features_altscreen "features.altscreen:config-features-altscreen" +#define WINHELP_CTX_features_retitle "features.retitle:config-features-retitle" +#define WINHELP_CTX_features_qtitle "features.qtitle:config-features-qtitle" +#define WINHELP_CTX_features_dbackspace "features.dbackspace:config-features-dbackspace" +#define WINHELP_CTX_features_charset "features.charset:config-features-charset" +#define WINHELP_CTX_features_arabicshaping "features.arabicshaping:config-features-shaping" +#define WINHELP_CTX_features_bidi "features.bidi:config-features-bidi" +#define WINHELP_CTX_terminal_autowrap "terminal.autowrap:config-autowrap" +#define WINHELP_CTX_terminal_decom "terminal.decom:config-decom" +#define WINHELP_CTX_terminal_lfhascr "terminal.lfhascr:config-crlf" +#define WINHELP_CTX_terminal_crhaslf "terminal.crhaslf:config-lfcr" +#define WINHELP_CTX_terminal_bce "terminal.bce:config-erase" +#define WINHELP_CTX_terminal_blink "terminal.blink:config-blink" +#define WINHELP_CTX_terminal_answerback "terminal.answerback:config-answerback" +#define WINHELP_CTX_terminal_localecho "terminal.localecho:config-localecho" +#define WINHELP_CTX_terminal_localedit "terminal.localedit:config-localedit" +#define WINHELP_CTX_terminal_printing "terminal.printing:config-printing" +#define WINHELP_CTX_bell_style "bell.style:config-bellstyle" +#define WINHELP_CTX_bell_taskbar "bell.taskbar:config-belltaskbar" +#define WINHELP_CTX_bell_overload "bell.overload:config-bellovl" +#define WINHELP_CTX_window_size "window.size:config-winsize" +#define WINHELP_CTX_window_resize "window.resize:config-winsizelock" +#define WINHELP_CTX_window_scrollback "window.scrollback:config-scrollback" +#define WINHELP_CTX_window_erased "window.erased:config-erasetoscrollback" +#define WINHELP_CTX_behaviour_closewarn "behaviour.closewarn:config-warnonclose" +#define WINHELP_CTX_behaviour_altf4 "behaviour.altf4:config-altf4" +#define WINHELP_CTX_behaviour_altspace "behaviour.altspace:config-altspace" +#define WINHELP_CTX_behaviour_altonly "behaviour.altonly:config-altonly" +#define WINHELP_CTX_behaviour_alwaysontop "behaviour.alwaysontop:config-alwaysontop" +#define WINHELP_CTX_behaviour_altenter "behaviour.altenter:config-fullscreen" +#define WINHELP_CTX_appearance_cursor "appearance.cursor:config-cursor" +#define WINHELP_CTX_appearance_font "appearance.font:config-font" +#define WINHELP_CTX_appearance_title "appearance.title:config-title" +#define WINHELP_CTX_appearance_hidemouse "appearance.hidemouse:config-mouseptr" +#define WINHELP_CTX_appearance_border "appearance.border:config-winborder" +#define WINHELP_CTX_connection_termtype "connection.termtype:config-termtype" +#define WINHELP_CTX_connection_termspeed "connection.termspeed:config-termspeed" +#define WINHELP_CTX_connection_username "connection.username:config-username" +#define WINHELP_CTX_connection_username_from_env "connection.usernamefromenv:config-username-from-env" +#define WINHELP_CTX_connection_keepalive "connection.keepalive:config-keepalive" +#define WINHELP_CTX_connection_nodelay "connection.nodelay:config-nodelay" +#define WINHELP_CTX_connection_ipversion "connection.ipversion:config-address-family" +#define WINHELP_CTX_connection_tcpkeepalive "connection.tcpkeepalive:config-tcp-keepalives" +#define WINHELP_CTX_connection_loghost "connection.loghost:config-loghost" +#define WINHELP_CTX_proxy_type "proxy.type:config-proxy-type" +#define WINHELP_CTX_proxy_main "proxy.main:config-proxy" +#define WINHELP_CTX_proxy_exclude "proxy.exclude:config-proxy-exclude" +#define WINHELP_CTX_proxy_dns "proxy.dns:config-proxy-dns" +#define WINHELP_CTX_proxy_auth "proxy.auth:config-proxy-auth" +#define WINHELP_CTX_proxy_command "proxy.command:config-proxy-command" +#define WINHELP_CTX_telnet_environ "telnet.environ:config-environ" +#define WINHELP_CTX_telnet_oldenviron "telnet.oldenviron:config-oldenviron" +#define WINHELP_CTX_telnet_passive "telnet.passive:config-ptelnet" +#define WINHELP_CTX_telnet_specialkeys "telnet.specialkeys:config-telnetkey" +#define WINHELP_CTX_telnet_newline "telnet.newline:config-telnetnl" +#define WINHELP_CTX_rlogin_localuser "rlogin.localuser:config-rlogin-localuser" +#define WINHELP_CTX_ssh_nopty "ssh.nopty:config-ssh-pty" +#define WINHELP_CTX_ssh_ttymodes "ssh.ttymodes:config-ttymodes" +#define WINHELP_CTX_ssh_noshell "ssh.noshell:config-ssh-noshell" +#define WINHELP_CTX_ssh_ciphers "ssh.ciphers:config-ssh-encryption" +#define WINHELP_CTX_ssh_protocol "ssh.protocol:config-ssh-prot" +#define WINHELP_CTX_ssh_command "ssh.command:config-command" +#define WINHELP_CTX_ssh_compress "ssh.compress:config-ssh-comp" +#define WINHELP_CTX_ssh_kexlist "ssh.kex.order:config-ssh-kex-order" +#define WINHELP_CTX_ssh_kex_repeat "ssh.kex.repeat:config-ssh-kex-rekey" +#define WINHELP_CTX_ssh_auth_bypass "ssh.auth.bypass:config-ssh-noauth" +#define WINHELP_CTX_ssh_auth_banner "ssh.auth.banner:config-ssh-banner" +#define WINHELP_CTX_ssh_auth_privkey "ssh.auth.privkey:config-ssh-privkey" +#define WINHELP_CTX_ssh_auth_agentfwd "ssh.auth.agentfwd:config-ssh-agentfwd" +#define WINHELP_CTX_ssh_auth_changeuser "ssh.auth.changeuser:config-ssh-changeuser" +#define WINHELP_CTX_ssh_auth_pageant "ssh.auth.pageant:config-ssh-tryagent" +#define WINHELP_CTX_ssh_auth_tis "ssh.auth.tis:config-ssh-tis" +#define WINHELP_CTX_ssh_auth_ki "ssh.auth.ki:config-ssh-ki" +#define WINHELP_CTX_ssh_gssapi "ssh.auth.gssapi:config-ssh-auth-gssapi" +#define WINHELP_CTX_ssh_gssapi_delegation "ssh.auth.gssapi.delegation:config-ssh-auth-gssapi-delegation" +#define WINHELP_CTX_ssh_gssapi_libraries "ssh.auth.gssapi.libraries:config-ssh-auth-gssapi-libraries" +#define WINHELP_CTX_selection_buttons "selection.buttons:config-mouse" +#define WINHELP_CTX_selection_shiftdrag "selection.shiftdrag:config-mouseshift" +#define WINHELP_CTX_selection_rect "selection.rect:config-rectselect" +#define WINHELP_CTX_selection_charclasses "selection.charclasses:config-charclasses" +#define WINHELP_CTX_selection_linedraw "selection.linedraw:config-linedrawpaste" +#define WINHELP_CTX_selection_rtf "selection.rtf:config-rtfpaste" +#define WINHELP_CTX_colours_ansi "colours.ansi:config-ansicolour" +#define WINHELP_CTX_colours_xterm256 "colours.xterm256:config-xtermcolour" +#define WINHELP_CTX_colours_bold "colours.bold:config-boldcolour" +#define WINHELP_CTX_colours_system "colours.system:config-syscolour" +#define WINHELP_CTX_colours_logpal "colours.logpal:config-logpalette" +#define WINHELP_CTX_colours_config "colours.config:config-colourcfg" +#define WINHELP_CTX_translation_codepage "translation.codepage:config-charset" +#define WINHELP_CTX_translation_cjk_ambig_wide "translation.cjkambigwide:config-cjk-ambig-wide" +#define WINHELP_CTX_translation_cyrillic "translation.cyrillic:config-cyr" +#define WINHELP_CTX_translation_linedraw "translation.linedraw:config-linedraw" +#define WINHELP_CTX_ssh_tunnels_x11 "ssh.tunnels.x11:config-ssh-x11" +#define WINHELP_CTX_ssh_tunnels_x11auth "ssh.tunnels.x11auth:config-ssh-x11auth" +#define WINHELP_CTX_ssh_tunnels_xauthority "ssh.tunnels.xauthority:config-ssh-xauthority" +#define WINHELP_CTX_ssh_tunnels_portfwd "ssh.tunnels.portfwd:config-ssh-portfwd" +#define WINHELP_CTX_ssh_tunnels_portfwd_localhost "ssh.tunnels.portfwd.localhost:config-ssh-portfwd-localhost" +#define WINHELP_CTX_ssh_tunnels_portfwd_ipversion "ssh.tunnels.portfwd.ipversion:config-ssh-portfwd-address-family" +#define WINHELP_CTX_ssh_bugs_ignore1 "ssh.bugs.ignore1:config-ssh-bug-ignore1" +#define WINHELP_CTX_ssh_bugs_plainpw1 "ssh.bugs.plainpw1:config-ssh-bug-plainpw1" +#define WINHELP_CTX_ssh_bugs_rsa1 "ssh.bugs.rsa1:config-ssh-bug-rsa1" +#define WINHELP_CTX_ssh_bugs_ignore2 "ssh.bugs.ignore2:config-ssh-bug-ignore2" +#define WINHELP_CTX_ssh_bugs_hmac2 "ssh.bugs.hmac2:config-ssh-bug-hmac2" +#define WINHELP_CTX_ssh_bugs_derivekey2 "ssh.bugs.derivekey2:config-ssh-bug-derivekey2" +#define WINHELP_CTX_ssh_bugs_rsapad2 "ssh.bugs.rsapad2:config-ssh-bug-sig" +#define WINHELP_CTX_ssh_bugs_pksessid2 "ssh.bugs.pksessid2:config-ssh-bug-pksessid2" +#define WINHELP_CTX_ssh_bugs_rekey2 "ssh.bugs.rekey2:config-ssh-bug-rekey" +#define WINHELP_CTX_ssh_bugs_maxpkt2 "ssh.bugs.maxpkt2:config-ssh-bug-maxpkt2" +#define WINHELP_CTX_serial_line "serial.line:config-serial-line" +#define WINHELP_CTX_serial_speed "serial.speed:config-serial-speed" +#define WINHELP_CTX_serial_databits "serial.databits:config-serial-databits" +#define WINHELP_CTX_serial_stopbits "serial.stopbits:config-serial-stopbits" +#define WINHELP_CTX_serial_parity "serial.parity:config-serial-parity" +#define WINHELP_CTX_serial_flow "serial.flow:config-serial-flow" + +#define WINHELP_CTX_pageant_general "pageant.general:pageant" +#define WINHELP_CTX_pageant_keylist "pageant.keylist:pageant-mainwin-keylist" +#define WINHELP_CTX_pageant_addkey "pageant.addkey:pageant-mainwin-addkey" +#define WINHELP_CTX_pageant_remkey "pageant.remkey:pageant-mainwin-remkey" +#define WINHELP_CTX_pgpfingerprints "pgpfingerprints:pgpkeys" +#define WINHELP_CTX_puttygen_general "puttygen.general:pubkey-puttygen" +#define WINHELP_CTX_puttygen_keytype "puttygen.keytype:puttygen-keytype" +#define WINHELP_CTX_puttygen_bits "puttygen.bits:puttygen-strength" +#define WINHELP_CTX_puttygen_generate "puttygen.generate:puttygen-generate" +#define WINHELP_CTX_puttygen_fingerprint "puttygen.fingerprint:puttygen-fingerprint" +#define WINHELP_CTX_puttygen_comment "puttygen.comment:puttygen-comment" +#define WINHELP_CTX_puttygen_passphrase "puttygen.passphrase:puttygen-passphrase" +#define WINHELP_CTX_puttygen_savepriv "puttygen.savepriv:puttygen-savepriv" +#define WINHELP_CTX_puttygen_savepub "puttygen.savepub:puttygen-savepub" +#define WINHELP_CTX_puttygen_pastekey "puttygen.pastekey:puttygen-pastekey" +#define WINHELP_CTX_puttygen_load "puttygen.load:puttygen-load" +#define WINHELP_CTX_puttygen_conversions "puttygen.conversions:puttygen-conversions" + +/* 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:errors-hostkey-absent" +#define WINHELP_CTXID_errors_hostkey_absent 1 +#define WINHELP_CTX_errors_hostkey_changed "errors.hostkey.changed:errors-hostkey-wrong" +#define WINHELP_CTXID_errors_hostkey_changed 2 +#define WINHELP_CTX_errors_cantloadkey "errors.cantloadkey:errors-cant-load-key" +#define WINHELP_CTXID_errors_cantloadkey 3 +#define WINHELP_CTX_option_cleanup "options.cleanup:using-cleanup" +#define WINHELP_CTXID_option_cleanup 4 +#define WINHELP_CTX_pgp_fingerprints "pgpfingerprints:pgpkeys" +#define WINHELP_CTXID_pgp_fingerprints 5 diff --git a/putty/WINDOWS/WINJUMP.C b/putty/WINDOWS/WINJUMP.C new file mode 100644 index 0000000..3455a8a --- /dev/null +++ b/putty/WINDOWS/WINJUMP.C @@ -0,0 +1,706 @@ +/* + * winjump.c: support for Windows 7 jump lists. + * + * The Windows 7 jumplist is a customizable list defined by the + * application. It is persistent across application restarts: the OS + * maintains the list when the app is not running. The list is shown + * when the user right-clicks on the taskbar button of a running app + * or a pinned non-running application. We use the jumplist to + * maintain a list of recently started saved sessions, started either + * by doubleclicking on a saved session, or with the command line + * "-load" parameter. + * + * Since the jumplist is write-only: it can only be replaced and the + * current list cannot be read, we must maintain the contents of the + * list persistantly in the registry. The file winstore.h contains + * functions to directly manipulate these registry entries. This file + * contains higher level functions to manipulate the jumplist. + */ + +#include + +#include "putty.h" +#include "storage.h" + +#define MAX_JUMPLIST_ITEMS 30 /* PuTTY will never show more items in + * the jumplist than this, regardless of + * user preferences. */ + +/* + * COM structures and functions. + */ +#ifndef PROPERTYKEY_DEFINED +#define PROPERTYKEY_DEFINED +typedef struct _tagpropertykey { + GUID fmtid; + DWORD pid; +} PROPERTYKEY; +#endif +#ifndef _REFPROPVARIANT_DEFINED +#define _REFPROPVARIANT_DEFINED +typedef PROPVARIANT *REFPROPVARIANT; +#endif +/* MinGW doesn't define this yet: */ +#ifndef _PROPVARIANTINIT_DEFINED_ +#define _PROPVARIANTINIT_DEFINED_ +#define PropVariantInit(pvar) memset((pvar),0,sizeof(PROPVARIANT)) +#endif + +#define IID_IShellLink IID_IShellLinkA + +typedef struct ICustomDestinationListVtbl { + HRESULT ( __stdcall *QueryInterface ) ( + /* [in] ICustomDestinationList*/ void *This, + /* [in] */ const GUID * const riid, + /* [out] */ void **ppvObject); + + ULONG ( __stdcall *AddRef )( + /* [in] ICustomDestinationList*/ void *This); + + ULONG ( __stdcall *Release )( + /* [in] ICustomDestinationList*/ void *This); + + HRESULT ( __stdcall *SetAppID )( + /* [in] ICustomDestinationList*/ void *This, + /* [string][in] */ LPCWSTR pszAppID); + + HRESULT ( __stdcall *BeginList )( + /* [in] ICustomDestinationList*/ void *This, + /* [out] */ UINT *pcMinSlots, + /* [in] */ const GUID * const riid, + /* [out] */ void **ppv); + + HRESULT ( __stdcall *AppendCategory )( + /* [in] ICustomDestinationList*/ void *This, + /* [string][in] */ LPCWSTR pszCategory, + /* [in] IObjectArray*/ void *poa); + + HRESULT ( __stdcall *AppendKnownCategory )( + /* [in] ICustomDestinationList*/ void *This, + /* [in] KNOWNDESTCATEGORY*/ int category); + + HRESULT ( __stdcall *AddUserTasks )( + /* [in] ICustomDestinationList*/ void *This, + /* [in] IObjectArray*/ void *poa); + + HRESULT ( __stdcall *CommitList )( + /* [in] ICustomDestinationList*/ void *This); + + HRESULT ( __stdcall *GetRemovedDestinations )( + /* [in] ICustomDestinationList*/ void *This, + /* [in] */ const IID * const riid, + /* [out] */ void **ppv); + + HRESULT ( __stdcall *DeleteList )( + /* [in] ICustomDestinationList*/ void *This, + /* [string][unique][in] */ LPCWSTR pszAppID); + + HRESULT ( __stdcall *AbortList )( + /* [in] ICustomDestinationList*/ void *This); + +} ICustomDestinationListVtbl; + +typedef struct ICustomDestinationList +{ + ICustomDestinationListVtbl *lpVtbl; +} ICustomDestinationList; + +typedef struct IObjectArrayVtbl +{ + HRESULT ( __stdcall *QueryInterface )( + /* [in] IObjectArray*/ void *This, + /* [in] */ const GUID * const riid, + /* [out] */ void **ppvObject); + + ULONG ( __stdcall *AddRef )( + /* [in] IObjectArray*/ void *This); + + ULONG ( __stdcall *Release )( + /* [in] IObjectArray*/ void *This); + + HRESULT ( __stdcall *GetCount )( + /* [in] IObjectArray*/ void *This, + /* [out] */ UINT *pcObjects); + + HRESULT ( __stdcall *GetAt )( + /* [in] IObjectArray*/ void *This, + /* [in] */ UINT uiIndex, + /* [in] */ const GUID * const riid, + /* [out] */ void **ppv); + +} IObjectArrayVtbl; + +typedef struct IObjectArray +{ + IObjectArrayVtbl *lpVtbl; +} IObjectArray; + +typedef struct IShellLinkVtbl +{ + HRESULT ( __stdcall *QueryInterface )( + /* [in] IShellLink*/ void *This, + /* [in] */ const GUID * const riid, + /* [out] */ void **ppvObject); + + ULONG ( __stdcall *AddRef )( + /* [in] IShellLink*/ void *This); + + ULONG ( __stdcall *Release )( + /* [in] IShellLink*/ void *This); + + HRESULT ( __stdcall *GetPath )( + /* [in] IShellLink*/ void *This, + /* [string][out] */ LPSTR pszFile, + /* [in] */ int cch, + /* [unique][out][in] */ WIN32_FIND_DATAA *pfd, + /* [in] */ DWORD fFlags); + + HRESULT ( __stdcall *GetIDList )( + /* [in] IShellLink*/ void *This, + /* [out] LPITEMIDLIST*/ void **ppidl); + + HRESULT ( __stdcall *SetIDList )( + /* [in] IShellLink*/ void *This, + /* [in] LPITEMIDLIST*/ void *pidl); + + HRESULT ( __stdcall *GetDescription )( + /* [in] IShellLink*/ void *This, + /* [string][out] */ LPSTR pszName, + /* [in] */ int cch); + + HRESULT ( __stdcall *SetDescription )( + /* [in] IShellLink*/ void *This, + /* [string][in] */ LPCSTR pszName); + + HRESULT ( __stdcall *GetWorkingDirectory )( + /* [in] IShellLink*/ void *This, + /* [string][out] */ LPSTR pszDir, + /* [in] */ int cch); + + HRESULT ( __stdcall *SetWorkingDirectory )( + /* [in] IShellLink*/ void *This, + /* [string][in] */ LPCSTR pszDir); + + HRESULT ( __stdcall *GetArguments )( + /* [in] IShellLink*/ void *This, + /* [string][out] */ LPSTR pszArgs, + /* [in] */ int cch); + + HRESULT ( __stdcall *SetArguments )( + /* [in] IShellLink*/ void *This, + /* [string][in] */ LPCSTR pszArgs); + + HRESULT ( __stdcall *GetHotkey )( + /* [in] IShellLink*/ void *This, + /* [out] */ WORD *pwHotkey); + + HRESULT ( __stdcall *SetHotkey )( + /* [in] IShellLink*/ void *This, + /* [in] */ WORD wHotkey); + + HRESULT ( __stdcall *GetShowCmd )( + /* [in] IShellLink*/ void *This, + /* [out] */ int *piShowCmd); + + HRESULT ( __stdcall *SetShowCmd )( + /* [in] IShellLink*/ void *This, + /* [in] */ int iShowCmd); + + HRESULT ( __stdcall *GetIconLocation )( + /* [in] IShellLink*/ void *This, + /* [string][out] */ LPSTR pszIconPath, + /* [in] */ int cch, + /* [out] */ int *piIcon); + + HRESULT ( __stdcall *SetIconLocation )( + /* [in] IShellLink*/ void *This, + /* [string][in] */ LPCSTR pszIconPath, + /* [in] */ int iIcon); + + HRESULT ( __stdcall *SetRelativePath )( + /* [in] IShellLink*/ void *This, + /* [string][in] */ LPCSTR pszPathRel, + /* [in] */ DWORD dwReserved); + + HRESULT ( __stdcall *Resolve )( + /* [in] IShellLink*/ void *This, + /* [unique][in] */ HWND hwnd, + /* [in] */ DWORD fFlags); + + HRESULT ( __stdcall *SetPath )( + /* [in] IShellLink*/ void *This, + /* [string][in] */ LPCSTR pszFile); + +} IShellLinkVtbl; + +typedef struct IShellLink +{ + IShellLinkVtbl *lpVtbl; +} IShellLink; + +typedef struct IObjectCollectionVtbl +{ + HRESULT ( __stdcall *QueryInterface )( + /* [in] IShellLink*/ void *This, + /* [in] */ const GUID * const riid, + /* [out] */ void **ppvObject); + + ULONG ( __stdcall *AddRef )( + /* [in] IShellLink*/ void *This); + + ULONG ( __stdcall *Release )( + /* [in] IShellLink*/ void *This); + + HRESULT ( __stdcall *GetCount )( + /* [in] IShellLink*/ void *This, + /* [out] */ UINT *pcObjects); + + HRESULT ( __stdcall *GetAt )( + /* [in] IShellLink*/ void *This, + /* [in] */ UINT uiIndex, + /* [in] */ const GUID * const riid, + /* [iid_is][out] */ void **ppv); + + HRESULT ( __stdcall *AddObject )( + /* [in] IShellLink*/ void *This, + /* [in] */ void *punk); + + HRESULT ( __stdcall *AddFromArray )( + /* [in] IShellLink*/ void *This, + /* [in] */ IObjectArray *poaSource); + + HRESULT ( __stdcall *RemoveObjectAt )( + /* [in] IShellLink*/ void *This, + /* [in] */ UINT uiIndex); + + HRESULT ( __stdcall *Clear )( + /* [in] IShellLink*/ void *This); + +} IObjectCollectionVtbl; + +typedef struct IObjectCollection +{ + IObjectCollectionVtbl *lpVtbl; +} IObjectCollection; + +typedef struct IPropertyStoreVtbl +{ + HRESULT ( __stdcall *QueryInterface )( + /* [in] IPropertyStore*/ void *This, + /* [in] */ const GUID * const riid, + /* [iid_is][out] */ void **ppvObject); + + ULONG ( __stdcall *AddRef )( + /* [in] IPropertyStore*/ void *This); + + ULONG ( __stdcall *Release )( + /* [in] IPropertyStore*/ void *This); + + HRESULT ( __stdcall *GetCount )( + /* [in] IPropertyStore*/ void *This, + /* [out] */ DWORD *cProps); + + HRESULT ( __stdcall *GetAt )( + /* [in] IPropertyStore*/ void *This, + /* [in] */ DWORD iProp, + /* [out] */ PROPERTYKEY *pkey); + + HRESULT ( __stdcall *GetValue )( + /* [in] IPropertyStore*/ void *This, + /* [in] */ const PROPERTYKEY * const key, + /* [out] */ PROPVARIANT *pv); + + HRESULT ( __stdcall *SetValue )( + /* [in] IPropertyStore*/ void *This, + /* [in] */ const PROPERTYKEY * const key, + /* [in] */ REFPROPVARIANT propvar); + + HRESULT ( __stdcall *Commit )( + /* [in] IPropertyStore*/ void *This); +} IPropertyStoreVtbl; + +typedef struct IPropertyStore +{ + IPropertyStoreVtbl *lpVtbl; +} IPropertyStore; + +static const CLSID CLSID_DestinationList = { + 0x77f10cf0, 0x3db5, 0x4966, {0xb5,0x20,0xb7,0xc5,0x4f,0xd3,0x5e,0xd6} +}; +static const CLSID CLSID_ShellLink = { + 0x00021401, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46} +}; +static const CLSID CLSID_EnumerableObjectCollection = { + 0x2d3468c1, 0x36a7, 0x43b6, {0xac,0x24,0xd3,0xf0,0x2f,0xd9,0x60,0x7a} +}; +static const IID IID_IObjectCollection = { + 0x5632b1a4, 0xe38a, 0x400a, {0x92,0x8a,0xd4,0xcd,0x63,0x23,0x02,0x95} +}; +static const IID IID_IShellLink = { + 0x000214ee, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46} +}; +static const IID IID_ICustomDestinationList = { + 0x6332debf, 0x87b5, 0x4670, {0x90,0xc0,0x5e,0x57,0xb4,0x08,0xa4,0x9e} +}; +static const IID IID_IObjectArray = { + 0x92ca9dcd, 0x5622, 0x4bba, {0xa8,0x05,0x5e,0x9f,0x54,0x1b,0xd8,0xc9} +}; +static const IID IID_IPropertyStore = { + 0x886d8eeb, 0x8cf2, 0x4446, {0x8d,0x02,0xcd,0xba,0x1d,0xbd,0xcf,0x99} +}; +static const PROPERTYKEY PKEY_Title = { + {0xf29f85e0, 0x4ff9, 0x1068, {0xab,0x91,0x08,0x00,0x2b,0x27,0xb3,0xd9}}, + 0x00000002 +}; + +/* Type-checking macro to provide arguments for CoCreateInstance() etc. + * The pointer arithmetic is a compile-time pointer type check that 'obj' + * really is a 'type **', but is intended to have no effect at runtime. */ +#define COMPTR(type, obj) &IID_##type, \ + (void **)(void *)((obj) + (sizeof((obj)-(type **)(obj))) \ + - (sizeof((obj)-(type **)(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; + void *psettings_tmp; + 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) { + psettings_tmp = open_settings_r(sessionname); + if (!psettings_tmp) + return NULL; + close_settings_r(psettings_tmp); + } + + /* Create the new item. */ + if (!SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER, + COMPTR(IShellLink, &ret)))) + return NULL; + + /* Set path, parameters, icon and description. */ + ret->lpVtbl->SetPath(ret, app_path); + + if (sessionname) { + param_string = dupcat("@", sessionname, NULL); + } else { + param_string = dupstr(""); + } + ret->lpVtbl->SetArguments(ret, param_string); + sfree(param_string); + + if (sessionname) { + desc_string = dupcat("Connect to PuTTY session '", + sessionname, "'", NULL); + } else { + assert(appname); + desc_string = dupprintf("Run %.*s", 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", 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; + int 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; + int 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 ((osVersion.dwMajorVersion < 6) || + (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion < 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 ((osVersion.dwMajorVersion < 6) || + (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion < 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(); + } +} diff --git a/putty/WINDOWS/WINMISC.C b/putty/WINDOWS/WINMISC.C new file mode 100644 index 0000000..e70e77e --- /dev/null +++ b/putty/WINDOWS/WINMISC.C @@ -0,0 +1,381 @@ +/* + * winmisc.c: miscellaneous Windows-specific things + */ + +#include +#include +#include "putty.h" +#include + +OSVERSIONINFO osVersion; + +char *platform_get_x_display(void) { + /* We may as well check for DISPLAY in case it's useful. */ + return dupstr(getenv("DISPLAY")); +} + +Filename filename_from_str(const char *str) +{ + Filename ret; + strncpy(ret.path, str, sizeof(ret.path)); + ret.path[sizeof(ret.path)-1] = '\0'; + return ret; +} + +const char *filename_to_str(const Filename *fn) +{ + return fn->path; +} + +int filename_equal(Filename f1, Filename f2) +{ + return !strcmp(f1.path, f2.path); +} + +int filename_is_null(Filename fn) +{ + return !*fn.path; +} + +char *get_username(void) +{ + DWORD namelen; + char *user; + int got_username = FALSE; + DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA, + (EXTENDED_NAME_FORMAT, LPSTR, PULONG)); + + { + static int tried_usernameex = FALSE; + if (!tried_usernameex) { + /* Not available on Win9x, so load dynamically */ + HMODULE secur32 = load_system32_dll("secur32.dll"); + GET_WINDOWS_FUNCTION(secur32, GetUserNameExA); + tried_usernameex = TRUE; + } + } + + if (p_GetUserNameExA) { + /* + * If available, use the principal -- this avoids the problem + * that the local username is case-insensitive but Kerberos + * usernames are case-sensitive. + */ + + /* Get the length */ + namelen = 0; + (void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen); + + user = snewn(namelen, char); + got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen); + if (got_username) { + char *p = strchr(user, '@'); + if (p) *p = 0; + } else { + sfree(user); + } + } + + if (!got_username) { + /* Fall back to local user name */ + namelen = 0; + if (GetUserName(NULL, &namelen) == FALSE) { + /* + * Apparently this doesn't work at least on Windows XP SP2. + * Thus assume a maximum of 256. It will fail again if it + * doesn't fit. + */ + namelen = 256; + } + + user = snewn(namelen, char); + got_username = GetUserName(user, &namelen); + if (!got_username) { + sfree(user); + } + } + + return got_username ? user : NULL; +} + +BOOL init_winver(void) +{ + ZeroMemory(&osVersion, sizeof(osVersion)); + osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + return GetVersionEx ( (OSVERSIONINFO *) &osVersion); +} + +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; + char *fullpath; + HMODULE ret; + + if (!sysdir) { + int size = 0, len; + do { + size = 3*size/2 + 512; + sysdir = sresize(sysdir, size, char); + len = GetSystemDirectory(sysdir, size); + } while (len >= size); + } + + fullpath = dupcat(sysdir, "\\", libname, NULL); + ret = LoadLibrary(fullpath); + sfree(fullpath); + return ret; +} + +#ifdef DEBUG +static FILE *debug_fp = NULL; +static HANDLE debug_hdl = INVALID_HANDLE_VALUE; +static int debug_got_console = 0; + +void dputs(char *buf) +{ + DWORD dw; + + if (!debug_got_console) { + if (AllocConsole()) { + debug_got_console = 1; + debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE); + } + } + if (!debug_fp) { + debug_fp = fopen("debug.log", "w"); + } + + if (debug_hdl != INVALID_HANDLE_VALUE) { + WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL); + } + fputs(buf, debug_fp); + fflush(debug_fp); +} +#endif + +#ifdef MINEFIELD +/* + * Minefield - a Windows equivalent for Electric Fence + */ + +#define PAGESIZE 4096 + +/* + * Design: + * + * We start by reserving as much virtual address space as Windows + * will sensibly (or not sensibly) let us have. We flag it all as + * invalid memory. + * + * Any allocation attempt is satisfied by committing one or more + * pages, with an uncommitted page on either side. The returned + * memory region is jammed up against the _end_ of the pages. + * + * Freeing anything causes instantaneous decommitment of the pages + * involved, so stale pointers are caught as soon as possible. + */ + +static int minefield_initialised = 0; +static void *minefield_region = NULL; +static long minefield_size = 0; +static long minefield_npages = 0; +static long minefield_curpos = 0; +static unsigned short *minefield_admin = NULL; +static void *minefield_pages = NULL; + +static void minefield_admin_hide(int hide) +{ + int access = hide ? PAGE_NOACCESS : PAGE_READWRITE; + VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL); +} + +static void minefield_init(void) +{ + int size; + int admin_size; + int i; + + for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) { + minefield_region = VirtualAlloc(NULL, size, + MEM_RESERVE, PAGE_NOACCESS); + if (minefield_region) + break; + } + minefield_size = size; + + /* + * Firstly, allocate a section of that to be the admin block. + * We'll need a two-byte field for each page. + */ + minefield_admin = minefield_region; + minefield_npages = minefield_size / PAGESIZE; + admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1); + minefield_npages = (minefield_size - admin_size) / PAGESIZE; + minefield_pages = (char *) minefield_region + admin_size; + + /* + * Commit the admin region. + */ + VirtualAlloc(minefield_admin, minefield_npages * 2, + MEM_COMMIT, PAGE_READWRITE); + + /* + * Mark all pages as unused (0xFFFF). + */ + for (i = 0; i < minefield_npages; i++) + minefield_admin[i] = 0xFFFF; + + /* + * Hide the admin region. + */ + minefield_admin_hide(1); + + minefield_initialised = 1; +} + +static void minefield_bomb(void) +{ + div(1, *(int *) minefield_pages); +} + +static void *minefield_alloc(int size) +{ + int npages; + int pos, lim, region_end, region_start; + int start; + int i; + + npages = (size + PAGESIZE - 1) / PAGESIZE; + + minefield_admin_hide(0); + + /* + * Search from current position until we find a contiguous + * bunch of npages+2 unused pages. + */ + pos = minefield_curpos; + lim = minefield_npages; + while (1) { + /* Skip over used pages. */ + while (pos < lim && minefield_admin[pos] != 0xFFFF) + pos++; + /* Count unused pages. */ + start = pos; + while (pos < lim && pos - start < npages + 2 && + minefield_admin[pos] == 0xFFFF) + pos++; + if (pos - start == npages + 2) + break; + /* If we've reached the limit, reset the limit or stop. */ + if (pos >= lim) { + if (lim == minefield_npages) { + /* go round and start again at zero */ + lim = minefield_curpos; + pos = 0; + } else { + minefield_admin_hide(1); + return NULL; + } + } + } + + minefield_curpos = pos - 1; + + /* + * We have npages+2 unused pages starting at start. We leave + * the first and last of these alone and use the rest. + */ + region_end = (start + npages + 1) * PAGESIZE; + region_start = region_end - size; + /* FIXME: could align here if we wanted */ + + /* + * Update the admin region. + */ + for (i = start + 2; i < start + npages + 1; i++) + minefield_admin[i] = 0xFFFE; /* used but no region starts here */ + minefield_admin[start + 1] = region_start % PAGESIZE; + + minefield_admin_hide(1); + + VirtualAlloc((char *) minefield_pages + region_start, size, + MEM_COMMIT, PAGE_READWRITE); + return (char *) minefield_pages + region_start; +} + +static void minefield_free(void *ptr) +{ + int region_start, i, j; + + minefield_admin_hide(0); + + region_start = (char *) ptr - (char *) minefield_pages; + i = region_start / PAGESIZE; + if (i < 0 || i >= minefield_npages || + minefield_admin[i] != region_start % PAGESIZE) + minefield_bomb(); + for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) { + minefield_admin[j] = 0xFFFF; + } + + VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT); + + minefield_admin_hide(1); +} + +static int minefield_get_size(void *ptr) +{ + int region_start, i, j; + + minefield_admin_hide(0); + + region_start = (char *) ptr - (char *) minefield_pages; + i = region_start / PAGESIZE; + if (i < 0 || i >= minefield_npages || + minefield_admin[i] != region_start % PAGESIZE) + minefield_bomb(); + for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++); + + minefield_admin_hide(1); + + return j * PAGESIZE - region_start; +} + +void *minefield_c_malloc(size_t size) +{ + if (!minefield_initialised) + minefield_init(); + return minefield_alloc(size); +} + +void minefield_c_free(void *p) +{ + if (!minefield_initialised) + minefield_init(); + minefield_free(p); +} + +/* + * realloc _always_ moves the chunk, for rapid detection of code + * that assumes it won't. + */ +void *minefield_c_realloc(void *p, size_t size) +{ + size_t oldsize; + void *q; + if (!minefield_initialised) + minefield_init(); + q = minefield_alloc(size); + oldsize = minefield_get_size(p); + memcpy(q, p, (oldsize < size ? oldsize : size)); + minefield_free(p); + return q; +} + +#endif /* MINEFIELD */ diff --git a/putty/WINDOWS/WINNET.C b/putty/WINDOWS/WINNET.C new file mode 100644 index 0000000..c5445c5 --- /dev/null +++ b/putty/WINDOWS/WINNET.C @@ -0,0 +1,1706 @@ +/* + * Windows networking abstraction. + * + * For the IPv6 code in here I am indebted to Jeroen Massar and + * unfix.org. + */ + +#include +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "network.h" +#include "tree234.h" + +#include + +#ifndef NO_IPV6 +const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; +const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; +#endif + +#define ipv4_is_loopback(addr) \ + ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L) + +/* + * We used to typedef struct Socket_tag *Socket. + * + * Since we have made the networking abstraction slightly more + * abstract, Socket no longer means a tcp socket (it could mean + * an ssl socket). So now we must use Actual_Socket when we know + * we are talking about a tcp socket. + */ +typedef struct Socket_tag *Actual_Socket; + +/* + * Mutable state that goes with a SockAddr: stores information + * about where in the list of candidate IP(v*) addresses we've + * currently got to. + */ +typedef struct SockAddrStep_tag SockAddrStep; +struct SockAddrStep_tag { +#ifndef NO_IPV6 + struct addrinfo *ai; /* steps along addr->ais */ +#endif + int curraddr; +}; + +struct Socket_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + char *error; + SOCKET s; + Plug plug; + void *private_ptr; + bufchain output_data; + int connected; + int writable; + int frozen; /* this causes readability notifications to be ignored */ + int frozen_readable; /* this means we missed at least one readability + * notification while we were frozen */ + int localhost_only; /* for listening sockets */ + char oobdata[1]; + int sending_oob; + int oobinline, nodelay, keepalive, privport; + SockAddr addr; + SockAddrStep step; + int port; + int pending_error; /* in case send() returns error */ + /* + * We sometimes need pairs of Socket structures to be linked: + * if we are listening on the same IPv6 and v4 port, for + * example. So here we define `parent' and `child' pointers to + * track this link. + */ + Actual_Socket parent, child; +}; + +struct SockAddr_tag { + int refcount; + char *error; + int resolved; +#ifndef NO_IPV6 + struct addrinfo *ais; /* Addresses IPv6 style. */ +#endif + unsigned long *addresses; /* Addresses IPv4 style. */ + int naddresses; + char hostname[512]; /* Store an unresolved host name. */ +}; + +/* + * Which address family this address belongs to. AF_INET for IPv4; + * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has + * not been done and a simple host name is held in this SockAddr + * structure. + */ +#ifndef NO_IPV6 +#define SOCKADDR_FAMILY(addr, step) \ + (!(addr)->resolved ? AF_UNSPEC : \ + (step).ai ? (step).ai->ai_family : AF_INET) +#else +#define SOCKADDR_FAMILY(addr, step) \ + (!(addr)->resolved ? AF_UNSPEC : AF_INET) +#endif + +/* + * Start a SockAddrStep structure to step through multiple + * addresses. + */ +#ifndef NO_IPV6 +#define START_STEP(addr, step) \ + ((step).ai = (addr)->ais, (step).curraddr = 0) +#else +#define START_STEP(addr, step) \ + ((step).curraddr = 0) +#endif + +static tree234 *sktree; + +static int cmpfortree(void *av, void *bv) +{ + Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv; + unsigned long as = (unsigned long) a->s, bs = (unsigned long) b->s; + if (as < bs) + return -1; + if (as > bs) + return +1; + if (a < b) + return -1; + if (a > b) + return +1; + return 0; +} + +static int cmpforsearch(void *av, void *bv) +{ + Actual_Socket b = (Actual_Socket) bv; + unsigned long as = (unsigned long) av, bs = (unsigned long) 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, 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, ioctlsocket, + (SOCKET, long, u_long FAR *)); +DECL_WINDOWS_FUNCTION(static, SOCKET, accept, + (SOCKET, struct sockaddr FAR *, int FAR *)); +DECL_WINDOWS_FUNCTION(static, int, recv, (SOCKET, char FAR *, int, int)); +DECL_WINDOWS_FUNCTION(static, int, WSAIoctl, + (SOCKET, DWORD, LPVOID, DWORD, LPVOID, DWORD, + LPDWORD, LPWSAOVERLAPPED, + LPWSAOVERLAPPED_COMPLETION_ROUTINE)); +#ifndef NO_IPV6 +DECL_WINDOWS_FUNCTION(static, int, getaddrinfo, + (const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res)); +DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res)); +DECL_WINDOWS_FUNCTION(static, int, getnameinfo, + (const struct sockaddr FAR * sa, socklen_t salen, + char FAR * host, size_t hostlen, char FAR * serv, + size_t servlen, int flags)); +DECL_WINDOWS_FUNCTION(static, char *, gai_strerror, (int ecode)); +DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA, + (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO, + LPSTR, LPDWORD)); +#endif + +static HMODULE winsock_module = NULL; +static WSADATA wsadata; +#ifndef NO_IPV6 +static HMODULE winsock2_module = NULL; +static HMODULE wship6_module = NULL; +#endif + +int sk_startup(int hi, int lo) +{ + WORD winsock_ver; + + winsock_ver = MAKEWORD(hi, lo); + + if (p_WSAStartup(winsock_ver, &wsadata)) { + return FALSE; + } + + if (LOBYTE(wsadata.wVersion) != LOBYTE(winsock_ver)) { + return FALSE; + } + +#ifdef NET_SETUP_DIAGNOSTICS + { + char buf[80]; + sprintf(buf, "Using WinSock %d.%d", hi, lo); + logevent(NULL, buf); + } +#endif + return TRUE; +} + +void sk_init(void) +{ +#ifndef NO_IPV6 + winsock2_module = +#endif + winsock_module = load_system32_dll("ws2_32.dll"); + if (!winsock_module) { + winsock_module = load_system32_dll("wsock32.dll"); + } + if (!winsock_module) + fatalbox("Unable to load any WinSock library"); + +#ifndef NO_IPV6 + /* Check if we have getaddrinfo in Winsock */ + if (GetProcAddress(winsock_module, "getaddrinfo") != NULL) { +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "Native WinSock IPv6 support detected"); +#endif + GET_WINDOWS_FUNCTION(winsock_module, getaddrinfo); + GET_WINDOWS_FUNCTION(winsock_module, freeaddrinfo); + GET_WINDOWS_FUNCTION(winsock_module, getnameinfo); + GET_WINDOWS_FUNCTION(winsock_module, gai_strerror); + } else { + /* Fall back to wship6.dll for Windows 2000 */ + wship6_module = load_system32_dll("wship6.dll"); + if (wship6_module) { +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "WSH IPv6 support detected"); +#endif + GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo); + GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo); + GET_WINDOWS_FUNCTION(wship6_module, getnameinfo); + GET_WINDOWS_FUNCTION(wship6_module, gai_strerror); + } else { +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "No IPv6 support detected"); +#endif + } + } + GET_WINDOWS_FUNCTION(winsock2_module, WSAAddressToStringA); +#else +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "PuTTY was built without IPv6 support"); +#endif +#endif + + GET_WINDOWS_FUNCTION(winsock_module, WSAAsyncSelect); + GET_WINDOWS_FUNCTION(winsock_module, WSAEventSelect); + GET_WINDOWS_FUNCTION(winsock_module, select); + GET_WINDOWS_FUNCTION(winsock_module, WSAGetLastError); + GET_WINDOWS_FUNCTION(winsock_module, WSAEnumNetworkEvents); + GET_WINDOWS_FUNCTION(winsock_module, WSAStartup); + GET_WINDOWS_FUNCTION(winsock_module, WSACleanup); + GET_WINDOWS_FUNCTION(winsock_module, closesocket); + GET_WINDOWS_FUNCTION(winsock_module, ntohl); + GET_WINDOWS_FUNCTION(winsock_module, htonl); + GET_WINDOWS_FUNCTION(winsock_module, htons); + GET_WINDOWS_FUNCTION(winsock_module, ntohs); + GET_WINDOWS_FUNCTION(winsock_module, gethostname); + GET_WINDOWS_FUNCTION(winsock_module, gethostbyname); + GET_WINDOWS_FUNCTION(winsock_module, getservbyname); + GET_WINDOWS_FUNCTION(winsock_module, inet_addr); + GET_WINDOWS_FUNCTION(winsock_module, inet_ntoa); + GET_WINDOWS_FUNCTION(winsock_module, 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, ioctlsocket); + GET_WINDOWS_FUNCTION(winsock_module, accept); + GET_WINDOWS_FUNCTION(winsock_module, recv); + GET_WINDOWS_FUNCTION(winsock_module, WSAIoctl); + + /* Try to get the best WinSock version we can get */ + if (!sk_startup(2,2) && + !sk_startup(2,0) && + !sk_startup(1,1)) { + fatalbox("Unable to initialise WinSock"); + } + + sktree = newtree234(cmpfortree); +} + +void sk_cleanup(void) +{ + Actual_Socket s; + int i; + + if (sktree) { + for (i = 0; (s = index234(sktree, i)) != NULL; i++) { + p_closesocket(s->s); + } + freetree234(sktree); + sktree = NULL; + } + + if (p_WSACleanup) + p_WSACleanup(); + if (winsock_module) + FreeLibrary(winsock_module); +#ifndef NO_IPV6 + if (wship6_module) + FreeLibrary(wship6_module); +#endif +} + +char *winsock_error_string(int error) +{ + 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"; + default: + return "Unknown network error"; + } +} + +SockAddr sk_namelookup(const char *host, char **canonicalname, + int address_family) +{ + SockAddr ret = snew(struct SockAddr_tag); + unsigned long a; + char realhost[8192]; + int hint_family; + + /* Default to IPv4. */ + hint_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : +#ifndef NO_IPV6 + address_family == ADDRTYPE_IPV6 ? AF_INET6 : +#endif + AF_UNSPEC); + + /* Clear the structure and default to IPv4. */ + memset(ret, 0, sizeof(struct SockAddr_tag)); +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->addresses = NULL; + ret->resolved = FALSE; + ret->refcount = 1; + *realhost = '\0'; + + if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) { + struct hostent *h = NULL; + int err; +#ifndef NO_IPV6 + /* + * Use getaddrinfo when it's available + */ + if (p_getaddrinfo) { + struct addrinfo hints; +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "Using getaddrinfo() for resolving"); +#endif + memset(&hints, 0, sizeof(hints)); + hints.ai_family = hint_family; + hints.ai_flags = AI_CANONNAME; + if ((err = p_getaddrinfo(host, NULL, &hints, &ret->ais)) == 0) + ret->resolved = TRUE; + } else +#endif + { +#ifdef NET_SETUP_DIAGNOSTICS + logevent(NULL, "Using gethostbyname() for resolving"); +#endif + /* + * Otherwise use the IPv4-only gethostbyname... + * (NOTE: we don't use gethostbyname as a fallback!) + */ + if ( (h = p_gethostbyname(host)) ) + ret->resolved = TRUE; + else + err = p_WSAGetLastError(); + } + + if (!ret->resolved) { + ret->error = (err == WSAENETDOWN ? "Network is down" : + err == WSAHOST_NOT_FOUND ? "Host does not exist" : + err == WSATRY_AGAIN ? "Host not found" : +#ifndef NO_IPV6 + p_getaddrinfo&&p_gai_strerror ? p_gai_strerror(err) : +#endif + "gethostbyname: unknown error"); + } else { + ret->error = NULL; + +#ifndef NO_IPV6 + /* If we got an address info use that... */ + if (ret->ais) { + /* Are we in IPv4 fallback mode? */ + /* We put the IPv4 address into the a variable so we can further-on use the IPv4 code... */ + if (ret->ais->ai_family == AF_INET) + memcpy(&a, + (char *) &((SOCKADDR_IN *) ret->ais-> + ai_addr)->sin_addr, sizeof(a)); + + if (ret->ais->ai_canonname) + strncpy(realhost, ret->ais->ai_canonname, lenof(realhost)); + else + strncpy(realhost, host, lenof(realhost)); + } + /* We used the IPv4-only gethostbyname()... */ + else +#endif + { + int n; + for (n = 0; h->h_addr_list[n]; n++); + ret->addresses = snewn(n, unsigned long); + ret->naddresses = n; + for (n = 0; n < ret->naddresses; n++) { + memcpy(&a, h->h_addr_list[n], sizeof(a)); + ret->addresses[n] = p_ntohl(a); + } + memcpy(&a, h->h_addr, sizeof(a)); + /* This way we are always sure the h->h_name is valid :) */ + strncpy(realhost, h->h_name, sizeof(realhost)); + } + } + } else { + /* + * This must be a numeric IPv4 address because it caused a + * success return from inet_addr. + */ + ret->addresses = snewn(1, unsigned long); + ret->naddresses = 1; + ret->addresses[0] = p_ntohl(a); + ret->resolved = TRUE; + strncpy(realhost, host, sizeof(realhost)); + } + realhost[lenof(realhost)-1] = '\0'; + *canonicalname = snewn(1+strlen(realhost), char); + strcpy(*canonicalname, realhost); + return ret; +} + +SockAddr sk_nonamelookup(const char *host) +{ + SockAddr ret = snew(struct SockAddr_tag); + ret->error = NULL; + ret->resolved = FALSE; +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->addresses = NULL; + ret->naddresses = 0; + ret->refcount = 1; + strncpy(ret->hostname, host, lenof(ret->hostname)); + ret->hostname[lenof(ret->hostname)-1] = '\0'; + return ret; +} + +int sk_nextaddr(SockAddr addr, SockAddrStep *step) +{ +#ifndef NO_IPV6 + if (step->ai) { + if (step->ai->ai_next) { + step->ai = step->ai->ai_next; + return TRUE; + } else + return FALSE; + } +#endif + if (step->curraddr+1 < addr->naddresses) { + step->curraddr++; + return TRUE; + } else { + return FALSE; + } +} + +void sk_getaddr(SockAddr addr, char *buf, int buflen) +{ + SockAddrStep step; + START_STEP(addr, step); + +#ifndef NO_IPV6 + if (step.ai) { + int err = 0; + if (p_WSAAddressToStringA) { + DWORD dwbuflen = buflen; + err = p_WSAAddressToStringA(step.ai->ai_addr, step.ai->ai_addrlen, + NULL, buf, &dwbuflen); + } else + err = -1; + if (err) { + strncpy(buf, addr->hostname, buflen); + if (!buf[0]) + strncpy(buf, "", buflen); + buf[buflen-1] = '\0'; + } + } else +#endif + if (SOCKADDR_FAMILY(addr, step) == AF_INET) { + struct in_addr a; + assert(addr->addresses && step.curraddr < addr->naddresses); + a.s_addr = p_htonl(addr->addresses[step.curraddr]); + strncpy(buf, p_inet_ntoa(a), buflen); + buf[buflen-1] = '\0'; + } else { + strncpy(buf, addr->hostname, buflen); + buf[buflen-1] = '\0'; + } +} + +int sk_hostname_is_local(char *name) +{ + return !strcmp(name, "localhost") || + !strcmp(name, "::1") || + !strncmp(name, "127.", 4); +} + +static INTERFACE_INFO local_interfaces[16]; +static int n_local_interfaces; /* 0=not yet, -1=failed, >0=number */ + +static int ipv4_is_local_addr(struct in_addr addr) +{ + if (ipv4_is_loopback(addr)) + return 1; /* loopback addresses are local */ + if (!n_local_interfaces) { + SOCKET s = p_socket(AF_INET, SOCK_DGRAM, 0); + DWORD retbytes; + + if (p_WSAIoctl && + p_WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0, + local_interfaces, sizeof(local_interfaces), + &retbytes, NULL, NULL) == 0) + n_local_interfaces = retbytes / sizeof(INTERFACE_INFO); + else + logevent(NULL, "Unable to get list of local IP addresses"); + } + if (n_local_interfaces > 0) { + int i; + for (i = 0; i < n_local_interfaces; i++) { + SOCKADDR_IN *address = + (SOCKADDR_IN *)&local_interfaces[i].iiAddress; + if (address->sin_addr.s_addr == addr.s_addr) + return 1; /* this address is local */ + } + } + return 0; /* this address is not local */ +} + +int sk_address_is_local(SockAddr addr) +{ + SockAddrStep step; + int family; + START_STEP(addr, step); + family = SOCKADDR_FAMILY(addr, step); + +#ifndef NO_IPV6 + if (family == AF_INET6) { + return IN6_IS_ADDR_LOOPBACK((const struct in6_addr *)step.ai->ai_addr); + } else +#endif + if (family == AF_INET) { +#ifndef NO_IPV6 + if (step.ai) { + return ipv4_is_local_addr(((struct sockaddr_in *)step.ai->ai_addr) + ->sin_addr); + } else +#endif + { + struct in_addr a; + assert(addr->addresses && step.curraddr < addr->naddresses); + a.s_addr = p_htonl(addr->addresses[step.curraddr]); + return ipv4_is_local_addr(a); + } + } else { + assert(family == AF_UNSPEC); + return 0; /* we don't know; assume not */ + } +} + +int sk_addrtype(SockAddr addr) +{ + SockAddrStep step; + int family; + START_STEP(addr, step); + family = SOCKADDR_FAMILY(addr, step); + + return (family == AF_INET ? ADDRTYPE_IPV4 : +#ifndef NO_IPV6 + family == AF_INET6 ? ADDRTYPE_IPV6 : +#endif + ADDRTYPE_NAME); +} + +void sk_addrcopy(SockAddr addr, char *buf) +{ + SockAddrStep step; + int family; + START_STEP(addr, step); + family = SOCKADDR_FAMILY(addr, step); + + assert(family != AF_UNSPEC); +#ifndef NO_IPV6 + if (step.ai) { + if (family == AF_INET) + memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr, + sizeof(struct in_addr)); + else if (family == AF_INET6) + memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr, + sizeof(struct in6_addr)); + else + assert(FALSE); + } else +#endif + if (family == AF_INET) { + struct in_addr a; + assert(addr->addresses && step.curraddr < addr->naddresses); + a.s_addr = p_htonl(addr->addresses[step.curraddr]); + memcpy(buf, (char*) &a.s_addr, 4); + } +} + +void sk_addr_free(SockAddr addr) +{ + if (--addr->refcount > 0) + return; +#ifndef NO_IPV6 + if (addr->ais && p_freeaddrinfo) + p_freeaddrinfo(addr->ais); +#endif + if (addr->addresses) + sfree(addr->addresses); + sfree(addr); +} + +SockAddr sk_addr_dup(SockAddr addr) +{ + addr->refcount++; + return addr; +} + +static Plug sk_tcp_plug(Socket sock, Plug p) +{ + Actual_Socket s = (Actual_Socket) sock; + Plug ret = s->plug; + if (p) + s->plug = p; + return ret; +} + +static void sk_tcp_flush(Socket s) +{ + /* + * We send data to the socket as soon as we can anyway, + * so we don't need to do anything here. :-) + */ +} + +static void sk_tcp_close(Socket s); +static int sk_tcp_write(Socket s, const char *data, int len); +static int sk_tcp_write_oob(Socket s, const char *data, int len); +static void sk_tcp_set_private_ptr(Socket s, void *ptr); +static void *sk_tcp_get_private_ptr(Socket s); +static void sk_tcp_set_frozen(Socket s, int is_frozen); +static const char *sk_tcp_socket_error(Socket s); + +extern char *do_select(SOCKET skt, int startup); + +Socket sk_register(void *sock, Plug plug) +{ + static const struct socket_function_table fn_table = { + sk_tcp_plug, + sk_tcp_close, + sk_tcp_write, + sk_tcp_write_oob, + sk_tcp_flush, + sk_tcp_set_private_ptr, + sk_tcp_get_private_ptr, + sk_tcp_set_frozen, + sk_tcp_socket_error + }; + + DWORD err; + char *errstr; + Actual_Socket ret; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = 1; /* to start with */ + ret->sending_oob = 0; + ret->frozen = 1; + ret->frozen_readable = 0; + ret->localhost_only = 0; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->addr = NULL; + + ret->s = (SOCKET)sock; + + if (ret->s == INVALID_SOCKET) { + err = p_WSAGetLastError(); + ret->error = winsock_error_string(err); + return (Socket) ret; + } + + ret->oobinline = 0; + + /* Set up a select mechanism. This could be an AsyncSelect on a + * window, or an EventSelect on an event object. */ + errstr = do_select(ret->s, 1); + if (errstr) { + ret->error = errstr; + return (Socket) ret; + } + + add234(sktree, ret); + + return (Socket) ret; +} + +static DWORD try_connect(Actual_Socket sock) +{ + SOCKET s; +#ifndef NO_IPV6 + SOCKADDR_IN6 a6; +#endif + SOCKADDR_IN a; + DWORD err; + char *errstr; + short localport; + int family; + + if (sock->s != INVALID_SOCKET) { + do_select(sock->s, 0); + p_closesocket(sock->s); + } + + plug_log(sock->plug, 0, sock->addr, sock->port, NULL, 0); + + /* + * Open socket. + */ + family = SOCKADDR_FAMILY(sock->addr, sock->step); + + /* + * Remove the socket from the tree before we overwrite its + * internal socket id, because that forms part of the tree's + * sorting criterion. We'll add it back before exiting this + * function, whether we changed anything or not. + */ + del234(sktree, sock); + + s = p_socket(family, SOCK_STREAM, 0); + sock->s = s; + + if (s == INVALID_SOCKET) { + err = p_WSAGetLastError(); + sock->error = winsock_error_string(err); + goto ret; + } + + if (sock->oobinline) { + BOOL b = TRUE; + p_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b)); + } + + if (sock->nodelay) { + BOOL b = TRUE; + p_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b)); + } + + if (sock->keepalive) { + BOOL b = TRUE; + p_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b)); + } + + /* + * 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, 1); + 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 = 1; + } + + err = 0; + + ret: + + /* + * No matter what happened, put the socket back in the tree. + */ + add234(sktree, sock); + + if (err) + plug_log(sock->plug, 1, sock->addr, sock->port, sock->error, err); + return err; +} + +Socket sk_new(SockAddr addr, int port, int privport, int oobinline, + int nodelay, int keepalive, Plug plug) +{ + static const struct socket_function_table fn_table = { + sk_tcp_plug, + sk_tcp_close, + sk_tcp_write, + sk_tcp_write_oob, + sk_tcp_flush, + sk_tcp_set_private_ptr, + sk_tcp_get_private_ptr, + sk_tcp_set_frozen, + sk_tcp_socket_error + }; + + Actual_Socket ret; + DWORD err; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->connected = 0; /* to start with */ + ret->writable = 0; /* to start with */ + ret->sending_oob = 0; + ret->frozen = 0; + ret->frozen_readable = 0; + ret->localhost_only = 0; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobinline = oobinline; + ret->nodelay = nodelay; + ret->keepalive = keepalive; + ret->privport = privport; + ret->port = port; + ret->addr = addr; + START_STEP(ret->addr, ret->step); + ret->s = INVALID_SOCKET; + + err = 0; + do { + err = try_connect(ret); + } while (err && sk_nextaddr(ret->addr, &ret->step)); + + return (Socket) ret; +} + +Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, + int orig_address_family) +{ + static const struct socket_function_table fn_table = { + sk_tcp_plug, + sk_tcp_close, + sk_tcp_write, + sk_tcp_write_oob, + sk_tcp_flush, + sk_tcp_set_private_ptr, + sk_tcp_get_private_ptr, + sk_tcp_set_frozen, + sk_tcp_socket_error + }; + + SOCKET s; +#ifndef NO_IPV6 + SOCKADDR_IN6 a6; +#endif + SOCKADDR_IN a; + + DWORD err; + char *errstr; + Actual_Socket ret; + int retcode; + int on = 1; + + int address_family; + + /* + * Create Socket structure. + */ + ret = snew(struct Socket_tag); + ret->fn = &fn_table; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = 0; /* to start with */ + ret->sending_oob = 0; + ret->frozen = 0; + ret->frozen_readable = 0; + ret->localhost_only = local_host_only; + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->addr = NULL; + + /* + * Translate address_family from platform-independent constants + * into local reality. + */ + address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET : +#ifndef NO_IPV6 + orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 : +#endif + AF_UNSPEC); + + /* + * Our default, if passed the `don't care' value + * ADDRTYPE_UNSPEC, is to listen on IPv4. If IPv6 is supported, + * we will also set up a second socket listening on IPv6, but + * the v4 one is primary since that ought to work even on + * non-v6-supporting systems. + */ + if (address_family == AF_UNSPEC) address_family = AF_INET; + + /* + * Open socket. + */ + s = p_socket(address_family, SOCK_STREAM, 0); + ret->s = s; + + if (s == INVALID_SOCKET) { + err = p_WSAGetLastError(); + ret->error = winsock_error_string(err); + return (Socket) ret; + } + + ret->oobinline = 0; + + p_setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)); + +#ifndef NO_IPV6 + if (address_family == AF_INET6) { + memset(&a6, 0, sizeof(a6)); + a6.sin6_family = AF_INET6; + /* FIXME: srcaddr is ignored for IPv6, because I (SGT) don't + * know how to do it. :-) + * (jeroen:) saddr is specified as an address.. eg 2001:db8::1 + * Thus we need either a parser that understands [2001:db8::1]:80 + * style addresses and/or enhance this to understand hostnames too. */ + if (local_host_only) + a6.sin6_addr = in6addr_loopback; + else + a6.sin6_addr = in6addr_any; + a6.sin6_port = p_htons(port); + } else +#endif + { + int got_addr = 0; + a.sin_family = AF_INET; + + /* + * Bind to source address. First try an explicitly + * specified one... + */ + if (srcaddr) { + a.sin_addr.s_addr = p_inet_addr(srcaddr); + if (a.sin_addr.s_addr != INADDR_NONE) { + /* Override localhost_only with specified listen addr. */ + ret->localhost_only = ipv4_is_loopback(a.sin_addr); + got_addr = 1; + } + } + + /* + * ... and failing that, go with one of the standard ones. + */ + if (!got_addr) { + if (local_host_only) + a.sin_addr.s_addr = p_htonl(INADDR_LOOPBACK); + else + a.sin_addr.s_addr = p_htonl(INADDR_ANY); + } + + a.sin_port = p_htons((short)port); + } +#ifndef NO_IPV6 + retcode = p_bind(s, (address_family == AF_INET6 ? + (struct sockaddr *) &a6 : + (struct sockaddr *) &a), + (address_family == + AF_INET6 ? sizeof(a6) : sizeof(a))); +#else + retcode = p_bind(s, (struct sockaddr *) &a, sizeof(a)); +#endif + if (retcode != SOCKET_ERROR) { + err = 0; + } else { + err = p_WSAGetLastError(); + } + + if (err) { + p_closesocket(s); + ret->error = winsock_error_string(err); + return (Socket) ret; + } + + + if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) { + p_closesocket(s); + ret->error = winsock_error_string(err); + return (Socket) ret; + } + + /* Set up a select mechanism. This could be an AsyncSelect on a + * window, or an EventSelect on an event object. */ + errstr = do_select(s, 1); + if (errstr) { + p_closesocket(s); + ret->error = errstr; + return (Socket) ret; + } + + add234(sktree, ret); + +#ifndef NO_IPV6 + /* + * If we were given ADDRTYPE_UNSPEC, we must also create an + * IPv6 listening socket and link it to this one. + */ + if (address_family == AF_INET && orig_address_family == ADDRTYPE_UNSPEC) { + Actual_Socket other; + + other = (Actual_Socket) sk_newlistener(srcaddr, port, plug, + local_host_only, ADDRTYPE_IPV6); + + if (other) { + if (!other->error) { + other->parent = ret; + ret->child = other; + } else { + sfree(other); + } + } + } +#endif + + return (Socket) ret; +} + +static void sk_tcp_close(Socket sock) +{ + extern char *do_select(SOCKET skt, int startup); + Actual_Socket s = (Actual_Socket) sock; + + if (s->child) + sk_tcp_close((Socket)s->child); + + del234(sktree, s); + do_select(s->s, 0); + p_closesocket(s->s); + if (s->addr) + sk_addr_free(s->addr); + sfree(s); +} + +/* + * The function which tries to send on a socket once it's deemed + * writable. + */ +void try_send(Actual_Socket s) +{ + while (s->sending_oob || bufchain_size(&s->output_data) > 0) { + int nsent; + DWORD err; + void *data; + int len, urgentflag; + + if (s->sending_oob) { + urgentflag = MSG_OOB; + len = s->sending_oob; + data = &s->oobdata; + } else { + urgentflag = 0; + bufchain_prefix(&s->output_data, &data, &len); + } + nsent = p_send(s->s, data, len, urgentflag); + noise_ultralight(nsent); + if (nsent <= 0) { + err = (nsent < 0 ? p_WSAGetLastError() : 0); + if ((err < WSABASEERR && nsent < 0) || err == WSAEWOULDBLOCK) { + /* + * Perfectly normal: we've sent all we can for the moment. + * + * (Some WinSock send() implementations can return + * <0 but leave no sensible error indication - + * WSAGetLastError() is called but returns zero or + * a small number - so we check that case and treat + * it just like WSAEWOULDBLOCK.) + */ + s->writable = FALSE; + return; + } else if (nsent == 0 || + err == WSAECONNABORTED || err == WSAECONNRESET) { + /* + * If send() returns CONNABORTED or CONNRESET, we + * unfortunately can't just call plug_closing(), + * because it's quite likely that we're currently + * _in_ a call from the code we'd be calling back + * to, so we'd have to make half the SSH code + * reentrant. Instead we flag a pending error on + * the socket, to be dealt with (by calling + * plug_closing()) at some suitable future moment. + */ + s->pending_error = err; + return; + } else { + /* We're inside the Windows frontend here, so we know + * that the frontend handle is unnecessary. */ + logevent(NULL, winsock_error_string(err)); + fatalbox("%s", winsock_error_string(err)); + } + } else { + if (s->sending_oob) { + if (nsent < len) { + memmove(s->oobdata, s->oobdata+nsent, len-nsent); + s->sending_oob = len - nsent; + } else { + s->sending_oob = 0; + } + } else { + bufchain_consume(&s->output_data, nsent); + } + } + } +} + +static int sk_tcp_write(Socket sock, const char *buf, int len) +{ + Actual_Socket s = (Actual_Socket) sock; + + /* + * Add the data to the buffer list on the socket. + */ + bufchain_add(&s->output_data, buf, len); + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); + + return bufchain_size(&s->output_data); +} + +static int sk_tcp_write_oob(Socket sock, const char *buf, int len) +{ + Actual_Socket s = (Actual_Socket) sock; + + /* + * 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; +} + +int select_result(WPARAM wParam, LPARAM lParam) +{ + int ret, open; + DWORD err; + char buf[20480]; /* nice big buffer for plenty of speed */ + Actual_Socket s; + u_long atmark; + + /* wParam is the socket itself */ + + if (wParam == 0) + return 1; /* boggle */ + + s = find234(sktree, (void *) wParam, cmpforsearch); + if (!s) + return 1; /* boggle */ + + if ((err = WSAGETSELECTERROR(lParam)) != 0) { + /* + * An error has occurred on this socket. Pass it to the + * plug. + */ + if (s->addr) { + plug_log(s->plug, 1, s->addr, s->port, + winsock_error_string(err), err); + while (s->addr && sk_nextaddr(s->addr, &s->step)) { + err = try_connect(s); + } + } + if (err != 0) + return plug_closing(s->plug, winsock_error_string(err), err, 0); + else + return 1; + } + + noise_ultralight(lParam); + + switch (WSAGETSELECTEVENT(lParam)) { + case FD_CONNECT: + s->connected = s->writable = 1; + /* + * Once a socket is connected, we can stop falling + * back through the candidate addresses to connect + * to. + */ + if (s->addr) { + sk_addr_free(s->addr); + s->addr = NULL; + } + break; + case FD_READ: + /* In the case the socket is still frozen, we don't even bother */ + if (s->frozen) { + s->frozen_readable = 1; + break; + } + + /* + * We have received data on the socket. For an oobinline + * socket, this might be data _before_ an urgent pointer, + * in which case we send it to the back end with type==1 + * (data prior to urgent). + */ + if (s->oobinline) { + atmark = 1; + p_ioctlsocket(s->s, SIOCATMARK, &atmark); + /* + * Avoid checking the return value from ioctlsocket(), + * on the grounds that some WinSock wrappers don't + * support it. If it does nothing, we get atmark==1, + * which is equivalent to `no OOB pending', so the + * effect will be to non-OOB-ify any OOB data. + */ + } else + atmark = 1; + + ret = p_recv(s->s, buf, sizeof(buf), 0); + noise_ultralight(ret); + if (ret < 0) { + err = p_WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { + break; + } + } + if (ret < 0) { + return plug_closing(s->plug, winsock_error_string(err), err, + 0); + } else if (0 == ret) { + return plug_closing(s->plug, NULL, 0, 0); + } else { + return plug_receive(s->plug, atmark ? 0 : 1, buf, ret); + } + break; + case FD_OOB: + /* + * This will only happen on a non-oobinline socket. It + * indicates that we can immediately perform an OOB read + * and get back OOB data, which we will send to the back + * end with type==2 (urgent data). + */ + ret = p_recv(s->s, buf, sizeof(buf), MSG_OOB); + noise_ultralight(ret); + if (ret <= 0) { + char *str = (ret == 0 ? "Internal networking trouble" : + winsock_error_string(p_WSAGetLastError())); + /* We're inside the Windows frontend here, so we know + * that the frontend handle is unnecessary. */ + logevent(NULL, str); + fatalbox("%s", str); + } else { + return plug_receive(s->plug, 2, buf, ret); + } + break; + case FD_WRITE: + { + int bufsize_before, bufsize_after; + s->writable = 1; + bufsize_before = s->sending_oob + bufchain_size(&s->output_data); + try_send(s); + bufsize_after = s->sending_oob + bufchain_size(&s->output_data); + if (bufsize_after < bufsize_before) + plug_sent(s->plug, bufsize_after); + } + break; + case FD_CLOSE: + /* Signal a close on the socket. First read any outstanding data. */ + open = 1; + do { + ret = p_recv(s->s, buf, sizeof(buf), 0); + if (ret < 0) { + err = p_WSAGetLastError(); + if (err == WSAEWOULDBLOCK) + break; + return plug_closing(s->plug, winsock_error_string(err), + err, 0); + } else { + if (ret) + open &= plug_receive(s->plug, 0, buf, ret); + else + open &= plug_closing(s->plug, NULL, 0, 0); + } + } while (ret > 0); + return open; + case FD_ACCEPT: + { +#ifdef NO_IPV6 + struct sockaddr_in isa; +#else + struct sockaddr_storage isa; +#endif + int addrlen = sizeof(isa); + SOCKET t; /* socket of connection */ + + 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; + } +#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, (void*)t)) { + p_closesocket(t); /* denied or error */ + } + } + } + + return 1; +} + +/* + * Deal with socket errors detected in try_send(). + */ +void net_pending_errors(void) +{ + int i; + Actual_Socket s; + + /* + * This might be a fiddly business, because it's just possible + * that handling a pending error on one socket might cause + * others to be closed. (I can't think of any reason this might + * happen in current SSH implementation, but to maintain + * generality of this network layer I'll assume the worst.) + * + * So what we'll do is search the socket list for _one_ socket + * with a pending error, and then handle it, and then search + * the list again _from the beginning_. Repeat until we make a + * pass with no socket errors present. That way we are + * protected against the socket list changing under our feet. + */ + + do { + for (i = 0; (s = index234(sktree, i)) != NULL; i++) { + if (s->pending_error) { + /* + * 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); + break; + } + } + } while (s); +} + +/* + * Each socket abstraction contains a `void *' private field in + * which the client can keep state. + */ +static void sk_tcp_set_private_ptr(Socket sock, void *ptr) +{ + Actual_Socket s = (Actual_Socket) sock; + s->private_ptr = ptr; +} + +static void *sk_tcp_get_private_ptr(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + return s->private_ptr; +} + +/* + * Special error values are returned from sk_namelookup and sk_new + * if there's a problem. These functions extract an error message, + * or return NULL if there's no problem. + */ +const char *sk_addr_error(SockAddr addr) +{ + return addr->error; +} +static const char *sk_tcp_socket_error(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + return s->error; +} + +static void sk_tcp_set_frozen(Socket sock, int is_frozen) +{ + Actual_Socket s = (Actual_Socket) sock; + if (s->frozen == is_frozen) + return; + s->frozen = is_frozen; + if (!is_frozen) { + do_select(s->s, 1); + if (s->frozen_readable) { + char c; + p_recv(s->s, &c, 1, MSG_PEEK); + } + } + s->frozen_readable = 0; +} + +void socket_reselect_all(void) +{ + Actual_Socket s; + int i; + + for (i = 0; (s = index234(sktree, i)) != NULL; i++) { + if (!s->frozen) + do_select(s->s, 1); + } +} + +/* + * For Plink: enumerate all sockets currently active. + */ +SOCKET first_socket(int *state) +{ + Actual_Socket s; + *state = 0; + s = index234(sktree, (*state)++); + return s ? s->s : INVALID_SOCKET; +} + +SOCKET next_socket(int *state) +{ + Actual_Socket s = index234(sktree, (*state)++); + return s ? s->s : INVALID_SOCKET; +} + +extern int socket_writable(SOCKET skt) +{ + Actual_Socket s = find234(sktree, (void *)skt, cmpforsearch); + + if (s) + return bufchain_size(&s->output_data) > 0; + else + return 0; +} + +int net_service_lookup(char *service) +{ + struct servent *se; + se = p_getservbyname(service, NULL); + if (se != NULL) + return p_ntohs(se->s_port); + else + return 0; +} + +char *get_hostname(void) +{ + int len = 128; + char *hostname = NULL; + do { + len *= 2; + hostname = sresize(hostname, len, char); + if (p_gethostname(hostname, len) < 0) { + sfree(hostname); + hostname = NULL; + break; + } + } while (strlen(hostname) >= (size_t)(len-1)); + return hostname; +} + +SockAddr platform_get_x11_unix_address(const char *display, int displaynum, + char **canonicalname) +{ + SockAddr ret = snew(struct SockAddr_tag); + memset(ret, 0, sizeof(struct SockAddr_tag)); + ret->error = "unix sockets not supported on this platform"; + ret->refcount = 1; + return ret; +} diff --git a/putty/WINDOWS/WINNOISE.C b/putty/WINDOWS/WINNOISE.C new file mode 100644 index 0000000..bdf8697 --- /dev/null +++ b/putty/WINDOWS/WINNOISE.C @@ -0,0 +1,128 @@ +/* + * Noise generation for PuTTY's cryptographic random number + * generator. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "storage.h" + +/* + * This function is called once, at PuTTY startup, and will do some + * seriously silly things like listing directories and getting disk + * free space and a process snapshot. + */ + +void noise_get_heavy(void (*func) (void *, int)) +{ + HANDLE srch; + WIN32_FIND_DATA finddata; + DWORD pid; + char winpath[MAX_PATH + 3]; + + GetWindowsDirectory(winpath, sizeof(winpath)); + strcat(winpath, "\\*"); + srch = FindFirstFile(winpath, &finddata); + if (srch != INVALID_HANDLE_VALUE) { + do { + func(&finddata, sizeof(finddata)); + } while (FindNextFile(srch, &finddata)); + FindClose(srch); + } + + pid = GetCurrentProcessId(); + func(&pid, sizeof(pid)); + + read_random_seed(func); + /* Update the seed immediately, in case another instance uses it. */ + random_save_seed(); +} + +void random_save_seed(void) +{ + int len; + void *data; + + if (random_active) { + random_get_savedata(&data, &len); + write_random_seed(data, len); + sfree(data); + } +} + +/* + * This function is called every time the random pool needs + * stirring, and will acquire the system time in all available + * forms. + */ +void noise_get_light(void (*func) (void *, int)) +{ + SYSTEMTIME systime; + DWORD adjust[2]; + BOOL rubbish; + + GetSystemTime(&systime); + func(&systime, sizeof(systime)); + + GetSystemTimeAdjustment(&adjust[0], &adjust[1], &rubbish); + func(&adjust, sizeof(adjust)); +} + +/* + * This function is called on a timer, and it will monitor + * frequently changing quantities such as the state of physical and + * virtual memory, the state of the process's message queue, which + * window is in the foreground, which owns the clipboard, etc. + */ +void noise_regular(void) +{ + HWND w; + DWORD z; + POINT pt; + MEMORYSTATUS memstat; + FILETIME times[4]; + + w = GetForegroundWindow(); + random_add_noise(&w, sizeof(w)); + w = GetCapture(); + random_add_noise(&w, sizeof(w)); + w = GetClipboardOwner(); + random_add_noise(&w, sizeof(w)); + z = GetQueueStatus(QS_ALLEVENTS); + random_add_noise(&z, sizeof(z)); + + GetCursorPos(&pt); + random_add_noise(&pt, sizeof(pt)); + + GlobalMemoryStatus(&memstat); + random_add_noise(&memstat, sizeof(memstat)); + + GetThreadTimes(GetCurrentThread(), times, times + 1, times + 2, + times + 3); + random_add_noise(×, sizeof(times)); + GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2, + times + 3); + random_add_noise(×, sizeof(times)); +} + +/* + * This function is called on every keypress or mouse move, and + * will add the current Windows time and performance monitor + * counter to the noise pool. It gets the scan code or mouse + * position passed in. + */ +void noise_ultralight(unsigned long data) +{ + DWORD wintime; + LARGE_INTEGER perftime; + + random_add_noise(&data, sizeof(DWORD)); + + wintime = GetTickCount(); + random_add_noise(&wintime, sizeof(DWORD)); + + if (QueryPerformanceCounter(&perftime)) + random_add_noise(&perftime, sizeof(perftime)); +} diff --git a/putty/WINDOWS/WINNOJMP.C b/putty/WINDOWS/WINNOJMP.C new file mode 100644 index 0000000..27d969a --- /dev/null +++ b/putty/WINDOWS/WINNOJMP.C @@ -0,0 +1,8 @@ +/* + * winnojmp.c: stub jump list functions for Windows executables that + * don't update the jump list. + */ + +void add_session_to_jumplist(const char * const sessionname) {} +void remove_session_from_jumplist(const char * const sessionname) {} +void clear_jumplist(void) {} diff --git a/putty/WINDOWS/WINPGEN.C b/putty/WINDOWS/WINPGEN.C new file mode 100644 index 0000000..13dfac7 --- /dev/null +++ b/putty/WINDOWS/WINPGEN.C @@ -0,0 +1,1431 @@ +/* + * PuTTY key generation front end (Windows). + */ + +#include +#include +#include + +#define PUTTY_DO_GLOBALS + +#include "putty.h" +#include "ssh.h" + +#include + +#ifdef MSVC4 +#define ICON_BIG 1 +#endif + +#define WM_DONEKEY (WM_APP + 1) + +#define DEFAULT_KEYSIZE 1024 + +static char *cmdline_keyfile = NULL; + +/* + * Print a modal (Really Bad) message box and perform a fatal exit. + */ +void modalfatalbox(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); +} + +/* ---------------------------------------------------------------------- + * Progress report code. This is really horrible :-) + */ +#define PROGRESSRANGE 65535 +#define MAXPHASE 5 +struct progress { + int nphases; + struct { + int exponential; + unsigned startpoint, total; + unsigned param, current, n; /* if exponential */ + unsigned mult; /* if linear */ + } phases[MAXPHASE]; + unsigned total, divisor, range; + HWND progbar; +}; + +static void progress_update(void *param, int action, int phase, int iprogress) +{ + struct progress *p = (struct progress *) param; + unsigned progress = iprogress; + int position; + + if (action < PROGFN_READY && p->nphases < phase) + p->nphases = phase; + switch (action) { + case PROGFN_INITIALISE: + p->nphases = 0; + break; + case PROGFN_LIN_PHASE: + p->phases[phase-1].exponential = 0; + p->phases[phase-1].mult = p->phases[phase].total / progress; + break; + case PROGFN_EXP_PHASE: + p->phases[phase-1].exponential = 1; + p->phases[phase-1].param = 0x10000 + progress; + p->phases[phase-1].current = p->phases[phase-1].total; + p->phases[phase-1].n = 0; + break; + case PROGFN_PHASE_EXTENT: + p->phases[phase-1].total = progress; + break; + case PROGFN_READY: + { + unsigned total = 0; + int i; + for (i = 0; i < p->nphases; i++) { + p->phases[i].startpoint = total; + total += p->phases[i].total; + } + p->total = total; + p->divisor = ((p->total + PROGRESSRANGE - 1) / PROGRESSRANGE); + p->range = p->total / p->divisor; + SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, p->range)); + } + break; + case PROGFN_PROGRESS: + if (p->phases[phase-1].exponential) { + while (p->phases[phase-1].n < progress) { + p->phases[phase-1].n++; + p->phases[phase-1].current *= p->phases[phase-1].param; + p->phases[phase-1].current /= 0x10000; + } + position = (p->phases[phase-1].startpoint + + p->phases[phase-1].total - p->phases[phase-1].current); + } else { + position = (p->phases[phase-1].startpoint + + progress * p->phases[phase-1].mult); + } + SendMessage(p->progbar, PBM_SETPOS, position / p->divisor, 0); + break; + } +} + +extern char ver[]; + +#define PASSPHRASE_MAXLEN 512 + +struct PassphraseProcStruct { + char *passphrase; + char *comment; +}; + +/* + * Dialog-box function for the passphrase box. + */ +static int 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); + *passphrase = 0; + 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) { + GetDlgItemText(hwnd, 102, passphrase, + PASSPHRASE_MAXLEN - 1); + passphrase[PASSPHRASE_MAXLEN - 1] = '\0'; + } + return 0; + } + return 0; + 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 int prompt_keyfile(HWND hwnd, char *dlgtitle, + char *filename, int save, int 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 CALLBACK LicenceProc(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); + } + + 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 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); + } + + SetDlgItemText(hwnd, 100, ver); + 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; + } + return 0; + case WM_CLOSE: + EndDialog(hwnd, 1); + return 0; + } + return 0; +} + +/* + * Thread to generate a key. + */ +struct rsa_key_thread_params { + HWND progressbar; /* notify this with progress */ + HWND dialog; /* notify this on completion */ + int keysize; /* bits in key */ + int is_dsa; + struct RSAKey *key; + struct dss_key *dsskey; +}; +static DWORD WINAPI generate_rsa_key_thread(void *param) +{ + struct rsa_key_thread_params *params = + (struct rsa_key_thread_params *) param; + struct progress prog; + prog.progbar = params->progressbar; + + progress_update(&prog, PROGFN_INITIALISE, 0, 0); + + if (params->is_dsa) + dsa_generate(params->dsskey, params->keysize, progress_update, &prog); + else + rsa_generate(params->key, params->keysize, progress_update, &prog); + + PostMessage(params->dialog, WM_DONEKEY, 0, 0); + + sfree(params); + return 0; +} + +struct MainDlgState { + int collecting_entropy; + int generation_thread_exists; + int key_exists; + int entropy_got, entropy_required, entropy_size; + int keysize; + int ssh2, is_dsa; + char **commentptr; /* points to key.comment or ssh2key.comment */ + struct ssh2_userkey ssh2key; + unsigned *entropy; + struct RSAKey key; + struct dss_key dsskey; + HMENU filemenu, keymenu, cvtmenu; +}; + +static void hidemany(HWND hwnd, const int *ids, int hideit) +{ + while (*ids) { + ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW)); + } +} + +static void setupbigedit1(HWND hwnd, int id, int idstatic, struct RSAKey *key) +{ + char *buffer; + char *dec1, *dec2; + + dec1 = bignum_decimal(key->exponent); + dec2 = bignum_decimal(key->modulus); + buffer = dupprintf("%d %s %s %s", bignum_bitcount(key->modulus), + dec1, dec2, key->comment); + SetDlgItemText(hwnd, id, buffer); + SetDlgItemText(hwnd, idstatic, + "&Public key for pasting into authorized_keys file:"); + sfree(dec1); + sfree(dec2); + sfree(buffer); +} + +static void setupbigedit2(HWND hwnd, int id, int idstatic, + struct ssh2_userkey *key) +{ + unsigned char *pub_blob; + char *buffer, *p; + int pub_len; + int i; + + pub_blob = key->alg->public_blob(key->data, &pub_len); + buffer = snewn(strlen(key->alg->name) + 4 * ((pub_len + 2) / 3) + + strlen(key->comment) + 3, char); + strcpy(buffer, key->alg->name); + p = buffer + strlen(buffer); + *p++ = ' '; + i = 0; + while (i < pub_len) { + int n = (pub_len - i < 3 ? pub_len - i : 3); + base64_encode_atom(pub_blob + i, n, p); + i += n; + p += 4; + } + *p++ = ' '; + strcpy(p, key->comment); + SetDlgItemText(hwnd, id, buffer); + SetDlgItemText(hwnd, idstatic, "&Public key for pasting into " + "OpenSSH authorized_keys file:"); + sfree(pub_blob); + sfree(buffer); +} + +static int save_ssh1_pubkey(char *filename, struct RSAKey *key) +{ + char *dec1, *dec2; + FILE *fp; + + dec1 = bignum_decimal(key->exponent); + dec2 = bignum_decimal(key->modulus); + fp = fopen(filename, "wb"); + if (!fp) + return 0; + fprintf(fp, "%d %s %s %s\n", + bignum_bitcount(key->modulus), dec1, dec2, key->comment); + fclose(fp); + sfree(dec1); + sfree(dec2); + return 1; +} + +/* + * 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 int save_ssh2_pubkey(char *filename, struct ssh2_userkey *key) +{ + unsigned char *pub_blob; + char *p; + int pub_len; + int i, column; + FILE *fp; + + pub_blob = key->alg->public_blob(key->data, &pub_len); + + fp = fopen(filename, "wb"); + if (!fp) + return 0; + + fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n"); + + fprintf(fp, "Comment: \""); + for (p = key->comment; *p; p++) { + if (*p == '\\' || *p == '\"') + fputc('\\', fp); + fputc(*p, fp); + } + fprintf(fp, "\"\n"); + + i = 0; + column = 0; + while (i < pub_len) { + char buf[5]; + int n = (pub_len - i < 3 ? pub_len - i : 3); + base64_encode_atom(pub_blob + i, n, buf); + i += n; + buf[4] = '\0'; + fputs(buf, fp); + if (++column >= 16) { + fputc('\n', fp); + column = 0; + } + } + if (column > 0) + fputc('\n', fp); + + fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n"); + fclose(fp); + sfree(pub_blob); + return 1; +} + +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_BITSSTATIC, IDC_BITS, + IDC_ABOUT, + IDC_GIVEHELP, + IDC_IMPORT, IDC_EXPORT_OPENSSH, 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_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->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND); + EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH, + 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_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->cvtmenu, IDC_IMPORT, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH, + 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_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->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, SSH_KEYTYPE_OPENSSH); + do_export_menuitem(IDC_EXPORT_SSHCOM, SSH_KEYTYPE_SSHCOM); +#undef do_export_menuitem + break; + } +} + +void load_key_file(HWND hwnd, struct MainDlgState *state, + Filename filename, int was_import_cmd) +{ + char passphrase[PASSPHRASE_MAXLEN]; + int needs_pass; + int type, realtype; + int ret; + const char *errmsg = NULL; + char *comment; + struct PassphraseProcStruct pps; + struct RSAKey newkey1; + struct 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(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; + if (realtype == SSH_KEYTYPE_SSH1) + needs_pass = rsakey_encrypted(&filename, &comment); + else if (realtype == SSH_KEYTYPE_SSH2) + needs_pass = + ssh2_userkey_encrypted(&filename, &comment); + else + needs_pass = import_encrypted(&filename, realtype, + &comment); + pps.passphrase = passphrase; + pps.comment = comment; + do { + if (needs_pass) { + int dlgret; + dlgret = DialogBoxParam(hinst, + MAKEINTRESOURCE(210), + NULL, PassphraseProc, + (LPARAM) &pps); + if (!dlgret) { + ret = -2; + break; + } + } else + *passphrase = '\0'; + if (type == SSH_KEYTYPE_SSH1) { + if (realtype == type) + ret = loadrsakey(&filename, &newkey1, + passphrase, &errmsg); + else + ret = import_ssh1(&filename, realtype, + &newkey1, passphrase, &errmsg); + } else { + if (realtype == type) + newkey2 = ssh2_load_userkey(&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(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 buf[128]; + char *savecomment; + + state->ssh2 = FALSE; + state->commentptr = &state->key.comment; + state->key = newkey1; + + /* + * Set the key fingerprint. + */ + savecomment = state->key.comment; + state->key.comment = NULL; + rsa_fingerprint(buf, sizeof(buf), + &state->key); + state->key.comment = savecomment; + + SetDlgItemText(hwnd, IDC_FINGERPRINT, buf); + /* + * 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 = + state->ssh2key.alg-> + fingerprint(state->ssh2key.data); + 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); + } + } +} + +/* + * Dialog-box function for the main PuTTYgen dialog box. + */ +static int CALLBACK MainDlgProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + static const char generating_msg[] = + "Please wait while a key is generated..."; + 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) 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(menu, MF_POPUP | MF_ENABLED, (UINT) 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, + "Export &OpenSSH key"); + AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_SSHCOM, + "Export &ssh.com key"); + AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) 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) 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; + + /* Accelerators used: acglops1rbd */ + + 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, 75); + SendDlgItemMessage(hwnd, IDC_FINGERPRINT, EM_SETREADONLY, 1, + 0); + staticedit(&cp, "Key &comment:", IDC_COMMENTSTATIC, + IDC_COMMENTEDIT, 75); + staticpassedit(&cp, "Key p&assphrase:", IDC_PASSPHRASE1STATIC, + IDC_PASSPHRASE1EDIT, 75); + staticpassedit(&cp, "C&onfirm passphrase:", + IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 75); + 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, 3, + "SSH-&1 (RSA)", IDC_KEYSSH1, + "SSH-2 &RSA", IDC_KEYSSH2RSA, + "SSH-2 &DSA", IDC_KEYSSH2DSA, NULL); + staticedit(&cp, "Number of &bits in a generated key:", + IDC_BITSSTATIC, IDC_BITS, 20); + endbox(&cp); + } + CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2DSA, IDC_KEYSSH2RSA); + CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2DSA, + IDC_KEYSSH2RSA, MF_BYCOMMAND); + SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEYSIZE, FALSE); + + /* + * 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) + load_key_file(hwnd, state, filename_from_str(cmdline_keyfile), 0); + + 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) { + struct rsa_key_thread_params *params; + DWORD threadid; + + /* + * Seed the entropy pool + */ + random_add_heavynoise(state->entropy, state->entropy_size); + memset(state->entropy, 0, state->entropy_size); + sfree(state->entropy); + state->collecting_entropy = FALSE; + + 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->keysize = state->keysize; + params->is_dsa = state->is_dsa; + params->key = &state->key; + params->dsskey = &state->dsskey; + + if (!CreateThread(NULL, 0, generate_rsa_key_thread, + params, 0, &threadid)) { + MessageBox(hwnd, "Out of thread resources", + "Key generation error", + MB_OK | MB_ICONERROR); + sfree(params); + } else { + state->generation_thread_exists = TRUE; + } + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_KEYSSH1: + case IDC_KEYSSH2RSA: + case IDC_KEYSSH2DSA: + { + state = (struct MainDlgState *) + GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (!IsDlgButtonChecked(hwnd, LOWORD(wParam))) + CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2DSA, + LOWORD(wParam)); + CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2DSA, + LOWORD(wParam), MF_BYCOMMAND); + } + 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) { + BOOL ok; + state->keysize = GetDlgItemInt(hwnd, IDC_BITS, &ok, FALSE); + if (!ok) + state->keysize = DEFAULT_KEYSIZE; + /* If we ever introduce a new key type, check it here! */ + state->ssh2 = !IsDlgButtonChecked(hwnd, IDC_KEYSSH1); + state->is_dsa = IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA); + if (state->keysize < 256) { + int ret = MessageBox(hwnd, + "PuTTYgen will not generate a key" + " smaller than 256 bits.\n" + "Key length reset to 256. Continue?", + "PuTTYgen Warning", + MB_ICONWARNING | MB_OKCANCEL); + if (ret != IDOK) + break; + state->keysize = 256; + SetDlgItemInt(hwnd, IDC_BITS, 256, FALSE); + } + ui_set_state(hwnd, state, 1); + SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg); + state->key_exists = FALSE; + state->collecting_entropy = TRUE; + + /* + * 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. + */ + state->entropy_required = (state->keysize / 2) * 2; + 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); + } + break; + case IDC_SAVE: + case IDC_EXPORT_OPENSSH: + 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[PASSPHRASE_MAXLEN]; + char passphrase2[PASSPHRASE_MAXLEN]; + int type, realtype; + + if (state->ssh2) + realtype = SSH_KEYTYPE_SSH2; + else + realtype = SSH_KEYTYPE_SSH1; + + if (LOWORD(wParam) == IDC_EXPORT_OPENSSH) + type = SSH_KEYTYPE_OPENSSH; + 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; + } + + GetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, + passphrase, sizeof(passphrase)); + GetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, + passphrase2, sizeof(passphrase2)); + if (strcmp(passphrase, passphrase2)) { + MessageBox(hwnd, + "The two passphrases given do not match.", + "PuTTYgen Error", MB_OK | MB_ICONERROR); + break; + } + 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) + break; + } + if (prompt_keyfile(hwnd, "Save private key as:", + filename, 1, (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) + 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 = ssh2_save_userkey(&fn, &state->ssh2key, + *passphrase ? passphrase : + NULL); + } else { + Filename fn = filename_from_str(filename); + if (type != realtype) + ret = export_ssh1(&fn, type, &state->key, + *passphrase ? passphrase : NULL); + else + ret = saversakey(&fn, &state->key, + *passphrase ? passphrase : NULL); + } + if (ret <= 0) { + MessageBox(hwnd, "Unable to save key file", + "PuTTYgen Error", MB_OK | MB_ICONERROR); + } + } + } + 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, 1, 0)) { + 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; + } + if (state->ssh2) { + ret = save_ssh2_pubkey(filename, &state->ssh2key); + } else { + ret = save_ssh1_pubkey(filename, &state->key); + } + if (ret <= 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, 0, LOWORD(wParam)==IDC_LOAD)) + load_key_file(hwnd, state, filename_from_str(filename), + LOWORD(wParam) != IDC_LOAD); + } + 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->is_dsa) { + state->ssh2key.data = &state->dsskey; + state->ssh2key.alg = &ssh_dss; + } else { + state->ssh2key.data = &state->key; + state->ssh2key.alg = &ssh_rsa; + } + 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->is_dsa) + strftime(*state->commentptr, 30, "dsa-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 *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) { + char *fp; + fp = state->ssh2key.alg->fingerprint(state->ssh2key.data); + SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); + sfree(fp); + } else { + char buf[128]; + rsa_fingerprint(buf, sizeof(buf), &state->key); + SetDlgItemText(hwnd, IDC_FINGERPRINT, buf); + } + *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; + 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: + 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: + 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); +} + +int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) +{ + int argc; + char **argv; + int ret; + + InitCommonControls(); + hinst = inst; + hwnd = NULL; + + /* + * See if we can find our Help file. + */ + init_help(); + + split_into_argv(cmdline, &argc, &argv, NULL); + + if (argc > 0) { + if (!strcmp(argv[0], "-pgpfp")) { + pgp_fingerprints(); + exit(1); + } else { + /* + * Assume the first argument to be a private key file, and + * attempt to load it. + */ + cmdline_keyfile = argv[0]; + } + } + + random_ref(); + ret = DialogBox(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc) != IDOK; + + cleanup_exit(ret); + return ret; /* just in case optimiser complains */ +} diff --git a/putty/WINDOWS/WINPGNT.C b/putty/WINDOWS/WINPGNT.C new file mode 100644 index 0000000..bf920f1 --- /dev/null +++ b/putty/WINDOWS/WINPGNT.C @@ -0,0 +1,2176 @@ +/* + * Pageant: the PuTTY Authentication Agent. + */ + +#include +#include +#include +#include +#include + +#define PUTTY_DO_GLOBALS + +#include "putty.h" +#include "ssh.h" +#include "misc.h" +#include "tree234.h" + +#include + +#ifndef NO_SECURITY +#include +#ifdef DEBUG_IPC +#define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */ +#include +#endif +#endif + +#define IDI_MAINICON 200 +#define IDI_TRAYICON 201 + +#define WM_SYSTRAY (WM_APP + 6) +#define WM_SYSTRAY2 (WM_APP + 7) + +#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ + +/* + * FIXME: maybe some day we can sort this out ... + */ +#define AGENT_MAX_MSGLEN 8192 + +/* 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_HELP 0x0040 +#define IDM_ABOUT 0x0050 + +#define APPNAME "Pageant" + +extern char ver[]; + +static HWND keylist; +static HWND aboutbox; +static HMENU systray_menu, session_menu; +static int already_running; + +static char *putty_path; + +/* CWD for "add key" file requester. */ +static filereq *keypath = NULL; + +#define IDM_PUTTY 0x0060 +#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(char *fmt, ...) +{ + va_list ap; + char *buf; + + va_start(ap, fmt); + buf = dupvprintf(fmt, ap); + va_end(ap); + MessageBox(hwnd, buf, "Pageant Fatal Error", + MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); + sfree(buf); + exit(1); +} + +/* Un-munge session names out of the registry. */ +static void unmungestr(char *in, char *out, int outlen) +{ + while (*in) { + if (*in == '%' && in[1] && in[2]) { + int i, j; + + i = in[1] - '0'; + i -= (i > 9 ? 7 : 0); + j = in[2] - '0'; + j -= (j > 9 ? 7 : 0); + + *out++ = (i << 4) + j; + if (!--outlen) + return; + in += 3; + } else { + *out++ = *in++; + if (!--outlen) + return; + } + } + *out = '\0'; + return; +} + +static tree234 *rsakeys, *ssh2keys; + +static int has_security; +#ifndef NO_SECURITY +DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo, + (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, + PSID *, PSID *, PACL *, PACL *, + PSECURITY_DESCRIPTOR *)); +#endif + +/* + * Forward references + */ +static void *make_keylist1(int *length); +static void *make_keylist2(int *length); +static void *get_keylist1(int *length); +static void *get_keylist2(int *length); + +/* + * We need this to link with the RSA code, because rsaencrypt() + * pads its data with random bytes. Since we only use rsadecrypt() + * and the signing functions, which are deterministic, this should + * never be called. + * + * If it _is_ called, there is a _serious_ problem, because it + * won't generate true random numbers. So we must scream, panic, + * and exit immediately if that should happen. + */ +int random_byte(void) +{ + MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR); + exit(0); + /* this line can't be reached but it placates MSVC's warnings :-) */ + return 0; +} + +/* + * Blob structure for passing to the asymmetric SSH-2 key compare + * function, prototyped here. + */ +struct blob { + unsigned char *blob; + int len; +}; +static int cmpkeys_ssh2_asymm(void *av, void *bv); + +#define PASSPHRASE_MAXLEN 512 + +struct PassphraseProcStruct { + char *passphrase; + char *comment; +}; + +static tree234 *passphrases = NULL; + +/* + * After processing a list of filenames, we want to forget the + * passphrases. + */ +static void forget_passphrases(void) +{ + while (count234(passphrases) > 0) { + char *pp = index234(passphrases, 0); + memset(pp, 0, strlen(pp)); + delpos234(passphrases, 0); + free(pp); + } +} + +/* + * Dialog-box function for the Licence box. + */ +static int CALLBACK LicenceProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + 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 CALLBACK AboutProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + SetDlgItemText(hwnd, 100, ver); + return 1; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + aboutbox = NULL; + DestroyWindow(hwnd); + return 0; + case 101: + EnableWindow(hwnd, 0); + DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc); + EnableWindow(hwnd, 1); + SetActiveWindow(hwnd); + return 0; + } + return 0; + case WM_CLOSE: + aboutbox = NULL; + DestroyWindow(hwnd); + return 0; + } + return 0; +} + +static HWND passphrase_box; + +/* + * Dialog-box function for the passphrase box. + */ +static int CALLBACK PassphraseProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + static char *passphrase = NULL; + struct PassphraseProcStruct *p; + + switch (msg) { + case WM_INITDIALOG: + passphrase_box = hwnd; + /* + * 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); + } + + SetForegroundWindow(hwnd); + SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + p = (struct PassphraseProcStruct *) lParam; + passphrase = p->passphrase; + if (p->comment) + SetDlgItemText(hwnd, 101, p->comment); + *passphrase = 0; + 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) { + GetDlgItemText(hwnd, 102, passphrase, + PASSPHRASE_MAXLEN - 1); + passphrase[PASSPHRASE_MAXLEN - 1] = '\0'; + } + return 0; + } + return 0; + case WM_CLOSE: + EndDialog(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); +} + +/* + * Update the visible key list. + */ +static void keylist_update(void) +{ + struct RSAKey *rkey; + struct ssh2_userkey *skey; + int i; + + if (keylist) { + SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0); + for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) { + char listentry[512], *p; + /* + * Replace two spaces in the fingerprint with tabs, for + * nice alignment in the box. + */ + strcpy(listentry, "ssh1\t"); + p = listentry + strlen(listentry); + rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey); + p = strchr(listentry, ' '); + if (p) + *p = '\t'; + p = strchr(listentry, ' '); + if (p) + *p = '\t'; + SendDlgItemMessage(keylist, 100, LB_ADDSTRING, + 0, (LPARAM) listentry); + } + for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) { + char listentry[512], *p; + int len; + /* + * Replace two spaces in the fingerprint with tabs, for + * nice alignment in the box. + */ + p = skey->alg->fingerprint(skey->data); + strncpy(listentry, p, sizeof(listentry)); + p = strchr(listentry, ' '); + if (p) + *p = '\t'; + p = strchr(listentry, ' '); + if (p) + *p = '\t'; + len = strlen(listentry); + if (len < sizeof(listentry) - 2) { + listentry[len] = '\t'; + strncpy(listentry + len + 1, skey->comment, + sizeof(listentry) - len - 1); + } + SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0, + (LPARAM) listentry); + } + SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0); + } +} + +/* + * This function loads a key from a file and adds it. + */ +static void add_keyfile(Filename filename) +{ + char passphrase[PASSPHRASE_MAXLEN]; + struct RSAKey *rkey = NULL; + struct ssh2_userkey *skey = NULL; + int needs_pass; + int ret; + int attempts; + char *comment; + const char *error = NULL; + struct PassphraseProcStruct pps; + int type; + int original_pass; + + type = key_type(&filename); + if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) { + char *msg = dupprintf("Couldn't load this key (%s)", + key_type_to_str(type)); + message_box(msg, APPNAME, MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + return; + } + + /* + * See if the key is already loaded (in the primary Pageant, + * which may or may not be us). + */ + { + void *blob; + unsigned char *keylist, *p; + int i, nkeys, bloblen, keylistlen; + + if (type == SSH_KEYTYPE_SSH1) { + if (!rsakey_pubblob(&filename, &blob, &bloblen, NULL, &error)) { + char *msg = dupprintf("Couldn't load private key (%s)", error); + message_box(msg, APPNAME, MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + return; + } + keylist = get_keylist1(&keylistlen); + } else { + unsigned char *blob2; + blob = ssh2_userkey_loadpub(&filename, NULL, &bloblen, + NULL, &error); + if (!blob) { + char *msg = dupprintf("Couldn't load private key (%s)", error); + message_box(msg, APPNAME, MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + return; + } + /* For our purposes we want the blob prefixed with its length */ + blob2 = snewn(bloblen+4, unsigned char); + PUT_32BIT(blob2, bloblen); + memcpy(blob2 + 4, blob, bloblen); + sfree(blob); + blob = blob2; + + keylist = get_keylist2(&keylistlen); + } + if (keylist) { + if (keylistlen < 4) { + MessageBox(NULL, "Received broken key list?!", APPNAME, + MB_OK | MB_ICONERROR); + return; + } + nkeys = GET_32BIT(keylist); + p = keylist + 4; + keylistlen -= 4; + + for (i = 0; i < nkeys; i++) { + if (!memcmp(blob, p, bloblen)) { + /* Key is already present; we can now leave. */ + sfree(keylist); + sfree(blob); + return; + } + /* Now skip over public blob */ + if (type == SSH_KEYTYPE_SSH1) { + int n = rsa_public_blob_len(p, keylistlen); + if (n < 0) { + MessageBox(NULL, "Received broken key list?!", APPNAME, + MB_OK | MB_ICONERROR); + return; + } + p += n; + keylistlen -= n; + } else { + int n; + if (keylistlen < 4) { + MessageBox(NULL, "Received broken key list?!", APPNAME, + MB_OK | MB_ICONERROR); + return; + } + n = 4 + GET_32BIT(p); + if (keylistlen < n) { + MessageBox(NULL, "Received broken key list?!", APPNAME, + MB_OK | MB_ICONERROR); + return; + } + p += n; + keylistlen -= n; + } + /* Now skip over comment field */ + { + int n; + if (keylistlen < 4) { + MessageBox(NULL, "Received broken key list?!", APPNAME, + MB_OK | MB_ICONERROR); + return; + } + n = 4 + GET_32BIT(p); + if (keylistlen < n) { + MessageBox(NULL, "Received broken key list?!", APPNAME, + MB_OK | MB_ICONERROR); + return; + } + p += n; + keylistlen -= n; + } + } + + sfree(keylist); + } + + sfree(blob); + } + + error = NULL; + if (type == SSH_KEYTYPE_SSH1) + needs_pass = rsakey_encrypted(&filename, &comment); + else + needs_pass = ssh2_userkey_encrypted(&filename, &comment); + attempts = 0; + if (type == SSH_KEYTYPE_SSH1) + rkey = snew(struct RSAKey); + pps.passphrase = passphrase; + pps.comment = comment; + original_pass = 0; + do { + if (needs_pass) { + /* try all the remembered passphrases first */ + char *pp = index234(passphrases, attempts); + if(pp) { + strcpy(passphrase, pp); + } else { + int dlgret; + original_pass = 1; + dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210), + NULL, PassphraseProc, (LPARAM) &pps); + passphrase_box = NULL; + if (!dlgret) { + if (comment) + sfree(comment); + if (type == SSH_KEYTYPE_SSH1) + sfree(rkey); + return; /* operation cancelled */ + } + } + } else + *passphrase = '\0'; + if (type == SSH_KEYTYPE_SSH1) + ret = loadrsakey(&filename, rkey, passphrase, &error); + else { + skey = ssh2_load_userkey(&filename, passphrase, &error); + if (skey == SSH2_WRONG_PASSPHRASE) + ret = -1; + else if (!skey) + ret = 0; + else + ret = 1; + } + attempts++; + } while (ret == -1); + + /* if they typed in an ok passphrase, remember it */ + if(original_pass && ret) { + char *pp = dupstr(passphrase); + addpos234(passphrases, pp, 0); + } + + if (comment) + sfree(comment); + if (ret == 0) { + char *msg = dupprintf("Couldn't load private key (%s)", error); + message_box(msg, APPNAME, MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + if (type == SSH_KEYTYPE_SSH1) + sfree(rkey); + return; + } + if (type == SSH_KEYTYPE_SSH1) { + if (already_running) { + unsigned char *request, *response; + void *vresponse; + int reqlen, clen, resplen, ret; + + clen = strlen(rkey->comment); + + reqlen = 4 + 1 + /* length, message type */ + 4 + /* bit count */ + ssh1_bignum_length(rkey->modulus) + + ssh1_bignum_length(rkey->exponent) + + ssh1_bignum_length(rkey->private_exponent) + + ssh1_bignum_length(rkey->iqmp) + + ssh1_bignum_length(rkey->p) + + ssh1_bignum_length(rkey->q) + 4 + clen /* comment */ + ; + + request = snewn(reqlen, unsigned char); + + request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY; + reqlen = 5; + PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus)); + reqlen += 4; + reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus); + reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent); + reqlen += + ssh1_write_bignum(request + reqlen, + rkey->private_exponent); + reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp); + reqlen += ssh1_write_bignum(request + reqlen, rkey->p); + reqlen += ssh1_write_bignum(request + reqlen, rkey->q); + PUT_32BIT(request + reqlen, clen); + memcpy(request + reqlen + 4, rkey->comment, clen); + reqlen += 4 + clen; + PUT_32BIT(request, reqlen - 4); + + ret = agent_query(request, reqlen, &vresponse, &resplen, + NULL, NULL); + assert(ret == 1); + response = vresponse; + if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) + MessageBox(NULL, "The already running Pageant " + "refused to add the key.", APPNAME, + MB_OK | MB_ICONERROR); + + sfree(request); + sfree(response); + } else { + if (add234(rsakeys, rkey) != rkey) + sfree(rkey); /* already present, don't waste RAM */ + } + } else { + if (already_running) { + unsigned char *request, *response; + void *vresponse; + int reqlen, alglen, clen, keybloblen, resplen, ret; + alglen = strlen(skey->alg->name); + clen = strlen(skey->comment); + + keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0); + + reqlen = 4 + 1 + /* length, message type */ + 4 + alglen + /* algorithm name */ + keybloblen + /* key data */ + 4 + clen /* comment */ + ; + + request = snewn(reqlen, unsigned char); + + request[4] = SSH2_AGENTC_ADD_IDENTITY; + reqlen = 5; + PUT_32BIT(request + reqlen, alglen); + reqlen += 4; + memcpy(request + reqlen, skey->alg->name, alglen); + reqlen += alglen; + reqlen += skey->alg->openssh_fmtkey(skey->data, + request + reqlen, + keybloblen); + PUT_32BIT(request + reqlen, clen); + memcpy(request + reqlen + 4, skey->comment, clen); + reqlen += clen + 4; + PUT_32BIT(request, reqlen - 4); + + ret = agent_query(request, reqlen, &vresponse, &resplen, + NULL, NULL); + assert(ret == 1); + response = vresponse; + if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) + MessageBox(NULL, "The already running Pageant " + "refused to add the key.", APPNAME, + MB_OK | MB_ICONERROR); + + sfree(request); + sfree(response); + } else { + if (add234(ssh2keys, skey) != skey) { + skey->alg->freekey(skey->data); + sfree(skey); /* already present, don't waste RAM */ + } + } + } +} + +/* + * Create an SSH-1 key list in a malloc'ed buffer; return its + * length. + */ +static void *make_keylist1(int *length) +{ + int i, nkeys, len; + struct RSAKey *key; + unsigned char *blob, *p, *ret; + int bloblen; + + /* + * Count up the number and length of keys we hold. + */ + len = 4; + nkeys = 0; + for (i = 0; NULL != (key = index234(rsakeys, i)); i++) { + nkeys++; + blob = rsa_public_blob(key, &bloblen); + len += bloblen; + sfree(blob); + len += 4 + strlen(key->comment); + } + + /* Allocate the buffer. */ + p = ret = snewn(len, unsigned char); + if (length) *length = len; + + PUT_32BIT(p, nkeys); + p += 4; + for (i = 0; NULL != (key = index234(rsakeys, i)); i++) { + blob = rsa_public_blob(key, &bloblen); + memcpy(p, blob, bloblen); + p += bloblen; + sfree(blob); + PUT_32BIT(p, strlen(key->comment)); + memcpy(p + 4, key->comment, strlen(key->comment)); + p += 4 + strlen(key->comment); + } + + assert(p - ret == len); + return ret; +} + +/* + * Create an SSH-2 key list in a malloc'ed buffer; return its + * length. + */ +static void *make_keylist2(int *length) +{ + struct ssh2_userkey *key; + int i, len, nkeys; + unsigned char *blob, *p, *ret; + int bloblen; + + /* + * Count up the number and length of keys we hold. + */ + len = 4; + nkeys = 0; + for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) { + nkeys++; + len += 4; /* length field */ + blob = key->alg->public_blob(key->data, &bloblen); + len += bloblen; + sfree(blob); + len += 4 + strlen(key->comment); + } + + /* Allocate the buffer. */ + p = ret = snewn(len, unsigned char); + if (length) *length = len; + + /* + * Packet header is the obvious five bytes, plus four + * bytes for the key count. + */ + PUT_32BIT(p, nkeys); + p += 4; + for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) { + blob = key->alg->public_blob(key->data, &bloblen); + PUT_32BIT(p, bloblen); + p += 4; + memcpy(p, blob, bloblen); + p += bloblen; + sfree(blob); + PUT_32BIT(p, strlen(key->comment)); + memcpy(p + 4, key->comment, strlen(key->comment)); + p += 4 + strlen(key->comment); + } + + assert(p - ret == len); + return ret; +} + +/* + * Acquire a keylist1 from the primary Pageant; this means either + * calling make_keylist1 (if that's us) or sending a message to the + * primary Pageant (if it's not). + */ +static void *get_keylist1(int *length) +{ + void *ret; + + if (already_running) { + unsigned char request[5], *response; + void *vresponse; + int resplen, retval; + request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES; + PUT_32BIT(request, 4); + + retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL); + assert(retval == 1); + response = vresponse; + if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) + return NULL; + + ret = snewn(resplen-5, unsigned char); + memcpy(ret, response+5, resplen-5); + sfree(response); + + if (length) + *length = resplen-5; + } else { + ret = make_keylist1(length); + } + return ret; +} + +/* + * Acquire a keylist2 from the primary Pageant; this means either + * calling make_keylist2 (if that's us) or sending a message to the + * primary Pageant (if it's not). + */ +static void *get_keylist2(int *length) +{ + void *ret; + + if (already_running) { + unsigned char request[5], *response; + void *vresponse; + int resplen, retval; + + request[4] = SSH2_AGENTC_REQUEST_IDENTITIES; + PUT_32BIT(request, 4); + + retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL); + assert(retval == 1); + response = vresponse; + if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) + return NULL; + + ret = snewn(resplen-5, unsigned char); + memcpy(ret, response+5, resplen-5); + sfree(response); + + if (length) + *length = resplen-5; + } else { + ret = make_keylist2(length); + } + return ret; +} + +/* + * This is the main agent function that answers messages. + */ +static void answer_msg(void *msg) +{ + unsigned char *p = msg; + unsigned char *ret = msg; + unsigned char *msgend; + int type; + + /* + * Get the message length. + */ + msgend = p + 4 + GET_32BIT(p); + + /* + * Get the message type. + */ + if (msgend < p+5) + goto failure; + type = p[4]; + + p += 5; + switch (type) { + case SSH1_AGENTC_REQUEST_RSA_IDENTITIES: + /* + * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER. + */ + { + int len; + void *keylist; + + ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER; + keylist = make_keylist1(&len); + if (len + 5 > AGENT_MAX_MSGLEN) { + sfree(keylist); + goto failure; + } + PUT_32BIT(ret, len + 1); + memcpy(ret + 5, keylist, len); + sfree(keylist); + } + break; + case SSH2_AGENTC_REQUEST_IDENTITIES: + /* + * Reply with SSH2_AGENT_IDENTITIES_ANSWER. + */ + { + int len; + void *keylist; + + ret[4] = SSH2_AGENT_IDENTITIES_ANSWER; + keylist = make_keylist2(&len); + if (len + 5 > AGENT_MAX_MSGLEN) { + sfree(keylist); + goto failure; + } + PUT_32BIT(ret, len + 1); + memcpy(ret + 5, keylist, len); + sfree(keylist); + } + break; + case SSH1_AGENTC_RSA_CHALLENGE: + /* + * Reply with either SSH1_AGENT_RSA_RESPONSE or + * SSH_AGENT_FAILURE, depending on whether we have that key + * or not. + */ + { + struct RSAKey reqkey, *key; + Bignum challenge, response; + unsigned char response_source[48], response_md5[16]; + struct MD5Context md5c; + int i, len; + + p += 4; + i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent); + if (i < 0) + goto failure; + p += i; + i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus); + if (i < 0) + goto failure; + p += i; + i = ssh1_read_bignum(p, msgend - p, &challenge); + if (i < 0) + goto failure; + p += i; + if (msgend < p+16) { + freebn(reqkey.exponent); + freebn(reqkey.modulus); + freebn(challenge); + goto failure; + } + memcpy(response_source + 32, p, 16); + p += 16; + if (msgend < p+4 || + GET_32BIT(p) != 1 || + (key = find234(rsakeys, &reqkey, NULL)) == NULL) { + freebn(reqkey.exponent); + freebn(reqkey.modulus); + freebn(challenge); + goto failure; + } + response = rsadecrypt(challenge, key); + for (i = 0; i < 32; i++) + response_source[i] = bignum_byte(response, 31 - i); + + MD5Init(&md5c); + MD5Update(&md5c, response_source, 48); + MD5Final(response_md5, &md5c); + memset(response_source, 0, 48); /* burn the evidence */ + freebn(response); /* and that evidence */ + freebn(challenge); /* yes, and that evidence */ + freebn(reqkey.exponent); /* and free some memory ... */ + freebn(reqkey.modulus); /* ... while we're at it. */ + + /* + * Packet is the obvious five byte header, plus sixteen + * bytes of MD5. + */ + len = 5 + 16; + PUT_32BIT(ret, len - 4); + ret[4] = SSH1_AGENT_RSA_RESPONSE; + memcpy(ret + 5, response_md5, 16); + } + break; + case SSH2_AGENTC_SIGN_REQUEST: + /* + * Reply with either SSH2_AGENT_SIGN_RESPONSE or + * SSH_AGENT_FAILURE, depending on whether we have that key + * or not. + */ + { + struct ssh2_userkey *key; + struct blob b; + unsigned char *data, *signature; + int datalen, siglen, len; + + if (msgend < p+4) + goto failure; + b.len = GET_32BIT(p); + p += 4; + if (msgend < p+b.len) + goto failure; + b.blob = p; + p += b.len; + if (msgend < p+4) + goto failure; + datalen = GET_32BIT(p); + p += 4; + if (msgend < p+datalen) + goto failure; + data = p; + key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm); + if (!key) + goto failure; + signature = key->alg->sign(key->data, data, datalen, &siglen); + len = 5 + 4 + siglen; + PUT_32BIT(ret, len - 4); + ret[4] = SSH2_AGENT_SIGN_RESPONSE; + PUT_32BIT(ret + 5, siglen); + memcpy(ret + 5 + 4, signature, siglen); + sfree(signature); + } + break; + case SSH1_AGENTC_ADD_RSA_IDENTITY: + /* + * Add to the list and return SSH_AGENT_SUCCESS, or + * SSH_AGENT_FAILURE if the key was malformed. + */ + { + struct RSAKey *key; + char *comment; + int n, commentlen; + + key = snew(struct RSAKey); + memset(key, 0, sizeof(struct RSAKey)); + + n = makekey(p, msgend - p, key, NULL, 1); + if (n < 0) { + freersakey(key); + sfree(key); + goto failure; + } + p += n; + + n = makeprivate(p, msgend - p, key); + if (n < 0) { + freersakey(key); + sfree(key); + goto failure; + } + p += n; + + n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */ + if (n < 0) { + freersakey(key); + sfree(key); + goto failure; + } + p += n; + + n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */ + if (n < 0) { + freersakey(key); + sfree(key); + goto failure; + } + p += n; + + n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */ + if (n < 0) { + freersakey(key); + sfree(key); + goto failure; + } + p += n; + + if (msgend < p+4) { + freersakey(key); + sfree(key); + goto failure; + } + commentlen = GET_32BIT(p); + + if (msgend < p+commentlen) { + freersakey(key); + sfree(key); + goto failure; + } + + comment = snewn(commentlen+1, char); + if (comment) { + memcpy(comment, p + 4, commentlen); + comment[commentlen] = '\0'; + key->comment = comment; + } + PUT_32BIT(ret, 1); + ret[4] = SSH_AGENT_FAILURE; + if (add234(rsakeys, key) == key) { + keylist_update(); + ret[4] = SSH_AGENT_SUCCESS; + } else { + freersakey(key); + sfree(key); + } + } + break; + case SSH2_AGENTC_ADD_IDENTITY: + /* + * Add to the list and return SSH_AGENT_SUCCESS, or + * SSH_AGENT_FAILURE if the key was malformed. + */ + { + struct ssh2_userkey *key; + char *comment, *alg; + int alglen, commlen; + int bloblen; + + + if (msgend < p+4) + goto failure; + alglen = GET_32BIT(p); + p += 4; + if (msgend < p+alglen) + goto failure; + alg = p; + p += alglen; + + key = snew(struct ssh2_userkey); + /* Add further algorithm names here. */ + if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7)) + key->alg = &ssh_rsa; + else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7)) + key->alg = &ssh_dss; + else { + sfree(key); + goto failure; + } + + bloblen = msgend - p; + key->data = key->alg->openssh_createkey(&p, &bloblen); + if (!key->data) { + sfree(key); + goto failure; + } + + /* + * p has been advanced by openssh_createkey, but + * certainly not _beyond_ the end of the buffer. + */ + assert(p <= msgend); + + if (msgend < p+4) { + key->alg->freekey(key->data); + sfree(key); + goto failure; + } + commlen = GET_32BIT(p); + p += 4; + + if (msgend < p+commlen) { + key->alg->freekey(key->data); + sfree(key); + goto failure; + } + comment = snewn(commlen + 1, char); + if (comment) { + memcpy(comment, p, commlen); + comment[commlen] = '\0'; + } + key->comment = comment; + + PUT_32BIT(ret, 1); + ret[4] = SSH_AGENT_FAILURE; + if (add234(ssh2keys, key) == key) { + keylist_update(); + ret[4] = SSH_AGENT_SUCCESS; + } else { + key->alg->freekey(key->data); + sfree(key->comment); + sfree(key); + } + } + break; + case SSH1_AGENTC_REMOVE_RSA_IDENTITY: + /* + * Remove from the list and return SSH_AGENT_SUCCESS, or + * perhaps SSH_AGENT_FAILURE if it wasn't in the list to + * start with. + */ + { + struct RSAKey reqkey, *key; + int n; + + n = makekey(p, msgend - p, &reqkey, NULL, 0); + if (n < 0) + goto failure; + + key = find234(rsakeys, &reqkey, NULL); + freebn(reqkey.exponent); + freebn(reqkey.modulus); + PUT_32BIT(ret, 1); + ret[4] = SSH_AGENT_FAILURE; + if (key) { + del234(rsakeys, key); + keylist_update(); + freersakey(key); + sfree(key); + ret[4] = SSH_AGENT_SUCCESS; + } + } + break; + case SSH2_AGENTC_REMOVE_IDENTITY: + /* + * Remove from the list and return SSH_AGENT_SUCCESS, or + * perhaps SSH_AGENT_FAILURE if it wasn't in the list to + * start with. + */ + { + struct ssh2_userkey *key; + struct blob b; + + if (msgend < p+4) + goto failure; + b.len = GET_32BIT(p); + p += 4; + + if (msgend < p+b.len) + goto failure; + b.blob = p; + p += b.len; + + key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm); + if (!key) + goto failure; + + PUT_32BIT(ret, 1); + ret[4] = SSH_AGENT_FAILURE; + if (key) { + del234(ssh2keys, key); + keylist_update(); + key->alg->freekey(key->data); + sfree(key); + ret[4] = SSH_AGENT_SUCCESS; + } + } + break; + case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES: + /* + * Remove all SSH-1 keys. Always returns success. + */ + { + struct RSAKey *rkey; + + while ((rkey = index234(rsakeys, 0)) != NULL) { + del234(rsakeys, rkey); + freersakey(rkey); + sfree(rkey); + } + keylist_update(); + + PUT_32BIT(ret, 1); + ret[4] = SSH_AGENT_SUCCESS; + } + break; + case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: + /* + * Remove all SSH-2 keys. Always returns success. + */ + { + struct ssh2_userkey *skey; + + while ((skey = index234(ssh2keys, 0)) != NULL) { + del234(ssh2keys, skey); + skey->alg->freekey(skey->data); + sfree(skey); + } + keylist_update(); + + PUT_32BIT(ret, 1); + ret[4] = SSH_AGENT_SUCCESS; + } + break; + default: + failure: + /* + * Unrecognised message. Return SSH_AGENT_FAILURE. + */ + PUT_32BIT(ret, 1); + ret[4] = SSH_AGENT_FAILURE; + break; + } +} + +/* + * Key comparison function for the 2-3-4 tree of RSA keys. + */ +static int cmpkeys_rsa(void *av, void *bv) +{ + struct RSAKey *a = (struct RSAKey *) av; + struct RSAKey *b = (struct RSAKey *) bv; + Bignum am, bm; + int alen, blen; + + am = a->modulus; + bm = b->modulus; + /* + * Compare by length of moduli. + */ + alen = bignum_bitcount(am); + blen = bignum_bitcount(bm); + if (alen > blen) + return +1; + else if (alen < blen) + return -1; + /* + * Now compare by moduli themselves. + */ + alen = (alen + 7) / 8; /* byte count */ + while (alen-- > 0) { + int abyte, bbyte; + abyte = bignum_byte(am, alen); + bbyte = bignum_byte(bm, alen); + if (abyte > bbyte) + return +1; + else if (abyte < bbyte) + return -1; + } + /* + * Give up. + */ + return 0; +} + +/* + * Key comparison function for the 2-3-4 tree of SSH-2 keys. + */ +static int cmpkeys_ssh2(void *av, void *bv) +{ + struct ssh2_userkey *a = (struct ssh2_userkey *) av; + struct ssh2_userkey *b = (struct ssh2_userkey *) bv; + int i; + int alen, blen; + unsigned char *ablob, *bblob; + int c; + + /* + * Compare purely by public blob. + */ + ablob = a->alg->public_blob(a->data, &alen); + bblob = b->alg->public_blob(b->data, &blen); + + c = 0; + for (i = 0; i < alen && i < blen; i++) { + if (ablob[i] < bblob[i]) { + c = -1; + break; + } else if (ablob[i] > bblob[i]) { + c = +1; + break; + } + } + if (c == 0 && i < alen) + c = +1; /* a is longer */ + if (c == 0 && i < blen) + c = -1; /* a is longer */ + + sfree(ablob); + sfree(bblob); + + return c; +} + +/* + * Key comparison function for looking up a blob in the 2-3-4 tree + * of SSH-2 keys. + */ +static int cmpkeys_ssh2_asymm(void *av, void *bv) +{ + struct blob *a = (struct blob *) av; + struct ssh2_userkey *b = (struct ssh2_userkey *) bv; + int i; + int alen, blen; + unsigned char *ablob, *bblob; + int c; + + /* + * Compare purely by public blob. + */ + ablob = a->blob; + alen = a->len; + bblob = b->alg->public_blob(b->data, &blen); + + c = 0; + for (i = 0; i < alen && i < blen; i++) { + if (ablob[i] < bblob[i]) { + c = -1; + break; + } else if (ablob[i] > bblob[i]) { + c = +1; + break; + } + } + if (c == 0 && i < alen) + c = +1; /* a is longer */ + if (c == 0 && i < blen) + c = -1; /* a is longer */ + + sfree(bblob); + + return c; +} + +/* + * Prompt for a key file to add, and add it. + */ +static void prompt_add_keyfile(void) +{ + OPENFILENAME of; + char *filelist = snewn(8192, char); + + if (!keypath) keypath = filereq_new(); + memset(&of, 0, sizeof(of)); + of.hwndOwner = hwnd; + 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? */ + add_keyfile(filename_from_str(filelist)); + 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, NULL); + add_keyfile(filename_from_str(filename)); + sfree(filename); + filewalker += strlen(filewalker) + 1; + } + } + + keylist_update(); + forget_passphrases(); + } + sfree(filelist); +} + +/* + * Dialog-box function for the key list box. + */ +static int CALLBACK KeyListProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + struct RSAKey *rkey; + struct ssh2_userkey *skey; + + 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); + } + + if (has_help()) + SetWindowLongPtr(hwnd, GWL_EXSTYLE, + GetWindowLongPtr(hwnd, GWL_EXSTYLE) | + WS_EX_CONTEXTHELP); + else { + HWND item = GetDlgItem(hwnd, 103); /* the Help button */ + if (item) + DestroyWindow(item); + } + + keylist = hwnd; + { + static int tabs[] = { 35, 60, 210 }; + SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS, + sizeof(tabs) / sizeof(*tabs), + (LPARAM) tabs); + } + keylist_update(); + return 0; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + keylist = NULL; + DestroyWindow(hwnd); + return 0; + case 101: /* add key */ + if (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED) { + if (passphrase_box) { + MessageBeep(MB_ICONERROR); + SetForegroundWindow(passphrase_box); + break; + } + prompt_add_keyfile(); + } + return 0; + case 102: /* remove key */ + 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, 100, 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, 100, LB_GETSELITEMS, + numSelected, (WPARAM)selectedArray); + + itemNum = numSelected - 1; + rCount = count234(rsakeys); + sCount = count234(ssh2keys); + + /* 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--) { + skey = index234(ssh2keys, i); + + if (selectedArray[itemNum] == rCount + i) { + del234(ssh2keys, skey); + skey->alg->freekey(skey->data); + sfree(skey); + itemNum--; + } + } + + /* do the same for the rsa keys */ + for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) { + rkey = index234(rsakeys, i); + + if(selectedArray[itemNum] == i) { + del234(rsakeys, rkey); + freersakey(rkey); + sfree(rkey); + itemNum--; + } + } + + sfree(selectedArray); + keylist_update(); + } + return 0; + case 103: /* help */ + if (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED) { + launch_help(hwnd, WINHELP_CTX_pageant_general); + } + return 0; + } + return 0; + case WM_HELP: + { + int id = ((LPHELPINFO)lParam)->iCtrlId; + char *topic = NULL; + switch (id) { + case 100: topic = WINHELP_CTX_pageant_keylist; break; + case 101: topic = WINHELP_CTX_pageant_addkey; break; + case 102: topic = WINHELP_CTX_pageant_remkey; 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; + + 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; + + while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) { + TCHAR session_name[MAX_PATH + 1]; + unmungestr(buf, session_name, MAX_PATH); + if(strcmp(buf, PUTTY_DEFAULT) != 0) { + 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 = session_name; + InsertMenuItem(session_menu, index_menu, TRUE, &mii); + index_menu++; + } + index_key++; + } + + 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); + } +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam) +{ + int ret; + static int 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 = 1; + update_sessions(); + SetForegroundWindow(hwnd); + ret = TrackPopupMenu(systray_menu, + TPM_RIGHTALIGN | TPM_BOTTOMALIGN | + TPM_RIGHTBUTTON, + wParam, lParam, 0, hwnd, NULL); + menuinprogress = 0; + } + break; + case WM_COMMAND: + case WM_SYSCOMMAND: + switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */ + case IDM_PUTTY: + if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""), + SW_SHOW) <= 32) { + MessageBox(NULL, "Unable to execute PuTTY!", + "Error", MB_OK | MB_ICONERROR); + } + break; + case IDM_CLOSE: + if (passphrase_box) + SendMessage(passphrase_box, WM_CLOSE, 0, 0); + SendMessage(hwnd, WM_CLOSE, 0, 0); + break; + case IDM_VIEWKEYS: + if (!keylist) { + keylist = CreateDialog(hinst, MAKEINTRESOURCE(211), + NULL, KeyListProc); + ShowWindow(keylist, SW_SHOWNORMAL); + } + /* + * 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: + if (passphrase_box) { + MessageBeep(MB_ICONERROR); + SetForegroundWindow(passphrase_box); + break; + } + prompt_add_keyfile(); + break; + case IDM_ABOUT: + if (!aboutbox) { + aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213), + 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); + strcpy(param, "@"); + strcat(param, mii.dwTypeData); + if((int)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; + case WM_COPYDATA: + { + COPYDATASTRUCT *cds; + char *mapname; + void *p; + HANDLE filemap; +#ifndef NO_SECURITY + PSID mapowner, ourself; + PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL; +#endif + int ret = 0; + + 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! */ +#ifdef DEBUG_IPC + debug(("mapname is :%s:\n", mapname)); +#endif + filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname); +#ifdef DEBUG_IPC + debug(("filemap is %p\n", filemap)); +#endif + if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) { +#ifndef NO_SECURITY + int rc; + if (has_security) { + if ((ourself = get_user_sid()) == NULL) { +#ifdef DEBUG_IPC + debug(("couldn't get user SID\n")); +#endif + return 0; + } + + if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION, + &mapowner, NULL, NULL, NULL, + &psd1) != ERROR_SUCCESS)) { +#ifdef DEBUG_IPC + debug(("couldn't get owner info for filemap: %d\n", + rc)); +#endif + return 0; + } +#ifdef DEBUG_IPC + { + LPTSTR ours, theirs; + ConvertSidToStringSid(mapowner, &theirs); + ConvertSidToStringSid(ourself, &ours); + debug(("got both sids: ours=%s theirs=%s\n", + ours, theirs)); + LocalFree(ours); + LocalFree(theirs); + } +#endif + if (!EqualSid(mapowner, ourself)) + return 0; /* security ID mismatch! */ +#ifdef DEBUG_IPC + debug(("security stuff matched\n")); +#endif + LocalFree(psd1); + LocalFree(psd2); + } else { +#ifdef DEBUG_IPC + debug(("security APIs not present\n")); +#endif + } +#endif + p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0); +#ifdef DEBUG_IPC + debug(("p is %p\n", p)); + { + int i; + for (i = 0; i < 5; i++) + debug(("p[%d]=%02x\n", i, + ((unsigned char *) p)[i])); + } +#endif + answer_msg(p); + ret = 1; + UnmapViewOfFile(p); + } + CloseHandle(filemap); + return ret; + } + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + +/* + * Fork and Exec the command in cmdline. [DBW] + */ +void spawn_cmd(char *cmdline, char * args, int show) +{ + if (ShellExecute(NULL, _T("open"), cmdline, + args, NULL, show) <= (HINSTANCE) 32) { + char *msg; + msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline, + (int)GetLastError()); + MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION); + sfree(msg); + } +} + +/* + * This is a can't-happen stub, since Pageant never makes + * asynchronous agent requests. + */ +void agent_schedule_callback(void (*callback)(void *, void *, int), + void *callback_ctx, void *data, int len) +{ + assert(!"We shouldn't get here"); +} + +void cleanup_exit(int code) +{ + shutdown_help(); + exit(code); +} + +int flags = FLAG_SYNCAGENT; + +int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) +{ + WNDCLASS wndclass; + MSG msg; + HMODULE advapi; + char *command = NULL; + int added_keys = 0; + int argc, i; + char **argv, **argstart; + + hinst = inst; + hwnd = NULL; + + /* + * Determine whether we're an NT system (should have security + * APIs) or a non-NT system (don't do security). + */ + if (!init_winver()) + { + modalfatalbox("Windows refuses to report a version"); + } + if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) { + has_security = TRUE; + } else + has_security = FALSE; + + if (has_security) { +#ifndef NO_SECURITY + /* + * Attempt to get the security API we need. + */ + if (!init_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 + } else + advapi = NULL; + + /* + * 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 storage for RSA keys. + */ + if (!already_running) { + rsakeys = newtree234(cmpkeys_rsa); + ssh2keys = newtree234(cmpkeys_ssh2); + } + + /* + * Initialise storage for short-term passphrase cache. + */ + passphrases = newtree234(NULL); + + /* + * Process the command line and add keys as listed on it. + */ + split_into_argv(cmdline, &argc, &argv, &argstart); + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "-pgpfp")) { + pgp_fingerprints(); + if (advapi) + FreeLibrary(advapi); + return 1; + } else if (!strcmp(argv[i], "-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 { + add_keyfile(filename_from_str(argv[i])); + added_keys = TRUE; + } + } + + /* + * Forget any passphrase that we retained while going over + * command line keyfiles. + */ + 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); + } + if (advapi) + FreeLibrary(advapi); + return 0; + } + + if (!prev) { + wndclass.style = 0; + wndclass.lpfnWndProc = WndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = inst; + wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON)); + wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM); + wndclass.hbrBackground = GetStockObject(BLACK_BRUSH); + wndclass.lpszMenuName = NULL; + wndclass.lpszClassName = APPNAME; + + RegisterClass(&wndclass); + } + + keylist = NULL; + + hwnd = CreateWindow(APPNAME, APPNAME, + WS_OVERLAPPEDWINDOW | WS_VSCROLL, + CW_USEDEFAULT, CW_USEDEFAULT, + 100, 100, NULL, NULL, inst, NULL); + + /* Set up a system tray icon */ + AddTrayIcon(hwnd); + + /* 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) 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_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(hwnd, SW_HIDE); + + /* + * Main message loop. + */ + while (GetMessage(&msg, NULL, 0, 0) == 1) { + if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) && + !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + /* Clean up the system tray icon */ + { + NOTIFYICONDATA tnid; + + tnid.cbSize = sizeof(NOTIFYICONDATA); + tnid.hWnd = hwnd; + tnid.uID = 1; + + Shell_NotifyIcon(NIM_DELETE, &tnid); + + DestroyMenu(systray_menu); + } + + if (keypath) filereq_free(keypath); + + if (advapi) + FreeLibrary(advapi); + + cleanup_exit(msg.wParam); + return msg.wParam; /* just in case optimiser complains */ +} diff --git a/putty/WINDOWS/WINPGNTC.C b/putty/WINDOWS/WINPGNTC.C new file mode 100644 index 0000000..0dabe71 --- /dev/null +++ b/putty/WINDOWS/WINPGNTC.C @@ -0,0 +1,265 @@ +/* + * Pageant client code. + */ + +#include +#include + +#include "putty.h" + +#ifndef NO_SECURITY +#include +#endif + +#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ +#define AGENT_MAX_MSGLEN 8192 + +int agent_exists(void) +{ + HWND hwnd; + hwnd = FindWindow("Pageant", "Pageant"); + if (!hwnd) + return FALSE; + else + return TRUE; +} + +/* + * Unfortunately, this asynchronous agent request mechanism doesn't + * appear to work terribly well. I'm going to comment it out for + * the moment, and see if I can come up with a better one :-/ + */ +#ifdef WINDOWS_ASYNC_AGENT + +struct agent_query_data { + COPYDATASTRUCT cds; + unsigned char *mapping; + HANDLE handle; + char *mapname; + HWND hwnd; + void (*callback)(void *, void *, int); + void *callback_ctx; +}; + +DWORD WINAPI agent_query_thread(LPVOID param) +{ + struct agent_query_data *data = (struct agent_query_data *)param; + unsigned char *ret; + int id, retlen; + + id = SendMessage(data->hwnd, WM_COPYDATA, (WPARAM) NULL, + (LPARAM) &data->cds); + ret = NULL; + if (id > 0) { + retlen = 4 + GET_32BIT(data->mapping); + ret = snewn(retlen, unsigned char); + if (ret) { + memcpy(ret, data->mapping, retlen); + } + } + if (!ret) + retlen = 0; + UnmapViewOfFile(data->mapping); + CloseHandle(data->handle); + sfree(data->mapname); + + agent_schedule_callback(data->callback, data->callback_ctx, ret, retlen); + + return 0; +} + +#endif + +/* + * Dynamically load advapi32.dll for SID manipulation. In its absence, + * we degrade gracefully. + */ +#ifndef NO_SECURITY +int advapi_initialised = FALSE; +static HMODULE advapi; +DECL_WINDOWS_FUNCTION(static, BOOL, OpenProcessToken, + (HANDLE, DWORD, PHANDLE)); +DECL_WINDOWS_FUNCTION(static, BOOL, GetTokenInformation, + (HANDLE, TOKEN_INFORMATION_CLASS, + LPVOID, DWORD, PDWORD)); +DECL_WINDOWS_FUNCTION(static, BOOL, InitializeSecurityDescriptor, + (PSECURITY_DESCRIPTOR, DWORD)); +DECL_WINDOWS_FUNCTION(static, BOOL, SetSecurityDescriptorOwner, + (PSECURITY_DESCRIPTOR, PSID, BOOL)); +DECL_WINDOWS_FUNCTION(, DWORD, GetSecurityInfo, + (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, + PSID *, PSID *, PACL *, PACL *, + PSECURITY_DESCRIPTOR *)); +int init_advapi(void) +{ + advapi = load_system32_dll("advapi32.dll"); + return advapi && + GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) && + GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) && + GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) && + GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) && + GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner); +} + +PSID get_user_sid(void) +{ + HANDLE proc = NULL, tok = NULL; + TOKEN_USER *user = NULL; + DWORD toklen, sidlen; + PSID sid = NULL, ret = NULL; + + 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 = 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; +} + +#endif + +int agent_query(void *in, int inlen, void **out, int *outlen, + void (*callback)(void *, void *, int), void *callback_ctx) +{ + HWND hwnd; + char *mapname; + HANDLE filemap; + unsigned char *p, *ret; + int id, retlen; + COPYDATASTRUCT cds; + SECURITY_ATTRIBUTES sa, *psa; + PSECURITY_DESCRIPTOR psd = NULL; + PSID usersid = NULL; + + *out = NULL; + *outlen = 0; + + hwnd = FindWindow("Pageant", "Pageant"); + if (!hwnd) + return 1; /* *out == NULL, so failure */ + mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId()); + +#ifndef NO_SECURITY + if (advapi_initialised || init_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(); + + psa = NULL; + 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) + return 1; /* *out == NULL, so failure */ + p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0); + memcpy(p, in, inlen); + cds.dwData = AGENT_COPYDATA_ID; + cds.cbData = 1 + strlen(mapname); + cds.lpData = mapname; +#ifdef WINDOWS_ASYNC_AGENT + if (callback != NULL && !(flags & FLAG_SYNCAGENT)) { + /* + * We need an asynchronous Pageant request. Since I know of + * no way to stop SendMessage from blocking the thread it's + * called in, I see no option but to start a fresh thread. + * When we're done we'll PostMessage the result back to our + * main window, so that the callback is done in the primary + * thread to avoid concurrency. + */ + struct agent_query_data *data = snew(struct agent_query_data); + DWORD threadid; + data->mapping = p; + data->handle = filemap; + data->mapname = mapname; + data->callback = callback; + data->callback_ctx = callback_ctx; + data->cds = cds; /* structure copy */ + data->hwnd = hwnd; + if (CreateThread(NULL, 0, agent_query_thread, data, 0, &threadid)) + return 0; + sfree(data); + } +#endif + + /* + * The user either passed a null callback (indicating that the + * query is required to be synchronous) or CreateThread failed. + * Either way, we need a synchronous request. + */ + id = SendMessage(hwnd, WM_COPYDATA, (WPARAM) NULL, (LPARAM) &cds); + if (id > 0) { + retlen = 4 + GET_32BIT(p); + ret = snewn(retlen, unsigned char); + if (ret) { + memcpy(ret, p, retlen); + *out = ret; + *outlen = retlen; + } + } + UnmapViewOfFile(p); + CloseHandle(filemap); + if (psd) + LocalFree(psd); + sfree(usersid); + return 1; +} diff --git a/putty/WINDOWS/WINPLINK.C b/putty/WINDOWS/WINPLINK.C new file mode 100644 index 0000000..7af9e1b --- /dev/null +++ b/putty/WINDOWS/WINPLINK.C @@ -0,0 +1,725 @@ +/* + * PLink - a Windows command-line (stdin/stdout) variant of PuTTY. + */ + +#include +#include +#include +#include + +#define PUTTY_DO_GLOBALS /* actually _define_ globals */ +#include "putty.h" +#include "storage.h" +#include "tree234.h" + +#define WM_AGENT_CALLBACK (WM_APP + 4) + +struct agent_callback { + void (*callback)(void *, void *, int); + void *callback_ctx; + void *data; + int len; +}; + +void fatalbox(char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + if (logctx) { + log_free(logctx); + logctx = NULL; + } + cleanup_exit(1); +} +void modalfatalbox(char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + if (logctx) { + log_free(logctx); + logctx = NULL; + } + cleanup_exit(1); +} +void connection_fatal(void *frontend, char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + if (logctx) { + log_free(logctx); + logctx = NULL; + } + cleanup_exit(1); +} +void cmdline_error(char *p, ...) +{ + va_list ap; + fprintf(stderr, "plink: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +HANDLE inhandle, outhandle, errhandle; +struct handle *stdin_handle, *stdout_handle, *stderr_handle; +DWORD orig_console_mode; +int connopen; + +WSAEVENT netevent; + +static Backend *back; +static void *backhandle; +static Config cfg; + +int term_ldisc(Terminal *term, int mode) +{ + return FALSE; +} +void ldisc_update(void *frontend, int echo, int edit) +{ + /* Update stdin read mode to reflect changes in line discipline. */ + 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); +} + +char *get_ttymode(void *frontend, const char *mode) { return NULL; } + +int from_backend(void *frontend_handle, int is_stderr, + const char *data, int len) +{ + if (is_stderr) { + handle_write(stderr_handle, data, len); + } else { + handle_write(stdout_handle, data, len); + } + + return handle_backlog(stdout_handle) + handle_backlog(stderr_handle); +} + +int from_backend_untrusted(void *frontend_handle, const char *data, int len) +{ + /* + * No "untrusted" output should get here (the way the code is + * currently, it's all diverted by FLAG_STDERR). + */ + assert(!"Unexpected call to from_backend_untrusted()"); + return 0; /* not reached */ +} + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = console_get_userpass_input(p, in, inlen); + return ret; +} + +static DWORD main_thread_id; + +void agent_schedule_callback(void (*callback)(void *, void *, int), + void *callback_ctx, void *data, int len) +{ + struct agent_callback *c = snew(struct agent_callback); + c->callback = callback; + c->callback_ctx = callback_ctx; + c->data = data; + c->len = len; + PostThreadMessage(main_thread_id, WM_AGENT_CALLBACK, 0, (LPARAM)c); +} + +/* + * Short description of parameters. + */ +static void usage(void) +{ + printf("PuTTY Link: command-line connection utility\n"); + printf("%s\n", ver); + printf("Usage: plink [options] [user@]host [command]\n"); + printf(" (\"host\" can also be a PuTTY saved session name)\n"); + printf("Options:\n"); + printf(" -V print version information and exit\n"); + printf(" -pgpfp print PGP key fingerprints and exit\n"); + printf(" -v show verbose messages\n"); + printf(" -load sessname Load settings from saved session\n"); + printf(" -ssh -telnet -rlogin -raw -serial\n"); + printf(" force use of a particular protocol\n"); + printf(" -P port connect to specified port\n"); + printf(" -l user connect with specified username\n"); + printf(" -batch disable all interactive prompts\n"); + printf("The following options only apply to SSH connections:\n"); + printf(" -pw passw login with specified password\n"); + printf(" -D [listen-IP:]listen-port\n"); + printf(" Dynamic SOCKS-based port forwarding\n"); + printf(" -L [listen-IP:]listen-port:host:port\n"); + printf(" Forward local port to remote address\n"); + printf(" -R [listen-IP:]listen-port:host:port\n"); + printf(" Forward remote port to local address\n"); + printf(" -X -x enable / disable X11 forwarding\n"); + printf(" -A -a enable / disable agent forwarding\n"); + printf(" -t -T enable / disable pty allocation\n"); + printf(" -1 -2 force use of particular protocol version\n"); + printf(" -4 -6 force use of IPv4 or IPv6\n"); + printf(" -C enable compression\n"); + printf(" -i key private key file for authentication\n"); + printf(" -noagent disable use of Pageant\n"); + printf(" -agent enable use of Pageant\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(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n"); + printf(" Specify the serial configuration (serial only)\n"); + exit(1); +} + +static void version(void) +{ + printf("plink: %s\n", ver); + exit(1); +} + +char *do_select(SOCKET skt, int startup) +{ + int events; + if (startup) { + events = (FD_CONNECT | FD_READ | FD_WRITE | + FD_OOB | FD_CLOSE | FD_ACCEPT); + } else { + events = 0; + } + if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) { + switch (p_WSAGetLastError()) { + case WSAENETDOWN: + return "Network is down"; + default: + return "WSAEventSelect(): unknown error"; + } + } + return NULL; +} + +int stdin_gotdata(struct handle *h, void *data, int len) +{ + if (len < 0) { + /* + * Special case: report read error. + */ + char buf[4096]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -len, 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(len); + if (connopen && back->connected(backhandle)) { + if (len > 0) { + return back->send(backhandle, data, len); + } else { + back->special(backhandle, TS_EOF); + return 0; + } + } else + return 0; +} + +void stdouterr_sent(struct handle *h, int new_backlog) +{ + if (new_backlog < 0) { + /* + * Special case: report write error. + */ + char buf[4096]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -new_backlog, 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 (connopen && back->connected(backhandle)) { + back->unthrottle(backhandle, (handle_backlog(stdout_handle) + + handle_backlog(stderr_handle))); + } +} + +int main(int argc, char **argv) +{ + int sending; + int portnumber = -1; + SOCKET *sklist; + int skcount, sksize; + int exitcode; + int errors; + int got_host = FALSE; + int use_subsystem = 0; + long now, next; + + sklist = NULL; + skcount = sksize = 0; + /* + * Initialise port and protocol to sensible defaults. (These + * will be overridden by more or less anything.) + */ + default_protocol = PROT_SSH; + default_port = 22; + + flags = FLAG_STDERR; + /* + * Process the command line. + */ + do_defaults(NULL, &cfg); + loaded_session = FALSE; + default_protocol = cfg.protocol; + default_port = cfg.port; + errors = 0; + { + /* + * Override the default protocol if PLINK_PROTOCOL is set. + */ + char *p = getenv("PLINK_PROTOCOL"); + if (p) { + const Backend *b = backend_from_name(p); + if (b) { + default_protocol = cfg.protocol = b->protocol; + default_port = cfg.port = b->default_port; + } + } + } + while (--argc) { + char *p = *++argv; + if (*p == '-') { + int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), + 1, &cfg); + if (ret == -2) { + fprintf(stderr, + "plink: option \"%s\" requires an argument\n", p); + errors = 1; + } else if (ret == 2) { + --argc, ++argv; + } else if (ret == 1) { + continue; + } else if (!strcmp(p, "-batch")) { + console_batch_mode = 1; + } else if (!strcmp(p, "-s")) { + /* Save status to write to cfg later. */ + use_subsystem = 1; + } else if (!strcmp(p, "-V")) { + version(); + } else if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints(); + exit(1); + } else { + fprintf(stderr, "plink: unknown option \"%s\"\n", p); + errors = 1; + } + } else if (*p) { + if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) { + char *q = p; + /* + * If the hostname starts with "telnet:", set the + * protocol to Telnet and process the string as a + * Telnet URL. + */ + if (!strncmp(q, "telnet:", 7)) { + char c; + + q += 7; + if (q[0] == '/' && q[1] == '/') + q += 2; + cfg.protocol = PROT_TELNET; + p = q; + while (*p && *p != ':' && *p != '/') + p++; + c = *p; + if (*p) + *p++ = '\0'; + if (c == ':') + cfg.port = atoi(p); + else + cfg.port = -1; + strncpy(cfg.host, q, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + got_host = TRUE; + } else { + char *r, *user, *host; + /* + * Before we process the [user@]host string, we + * first check for the presence of a protocol + * prefix (a protocol name followed by ","). + */ + r = strchr(p, ','); + if (r) { + const Backend *b; + *r = '\0'; + b = backend_from_name(p); + if (b) { + default_protocol = cfg.protocol = b->protocol; + portnumber = b->default_port; + } + p = r + 1; + } + + /* + * A nonzero length string followed by an @ is treated + * as a username. (We discount an _initial_ @.) The + * rest of the string (or the whole string if no @) + * is treated as a session name and/or hostname. + */ + r = strrchr(p, '@'); + if (r == p) + p++, r = NULL; /* discount initial @ */ + if (r) { + *r++ = '\0'; + user = p, host = r; + } else { + user = NULL, host = p; + } + + /* + * Now attempt to load a saved session with the + * same name as the hostname. + */ + { + Config cfg2; + do_defaults(host, &cfg2); + if (loaded_session || !cfg_launchable(&cfg2)) { + /* No settings for this host; use defaults */ + /* (or session was already loaded with -load) */ + strncpy(cfg.host, host, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + cfg.port = default_port; + got_host = TRUE; + } else { + cfg = cfg2; + loaded_session = TRUE; + } + } + + if (user) { + /* Patch in specified username. */ + strncpy(cfg.username, user, + sizeof(cfg.username) - 1); + cfg.username[sizeof(cfg.username) - 1] = '\0'; + } + + } + } else { + char *command; + int cmdlen, cmdsize; + cmdlen = cmdsize = 0; + command = NULL; + + while (argc) { + while (*p) { + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = sresize(command, cmdsize, char); + } + command[cmdlen++]=*p++; + } + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = sresize(command, cmdsize, char); + } + command[cmdlen++]=' '; /* always add trailing space */ + if (--argc) p = *++argv; + } + if (cmdlen) command[--cmdlen]='\0'; + /* change trailing blank to NUL */ + cfg.remote_cmd_ptr = command; + cfg.remote_cmd_ptr2 = NULL; + cfg.nopty = TRUE; /* command => no terminal */ + + break; /* done with cmdline */ + } + } + } + + if (errors) + return 1; + + if (!cfg_launchable(&cfg) || !(got_host || loaded_session)) { + usage(); + } + + /* + * Trim leading whitespace off the hostname if it's there. + */ + { + int space = strspn(cfg.host, " \t"); + memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space); + } + + /* See if host is of the form user@host */ + if (cfg_launchable(&cfg)) { + char *atsign = strrchr(cfg.host, '@'); + /* Make sure we're not overflowing the user field */ + if (atsign) { + if (atsign - cfg.host < sizeof cfg.username) { + strncpy(cfg.username, cfg.host, atsign - cfg.host); + cfg.username[atsign - cfg.host] = '\0'; + } + memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1)); + } + } + + /* + * Perform command-line overrides on session configuration. + */ + cmdline_run_saved(&cfg); + + /* + * Apply subsystem status. + */ + if (use_subsystem) + cfg.ssh_subsys = TRUE; + + /* + * Trim a colon suffix off the hostname if it's there. + */ + cfg.host[strcspn(cfg.host, ":")] = '\0'; + + /* + * Remove any remaining whitespace from the hostname. + */ + { + int p1 = 0, p2 = 0; + while (cfg.host[p2] != '\0') { + if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') { + cfg.host[p1] = cfg.host[p2]; + p1++; + } + p2++; + } + cfg.host[p1] = '\0'; + } + + if (!cfg.remote_cmd_ptr && !*cfg.remote_cmd && !*cfg.ssh_nc_host) + flags |= FLAG_INTERACTIVE; + + /* + * Select protocol. This is farmed out into a table in a + * separate file to enable an ssh-free variant. + */ + back = backend_from_proto(cfg.protocol); + if (back == NULL) { + fprintf(stderr, + "Internal fault: Unsupported protocol found\n"); + return 1; + } + + /* + * Select port. + */ + if (portnumber != -1) + cfg.port = portnumber; + + sk_init(); + if (p_WSAEventSelect == NULL) { + fprintf(stderr, "Plink requires WinSock 2\n"); + return 1; + } + + logctx = log_init(NULL, &cfg); + console_provide_logctx(logctx); + + /* + * Start up the connection. + */ + netevent = CreateEvent(NULL, FALSE, FALSE, NULL); + { + const char *error; + char *realhost; + /* nodelay is only useful if stdin is a character device (console) */ + int nodelay = cfg.tcp_nodelay && + (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR); + + error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, + &realhost, nodelay, cfg.tcp_keepalives); + if (error) { + fprintf(stderr, "Unable to open connection:\n%s", error); + return 1; + } + back->provide_logctx(backhandle, logctx); + sfree(realhost); + } + connopen = 1; + + 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); + + main_thread_id = GetCurrentThreadId(); + + sending = FALSE; + + now = GETTICKCOUNT(); + + while (1) { + int nhandles; + HANDLE *handles; + int n; + DWORD ticks; + + if (!sending && back->sendok(backhandle)) { + stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL, + 0); + sending = TRUE; + } + + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + if (ticks < 0) ticks = 0; /* just in case */ + } else { + ticks = INFINITE; + } + + handles = handle_get_events(&nhandles); + handles = sresize(handles, nhandles+1, HANDLE); + handles[nhandles] = netevent; + n = MsgWaitForMultipleObjects(nhandles+1, handles, FALSE, ticks, + QS_POSTMESSAGE); + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { + handle_got_event(handles[n - WAIT_OBJECT_0]); + } else if (n == WAIT_OBJECT_0 + nhandles) { + WSANETWORKEVENTS things; + SOCKET socket; + extern SOCKET first_socket(int *), next_socket(int *); + extern int select_result(WPARAM, LPARAM); + 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. */ + if (i > sksize) { + sksize = i + 16; + sklist = sresize(sklist, sksize, SOCKET); + } + + /* 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(socket); + noise_ultralight(things.lNetworkEvents); + + 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); + connopen &= select_result(wp, lp); + } + } + } + } else if (n == WAIT_OBJECT_0 + nhandles + 1) { + MSG msg; + while (PeekMessage(&msg, INVALID_HANDLE_VALUE, + WM_AGENT_CALLBACK, WM_AGENT_CALLBACK, + PM_REMOVE)) { + struct agent_callback *c = (struct agent_callback *)msg.lParam; + c->callback(c->callback_ctx, c->data, c->len); + sfree(c); + } + } + + if (n == WAIT_TIMEOUT) { + now = next; + } else { + now = GETTICKCOUNT(); + } + + sfree(handles); + + if (sending) + handle_unthrottle(stdin_handle, back->sendbuffer(backhandle)); + + if ((!connopen || !back->connected(backhandle)) && + handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0) + break; /* we closed the connection */ + } + exitcode = back->exitcode(backhandle); + if (exitcode < 0) { + fprintf(stderr, "Remote process exit code unavailable\n"); + exitcode = 1; /* this is an error condition */ + } + cleanup_exit(exitcode); + return 0; /* placate compiler warning */ +} diff --git a/putty/WINDOWS/WINPRINT.C b/putty/WINDOWS/WINPRINT.C new file mode 100644 index 0000000..4098978 --- /dev/null +++ b/putty/WINDOWS/WINPRINT.C @@ -0,0 +1,185 @@ +/* + * Printing interface for PuTTY. + */ + +#include "putty.h" +#include + +struct printer_enum_tag { + int nprinters; + DWORD enum_level; + union { + LPPRINTER_INFO_4 i4; + LPPRINTER_INFO_5 i5; + } info; +}; + +struct printer_job_tag { + HANDLE hprinter; +}; + +static char *printer_add_enum(int param, DWORD level, char *buffer, + int offset, int *nprinters_ptr) +{ + DWORD needed = 0, nprinters = 0; + + 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. + */ + EnumPrinters(param, NULL, level, buffer+offset, 512, + &needed, &nprinters); + + if (needed < 512) + needed = 512; + + buffer = sresize(buffer, offset+needed, char); + + if (EnumPrinters(param, NULL, level, buffer+offset, + needed, &needed, &nprinters) == 0) + return NULL; + + *nprinters_ptr += nprinters; + + return buffer; +} + +printer_enum *printer_start_enum(int *nprinters_ptr) +{ + printer_enum *ret = snew(printer_enum); + char *buffer = NULL, *retval; + + *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 (osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT) { + ret->enum_level = 5; + } else { + ret->enum_level = 4; + } + + retval = printer_add_enum(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, + ret->enum_level, buffer, 0, nprinters_ptr); + if (!retval) + goto error; + else + buffer = retval; + + 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; + int jobstarted = 0, pagestarted = 0; + + ret->hprinter = NULL; + if (!OpenPrinter(printer, &ret->hprinter, NULL)) + goto error; + + docinfo.pDocName = "PuTTY remote printer output"; + docinfo.pOutputFile = NULL; + docinfo.pDatatype = "RAW"; + + if (!StartDocPrinter(ret->hprinter, 1, (LPSTR)&docinfo)) + goto error; + jobstarted = 1; + + if (!StartPagePrinter(ret->hprinter)) + goto error; + pagestarted = 1; + + return ret; + + error: + if (pagestarted) + EndPagePrinter(ret->hprinter); + if (jobstarted) + EndDocPrinter(ret->hprinter); + if (ret->hprinter) + ClosePrinter(ret->hprinter); + sfree(ret); + return NULL; +} + +void printer_job_data(printer_job *pj, void *data, int len) +{ + DWORD written; + + if (!pj) + return; + + WritePrinter(pj->hprinter, data, len, &written); +} + +void printer_finish_job(printer_job *pj) +{ + if (!pj) + return; + + EndPagePrinter(pj->hprinter); + EndDocPrinter(pj->hprinter); + ClosePrinter(pj->hprinter); + sfree(pj); +} diff --git a/putty/WINDOWS/WINPROXY.C b/putty/WINDOWS/WINPROXY.C new file mode 100644 index 0000000..4da4d2e --- /dev/null +++ b/putty/WINDOWS/WINPROXY.C @@ -0,0 +1,219 @@ +/* + * winproxy.c: Windows implementation of platform_new_connection(), + * supporting an OpenSSH-like proxy command via the winhandl.c + * mechanism. + */ + +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" + +typedef struct Socket_localproxy_tag *Local_Proxy_Socket; + +struct Socket_localproxy_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + HANDLE to_cmd_H, from_cmd_H; + struct handle *to_cmd_h, *from_cmd_h; + + char *error; + + Plug plug; + + void *privptr; +}; + +int localproxy_gotdata(struct handle *h, void *data, int len) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) handle_get_privdata(h); + + if (len < 0) { + return plug_closing(ps->plug, "Read error from local proxy command", + 0, 0); + } else if (len == 0) { + return plug_closing(ps->plug, NULL, 0, 0); + } else { + return plug_receive(ps->plug, 0, data, len); + } +} + +void localproxy_sentdata(struct handle *h, int new_backlog) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) handle_get_privdata(h); + + plug_sent(ps->plug, new_backlog); +} + +static Plug sk_localproxy_plug (Socket s, Plug p) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_localproxy_close (Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + handle_free(ps->to_cmd_h); + handle_free(ps->from_cmd_h); + CloseHandle(ps->to_cmd_H); + CloseHandle(ps->from_cmd_H); + + sfree(ps); +} + +static int sk_localproxy_write (Socket s, const char *data, int len) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + return handle_write(ps->to_cmd_h, data, len); +} + +static int sk_localproxy_write_oob(Socket s, const char *data, int len) +{ + /* + * oob data is treated as inband; nasty, but nothing really + * better we can do + */ + return sk_localproxy_write(s, data, len); +} + +static void sk_localproxy_flush(Socket s) +{ + /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */ + /* do nothing */ +} + +static void sk_localproxy_set_private_ptr(Socket s, void *ptr) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + ps->privptr = ptr; +} + +static void *sk_localproxy_get_private_ptr(Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + return ps->privptr; +} + +static void sk_localproxy_set_frozen(Socket s, int is_frozen) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + + /* + * FIXME + */ +} + +static const char *sk_localproxy_socket_error(Socket s) +{ + Local_Proxy_Socket ps = (Local_Proxy_Socket) s; + return ps->error; +} + +Socket platform_new_connection(SockAddr addr, char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug plug, const Config *cfg) +{ + char *cmd; + + static const struct socket_function_table socket_fn_table = { + sk_localproxy_plug, + sk_localproxy_close, + sk_localproxy_write, + sk_localproxy_write_oob, + sk_localproxy_flush, + sk_localproxy_set_private_ptr, + sk_localproxy_get_private_ptr, + sk_localproxy_set_frozen, + sk_localproxy_socket_error + }; + + Local_Proxy_Socket ret; + HANDLE us_to_cmd, us_from_cmd, cmd_to_us, cmd_from_us; + SECURITY_ATTRIBUTES sa; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + if (cfg->proxy_type != PROXY_CMD) + return NULL; + + cmd = format_telnet_command(addr, port, cfg); + + { + char *msg = dupprintf("Starting local proxy command: %s", cmd); + /* We're allowed to pass NULL here, because we're part of the Windows + * front end so we know logevent doesn't expect any data. */ + logevent(NULL, msg); + sfree(msg); + } + + ret = snew(struct Socket_localproxy_tag); + ret->fn = &socket_fn_table; + ret->plug = plug; + ret->error = NULL; + + /* + * 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)) { + ret->error = dupprintf("Unable to create pipes for proxy command"); + return (Socket)ret; + } + + if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) { + CloseHandle(us_from_cmd); + CloseHandle(cmd_to_us); + ret->error = dupprintf("Unable to create pipes for proxy command"); + return (Socket)ret; + } + + SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(us_from_cmd, 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 = NULL; + CreateProcess(NULL, cmd, NULL, NULL, TRUE, + CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, + NULL, NULL, &si, &pi); + + sfree(cmd); + + CloseHandle(cmd_from_us); + CloseHandle(cmd_to_us); + + ret->to_cmd_H = us_to_cmd; + ret->from_cmd_H = us_from_cmd; + + ret->from_cmd_h = handle_input_new(ret->from_cmd_H, localproxy_gotdata, + ret, 0); + ret->to_cmd_h = handle_output_new(ret->to_cmd_H, localproxy_sentdata, + ret, 0); + + /* We are responsible for this and don't need it any more */ + sk_addr_free(addr); + + return (Socket) ret; +} diff --git a/putty/WINDOWS/WINSER.C b/putty/WINDOWS/WINSER.C new file mode 100644 index 0000000..1928324 --- /dev/null +++ b/putty/WINDOWS/WINSER.C @@ -0,0 +1,460 @@ +/* + * Serial back end (Windows-specific). + */ + +#include +#include +#include + +#include "putty.h" + +#define SERIAL_MAX_BACKLOG 4096 + +typedef struct serial_backend_data { + HANDLE port; + struct handle *out, *in; + void *frontend; + int bufsize; + long clearbreak_time; + int break_in_progress; +} *Serial; + +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 int serial_gotdata(struct handle *h, void *data, int len) +{ + Serial serial = (Serial)handle_get_privdata(h); + if (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 (len == 0) + error_msg = "End of file reading from serial device"; + else + error_msg = "Error reading from serial device"; + + serial_terminate(serial); + + notify_remote_exit(serial->frontend); + + logevent(serial->frontend, error_msg); + + connection_fatal(serial->frontend, "%s", error_msg); + + return 0; /* placate optimiser */ + } else { + return from_backend(serial->frontend, 0, data, len); + } +} + +static void serial_sentdata(struct handle *h, int new_backlog) +{ + Serial serial = (Serial)handle_get_privdata(h); + if (new_backlog < 0) { + const char *error_msg = "Error writing to serial device"; + + serial_terminate(serial); + + notify_remote_exit(serial->frontend); + + logevent(serial->frontend, error_msg); + + connection_fatal(serial->frontend, "%s", error_msg); + } else { + serial->bufsize = new_backlog; + } +} + +static const char *serial_configure(Serial serial, HANDLE serport, Config *cfg) +{ + 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)) { + char *msg; + 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 = cfg->serspeed; + msg = dupprintf("Configuring baud rate %d", cfg->serspeed); + logevent(serial->frontend, msg); + sfree(msg); + + dcb.ByteSize = cfg->serdatabits; + msg = dupprintf("Configuring %d data bits", cfg->serdatabits); + logevent(serial->frontend, msg); + sfree(msg); + + switch (cfg->serstopbits) { + case 2: dcb.StopBits = ONESTOPBIT; str = "1"; break; + case 3: dcb.StopBits = ONE5STOPBITS; str = "1.5"; break; + case 4: dcb.StopBits = TWOSTOPBITS; str = "2"; break; + default: return "Invalid number of stop bits (need 1, 1.5 or 2)"; + } + msg = dupprintf("Configuring %s data bits", str); + logevent(serial->frontend, msg); + sfree(msg); + + switch (cfg->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; + } + msg = dupprintf("Configuring %s parity", str); + logevent(serial->frontend, msg); + sfree(msg); + + switch (cfg->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; + } + msg = dupprintf("Configuring %s flow control", str); + logevent(serial->frontend, msg); + sfree(msg); + + if (!SetCommState(serport, &dcb)) + return "Unable to configure serial port"; + + timeouts.ReadIntervalTimeout = 1; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + if (!SetCommTimeouts(serport, &timeouts)) + return "Unable to configure serial timeouts"; + } + + return NULL; +} + +/* + * Called to set up the serial connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *serial_init(void *frontend_handle, void **backend_handle, + Config *cfg, + char *host, int port, char **realhost, int nodelay, + int keepalive) +{ + Serial serial; + HANDLE serport; + const char *err; + + serial = snew(struct serial_backend_data); + serial->port = INVALID_HANDLE_VALUE; + serial->out = serial->in = NULL; + serial->bufsize = 0; + serial->break_in_progress = FALSE; + *backend_handle = serial; + + serial->frontend = frontend_handle; + + { + char *msg = dupprintf("Opening serial device %s", cfg->serline); + logevent(serial->frontend, msg); + } + + { + /* + * Munge the string supplied by the user into a Windows filename. + * + * Windows supports opening a few "legacy" devices (including + * COM1-9) by specifying their names verbatim as a filename to + * open. (Thus, no files can ever have these names. See + * + * ("Naming a File") for the complete list of reserved names.) + * + * However, this doesn't let you get at devices COM10 and above. + * For that, you need to specify a filename like "\\.\COM10". + * This is also necessary for special serial and serial-like + * devices such as \\.\WCEUSBSH001. It also works for the "legacy" + * names, so you can do \\.\COM1 (verified as far back as Win95). + * See + * (CreateFile() docs). + * + * So, we believe that prepending "\\.\" should always be the + * Right Thing. However, just in case someone finds something to + * talk to that doesn't exist under there, if the serial line + * contains a backslash, we use it verbatim. (This also lets + * existing configurations using \\.\ continue working.) + */ + char *serfilename = + dupprintf("%s%s", + strchr(cfg->serline, '\\') ? "" : "\\\\.\\", + cfg->serline); + serport = CreateFile(serfilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + sfree(serfilename); + } + + if (serport == INVALID_HANDLE_VALUE) + return "Unable to open serial port"; + + err = serial_configure(serial, serport, cfg); + 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(cfg->serline); + + /* + * Specials are always available. + */ + update_specials_menu(serial->frontend); + + return NULL; +} + +static void serial_free(void *handle) +{ + Serial serial = (Serial) handle; + + serial_terminate(serial); + expire_timer_context(serial); + sfree(serial); +} + +static void serial_reconfig(void *handle, Config *cfg) +{ + Serial serial = (Serial) handle; + const char *err; + + err = serial_configure(serial, serial->port, cfg); + + /* + * FIXME: what should we do if err returns something? + */ +} + +/* + * Called to send data down the serial connection. + */ +static int serial_send(void *handle, char *buf, int len) +{ + Serial serial = (Serial) handle; + + 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 int serial_sendbuffer(void *handle) +{ + Serial serial = (Serial) handle; + return serial->bufsize; +} + +/* + * Called to set the size of the window + */ +static void serial_size(void *handle, int width, int height) +{ + /* Do nothing! */ + return; +} + +static void serbreak_timer(void *ctx, long now) +{ + Serial serial = (Serial)ctx; + + if (now >= serial->clearbreak_time && serial->port) { + ClearCommBreak(serial->port); + serial->break_in_progress = FALSE; + logevent(serial->frontend, "Finished serial break"); + } +} + +/* + * Send serial special codes. + */ +static void serial_special(void *handle, Telnet_Special code) +{ + Serial serial = (Serial) handle; + + if (serial->port && code == TS_BRK) { + logevent(serial->frontend, "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 struct telnet_special *serial_get_specials(void *handle) +{ + static const struct telnet_special specials[] = { + {"Break", TS_BRK}, + {NULL, TS_EXITMENU} + }; + return specials; +} + +static int serial_connected(void *handle) +{ + return 1; /* always connected */ +} + +static int serial_sendok(void *handle) +{ + return 1; +} + +static void serial_unthrottle(void *handle, int backlog) +{ + Serial serial = (Serial) handle; + if (serial->in) + handle_unthrottle(serial->in, backlog); +} + +static int serial_ldisc(void *handle, int option) +{ + /* + * Local editing and local echo are off by default. + */ + return 0; +} + +static void serial_provide_ldisc(void *handle, void *ldisc) +{ + /* This is a stub. */ +} + +static void serial_provide_logctx(void *handle, void *logctx) +{ + /* This is a stub. */ +} + +static int serial_exitcode(void *handle) +{ + Serial serial = (Serial) handle; + if (serial->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(void *handle) +{ + return 0; +} + +Backend serial_backend = { + serial_init, + serial_free, + serial_reconfig, + serial_send, + serial_sendbuffer, + serial_size, + serial_special, + serial_get_specials, + serial_connected, + serial_exitcode, + serial_sendok, + serial_ldisc, + serial_provide_ldisc, + serial_provide_logctx, + serial_unthrottle, + serial_cfg_info, + "serial", + PROT_SERIAL, + 0 +}; diff --git a/putty/WINDOWS/WINSFTP.C b/putty/WINDOWS/WINSFTP.C new file mode 100644 index 0000000..8a36dcb --- /dev/null +++ b/putty/WINDOWS/WINSFTP.C @@ -0,0 +1,721 @@ +/* + * winsftp.c: the Windows-specific parts of PSFTP and PSCP. + */ + +#include + +#include "putty.h" +#include "psftp.h" +#include "ssh.h" +#include "int64.h" + +char *get_ttymode(void *frontend, const char *mode) { return NULL; } + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = console_get_userpass_input(p, in, inlen); + return ret; +} + +void platform_get_x11_auth(struct X11Display *display, const Config *cfg) +{ + /* Do nothing, therefore no auth. */ +} +const int 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); + int len = GetCurrentDirectory(256, ret); + if (len > 256) + ret = sresize(ret, len, char); + GetCurrentDirectory(len, ret); + return ret; +} + +#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(char *name, uint64 *size, + unsigned long *mtime, unsigned long *atime) +{ + 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) + size->lo=GetFileSize(h, &(size->hi)); + + 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); + } + + return ret; +} + +int read_from_file(RFile *f, void *buffer, int length) +{ + int ret; + DWORD read; + ret = ReadFile(f->h, buffer, length, &read, NULL); + if (!ret) + return -1; /* error */ + else + return read; +} + +void close_rfile(RFile *f) +{ + CloseHandle(f->h); + sfree(f); +} + +struct WFile { + HANDLE h; +}; + +WFile *open_new_file(char *name) +{ + 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(char *name, uint64 *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) + size->lo=GetFileSize(h, &(size->hi)); + + return ret; +} + +int write_to_file(WFile *f, void *buffer, int length) +{ + int ret; + DWORD written; + ret = WriteFile(f->h, buffer, length, &written, NULL); + if (!ret) + 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 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; + } + + SetFilePointer(f->h, offset.lo, &(offset.hi), movemethod); + + if (GetLastError() != NO_ERROR) + return -1; + else + return 0; +} + +uint64 get_file_posn(WFile *f) +{ + uint64 ret; + + ret.hi = 0L; + ret.lo = SetFilePointer(f->h, 0L, &(ret.hi), FILE_CURRENT); + + return ret; +} + +int file_type(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(char *name) +{ + HANDLE h; + WIN32_FIND_DATA fdat; + char *findfile; + DirHandle *ret; + + /* Enumerate files in dir `foo'. */ + findfile = dupcat(name, "/*", NULL); + h = FindFirstFile(findfile, &fdat); + if (h == INVALID_HANDLE_VALUE) + 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; + int ok = FindNextFile(dir->h, &fdat); + if (!ok) + 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(char *name, int 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; +}; + +/* + * Return a pointer to the portion of str that comes after the last + * slash (or backslash or colon, if `local' is TRUE). + */ +static char *stripslashes(char *str, int local) +{ + char *p; + + if (local) { + p = strchr(str, ':'); + if (p) str = p+1; + } + + p = strrchr(str, '/'); + if (p) str = p+1; + + if (local) { + p = strrchr(str, '\\'); + if (p) str = p+1; + } + + return str; +} + +WildcardMatcher *begin_wildcard_matching(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, 1); + *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, NULL); + + return ret; +} + +char *wildcard_get_filename(WildcardMatcher *dir) +{ + while (!dir->name) { + WIN32_FIND_DATA fdat; + int ok = FindNextFile(dir->h, &fdat); + + if (!ok) + 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, NULL); + } + + 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); +} + +int vet_filename(char *name) +{ + if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':')) + return FALSE; + + if (!name[strspn(name, ".")]) /* entirely composed of dots */ + return FALSE; + + return TRUE; +} + +int create_directory(char *name) +{ + return CreateDirectory(name, NULL) != 0; +} + +char *dir_file_cat(char *dir, char *file) +{ + return dupcat(dir, "\\", file, NULL); +} + +/* ---------------------------------------------------------------------- + * Platform-specific network handling. + */ + +/* + * Be told what socket we're supposed to be using. + */ +static SOCKET sftp_ssh_socket = INVALID_SOCKET; +static HANDLE netevent = INVALID_HANDLE_VALUE; +char *do_select(SOCKET skt, int startup) +{ + int events; + if (startup) + sftp_ssh_socket = skt; + else + sftp_ssh_socket = INVALID_SOCKET; + + if (p_WSAEventSelect) { + if (startup) { + events = (FD_CONNECT | FD_READ | FD_WRITE | + FD_OOB | FD_CLOSE | FD_ACCEPT); + netevent = CreateEvent(NULL, FALSE, FALSE, NULL); + } else { + events = 0; + } + if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) { + switch (p_WSAGetLastError()) { + case WSAENETDOWN: + return "Network is down"; + default: + return "WSAEventSelect(): unknown error"; + } + } + } + return NULL; +} +extern int select_result(WPARAM, LPARAM); + +int do_eventsel_loop(HANDLE other_event) +{ + int n, nhandles, nallhandles, netindex, otherindex; + long next, ticks; + HANDLE *handles; + SOCKET *sklist; + int skcount; + long now = GETTICKCOUNT(); + + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + if (ticks < 0) ticks = 0; /* just in case */ + } else { + ticks = INFINITE; + } + + handles = handle_get_events(&nhandles); + handles = sresize(handles, nhandles+2, HANDLE); + nallhandles = nhandles; + + if (netevent != INVALID_HANDLE_VALUE) + handles[netindex = nallhandles++] = netevent; + else + netindex = -1; + if (other_event != INVALID_HANDLE_VALUE) + handles[otherindex = nallhandles++] = other_event; + else + otherindex = -1; + + n = WaitForMultipleObjects(nallhandles, handles, FALSE, ticks); + + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { + handle_got_event(handles[n - WAIT_OBJECT_0]); + } else if (netindex >= 0 && n == WAIT_OBJECT_0 + netindex) { + WSANETWORKEVENTS things; + SOCKET socket; + extern SOCKET first_socket(int *), next_socket(int *); + extern int select_result(WPARAM, LPARAM); + 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. */ + sklist = snewn(i, SOCKET); + + /* 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(socket); + noise_ultralight(things.lNetworkEvents); + + 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); + } + } + } + + sfree(sklist); + } + + sfree(handles); + + if (n == WAIT_TIMEOUT) { + now = next; + } else { + now = GETTICKCOUNT(); + } + + if (otherindex >= 0 && n == WAIT_OBJECT_0 + otherindex) + return 1; + + return 0; +} + +/* + * 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; + long now = GETTICKCOUNT(); + + if (sftp_ssh_socket == INVALID_SOCKET) + return -1; /* doom */ + + if (socket_writable(sftp_ssh_socket)) + select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_WRITE); + + do { + long next, ticks; + struct timeval tv, *ptv; + + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + if (ticks <= 0) + ticks = 1; /* just in case */ + tv.tv_sec = ticks / 1000; + tv.tv_usec = ticks % 1000 * 1000; + ptv = &tv; + } else { + ptv = NULL; + } + + FD_ZERO(&readfds); + FD_SET(sftp_ssh_socket, &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) sftp_ssh_socket, (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(char *prompt, int no_fds_ok) +{ + int ret; + struct command_read_ctx actx, *ctx = &actx; + DWORD threadid; + + fputs(prompt, stdout); + fflush(stdout); + + if ((sftp_ssh_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; + + if (!CreateThread(NULL, 0, command_read_thread, + ctx, 0, &threadid)) { + fprintf(stderr, "Unable to create command input thread\n"); + cleanup_exit(1); + } + + do { + ret = do_eventsel_loop(ctx->event); + + /* Error return can only occur if netevent==NULL, and it ain't. */ + assert(ret >= 0); + } while (ret == 0); + + return ctx->line; +} + +/* ---------------------------------------------------------------------- + * Main program. Parse arguments etc. + */ +int main(int argc, char *argv[]) +{ + int ret; + + ret = psftp_main(argc, argv); + + return ret; +} diff --git a/putty/WINDOWS/WINSTORE.C b/putty/WINDOWS/WINSTORE.C new file mode 100644 index 0000000..13ee184 --- /dev/null +++ b/putty/WINDOWS/WINSTORE.C @@ -0,0 +1,826 @@ +/* + * winstore.c: Windows-specific implementation of the interface + * defined in storage.h. + */ + +#include +#include +#include +#include "putty.h" +#include "storage.h" + +#include +#ifndef CSIDL_APPDATA +#define CSIDL_APPDATA 0x001a +#endif +#ifndef CSIDL_LOCAL_APPDATA +#define CSIDL_LOCAL_APPDATA 0x001c +#endif + +static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist"; +static const char *const reg_jumplist_value = "Recent sessions"; +static const char *const puttystr = PUTTY_REG_POS "\\Sessions"; + +static const char hex[16] = "0123456789ABCDEF"; + +static int tried_shgetfolderpath = FALSE; +static HMODULE shell32_module = NULL; +DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA, + (HWND, int, HANDLE, DWORD, LPSTR)); + +static void mungestr(const char *in, char *out) +{ + int candot = 0; + + while (*in) { + if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' || + *in == '%' || *in < ' ' || *in > '~' || (*in == '.' + && !candot)) { + *out++ = '%'; + *out++ = hex[((unsigned char) *in) >> 4]; + *out++ = hex[((unsigned char) *in) & 15]; + } else + *out++ = *in; + in++; + candot = 1; + } + *out = '\0'; + return; +} + +static void unmungestr(const char *in, char *out, int outlen) +{ + while (*in) { + if (*in == '%' && in[1] && in[2]) { + int i, j; + + i = in[1] - '0'; + i -= (i > 9 ? 7 : 0); + j = in[2] - '0'; + j -= (j > 9 ? 7 : 0); + + *out++ = (i << 4) + j; + if (!--outlen) + return; + in += 3; + } else { + *out++ = *in++; + if (!--outlen) + return; + } + } + *out = '\0'; + return; +} + +void *open_settings_w(const char *sessionname, char **errmsg) +{ + HKEY subkey1, sesskey; + int ret; + char *p; + + *errmsg = NULL; + + if (!sessionname || !*sessionname) + sessionname = "Default Settings"; + + p = snewn(3 * strlen(sessionname) + 1, char); + mungestr(sessionname, p); + + ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1); + if (ret != ERROR_SUCCESS) { + sfree(p); + *errmsg = dupprintf("Unable to create registry key\n" + "HKEY_CURRENT_USER\\%s", puttystr); + return NULL; + } + ret = RegCreateKey(subkey1, p, &sesskey); + RegCloseKey(subkey1); + if (ret != ERROR_SUCCESS) { + *errmsg = dupprintf("Unable to create registry key\n" + "HKEY_CURRENT_USER\\%s\\%s", puttystr, p); + sfree(p); + return NULL; + } + sfree(p); + return (void *) sesskey; +} + +void write_setting_s(void *handle, const char *key, const char *value) +{ + if (handle) + RegSetValueEx((HKEY) handle, key, 0, REG_SZ, value, + 1 + strlen(value)); +} + +void write_setting_i(void *handle, const char *key, int value) +{ + if (handle) + RegSetValueEx((HKEY) handle, key, 0, REG_DWORD, + (CONST BYTE *) &value, sizeof(value)); +} + +void close_settings_w(void *handle) +{ + RegCloseKey((HKEY) handle); +} + +void *open_settings_r(const char *sessionname) +{ + HKEY subkey1, sesskey; + char *p; + + if (!sessionname || !*sessionname) + sessionname = "Default Settings"; + + p = snewn(3 * strlen(sessionname) + 1, char); + mungestr(sessionname, p); + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) { + sesskey = NULL; + } else { + if (RegOpenKey(subkey1, p, &sesskey) != ERROR_SUCCESS) { + sesskey = NULL; + } + RegCloseKey(subkey1); + } + + sfree(p); + + return (void *) sesskey; +} + +char *read_setting_s(void *handle, const char *key, char *buffer, int buflen) +{ + DWORD type, size; + size = buflen; + + if (!handle || + RegQueryValueEx((HKEY) handle, key, 0, + &type, buffer, &size) != ERROR_SUCCESS || + type != REG_SZ) return NULL; + else + return buffer; +} + +int read_setting_i(void *handle, const char *key, int defvalue) +{ + DWORD type, val, size; + size = sizeof(val); + + if (!handle || + RegQueryValueEx((HKEY) handle, key, 0, &type, + (BYTE *) &val, &size) != ERROR_SUCCESS || + size != sizeof(val) || type != REG_DWORD) + return defvalue; + else + return val; +} + +int read_setting_fontspec(void *handle, const char *name, FontSpec *result) +{ + char *settingname; + FontSpec ret; + + if (!read_setting_s(handle, name, ret.name, sizeof(ret.name))) + return 0; + settingname = dupcat(name, "IsBold", NULL); + ret.isbold = read_setting_i(handle, settingname, -1); + sfree(settingname); + if (ret.isbold == -1) return 0; + settingname = dupcat(name, "CharSet", NULL); + ret.charset = read_setting_i(handle, settingname, -1); + sfree(settingname); + if (ret.charset == -1) return 0; + settingname = dupcat(name, "Height", NULL); + ret.height = read_setting_i(handle, settingname, INT_MIN); + sfree(settingname); + if (ret.height == INT_MIN) return 0; + *result = ret; + return 1; +} + +void write_setting_fontspec(void *handle, const char *name, FontSpec font) +{ + char *settingname; + + write_setting_s(handle, name, font.name); + settingname = dupcat(name, "IsBold", NULL); + write_setting_i(handle, settingname, font.isbold); + sfree(settingname); + settingname = dupcat(name, "CharSet", NULL); + write_setting_i(handle, settingname, font.charset); + sfree(settingname); + settingname = dupcat(name, "Height", NULL); + write_setting_i(handle, settingname, font.height); + sfree(settingname); +} + +int read_setting_filename(void *handle, const char *name, Filename *result) +{ + return !!read_setting_s(handle, name, result->path, sizeof(result->path)); +} + +void write_setting_filename(void *handle, const char *name, Filename result) +{ + write_setting_s(handle, name, result.path); +} + +void close_settings_r(void *handle) +{ + RegCloseKey((HKEY) handle); +} + +void del_settings(const char *sessionname) +{ + HKEY subkey1; + char *p; + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) + return; + + p = snewn(3 * strlen(sessionname) + 1, char); + mungestr(sessionname, p); + RegDeleteKey(subkey1, p); + sfree(p); + + RegCloseKey(subkey1); + + remove_session_from_jumplist(sessionname); +} + +struct enumsettings { + HKEY key; + int i; +}; + +void *enum_settings_start(void) +{ + struct enumsettings *ret; + HKEY key; + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS) + return NULL; + + ret = snew(struct enumsettings); + if (ret) { + ret->key = key; + ret->i = 0; + } + + return ret; +} + +char *enum_settings_next(void *handle, char *buffer, int buflen) +{ + struct enumsettings *e = (struct enumsettings *) handle; + char *otherbuf; + otherbuf = snewn(3 * buflen, char); + if (RegEnumKey(e->key, e->i++, otherbuf, 3 * buflen) == ERROR_SUCCESS) { + unmungestr(otherbuf, buffer, buflen); + sfree(otherbuf); + return buffer; + } else { + sfree(otherbuf); + return NULL; + } +} + +void enum_settings_finish(void *handle) +{ + struct enumsettings *e = (struct enumsettings *) handle; + RegCloseKey(e->key); + sfree(e); +} + +static void hostkey_regname(char *buffer, const char *hostname, + int port, const char *keytype) +{ + int len; + strcpy(buffer, keytype); + strcat(buffer, "@"); + len = strlen(buffer); + len += sprintf(buffer + len, "%d:", port); + mungestr(hostname, buffer + strlen(buffer)); +} + +int verify_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + char *otherstr, *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. + */ + otherstr = snewn(len, char); + regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char); + + hostkey_regname(regname, hostname, port, keytype); + + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", + &rkey) != ERROR_SUCCESS) + return 1; /* key does not exist in registry */ + + readlen = len; + ret = RegQueryValueEx(rkey, regname, NULL, &type, otherstr, &readlen); + + if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA && + !strcmp(keytype, "rsa")) { + /* + * Key didn't exist. If the key type is RSA, we'll try + * another trick, which is to look up the _old_ key format + * under just the hostname and translate that. + */ + char *justhost = regname + 1 + strcspn(regname, ":"); + char *oldstyle = snewn(len + 10, char); /* safety margin */ + readlen = len; + ret = RegQueryValueEx(rkey, justhost, NULL, &type, + oldstyle, &readlen); + + if (ret == ERROR_SUCCESS && type == REG_SZ) { + /* + * The old format is two old-style bignums separated by + * a slash. An old-style bignum is made of groups of + * four hex digits: digits are ordered in sensible + * (most to least significant) order within each group, + * but groups are ordered in silly (least to most) + * order within the bignum. The new format is two + * ordinary C-format hex numbers (0xABCDEFG...XYZ, with + * A nonzero except in the special case 0x0, which + * doesn't appear anyway in RSA keys) separated by a + * comma. All hex digits are lowercase in both formats. + */ + char *p = otherstr; + char *q = oldstyle; + int i, j; + + for (i = 0; i < 2; i++) { + int ndigits, nwords; + *p++ = '0'; + *p++ = 'x'; + ndigits = strcspn(q, "/"); /* find / or end of string */ + nwords = ndigits / 4; + /* now trim ndigits to remove leading zeros */ + while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1) + ndigits--; + /* now move digits over to new string */ + for (j = 0; j < ndigits; j++) + p[ndigits - 1 - j] = q[j ^ 3]; + p += ndigits; + q += nwords * 4; + if (*q) { + q++; /* eat the slash */ + *p++ = ','; /* add a comma */ + } + *p = '\0'; /* terminate the string */ + } + + /* + * Now _if_ this key matches, we'll enter it in the new + * format. If not, we'll assume something odd went + * wrong, and hyper-cautiously do nothing. + */ + if (!strcmp(otherstr, key)) + RegSetValueEx(rkey, regname, 0, REG_SZ, otherstr, + strlen(otherstr) + 1); + } + } + + RegCloseKey(rkey); + + compare = strcmp(otherstr, key); + + sfree(otherstr); + sfree(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 */ +} + +void store_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + char *regname; + HKEY rkey; + + regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char); + + hostkey_regname(regname, hostname, port, keytype); + + if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", + &rkey) == ERROR_SUCCESS) { + RegSetValueEx(rkey, regname, 0, REG_SZ, key, strlen(key) + 1); + RegCloseKey(rkey); + } /* else key does not exist in registry */ + + sfree(regname); +} + +/* + * Open (or delete) the random seed file. + */ +enum { DEL, OPEN_R, OPEN_W }; +static int try_random_seed(char const *path, int action, HANDLE *ret) +{ + if (action == DEL) { + remove(path); + *ret = INVALID_HANDLE_VALUE; + return FALSE; /* so we'll do the next ones too */ + } + + *ret = CreateFile(path, + action == OPEN_W ? GENERIC_WRITE : GENERIC_READ, + action == OPEN_W ? 0 : (FILE_SHARE_READ | + FILE_SHARE_WRITE), + NULL, + action == OPEN_W ? CREATE_ALWAYS : OPEN_EXISTING, + action == OPEN_W ? FILE_ATTRIBUTE_NORMAL : 0, + NULL); + + return (*ret != INVALID_HANDLE_VALUE); +} + +static HANDLE access_random_seed(int action) +{ + HKEY rkey; + DWORD type, size; + HANDLE rethandle; + char seedpath[2 * MAX_PATH + 10] = "\0"; + + /* + * Iterate over a selection of possible random seed paths until + * we find one that works. + * + * We do this iteration separately for reading and writing, + * meaning that we will automatically migrate random seed files + * if a better location becomes available (by reading from the + * best location in which we actually find one, and then + * writing to the best location in which we can _create_ one). + */ + + /* + * First, try the location specified by the user in the + * Registry, if any. + */ + size = sizeof(seedpath); + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) == + ERROR_SUCCESS) { + int ret = RegQueryValueEx(rkey, "RandSeedFile", + 0, &type, seedpath, &size); + if (ret != ERROR_SUCCESS || type != REG_SZ) + seedpath[0] = '\0'; + RegCloseKey(rkey); + + if (*seedpath && try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + + /* + * Next, try the user's local Application Data directory, + * followed by their non-local one. This is found using the + * SHGetFolderPath function, which won't be present on all + * versions of Windows. + */ + if (!tried_shgetfolderpath) { + /* This is likely only to bear fruit on systems with IE5+ + * installed, or WinMe/2K+. There is some faffing with + * SHFOLDER.DLL we could do to try to find an equivalent + * on older versions of Windows if we cared enough. + * However, the invocation below requires IE5+ anyway, + * so stuff that. */ + shell32_module = load_system32_dll("shell32.dll"); + GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA); + tried_shgetfolderpath = TRUE; + } + if (p_SHGetFolderPathA) { + if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, + NULL, SHGFP_TYPE_CURRENT, seedpath))) { + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + + if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA, + NULL, SHGFP_TYPE_CURRENT, seedpath))) { + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + } + + /* + * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the + * user's home directory. + */ + { + int len, ret; + + len = + GetEnvironmentVariable("HOMEDRIVE", seedpath, + sizeof(seedpath)); + ret = + GetEnvironmentVariable("HOMEPATH", seedpath + len, + sizeof(seedpath) - len); + if (ret != 0) { + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + } + } + + /* + * And finally, fall back to C:\WINDOWS. + */ + GetWindowsDirectory(seedpath, sizeof(seedpath)); + strcat(seedpath, "\\PUTTY.RND"); + if (try_random_seed(seedpath, action, &rethandle)) + return rethandle; + + /* + * If even that failed, give up. + */ + return INVALID_HANDLE_VALUE; +} + +void read_random_seed(noise_consumer_t consumer) +{ + HANDLE seedf = access_random_seed(OPEN_R); + + if (seedf != INVALID_HANDLE_VALUE) { + while (1) { + char buf[1024]; + DWORD len; + + if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len) + consumer(buf, len); + else + break; + } + CloseHandle(seedf); + } +} + +void write_random_seed(void *data, int len) +{ + HANDLE seedf = access_random_seed(OPEN_W); + + if (seedf != INVALID_HANDLE_VALUE) { + DWORD lenwritten; + + WriteFile(seedf, data, len, &lenwritten, NULL); + CloseHandle(seedf); + } +} + +/* + * Internal function supporting the jump list registry code. All the + * functions to add, remove and read the list have substantially + * similar content, so this is a generalisation of all of them which + * transforms the list in the registry by prepending 'add' (if + * non-null), removing 'rem' from what's left (if non-null), and + * returning the resulting concatenated list of strings in 'out' (if + * non-null). + */ +static int transform_jumplist_registry + (const char *add, const char *rem, char **out) +{ + int ret; + HKEY pjumplist_key, psettings_tmp; + DWORD type; + int value_length; + char *old_value, *new_value; + char *piterator_old, *piterator_new, *piterator_tmp; + + ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL, + REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL, + &pjumplist_key, NULL); + if (ret != ERROR_SUCCESS) { + return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE; + } + + /* Get current list of saved sessions in the registry. */ + value_length = 200; + old_value = snewn(value_length, char); + ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type, + old_value, &value_length); + /* When the passed buffer is too small, ERROR_MORE_DATA is + * returned and the required size is returned in the length + * argument. */ + if (ret == ERROR_MORE_DATA) { + sfree(old_value); + old_value = snewn(value_length, char); + ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type, + old_value, &value_length); + } + + if (ret == ERROR_FILE_NOT_FOUND) { + /* Value doesn't exist yet. Start from an empty value. */ + *old_value = '\0'; + *(old_value + 1) = '\0'; + } else if (ret != ERROR_SUCCESS) { + /* Some non-recoverable error occurred. */ + sfree(old_value); + RegCloseKey(pjumplist_key); + return JUMPLISTREG_ERROR_VALUEREAD_FAILURE; + } else if (type != REG_MULTI_SZ) { + /* The value present in the registry has the wrong type: we + * try to delete it and start from an empty value. */ + ret = RegDeleteValue(pjumplist_key, reg_jumplist_value); + if (ret != ERROR_SUCCESS) { + sfree(old_value); + RegCloseKey(pjumplist_key); + return JUMPLISTREG_ERROR_VALUEREAD_FAILURE; + } + + *old_value = '\0'; + *(old_value + 1) = '\0'; + } + + /* Check validity of registry data: REG_MULTI_SZ value must end + * with \0\0. */ + piterator_tmp = old_value; + while (((piterator_tmp - old_value) < (value_length - 1)) && + !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) { + ++piterator_tmp; + } + + if ((piterator_tmp - old_value) >= (value_length-1)) { + /* Invalid value. Start from an empty value. */ + *old_value = '\0'; + *(old_value + 1) = '\0'; + } + + /* + * Modify the list, if we're modifying. + */ + if (add || rem) { + /* Walk through the existing list and construct the new list of + * saved sessions. */ + new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char); + piterator_new = new_value; + piterator_old = old_value; + + /* First add the new item to the beginning of the list. */ + if (add) { + strcpy(piterator_new, add); + piterator_new += strlen(piterator_new) + 1; + } + /* Now add the existing list, taking care to leave out the removed + * item, if it was already in the existing list. */ + while (*piterator_old != '\0') { + if (!rem || strcmp(piterator_old, rem) != 0) { + /* Check if this is a valid session, otherwise don't add. */ + psettings_tmp = open_settings_r(piterator_old); + if (psettings_tmp != NULL) { + close_settings_r(psettings_tmp); + strcpy(piterator_new, piterator_old); + piterator_new += strlen(piterator_new) + 1; + } + } + piterator_old += strlen(piterator_old) + 1; + } + *piterator_new = '\0'; + ++piterator_new; + + /* Save the new list to the registry. */ + ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ, + new_value, piterator_new - new_value); + + sfree(old_value); + old_value = new_value; + } else + ret = ERROR_SUCCESS; + + /* + * Either return or free the result. + */ + if (out) + *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) != ERROR_SUCCESS) { + 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/putty/WINDOWS/WINSTUFF.H b/putty/WINDOWS/WINSTUFF.H new file mode 100644 index 0000000..81890a8 --- /dev/null +++ b/putty/WINDOWS/WINSTUFF.H @@ -0,0 +1,555 @@ +/* + * winstuff.h: Windows-specific inter-module stuff. + */ + +#ifndef PUTTY_WINSTUFF_H +#define PUTTY_WINSTUFF_H + +#ifndef AUTO_WINSOCK +#include +#endif +#include +#include /* for FILENAME_MAX */ + +#include "tree234.h" + +#include "winhelp.h" + +struct Filename { + char path[FILENAME_MAX]; +}; +#define f_open(filename, mode, isprivate) ( fopen((filename).path, (mode)) ) + +struct FontSpec { + char name[64]; + int isbold; + int height; + int charset; +}; + +#ifndef CLEARTYPE_QUALITY +#define CLEARTYPE_QUALITY 5 +#endif +#define FONT_QUALITY(fq) ( \ + (fq) == FQ_DEFAULT ? DEFAULT_QUALITY : \ + (fq) == FQ_ANTIALIASED ? ANTIALIASED_QUALITY : \ + (fq) == FQ_NONANTIALIASED ? NONANTIALIASED_QUALITY : \ + CLEARTYPE_QUALITY) + +#define PLATFORM_IS_UTF16 /* enable UTF-16 processing when exchanging + * wchar_t strings with environment */ + +/* + * Where we can, we use GetWindowLongPtr and friends because they're + * more useful on 64-bit platforms, but they're a relatively recent + * innovation, missing from VC++ 6 and older MinGW. Degrade nicely. + * (NB that on some systems, some of these things are available but + * not others...) + */ + +#ifndef GCLP_HCURSOR +/* GetClassLongPtr and friends */ +#undef GetClassLongPtr +#define GetClassLongPtr GetClassLong +#undef SetClassLongPtr +#define SetClassLongPtr SetClassLong +#define GCLP_HCURSOR GCL_HCURSOR +/* GetWindowLongPtr and friends */ +#undef GetWindowLongPtr +#define GetWindowLongPtr GetWindowLong +#undef SetWindowLongPtr +#define SetWindowLongPtr SetWindowLong +#undef GWLP_USERDATA +#define GWLP_USERDATA GWL_USERDATA +#undef DWLP_MSGRESULT +#define DWLP_MSGRESULT DWL_MSGRESULT +/* Since we've clobbered the above functions, we should clobber the + * associated type regardless of whether it's defined. */ +#undef LONG_PTR +#define LONG_PTR LONG +#endif + +#define BOXFLAGS DLGWINDOWEXTRA +#define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR)) +#define DF_END 0x0001 + +/* + * Dynamically linked functions. These come in two flavours: + * + * - GET_WINDOWS_FUNCTION does not expose "name" to the preprocessor, + * so will always dynamically link against exactly what is specified + * in "name". If you're not sure, use this one. + * + * - GET_WINDOWS_FUNCTION_PP allows "name" to be redirected via + * preprocessor definitions like "#define foo bar"; this is principally + * intended for the ANSI/Unicode DoSomething/DoSomethingA/DoSomethingW. + * If your function has an argument of type "LPTSTR" or similar, this + * is the variant to use. + * (However, it can't always be used, as it trips over more complicated + * macro trickery such as the WspiapiGetAddrInfo wrapper for getaddrinfo.) + * + * (DECL_WINDOWS_FUNCTION works with both these variants.) + */ +#define DECL_WINDOWS_FUNCTION(linkage, rettype, name, params) \ + typedef rettype (WINAPI *t_##name) params; \ + linkage t_##name p_##name +#define STR1(x) #x +#define STR(x) STR1(x) +#define GET_WINDOWS_FUNCTION_PP(module, name) \ + (p_##name = module ? (t_##name) GetProcAddress(module, STR(name)) : NULL) +#define GET_WINDOWS_FUNCTION(module, name) \ + (p_##name = module ? (t_##name) GetProcAddress(module, #name) : NULL) + +/* + * Global variables. Most modules declare these `extern', but + * window.c will do `#define PUTTY_DO_GLOBALS' before including this + * module, and so will get them properly defined. +*/ +#ifndef GLOBAL +#ifdef PUTTY_DO_GLOBALS +#define GLOBAL +#else +#define GLOBAL extern +#endif +#endif + +#ifndef DONE_TYPEDEFS +#define DONE_TYPEDEFS +typedef struct config_tag Config; +typedef struct backend_tag Backend; +typedef struct terminal_tag Terminal; +#endif + +#define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY" +#define PUTTY_REG_PARENT "Software\\SimonTatham" +#define PUTTY_REG_PARENT_CHILD "PuTTY" +#define PUTTY_REG_GPARENT "Software" +#define PUTTY_REG_GPARENT_CHILD "SimonTatham" + +/* Result values for the jumplist registry functions. */ +#define JUMPLISTREG_OK 0 +#define JUMPLISTREG_ERROR_INVALID_PARAMETER 1 +#define JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE 2 +#define JUMPLISTREG_ERROR_VALUEREAD_FAILURE 3 +#define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4 +#define JUMPLISTREG_ERROR_INVALID_VALUE 5 + +#define PUTTY_HELP_FILE "putty.hlp" +#define PUTTY_CHM_FILE "putty.chm" +#define PUTTY_HELP_CONTENTS "putty.cnt" + +#define GETTICKCOUNT GetTickCount +#define CURSORBLINK GetCaretBlinkTime() +#define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */ + +#define DEFAULT_CODEPAGE CP_ACP + +typedef HDC Context; + +typedef unsigned int uint32; /* int is 32-bits on Win32 and Win64. */ +#define PUTTY_UINT32_DEFINED + +#ifndef NO_GSSAPI +/* + * GSS-API stuff + */ +#define GSS_CC CALLBACK +/* +typedef struct Ssh_gss_buf { + size_t length; + char *value; +} Ssh_gss_buf; + +#define SSH_GSS_EMPTY_BUF (Ssh_gss_buf) {0,NULL} +typedef void *Ssh_gss_name; +*/ +#endif + +/* + * Window handles for the windows that can be running during a + * PuTTY session. + */ +GLOBAL HWND hwnd; /* the main terminal window */ +GLOBAL HWND logbox; + +/* + * The all-important instance handle. + */ +GLOBAL HINSTANCE hinst; + +/* + * Help file stuff in winhelp.c. + */ +void init_help(void); +void shutdown_help(void); +int has_help(void); +void launch_help(HWND hwnd, const char *topic); +void quit_help(HWND hwnd); + +/* + * The terminal and logging context are notionally local to the + * Windows front end, but they must be shared between window.c and + * windlg.c. Likewise the saved-sessions list. + */ +GLOBAL Terminal *term; +GLOBAL void *logctx; + +#define WM_NETEVENT (WM_APP + 5) + +/* + * On Windows, we send MA_2CLK as the only event marking the second + * press of a mouse button. Compare unix.h. + */ +#define MULTICLICK_ONLY_EVENT 1 + +/* + * On Windows, data written to the clipboard must be NUL-terminated. + */ +#define SELECTION_NUL_TERMINATED 1 + +/* + * On Windows, copying to the clipboard terminates lines with CRLF. + */ +#define SEL_NL { 13, 10 } + +/* + * sk_getxdmdata() does not exist under Windows (not that I + * couldn't write it if I wanted to, but I haven't bothered), so + * it's a macro which always returns NULL. With any luck this will + * cause the compiler to notice it can optimise away the + * implementation of XDM-AUTHORIZATION-1 in x11fwd.c :-) + */ +#define sk_getxdmdata(socket, lenp) (NULL) + +/* + * File-selector filter strings used in the config box. On Windows, + * these strings are of exactly the type needed to go in + * `lpstrFilter' in an OPENFILENAME structure. + */ +#define FILTER_KEY_FILES ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \ + "All Files (*.*)\0*\0\0\0") +#define FILTER_WAVE_FILES ("Wave Files (*.wav)\0*.WAV\0" \ + "All Files (*.*)\0*\0\0\0") +#define FILTER_DYNLIB_FILES ("Dynamic Library Files (*.dll)\0*.dll\0" \ + "All Files (*.*)\0*\0\0\0") + +/* + * On some versions of Windows, it has been known for WM_TIMER to + * occasionally get its callback time simply wrong, and call us + * back several minutes early. Defining these symbols enables + * compensation code in timing.c. + */ +#define TIMING_SYNC +#define TIMING_SYNC_TICKCOUNT + +/* + * winnet.c dynamically loads WinSock 2 or WinSock 1 depending on + * what it can get, which means any WinSock routines used outside + * that module must be exported from it as function pointers. So + * here they are. + */ +DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAAsyncSelect, + (SOCKET, HWND, u_int, long)); +DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEventSelect, + (SOCKET, WSAEVENT, long)); +DECL_WINDOWS_FUNCTION(GLOBAL, int, select, + (int, fd_set FAR *, fd_set FAR *, + fd_set FAR *, const struct timeval FAR *)); +DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAGetLastError, (void)); +DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEnumNetworkEvents, + (SOCKET, WSAEVENT, LPWSANETWORKEVENTS)); + +extern int socket_writable(SOCKET skt); + +extern void socket_reselect_all(void); + +/* + * Exports from winctrls.c. + */ + +struct ctlpos { + HWND hwnd; + WPARAM font; + int dlu4inpix; + int ypos, width; + int xoff; + int boxystart, boxid; + char *boxtext; +}; + +/* + * Exports from winutils.c. + */ +typedef struct filereq_tag filereq; /* cwd for file requester */ +BOOL request_file(filereq *state, OPENFILENAME *of, int preserve, int save); +filereq *filereq_new(void); +void filereq_free(filereq *state); +int message_box(LPCTSTR text, LPCTSTR caption, DWORD style, DWORD helpctxid); +void split_into_argv(char *, int *, char ***, char ***); + +/* + * Private structure for prefslist state. Only in the header file + * so that we can delegate allocation to callers. + */ +struct prefslist { + int listid, upbid, dnbid; + int srcitem; + int dummyitem; + int dragging; +}; + +/* + * This structure is passed to event handler functions as the `dlg' + * parameter, and hence is passed back to winctrls access functions. + */ +struct dlgparam { + HWND hwnd; /* the hwnd of the dialog box */ + struct winctrls *controltrees[8]; /* can have several of these */ + int nctrltrees; + char *wintitle; /* title of actual window */ + char *errtitle; /* title of error sub-messageboxes */ + void *data; /* data to pass in refresh events */ + union control *focused, *lastfocused; /* which ctrl has focus now/before */ + char shortcuts[128]; /* track which shortcuts in use */ + int coloursel_wanted; /* has an event handler asked for + * a colour selector? */ + struct { unsigned char r, g, b, ok; } coloursel_result; /* 0-255 */ + tree234 *privdata; /* stores per-control private data */ + int ended, endresult; /* has the dialog been ended? */ + int fixed_pitch_fonts; /* are we constrained to fixed fonts? */ +}; + +/* + * Exports from winctrls.c. + */ +void ctlposinit(struct ctlpos *cp, HWND hwnd, + int leftborder, int rightborder, int topborder); +HWND doctl(struct ctlpos *cp, RECT r, + char *wclass, int wstyle, int exstyle, char *wtext, int wid); +void bartitle(struct ctlpos *cp, char *name, int id); +void beginbox(struct ctlpos *cp, char *name, int idbox); +void endbox(struct ctlpos *cp); +void editboxfw(struct ctlpos *cp, int password, char *text, + int staticid, int editid); +void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...); +void bareradioline(struct ctlpos *cp, int nacross, ...); +void radiobig(struct ctlpos *cp, char *text, int id, ...); +void checkbox(struct ctlpos *cp, char *text, int id); +void statictext(struct ctlpos *cp, char *text, int lines, int id); +void staticbtn(struct ctlpos *cp, char *stext, int sid, + char *btext, int bid); +void static2btn(struct ctlpos *cp, char *stext, int sid, + char *btext1, int bid1, char *btext2, int bid2); +void staticedit(struct ctlpos *cp, char *stext, + int sid, int eid, int percentedit); +void staticddl(struct ctlpos *cp, char *stext, + int sid, int lid, int percentlist); +void combobox(struct ctlpos *cp, char *text, int staticid, int listid); +void staticpassedit(struct ctlpos *cp, char *stext, + int sid, int eid, int percentedit); +void bigeditctrl(struct ctlpos *cp, char *stext, + int sid, int eid, int lines); +void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id); +void editbutton(struct ctlpos *cp, char *stext, int sid, + int eid, char *btext, int bid); +void sesssaver(struct ctlpos *cp, char *text, + int staticid, int editid, int listid, ...); +void envsetter(struct ctlpos *cp, char *stext, int sid, + char *e1stext, int e1sid, int e1id, + char *e2stext, int e2sid, int e2id, + int listid, char *b1text, int b1id, char *b2text, int b2id); +void charclass(struct ctlpos *cp, char *stext, int sid, int listid, + char *btext, int bid, int eid, char *s2text, int s2id); +void colouredit(struct ctlpos *cp, char *stext, int sid, int listid, + char *btext, int bid, ...); +void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines, + char *stext, int sid, int listid, int upbid, int dnbid); +int handle_prefslist(struct prefslist *hdl, + int *array, int maxmemb, + int is_dlmsg, HWND hwnd, + WPARAM wParam, LPARAM lParam); +void progressbar(struct ctlpos *cp, int id); +void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid, + char *e1stext, int e1sid, int e1id, + char *e2stext, int e2sid, int e2id, + char *btext, int bid, + char *r1text, int r1id, char *r2text, int r2id); + +void dlg_auto_set_fixed_pitch_flag(void *dlg); +int dlg_get_fixed_pitch_flag(void *dlg); +void dlg_set_fixed_pitch_flag(void *dlg, int flag); + +#define MAX_SHORTCUTS_PER_CTRL 16 + +/* + * This structure is what's stored for each `union control' in the + * portable-dialog interface. + */ +struct winctrl { + union control *ctrl; + /* + * The control may have several components at the Windows + * level, with different dialog IDs. To avoid needing N + * separate platformsidectrl structures (which could be stored + * separately in a tree234 so that lookup by ID worked), we + * impose the constraint that those IDs must be in a contiguous + * block. + */ + int base_id; + int num_ids; + /* + * Remember what keyboard shortcuts were used by this control, + * so that when we remove it again we can take them out of the + * list in the dlgparam. + */ + char shortcuts[MAX_SHORTCUTS_PER_CTRL]; + /* + * Some controls need a piece of allocated memory in which to + * store temporary data about the control. + */ + void *data; +}; +/* + * And this structure holds a set of the above, in two separate + * tree234s so that it can find an item by `union control' or by + * dialog ID. + */ +struct winctrls { + tree234 *byctrl, *byid; +}; +struct controlset; +struct controlbox; + +void winctrl_init(struct winctrls *); +void winctrl_cleanup(struct winctrls *); +void winctrl_add(struct winctrls *, struct winctrl *); +void winctrl_remove(struct winctrls *, struct winctrl *); +struct winctrl *winctrl_findbyctrl(struct winctrls *, union control *); +struct winctrl *winctrl_findbyid(struct winctrls *, int); +struct winctrl *winctrl_findbyindex(struct winctrls *, int); +void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, + struct ctlpos *cp, struct controlset *s, int *id); +int winctrl_handle_command(struct dlgparam *dp, UINT msg, + WPARAM wParam, LPARAM lParam); +void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c); +int winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id); + +void dp_init(struct dlgparam *dp); +void dp_add_tree(struct dlgparam *dp, struct winctrls *tree); +void dp_cleanup(struct dlgparam *dp); + +/* + * Exports from wincfg.c. + */ +void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help, + int midsession, int protocol); + +/* + * Exports from windlg.c. + */ +void defuse_showwindow(void); +int do_config(void); +int do_reconfig(HWND, int); +void showeventlog(HWND); +void showabout(HWND); +void force_normal(HWND hwnd); +void modal_about_box(HWND hwnd); +void show_help(HWND hwnd); + +/* + * Exports from winmisc.c. + */ +extern OSVERSIONINFO osVersion; +BOOL init_winver(void); +HMODULE load_system32_dll(const char *libname); + +/* + * Exports from sizetip.c. + */ +void UpdateSizeTip(HWND src, int cx, int cy); +void EnableSizeTip(int bEnable); + +/* + * Exports from unicode.c. + */ +struct unicode_data; +void init_ucs(Config *, struct unicode_data *); + +/* + * Exports from winhandl.c. + */ +#define HANDLE_FLAG_OVERLAPPED 1 +#define HANDLE_FLAG_IGNOREEOF 2 +#define HANDLE_FLAG_UNITBUFFER 4 +struct handle; +typedef int (*handle_inputfn_t)(struct handle *h, void *data, int len); +typedef void (*handle_outputfn_t)(struct handle *h, int new_backlog); +struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, + void *privdata, int flags); +struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, + void *privdata, int flags); +int handle_write(struct handle *h, const void *data, int len); +HANDLE *handle_get_events(int *nevents); +void handle_free(struct handle *h); +void handle_got_event(HANDLE event); +void handle_unthrottle(struct handle *h, int backlog); +int handle_backlog(struct handle *h); +void *handle_get_privdata(struct handle *h); + +/* + * winpgntc.c needs to schedule callbacks for asynchronous agent + * requests. This has to be done differently in GUI and console, so + * there's an exported function used for the purpose. + * + * Also, we supply FLAG_SYNCAGENT to force agent requests to be + * synchronous in pscp and psftp. + */ +void agent_schedule_callback(void (*callback)(void *, void *, int), + void *callback_ctx, void *data, int len); +#define FLAG_SYNCAGENT 0x1000 + +/* + * winpgntc.c also exports these two functions which are used by the + * server side of Pageant as well, to get the user SID for comparing + * with clients'. + */ +int init_advapi(void); /* initialises everything needed by get_user_sid */ +PSID get_user_sid(void); + +/* + * Exports from winser.c. + */ +extern Backend serial_backend; + +/* + * Exports from winjump.c. + */ +#define JUMPLIST_SUPPORTED /* suppress #defines in putty.h */ +void add_session_to_jumplist(const char * const sessionname); +void remove_session_from_jumplist(const char * const sessionname); +void clear_jumplist(void); + +/* + * Extra functions in winstore.c over and above the interface in + * storage.h. + * + * These functions manipulate the Registry section which mirrors the + * current Windows 7 jump list. (Because the real jump list storage is + * write-only, we need to keep another copy of whatever we put in it, + * so that we can put in a slightly modified version the next time.) + */ + +/* Adds a saved session to the registry jump list mirror. 'item' is a + * string naming a saved session. */ +int add_to_jumplist_registry(const char *item); + +/* Removes an item from the registry jump list mirror. */ +int remove_from_jumplist_registry(const char *item); + +/* Returns the current jump list entries from the registry. Caller + * must free the returned pointer, which points to a contiguous + * sequence of NUL-terminated strings in memory, terminated with an + * empty one. */ +char *get_jumplist_registry_entries(void); + +#endif diff --git a/putty/WINDOWS/WINTIME.C b/putty/WINDOWS/WINTIME.C new file mode 100644 index 0000000..99564d2 --- /dev/null +++ b/putty/WINDOWS/WINTIME.C @@ -0,0 +1,24 @@ +/* + * wintime.c - Avoid trouble with time() returning (time_t)-1 on Windows. + */ + +#include "putty.h" +#include + +struct tm ltime(void) +{ + SYSTEMTIME st; + struct tm tm; + + GetLocalTime(&st); + tm.tm_sec=st.wSecond; + tm.tm_min=st.wMinute; + tm.tm_hour=st.wHour; + tm.tm_mday=st.wDay; + tm.tm_mon=st.wMonth-1; + tm.tm_year=(st.wYear>=1900?st.wYear-1900:0); + tm.tm_wday=st.wDayOfWeek; + tm.tm_yday=-1; /* GetLocalTime doesn't tell us */ + tm.tm_isdst=0; /* GetLocalTime doesn't tell us */ + return tm; +} diff --git a/putty/WINDOWS/WINUCS.C b/putty/WINDOWS/WINUCS.C new file mode 100644 index 0000000..1b72147 --- /dev/null +++ b/putty/WINDOWS/WINUCS.C @@ -0,0 +1,1251 @@ +#include +#include +#include +#include +#include + +#include "putty.h" +#include "terminal.h" +#include "misc.h" + +/* Character conversion arrays; they are usually taken from windows, + * the xterm one has the four scanlines that have no unicode 2.0 + * equivalents mapped to their unicode 3.0 locations. + */ +static const WCHAR unitab_xterm_std[32] = { + 0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, + 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, + 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, + 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020 +}; + +/* + * If the codepage is non-zero it's a window codepage, zero means use a + * local codepage. The name is always converted to the first of any + * duplicate definitions. + */ + +/* + * Tables for ISO-8859-{1-10,13-16} derived from those downloaded + * 2001-10-02 from -- jtn + * Table for ISO-8859-11 derived from same on 2002-11-18. -- bjh21 + */ + +/* XXX: This could be done algorithmically, but I'm not sure it's + * worth the hassle -- jtn */ +/* ISO/IEC 8859-1:1998 (Latin-1, "Western", "West European") */ +static const wchar_t iso_8859_1[] = { + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF +}; + +/* ISO/IEC 8859-2:1999 (Latin-2, "Central European", "East European") */ +static const wchar_t iso_8859_2[] = { + 0x00A0, 0x0104, 0x02D8, 0x0141, 0x00A4, 0x013D, 0x015A, 0x00A7, + 0x00A8, 0x0160, 0x015E, 0x0164, 0x0179, 0x00AD, 0x017D, 0x017B, + 0x00B0, 0x0105, 0x02DB, 0x0142, 0x00B4, 0x013E, 0x015B, 0x02C7, + 0x00B8, 0x0161, 0x015F, 0x0165, 0x017A, 0x02DD, 0x017E, 0x017C, + 0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7, + 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E, + 0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7, + 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF, + 0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7, + 0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F, + 0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7, + 0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9 +}; + +/* ISO/IEC 8859-3:1999 (Latin-3, "South European", "Maltese & Esperanto") */ +static const wchar_t iso_8859_3[] = { + 0x00A0, 0x0126, 0x02D8, 0x00A3, 0x00A4, 0xFFFD, 0x0124, 0x00A7, + 0x00A8, 0x0130, 0x015E, 0x011E, 0x0134, 0x00AD, 0xFFFD, 0x017B, + 0x00B0, 0x0127, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x0125, 0x00B7, + 0x00B8, 0x0131, 0x015F, 0x011F, 0x0135, 0x00BD, 0xFFFD, 0x017C, + 0x00C0, 0x00C1, 0x00C2, 0xFFFD, 0x00C4, 0x010A, 0x0108, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0xFFFD, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x0120, 0x00D6, 0x00D7, + 0x011C, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x016C, 0x015C, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0xFFFD, 0x00E4, 0x010B, 0x0109, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0xFFFD, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x0121, 0x00F6, 0x00F7, + 0x011D, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x016D, 0x015D, 0x02D9 +}; + +/* ISO/IEC 8859-4:1998 (Latin-4, "North European") */ +static const wchar_t iso_8859_4[] = { + 0x00A0, 0x0104, 0x0138, 0x0156, 0x00A4, 0x0128, 0x013B, 0x00A7, + 0x00A8, 0x0160, 0x0112, 0x0122, 0x0166, 0x00AD, 0x017D, 0x00AF, + 0x00B0, 0x0105, 0x02DB, 0x0157, 0x00B4, 0x0129, 0x013C, 0x02C7, + 0x00B8, 0x0161, 0x0113, 0x0123, 0x0167, 0x014A, 0x017E, 0x014B, + 0x0100, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x012E, + 0x010C, 0x00C9, 0x0118, 0x00CB, 0x0116, 0x00CD, 0x00CE, 0x012A, + 0x0110, 0x0145, 0x014C, 0x0136, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00D8, 0x0172, 0x00DA, 0x00DB, 0x00DC, 0x0168, 0x016A, 0x00DF, + 0x0101, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x012F, + 0x010D, 0x00E9, 0x0119, 0x00EB, 0x0117, 0x00ED, 0x00EE, 0x012B, + 0x0111, 0x0146, 0x014D, 0x0137, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x0173, 0x00FA, 0x00FB, 0x00FC, 0x0169, 0x016B, 0x02D9 +}; + +/* ISO/IEC 8859-5:1999 (Latin/Cyrillic) */ +static const wchar_t iso_8859_5[] = { + 0x00A0, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, + 0x0408, 0x0409, 0x040A, 0x040B, 0x040C, 0x00AD, 0x040E, 0x040F, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + 0x2116, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457, + 0x0458, 0x0459, 0x045A, 0x045B, 0x045C, 0x00A7, 0x045E, 0x045F +}; + +/* ISO/IEC 8859-6:1999 (Latin/Arabic) */ +static const wchar_t iso_8859_6[] = { + 0x00A0, 0xFFFD, 0xFFFD, 0xFFFD, 0x00A4, 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0x060C, 0x00AD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD, 0xFFFD, 0x061B, 0xFFFD, 0xFFFD, 0xFFFD, 0x061F, + 0xFFFD, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, + 0x0638, 0x0639, 0x063A, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, + 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, + 0x0648, 0x0649, 0x064A, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, + 0x0650, 0x0651, 0x0652, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD +}; + +/* ISO 8859-7:1987 (Latin/Greek) */ +static const wchar_t iso_8859_7[] = { + 0x00A0, 0x2018, 0x2019, 0x00A3, 0xFFFD, 0xFFFD, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0xFFFD, 0x00AB, 0x00AC, 0x00AD, 0xFFFD, 0x2015, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x0384, 0x0385, 0x0386, 0x00B7, + 0x0388, 0x0389, 0x038A, 0x00BB, 0x038C, 0x00BD, 0x038E, 0x038F, + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, + 0x03A0, 0x03A1, 0xFFFD, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, + 0x03A8, 0x03A9, 0x03AA, 0x03AB, 0x03AC, 0x03AD, 0x03AE, 0x03AF, + 0x03B0, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, + 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, + 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, + 0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03CC, 0x03CD, 0x03CE, 0xFFFD +}; + +/* ISO/IEC 8859-8:1999 (Latin/Hebrew) */ +static const wchar_t iso_8859_8[] = { + 0x00A0, 0xFFFD, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00D7, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00F7, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0xFFFD, + 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0x2017, + 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, + 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, + 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, + 0x05E8, 0x05E9, 0x05EA, 0xFFFD, 0xFFFD, 0x200E, 0x200F, 0xFFFD +}; + +/* ISO/IEC 8859-9:1999 (Latin-5, "Turkish") */ +static const wchar_t iso_8859_9[] = { + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x011E, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0130, 0x015E, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x011F, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0131, 0x015F, 0x00FF +}; + +/* ISO/IEC 8859-10:1998 (Latin-6, "Nordic" [Sami, Inuit, Icelandic]) */ +static const wchar_t iso_8859_10[] = { + 0x00A0, 0x0104, 0x0112, 0x0122, 0x012A, 0x0128, 0x0136, 0x00A7, + 0x013B, 0x0110, 0x0160, 0x0166, 0x017D, 0x00AD, 0x016A, 0x014A, + 0x00B0, 0x0105, 0x0113, 0x0123, 0x012B, 0x0129, 0x0137, 0x00B7, + 0x013C, 0x0111, 0x0161, 0x0167, 0x017E, 0x2015, 0x016B, 0x014B, + 0x0100, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x012E, + 0x010C, 0x00C9, 0x0118, 0x00CB, 0x0116, 0x00CD, 0x00CE, 0x00CF, + 0x00D0, 0x0145, 0x014C, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x0168, + 0x00D8, 0x0172, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, + 0x0101, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x012F, + 0x010D, 0x00E9, 0x0119, 0x00EB, 0x0117, 0x00ED, 0x00EE, 0x00EF, + 0x00F0, 0x0146, 0x014D, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x0169, + 0x00F8, 0x0173, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x0138 +}; + +/* ISO/IEC 8859-11:2001 ("Thai", "TIS620") */ +static const wchar_t iso_8859_11[] = { + 0x00A0, 0x0E01, 0x0E02, 0x0E03, 0x0E04, 0x0E05, 0x0E06, 0x0E07, + 0x0E08, 0x0E09, 0x0E0A, 0x0E0B, 0x0E0C, 0x0E0D, 0x0E0E, 0x0E0F, + 0x0E10, 0x0E11, 0x0E12, 0x0E13, 0x0E14, 0x0E15, 0x0E16, 0x0E17, + 0x0E18, 0x0E19, 0x0E1A, 0x0E1B, 0x0E1C, 0x0E1D, 0x0E1E, 0x0E1F, + 0x0E20, 0x0E21, 0x0E22, 0x0E23, 0x0E24, 0x0E25, 0x0E26, 0x0E27, + 0x0E28, 0x0E29, 0x0E2A, 0x0E2B, 0x0E2C, 0x0E2D, 0x0E2E, 0x0E2F, + 0x0E30, 0x0E31, 0x0E32, 0x0E33, 0x0E34, 0x0E35, 0x0E36, 0x0E37, + 0x0E38, 0x0E39, 0x0E3A, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0x0E3F, + 0x0E40, 0x0E41, 0x0E42, 0x0E43, 0x0E44, 0x0E45, 0x0E46, 0x0E47, + 0x0E48, 0x0E49, 0x0E4A, 0x0E4B, 0x0E4C, 0x0E4D, 0x0E4E, 0x0E4F, + 0x0E50, 0x0E51, 0x0E52, 0x0E53, 0x0E54, 0x0E55, 0x0E56, 0x0E57, + 0x0E58, 0x0E59, 0x0E5A, 0x0E5B, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD +}; + +/* ISO/IEC 8859-13:1998 (Latin-7, "Baltic Rim") */ +static const wchar_t iso_8859_13[] = { + 0x00A0, 0x201D, 0x00A2, 0x00A3, 0x00A4, 0x201E, 0x00A6, 0x00A7, + 0x00D8, 0x00A9, 0x0156, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00C6, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x201C, 0x00B5, 0x00B6, 0x00B7, + 0x00F8, 0x00B9, 0x0157, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00E6, + 0x0104, 0x012E, 0x0100, 0x0106, 0x00C4, 0x00C5, 0x0118, 0x0112, + 0x010C, 0x00C9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012A, 0x013B, + 0x0160, 0x0143, 0x0145, 0x00D3, 0x014C, 0x00D5, 0x00D6, 0x00D7, + 0x0172, 0x0141, 0x015A, 0x016A, 0x00DC, 0x017B, 0x017D, 0x00DF, + 0x0105, 0x012F, 0x0101, 0x0107, 0x00E4, 0x00E5, 0x0119, 0x0113, + 0x010D, 0x00E9, 0x017A, 0x0117, 0x0123, 0x0137, 0x012B, 0x013C, + 0x0161, 0x0144, 0x0146, 0x00F3, 0x014D, 0x00F5, 0x00F6, 0x00F7, + 0x0173, 0x0142, 0x015B, 0x016B, 0x00FC, 0x017C, 0x017E, 0x2019 +}; + +/* ISO/IEC 8859-14:1998 (Latin-8, "Celtic", "Gaelic/Welsh") */ +static const wchar_t iso_8859_14[] = { + 0x00A0, 0x1E02, 0x1E03, 0x00A3, 0x010A, 0x010B, 0x1E0A, 0x00A7, + 0x1E80, 0x00A9, 0x1E82, 0x1E0B, 0x1EF2, 0x00AD, 0x00AE, 0x0178, + 0x1E1E, 0x1E1F, 0x0120, 0x0121, 0x1E40, 0x1E41, 0x00B6, 0x1E56, + 0x1E81, 0x1E57, 0x1E83, 0x1E60, 0x1EF3, 0x1E84, 0x1E85, 0x1E61, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x0174, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x1E6A, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x0176, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x0175, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x1E6B, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x0177, 0x00FF +}; + +/* ISO/IEC 8859-15:1999 (Latin-9 aka -0, "euro") */ +static const wchar_t iso_8859_15[] = { + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x20AC, 0x00A5, 0x0160, 0x00A7, + 0x0161, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x017D, 0x00B5, 0x00B6, 0x00B7, + 0x017E, 0x00B9, 0x00BA, 0x00BB, 0x0152, 0x0153, 0x0178, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF +}; + +/* ISO/IEC 8859-16:2001 (Latin-10, "Balkan") */ +static const wchar_t iso_8859_16[] = { + 0x00A0, 0x0104, 0x0105, 0x0141, 0x20AC, 0x201E, 0x0160, 0x00A7, + 0x0161, 0x00A9, 0x0218, 0x00AB, 0x0179, 0x00AD, 0x017A, 0x017B, + 0x00B0, 0x00B1, 0x010C, 0x0142, 0x017D, 0x201D, 0x00B6, 0x00B7, + 0x017E, 0x010D, 0x0219, 0x00BB, 0x0152, 0x0153, 0x0178, 0x017C, + 0x00C0, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0106, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x0110, 0x0143, 0x00D2, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x015A, + 0x0170, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0118, 0x021A, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x0107, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x0111, 0x0144, 0x00F2, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x015B, + 0x0171, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0119, 0x021B, 0x00FF +}; + +static const wchar_t roman8[] = { + 0x00A0, 0x00C0, 0x00C2, 0x00C8, 0x00CA, 0x00CB, 0x00CE, 0x00CF, + 0x00B4, 0x02CB, 0x02C6, 0x00A8, 0x02DC, 0x00D9, 0x00DB, 0x20A4, + 0x00AF, 0x00DD, 0x00FD, 0x00B0, 0x00C7, 0x00E7, 0x00D1, 0x00F1, + 0x00A1, 0x00BF, 0x00A4, 0x00A3, 0x00A5, 0x00A7, 0x0192, 0x00A2, + 0x00E2, 0x00EA, 0x00F4, 0x00FB, 0x00E1, 0x00E9, 0x00F3, 0x00FA, + 0x00E0, 0x00E8, 0x00F2, 0x00F9, 0x00E4, 0x00EB, 0x00F6, 0x00FC, + 0x00C5, 0x00EE, 0x00D8, 0x00C6, 0x00E5, 0x00ED, 0x00F8, 0x00E6, + 0x00C4, 0x00EC, 0x00D6, 0x00DC, 0x00C9, 0x00EF, 0x00DF, 0x00D4, + 0x00C1, 0x00C3, 0x00E3, 0x00D0, 0x00F0, 0x00CD, 0x00CC, 0x00D3, + 0x00D2, 0x00D5, 0x00F5, 0x0160, 0x0161, 0x00DA, 0x0178, 0x00FF, + 0x00DE, 0x00FE, 0x00B7, 0x00B5, 0x00B6, 0x00BE, 0x2014, 0x00BC, + 0x00BD, 0x00AA, 0x00BA, 0x00AB, 0x25A0, 0x00BB, 0x00B1, 0xFFFD +}; + +static const wchar_t koi8_u[] = { + 0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524, + 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590, + 0x2591, 0x2592, 0x2593, 0x2320, 0x25A0, 0x2022, 0x221A, 0x2248, + 0x2264, 0x2265, 0x00A0, 0x2321, 0x00B0, 0x00B2, 0x00B7, 0x00F7, + 0x2550, 0x2551, 0x2552, 0x0451, 0x0454, 0x2554, 0x0456, 0x0457, + 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x0491, 0x255D, 0x255E, + 0x255F, 0x2560, 0x2561, 0x0401, 0x0404, 0x2563, 0x0406, 0x0407, + 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x0490, 0x256C, 0x00A9, + 0x044E, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, + 0x0445, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, + 0x043F, 0x044F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, + 0x044C, 0x044B, 0x0437, 0x0448, 0x044D, 0x0449, 0x0447, 0x044A, + 0x042E, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, + 0x0425, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, + 0x041F, 0x042F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, + 0x042C, 0x042B, 0x0417, 0x0428, 0x042D, 0x0429, 0x0427, 0x042A +}; + +static const wchar_t vscii[] = { + 0x0000, 0x0001, 0x1EB2, 0x0003, 0x0004, 0x1EB4, 0x1EAA, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x1EF6, 0x0015, 0x0016, 0x0017, + 0x0018, 0x1EF8, 0x001a, 0x001b, 0x001c, 0x001d, 0x1EF4, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007f, + 0x1EA0, 0x1EAE, 0x1EB0, 0x1EB6, 0x1EA4, 0x1EA6, 0x1EA8, 0x1EAC, + 0x1EBC, 0x1EB8, 0x1EBE, 0x1EC0, 0x1EC2, 0x1EC4, 0x1EC6, 0x1ED0, + 0x1ED2, 0x1ED4, 0x1ED6, 0x1ED8, 0x1EE2, 0x1EDA, 0x1EDC, 0x1EDE, + 0x1ECA, 0x1ECE, 0x1ECC, 0x1EC8, 0x1EE6, 0x0168, 0x1EE4, 0x1EF2, + 0x00D5, 0x1EAF, 0x1EB1, 0x1EB7, 0x1EA5, 0x1EA7, 0x1EA8, 0x1EAD, + 0x1EBD, 0x1EB9, 0x1EBF, 0x1EC1, 0x1EC3, 0x1EC5, 0x1EC7, 0x1ED1, + 0x1ED3, 0x1ED5, 0x1ED7, 0x1EE0, 0x01A0, 0x1ED9, 0x1EDD, 0x1EDF, + 0x1ECB, 0x1EF0, 0x1EE8, 0x1EEA, 0x1EEC, 0x01A1, 0x1EDB, 0x01AF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x1EA2, 0x0102, 0x1EB3, 0x1EB5, + 0x00C8, 0x00C9, 0x00CA, 0x1EBA, 0x00CC, 0x00CD, 0x0128, 0x1EF3, + 0x0110, 0x1EE9, 0x00D2, 0x00D3, 0x00D4, 0x1EA1, 0x1EF7, 0x1EEB, + 0x1EED, 0x00D9, 0x00DA, 0x1EF9, 0x1EF5, 0x00DD, 0x1EE1, 0x01B0, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x1EA3, 0x0103, 0x1EEF, 0x1EAB, + 0x00E8, 0x00E9, 0x00EA, 0x1EBB, 0x00EC, 0x00ED, 0x0129, 0x1EC9, + 0x0111, 0x1EF1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x1ECF, 0x1ECD, + 0x1EE5, 0x00F9, 0x00FA, 0x0169, 0x1EE7, 0x00FD, 0x1EE3, 0x1EEE +}; + +static const wchar_t dec_mcs[] = { + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0xFFFD, 0x00A5, 0xFFFD, 0x00A7, + 0x00A4, 0x00A9, 0x00AA, 0x00AB, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0xFFFD, 0x00B5, 0x00B6, 0x00B7, + 0xFFFD, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0xFFFD, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0xFFFD, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x0152, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0178, 0xFFFD, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0xFFFD, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x0153, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FF, 0xFFFD, 0xFFFD +}; + +/* Mazovia (Polish) aka CP620 + * from "Mazowia to Unicode table", 04/24/96, Mikolaj Jedrzejak */ +static const wchar_t mazovia[] = { + /* Code point 0x9B is "zloty" symbol (zŽ), which is not + * widely used and for which there is no Unicode equivalent. + * One reference shows 0xA8 as U+00A7 SECTION SIGN, but we're + * told that's incorrect. */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x0105, 0x00E7, + 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0107, 0x00C4, 0x0104, + 0x0118, 0x0119, 0x0142, 0x00F4, 0x00F6, 0x0106, 0x00FB, 0x00F9, + 0x015a, 0x00D6, 0x00DC, 0xFFFD, 0x0141, 0x00A5, 0x015b, 0x0192, + 0x0179, 0x017b, 0x00F3, 0x00d3, 0x0144, 0x0143, 0x017a, 0x017c, + 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, + 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, + 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, + 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; + +struct cp_list_item { + char *name; + int codepage; + int cp_size; + const wchar_t *cp_table; +}; + +static const struct cp_list_item cp_list[] = { + {"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}, + + {"UTF-8", CP_UTF8}, + + {"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}, + {"CP878", 20866}, + + {"Use font encoding", -1}, + + {0, 0} +}; + +static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr); + +void init_ucs(Config *cfg, struct unicode_data *ucsdata) +{ + int i, j; + int used_dtf = 0; + char tbuf[256]; + + for (i = 0; i < 256; i++) + tbuf[i] = i; + + /* Decide on the Line and Font codepages */ + ucsdata->line_codepage = decode_codepage(cfg->line_codepage); + + if (ucsdata->font_codepage <= 0) { + ucsdata->font_codepage=0; + ucsdata->dbcs_screenfont=0; + } + + if (cfg->vtmode == VT_OEMONLY) { + ucsdata->font_codepage = 437; + ucsdata->dbcs_screenfont = 0; + 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 (cfg->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 (cfg->vtmode == VT_OEMANSI || cfg->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 || + cfg->vtmode == VT_POORMAN || ucsdata->font_codepage==0)) { + + /* For DBCS and POOR fonts force direct to font */ + used_dtf = 1; + 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 (cfg->vtmode == VT_OEMANSI || cfg->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 (cfg->vtmode == VT_OEMANSI || cfg->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 (cfg->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) { + /* + * Here we select a plausible default code page based on + * the locale the user is in. We wish to select an ISO code + * page or appropriate local default _rather_ than go with + * the Win125* series, because it's more important to have + * CSI and friends enabled by default than the ghastly + * Windows extra quote characters, and because it's more + * likely the user is connecting to a remote server that + * does something Unixy or VMSy and hence standards- + * compliant than that they're connecting back to a Windows + * box using horrible nonstandard charsets. + * + * Accordingly, Robert de Bath suggests a method for + * picking a default character set that runs as follows: + * first call GetACP to get the system's ANSI code page + * identifier, and translate as follows: + * + * 1250 -> ISO 8859-2 + * 1251 -> KOI8-U + * 1252 -> ISO 8859-1 + * 1253 -> ISO 8859-7 + * 1254 -> ISO 8859-9 + * 1255 -> ISO 8859-8 + * 1256 -> ISO 8859-6 + * 1257 -> ISO 8859-13 (changed from 8859-4 on advice of a Lithuanian) + * + * and for anything else, choose direct-to-font. + */ + int cp = GetACP(); + switch (cp) { + case 1250: cp_name = "ISO-8859-2"; break; + case 1251: cp_name = "KOI8-U"; break; + case 1252: cp_name = "ISO-8859-1"; break; + case 1253: cp_name = "ISO-8859-7"; break; + case 1254: cp_name = "ISO-8859-9"; break; + case 1255: cp_name = "ISO-8859-8"; break; + case 1256: cp_name = "ISO-8859-6"; break; + case 1257: cp_name = "ISO-8859-13"; break; + /* default: leave it blank, which will select -1, direct->font */ + } + } + + if (cp_name && *cp_name) + 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(*s++) != tolower(*d++)) + break; + } + } + + if (cp_name && *cp_name) { + d = cp_name; + if (tolower(d[0]) == 'c' && tolower(d[1]) == 'p') + d += 2; + if (tolower(d[0]) == 'i' && tolower(d[1]) == 'b' + && tolower(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, wchar_t *wcstr, int wclen, + char *mbstr, int mblen, char *defchr, int *defused, + 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; + if (ucsdata->uni_tbl && (p1 = ucsdata->uni_tbl[(ch >> 8) & 0xFF]) + && (by = p1[ch & 0xFF])) + *p++ = by; + else if (ch < 0x80) + *p++ = (char) ch; + else if (defchr) { + int j; + for (j = 0; defchr[j]; j++) + *p++ = defchr[j]; + if (defused) *defused = 1; + } +#if 1 + else + *p++ = '.'; +#endif + assert(p - mbstr < mblen); + } + return p - mbstr; + } else + return WideCharToMultiByte(codepage, flags, wcstr, wclen, + mbstr, mblen, defchr, defused); +} + +int mb_to_wc(int codepage, int flags, char *mbstr, int mblen, + wchar_t *wcstr, int wclen) +{ + return MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); +} + +int is_dbcs_leadbyte(int codepage, char byte) +{ + return IsDBCSLeadByteEx(codepage, byte); +} diff --git a/putty/WINDOWS/WINUTILS.C b/putty/WINDOWS/WINUTILS.C new file mode 100644 index 0000000..2daf1eb --- /dev/null +++ b/putty/WINDOWS/WINUTILS.C @@ -0,0 +1,598 @@ +/* + * winutils.c: miscellaneous Windows utilities for GUI apps + */ + +#include +#include +#include + +#include "putty.h" +#include "misc.h" + +#ifdef TESTMODE +/* Definitions to allow this module to be compiled standalone for testing + * split_into_argv(). */ +#define smalloc malloc +#define srealloc realloc +#define sfree free +#endif + +/* + * GetOpenFileName/GetSaveFileName tend to muck around with the process' + * working directory on at least some versions of Windows. + * Here's a wrapper that gives more control over this, and hides a little + * bit of other grottiness. + */ + +struct filereq_tag { + TCHAR cwd[MAX_PATH]; +}; + +/* + * `of' is expected to be initialised with most interesting fields, but + * this function does some administrivia. (assume `of' was memset to 0) + * save==1 -> GetSaveFileName; save==0 -> GetOpenFileName + * `state' is optional. + */ +BOOL request_file(filereq *state, OPENFILENAME *of, int preserve, int 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 = 0; + } + + /* 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. + */ + +/* Callback function to launch context help. */ +static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo) +{ + 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(hwnd, context); +} + +int message_box(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 = hwnd; + 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(void) +{ + message_box("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 (RSA), 1024-bit:\n" + " " PGP_RSA_MASTER_KEY_FP "\n" + "PuTTY Master Key (DSA), 1024-bit:\n" + " " PGP_DSA_MASTER_KEY_FP, + "PGP fingerprints", MB_ICONINFORMATION | MB_OK, + HELPCTXID(pgp_fingerprints)); +} + +/* + * Split a complete command line into argc/argv, attempting to do + * it exactly the same way Windows itself would do it (so that + * console utilities, which receive argc and argv from Windows, + * will have their command lines processed in the same way as GUI + * utilities which get a whole command line and must break it + * themselves). + * + * 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. + */ +void split_into_argv(char *cmdline, int *argc, char ***argv, + char ***argstart) +{ + char *p; + char *outputline, *q; + char **outputargv, **outputargstart; + int outputargc; + + /* + * At first glance the rules appeared to be: + * + * - 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. And the + * answer to that appears somewhat bizarre. 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,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 + * + * + * [Test fragment was of the form "a\\\"""b c" d.] + * + * 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. + */ + + /* + * 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) { + int 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 = 0; + + /* 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--, quote = 1; + + /* Now we 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); + } + } + } 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'. + */ + {"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}}, +}; + +int main(int argc, char **argv) +{ + int i, j; + + if (argc > 1) { + /* + * Generation of tests. + * + * Given `-splat ', we print out a C-style + * representation of each argument (in the form "a", "b", + * NULL), backslash-escaping each backslash and double + * quote. + * + * Given `-split ', we first doctor `string' by + * turning forward slashes into backslashes, single quotes + * into double quotes and underscores into spaces; and then + * we feed the resulting string to ourself with `-splat'. + * + * Given `-generate', we concoct a variety of fun test + * cases, encode them in quote-safe form (mapping \, " and + * space to /, ' and _ respectively) and feed each one to + * `-split'. + */ + if (!strcmp(argv[1], "-splat")) { + int i; + char *p; + for (i = 2; i < argc; i++) { + putchar('"'); + for (p = argv[i]; *p; p++) { + if (*p == '\\' || *p == '"') + putchar('\\'); + putchar(*p); + } + printf("\", "); + } + printf("NULL"); + return 0; + } + + if (!strcmp(argv[1], "-split") && argc > 2) { + char *str = malloc(20 + strlen(argv[0]) + strlen(argv[2])); + char *p, *q; + + q = str + sprintf(str, "%s -splat ", argv[0]); + printf(" {\""); + for (p = argv[2]; *p; p++, q++) { + switch (*p) { + case '/': printf("\\\\"); *q = '\\'; break; + case '\'': printf("\\\""); *q = '"'; break; + case '_': printf(" "); *q = ' '; break; + default: putchar(*p); *q = *p; break; + } + } + *p = '\0'; + printf("\", {"); + fflush(stdout); + + system(str); + + printf("}},\n"); + + return 0; + } + + if (!strcmp(argv[1], "-generate")) { + char *teststr, *p; + int i, initialquote, backslashes, quotes; + + teststr = malloc(200 + strlen(argv[0])); + + for (initialquote = 0; initialquote <= 1; initialquote++) { + for (backslashes = 0; backslashes < 5; backslashes++) { + for (quotes = 0; quotes < 9; quotes++) { + p = teststr + sprintf(teststr, "%s -split ", argv[0]); + if (initialquote) *p++ = '\''; + *p++ = 'a'; + for (i = 0; i < backslashes; i++) *p++ = '/'; + for (i = 0; i < quotes; i++) *p++ = '\''; + *p++ = 'b'; + *p++ = '_'; + *p++ = 'c'; + *p++ = '\''; + *p++ = '_'; + *p++ = 'd'; + *p = '\0'; + + system(teststr); + } + } + } + return 0; + } + + fprintf(stderr, "unrecognised option: \"%s\"\n", argv[1]); + return 1; + } + + /* + * If we get here, we were invoked with no arguments, so just + * run the tests. + */ + + for (i = 0; i < lenof(argv_tests); i++) { + int ac; + char **av; + + split_into_argv(argv_tests[i].cmdline, &ac, &av); + + for (j = 0; j < ac && argv_tests[i].argv[j]; j++) { + if (strcmp(av[j], argv_tests[i].argv[j])) { + printf("failed test %d (|%s|) arg %d: |%s| should be |%s|\n", + i, argv_tests[i].cmdline, + j, av[j], argv_tests[i].argv[j]); + } +#ifdef VERBOSE + else { + printf("test %d (|%s|) arg %d: |%s| == |%s|\n", + i, argv_tests[i].cmdline, + j, av[j], argv_tests[i].argv[j]); + } +#endif + } + if (j < ac) + printf("failed test %d (|%s|): %d args returned, should be %d\n", + i, argv_tests[i].cmdline, ac, j); + if (argv_tests[i].argv[j]) + printf("failed test %d (|%s|): %d args returned, should be more\n", + i, argv_tests[i].cmdline, ac); + } + + return 0; +} + +#endif diff --git a/putty/WINDOWS/WINX11.C b/putty/WINDOWS/WINX11.C new file mode 100644 index 0000000..c8951b0 --- /dev/null +++ b/putty/WINDOWS/WINX11.C @@ -0,0 +1,18 @@ +/* + * winx11.c: fetch local auth data for X forwarding. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" + +void platform_get_x11_auth(struct X11Display *disp, const Config *cfg) +{ + if (cfg->xauthfile.path[0]) + x11_get_auth_from_authfile(disp, cfg->xauthfile.path); +} + +const int platform_uses_x11_unix_by_default = FALSE; diff --git a/putty/WINDOWS/WIN_RES.H b/putty/WINDOWS/WIN_RES.H new file mode 100644 index 0000000..3c5ec74 --- /dev/null +++ b/putty/WINDOWS/WIN_RES.H @@ -0,0 +1,34 @@ +/* + * 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 IDN_LIST 1001 +#define IDN_COPY 1002 + +#define IDA_ICON 1001 +#define IDA_TEXT1 1002 +#define IDA_VERSION 1003 +#define IDA_TEXT2 1004 +#define IDA_LICENCE 1005 +#define IDA_WEB 1006 + +#define IDC_TAB 1001 +#define IDC_TABSTATIC1 1002 +#define IDC_TABSTATIC2 1003 +#define IDC_TABLIST 1004 +#define IDC_HELPBTN 1005 +#define IDC_ABOUT 1006 + +#endif diff --git a/putty/WINDOWS/WIN_RES.RC2 b/putty/WINDOWS/WIN_RES.RC2 new file mode 100644 index 0000000..e159c56 --- /dev/null +++ b/putty/WINDOWS/WIN_RES.RC2 @@ -0,0 +1,92 @@ +/* + * 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. + * FIXME: This file is called '.rc2' rather than '.rc' to avoid MSVC trying + * to compile it on its own when using the project files. Nicer solutions + * welcome. + */ + +#include "win_res.h" + +IDI_MAINICON ICON "putty.ico" + +IDI_CFGICON ICON "puttycfg.ico" + +/* Accelerators used: clw */ +IDD_ABOUTBOX DIALOG DISCARDABLE 140, 40, 214, 70 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About PuTTY" +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "&Close", IDOK, 160, 52, 48, 14 + PUSHBUTTON "View &Licence", IDA_LICENCE, 6, 52, 70, 14 + PUSHBUTTON "Visit &Web Site", IDA_WEB, 84, 52, 70, 14 + CTEXT "PuTTY", IDA_TEXT1, 10, 6, 194, 8 + CTEXT "", IDA_VERSION, 10, 16, 194, 16 + CTEXT "\251 1997-2011 Simon Tatham. All rights reserved.", + IDA_TEXT2, 10, 34, 194, 16 +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, 226, 263 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Licence" +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "OK", IDOK, 98, 243, 44, 14 + + LTEXT "Copyright \251 1997-2011 Simon Tatham", 1000, 10, 10, 206, 8 + + LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8 + LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8 + LTEXT "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa,", 1003, 10, 42, 206, 8 + LTEXT "Markus Kuhn, Colin Watson, and CORE SDI S.A.", 1004, 10, 50, 206, 8 + + LTEXT "Permission is hereby granted, free of charge, to any person", 1005, 10, 66, 206, 8 + LTEXT "obtaining a copy of this software and associated documentation", 1006, 10, 74, 206, 8 + LTEXT "files (the ""Software""), to deal in the Software without restriction,", 1007, 10, 82, 206, 8 + LTEXT "including without limitation the rights to use, copy, modify, merge,", 1008, 10, 90, 206, 8 + LTEXT "publish, distribute, sublicense, and/or sell copies of the Software,", 1009, 10, 98, 206, 8 + LTEXT "and to permit persons to whom the Software is furnished to do so,", 1010, 10, 106, 206, 8 + LTEXT "subject to the following conditions:", 1011, 10, 114, 206, 8 + + LTEXT "The above copyright notice and this permission notice shall be", 1012, 10, 130, 206, 8 + LTEXT "included in all copies or substantial portions of the Software.", 1013, 10, 138, 206, 8 + + LTEXT "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT", 1014, 10, 154, 206, 8 + LTEXT "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,", 1015, 10, 162, 206, 8 + LTEXT "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", 1016, 10, 170, 206, 8 + LTEXT "MERCHANTABILITY, FITNESS FOR A PARTICULAR", 1017, 10, 178, 206, 8 + LTEXT "PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", 1018, 10, 186, 206, 8 + LTEXT "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES", 1019, 10, 194, 206, 8 + LTEXT "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,", 1020, 10, 202, 206, 8 + LTEXT "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN", 1021, 10, 210, 206, 8 + LTEXT "CONNECTION WITH THE SOFTWARE OR THE USE OR", 1022, 10, 218, 206, 8 + LTEXT "OTHER DEALINGS IN THE SOFTWARE.", 1023, 10, 226, 206, 8 + +END + +#include "version.rc2" diff --git a/putty/X11FWD.C b/putty/X11FWD.C new file mode 100644 index 0000000..9f22a23 --- /dev/null +++ b/putty/X11FWD.C @@ -0,0 +1,791 @@ +/* + * Platform-independent bits of X11 forwarding. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "tree234.h" + +#define GET_16BIT(endian, cp) \ + (endian=='B' ? GET_16BIT_MSB_FIRST(cp) : GET_16BIT_LSB_FIRST(cp)) + +#define PUT_16BIT(endian, cp, val) \ + (endian=='B' ? PUT_16BIT_MSB_FIRST(cp, val) : PUT_16BIT_LSB_FIRST(cp, val)) + +const char *const x11_authnames[] = { + "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1" +}; + +struct XDMSeen { + unsigned int time; + unsigned char clientid[6]; +}; + +struct X11Private { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + unsigned char firstpkt[12]; /* first X data packet */ + struct X11Display *disp; + char *auth_protocol; + unsigned char *auth_data; + int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize; + int verified; + int throttled, throttle_override; + unsigned long peer_ip; + int peer_port; + void *c; /* data used by ssh.c */ + Socket s; +}; + +static int xdmseen_cmp(void *a, void *b) +{ + struct XDMSeen *sa = a, *sb = b; + return sa->time > sb->time ? 1 : + sa->time < sb->time ? -1 : + memcmp(sa->clientid, sb->clientid, sizeof(sa->clientid)); +} + +/* Do-nothing "plug" implementation, used by x11_setup_display() when it + * creates a trial connection (and then immediately closes it). + * XXX: bit out of place here, could in principle live in a platform- + * independent network.c or something */ +static void dummy_plug_log(Plug p, int type, SockAddr addr, int port, + const char *error_msg, int error_code) { } +static int dummy_plug_closing + (Plug p, const char *error_msg, int error_code, int calling_back) +{ return 1; } +static int dummy_plug_receive(Plug p, int urgent, char *data, int len) +{ return 1; } +static void dummy_plug_sent(Plug p, int bufsize) { } +static int dummy_plug_accepting(Plug p, OSSocket sock) { return 1; } +static const struct plug_function_table dummy_plug = { + dummy_plug_log, dummy_plug_closing, dummy_plug_receive, + dummy_plug_sent, dummy_plug_accepting +}; + +struct X11Display *x11_setup_display(char *display, int authtype, + const Config *cfg) +{ + struct X11Display *disp = snew(struct X11Display); + char *localcopy; + int i; + + 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 = strrchr(localcopy, ':'); + if (!colon) { + sfree(disp); + sfree(localcopy); + return NULL; /* FIXME: report a specific error? */ + } + + *colon++ = '\0'; + dot = strchr(colon, '.'); + if (dot) + *dot++ = '\0'; + + disp->displaynum = atoi(colon); + if (dot) + disp->screennum = atoi(dot); + else + disp->screennum = 0; + + protocol = NULL; + hostname = localcopy; + if (colon > localcopy) { + slash = strchr(localcopy, '/'); + if (slash) { + *slash++ = '\0'; + protocol = localcopy; + hostname = slash; + } + } + + disp->hostname = *hostname ? dupstr(hostname) : NULL; + + if (protocol) + disp->unixdomain = (!strcmp(protocol, "local") || + !strcmp(protocol, "unix")); + else if (!*hostname || !strcmp(hostname, "unix")) + disp->unixdomain = platform_uses_x11_unix_by_default; + else + disp->unixdomain = FALSE; + + if (!disp->hostname && !disp->unixdomain) + disp->hostname = dupstr("localhost"); + + disp->unixsocketpath = NULL; + disp->addr = NULL; + + sfree(localcopy); + } + + /* + * Look up the display hostname, if we need to. + */ + if (!disp->unixdomain) { + const char *err; + + disp->port = 6000 + disp->displaynum; + disp->addr = name_lookup(disp->hostname, disp->port, + &disp->realhost, cfg, ADDRTYPE_UNSPEC); + + if ((err = sk_addr_error(disp->addr)) != NULL) { + sk_addr_free(disp->addr); + sfree(disp->hostname); + sfree(disp->unixsocketpath); + return NULL; /* FIXME: report an error */ + } + } + + /* + * Try upgrading an IP-style localhost display to a Unix-socket + * display (as the standard X connection libraries do). + */ + if (!disp->unixdomain && sk_address_is_local(disp->addr)) { + SockAddr ux = platform_get_x11_unix_address(NULL, disp->displaynum); + const char *err = sk_addr_error(ux); + if (!err) { + /* Create trial connection to see if there is a useful Unix-domain + * socket */ + const struct plug_function_table *dummy = &dummy_plug; + Socket s = sk_new(sk_addr_dup(ux), 0, 0, 0, 0, 0, (Plug)&dummy); + err = sk_socket_error(s); + sk_close(s); + } + if (err) { + sk_addr_free(ux); + } else { + sk_addr_free(disp->addr); + disp->unixdomain = TRUE; + disp->addr = ux; + /* Fill in the rest in a moment */ + } + } + + if (disp->unixdomain) { + if (!disp->addr) + disp->addr = platform_get_x11_unix_address(disp->unixsocketpath, + disp->displaynum); + if (disp->unixsocketpath) + disp->realhost = dupstr(disp->unixsocketpath); + else + disp->realhost = dupprintf("unix:%d", disp->displaynum); + disp->port = 0; + } + + /* + * Invent the remote authorisation details. + */ + if (authtype == X11_MIT) { + disp->remoteauthproto = X11_MIT; + + /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */ + disp->remoteauthdata = snewn(16, unsigned char); + for (i = 0; i < 16; i++) + disp->remoteauthdata[i] = random_byte(); + disp->remoteauthdatalen = 16; + + disp->xdmseen = NULL; + } else { + assert(authtype == X11_XDM); + disp->remoteauthproto = X11_XDM; + + /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */ + disp->remoteauthdata = snewn(16, unsigned char); + for (i = 0; i < 16; i++) + disp->remoteauthdata[i] = (i == 8 ? 0 : random_byte()); + disp->remoteauthdatalen = 16; + + disp->xdmseen = newtree234(xdmseen_cmp); + } + disp->remoteauthprotoname = dupstr(x11_authnames[disp->remoteauthproto]); + disp->remoteauthdatastring = snewn(disp->remoteauthdatalen * 2 + 1, char); + for (i = 0; i < disp->remoteauthdatalen; i++) + sprintf(disp->remoteauthdatastring + i*2, "%02x", + disp->remoteauthdata[i]); + + /* + * Fetch the local authorisation details. + */ + disp->localauthproto = X11_NO_AUTH; + disp->localauthdata = NULL; + disp->localauthdatalen = 0; + platform_get_x11_auth(disp, cfg); + + return disp; +} + +void x11_free_display(struct X11Display *disp) +{ + if (disp->xdmseen != NULL) { + struct XDMSeen *seen; + while ((seen = delpos234(disp->xdmseen, 0)) != NULL) + sfree(seen); + freetree234(disp->xdmseen); + } + sfree(disp->hostname); + sfree(disp->unixsocketpath); + if (disp->localauthdata) + memset(disp->localauthdata, 0, disp->localauthdatalen); + sfree(disp->localauthdata); + if (disp->remoteauthdata) + memset(disp->remoteauthdata, 0, disp->remoteauthdatalen); + sfree(disp->remoteauthdata); + sfree(disp->remoteauthprotoname); + sfree(disp->remoteauthdatastring); + sk_addr_free(disp->addr); + sfree(disp); +} + +#define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */ + +static char *x11_verify(unsigned long peer_ip, int peer_port, + struct X11Display *disp, char *proto, + unsigned char *data, int dlen) +{ + if (strcmp(proto, x11_authnames[disp->remoteauthproto]) != 0) + return "wrong authorisation protocol attempted"; + if (disp->remoteauthproto == X11_MIT) { + if (dlen != disp->remoteauthdatalen) + return "MIT-MAGIC-COOKIE-1 data was wrong length"; + if (memcmp(disp->remoteauthdata, data, dlen) != 0) + return "MIT-MAGIC-COOKIE-1 data did not match"; + } + if (disp->remoteauthproto == 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(disp->remoteauthdata+9, data, 24); + if (memcmp(disp->remoteauthdata, data, 8) != 0) + return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */ + if (GET_32BIT_MSB_FIRST(data+8) != peer_ip) + return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */ + if ((int)GET_16BIT_MSB_FIRST(data+12) != peer_port) + return "XDM-AUTHORIZATION-1 data failed check"; /* port wrong */ + t = GET_32BIT_MSB_FIRST(data+14); + for (i = 18; i < 24; i++) + if (data[i] != 0) /* zero padding wrong */ + return "XDM-AUTHORIZATION-1 data failed check"; + tim = time(NULL); + if (abs(t - tim) > XDM_MAXSKEW) + return "XDM-AUTHORIZATION-1 time stamp was too far out"; + seen = snew(struct XDMSeen); + seen->time = t; + memcpy(seen->clientid, data+8, 6); + assert(disp->xdmseen != NULL); + ret = add234(disp->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(disp->xdmseen, 0); + assert(seen != NULL); + if (t - seen->time <= XDM_MAXSKEW) + break; + sfree(delpos234(disp->xdmseen, 0)); + } + } + /* implement other protocols here if ever required */ + return NULL; +} + +void x11_get_auth_from_authfile(struct X11Display *disp, + const char *authfilename) +{ + FILE *authfp; + char *buf, *ptr, *str[4]; + int len[4]; + int family, protocol; + int ideal_match = FALSE; + char *ourhostname = get_hostname(); + + /* + * Normally we should look for precisely the details specified in + * `disp'. However, there's an oddity when the display is local: + * displays like "localhost:0" usually have their details stored + * in a Unix-domain-socket record (even if there isn't actually a + * real Unix-domain socket available, as with OpenSSH's proxy X11 + * server). + * + * This is apparently a fudge to get round the meaninglessness of + * "localhost" in a shared-home-directory context -- xauth entries + * for Unix-domain sockets already disambiguate this by storing + * the *local* hostname in the conveniently-blank hostname field, + * but IP "localhost" records couldn't do this. So, typically, an + * IP "localhost" entry in the auth database isn't present and if + * it were it would be ignored. + * + * However, we don't entirely trust that (say) Windows X servers + * won't rely on a straight "localhost" entry, bad idea though + * that is; so if we can't find a Unix-domain-socket entry we'll + * fall back to an IP-based entry if we can find one. + */ + int localhost = !disp->unixdomain && sk_address_is_local(disp->addr); + + authfp = fopen(authfilename, "rb"); + if (!authfp) + return; + + /* Records in .Xauthority contain four strings of up to 64K each */ + buf = snewn(65537 * 4, char); + + while (!ideal_match) { + int c, i, j, match = FALSE; + +#define GET do { c = fgetc(authfp); if (c == EOF) goto done; c = (unsigned char)c; } while (0) + /* Expect a big-endian 2-byte number giving address family */ + GET; family = c; + GET; family = (family << 8) | c; + /* Then expect four strings, each composed of a big-endian 2-byte + * length field followed by that many bytes of data */ + ptr = buf; + for (i = 0; i < 4; i++) { + GET; len[i] = c; + GET; len[i] = (len[i] << 8) | c; + str[i] = ptr; + for (j = 0; j < len[i]; j++) { + GET; *ptr++ = c; + } + *ptr++ = '\0'; + } +#undef GET + + /* + * Now we have a full X authority record in memory. See + * whether it matches the display we're trying to + * authenticate to. + * + * The details we've just read should be interpreted as + * follows: + * + * - 'family' is the network address family used to + * connect to the display. 0 means IPv4; 6 means IPv6; + * 256 means Unix-domain sockets. + * + * - str[0] is the network address itself. For IPv4 and + * IPv6, this is a string of binary data of the + * appropriate length (respectively 4 and 16 bytes) + * representing the address in big-endian format, e.g. + * 7F 00 00 01 means IPv4 localhost. For Unix-domain + * sockets, this is the host name of the machine on + * which the Unix-domain display resides (so that an + * .Xauthority file on a shared file system can contain + * authority entries for Unix-domain displays on + * several machines without them clashing). + * + * - str[1] is the display number. I've no idea why + * .Xauthority stores this as a string when it has a + * perfectly good integer format, but there we go. + * + * - str[2] is the authorisation method, encoded as its + * canonical string name (i.e. "MIT-MAGIC-COOKIE-1", + * "XDM-AUTHORIZATION-1" or something we don't + * recognise). + * + * - str[3] is the actual authorisation data, stored in + * binary form. + */ + + if (disp->displaynum < 0 || disp->displaynum != atoi(str[1])) + continue; /* not the one */ + + for (protocol = 1; protocol < lenof(x11_authnames); protocol++) + if (!strcmp(str[2], x11_authnames[protocol])) + break; + if (protocol == lenof(x11_authnames)) + continue; /* don't recognise this protocol, look for another */ + + switch (family) { + case 0: /* IPv4 */ + if (!disp->unixdomain && + sk_addrtype(disp->addr) == ADDRTYPE_IPV4) { + char buf[4]; + sk_addrcopy(disp->addr, buf); + if (len[0] == 4 && !memcmp(str[0], buf, 4)) { + match = TRUE; + /* If this is a "localhost" entry, note it down + * but carry on looking for a Unix-domain entry. */ + ideal_match = !localhost; + } + } + break; + case 6: /* IPv6 */ + if (!disp->unixdomain && + sk_addrtype(disp->addr) == ADDRTYPE_IPV6) { + char buf[16]; + sk_addrcopy(disp->addr, buf); + if (len[0] == 16 && !memcmp(str[0], buf, 16)) { + match = TRUE; + ideal_match = !localhost; + } + } + break; + case 256: /* Unix-domain / localhost */ + if ((disp->unixdomain || localhost) + && ourhostname && !strcmp(ourhostname, str[0])) + /* A matching Unix-domain socket is always the best + * match. */ + match = ideal_match = TRUE; + break; + } + + if (match) { + /* Current best guess -- may be overridden if !ideal_match */ + disp->localauthproto = protocol; + sfree(disp->localauthdata); /* free previous guess, if any */ + disp->localauthdata = snewn(len[3], unsigned char); + memcpy(disp->localauthdata, str[3], len[3]); + disp->localauthdatalen = len[3]; + } + } + + done: + fclose(authfp); + memset(buf, 0, 65537 * 4); + sfree(buf); + sfree(ourhostname); +} + +static void x11_log(Plug p, int type, SockAddr addr, int port, + const char *error_msg, int error_code) +{ + /* We have no interface to the logging module here, so we drop these. */ +} + +static int x11_closing(Plug plug, const char *error_msg, int error_code, + int calling_back) +{ + struct X11Private *pr = (struct X11Private *) plug; + + /* + * We have no way to communicate down the forwarded connection, + * so if an error occurred on the socket, we just ignore it + * and treat it like a proper close. + */ + sshfwd_close(pr->c); + x11_close(pr->s); + return 1; +} + +static int x11_receive(Plug plug, int urgent, char *data, int len) +{ + struct X11Private *pr = (struct X11Private *) plug; + + if (sshfwd_write(pr->c, data, len) > 0) { + pr->throttled = 1; + sk_set_frozen(pr->s, 1); + } + + return 1; +} + +static void x11_sent(Plug plug, int bufsize) +{ + struct X11Private *pr = (struct X11Private *) plug; + + sshfwd_unthrottle(pr->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 = strcspn(display, ":"); + if (!display[n]) + return 0; + n = strcspn(display, "."); + if (!display[n]) + return 0; + return atoi(display + n + 1); +} + +/* + * Called to set up the raw connection. + * + * Returns an error message, or NULL on success. + * also, fills the SocketsStructure + */ +extern const char *x11_init(Socket *s, struct X11Display *disp, void *c, + const char *peeraddr, int peerport, + const Config *cfg) +{ + static const struct plug_function_table fn_table = { + x11_log, + x11_closing, + x11_receive, + x11_sent, + NULL + }; + + const char *err; + struct X11Private *pr; + + /* + * Open socket. + */ + pr = snew(struct X11Private); + pr->fn = &fn_table; + pr->auth_protocol = NULL; + pr->disp = disp; + pr->verified = 0; + pr->data_read = 0; + pr->throttled = pr->throttle_override = 0; + pr->c = c; + + pr->s = *s = new_connection(sk_addr_dup(disp->addr), + disp->realhost, disp->port, + 0, 1, 0, 0, (Plug) pr, cfg); + if ((err = sk_socket_error(*s)) != NULL) { + sfree(pr); + return err; + } + + /* + * See if we can make sense of the peer address we were given. + */ + { + int i[4]; + if (peeraddr && + 4 == sscanf(peeraddr, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { + pr->peer_ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; + pr->peer_port = peerport; + } else { + pr->peer_ip = 0; + pr->peer_port = -1; + } + } + + sk_set_private_ptr(*s, pr); + return NULL; +} + +void x11_close(Socket s) +{ + struct X11Private *pr; + if (!s) + return; + pr = (struct X11Private *) sk_get_private_ptr(s); + if (pr->auth_protocol) { + sfree(pr->auth_protocol); + sfree(pr->auth_data); + } + + sfree(pr); + + sk_close(s); +} + +void x11_unthrottle(Socket s) +{ + struct X11Private *pr; + if (!s) + return; + pr = (struct X11Private *) sk_get_private_ptr(s); + + pr->throttled = 0; + sk_set_frozen(s, pr->throttled || pr->throttle_override); +} + +void x11_override_throttle(Socket s, int enable) +{ + struct X11Private *pr; + if (!s) + return; + pr = (struct X11Private *) sk_get_private_ptr(s); + + pr->throttle_override = enable; + sk_set_frozen(s, pr->throttled || pr->throttle_override); +} + +/* + * Called to send data down the raw connection. + */ +int x11_send(Socket s, char *data, int len) +{ + struct X11Private *pr; + if (!s) + return 0; + pr = (struct X11Private *) sk_get_private_ptr(s); + + /* + * Read the first packet. + */ + while (len > 0 && pr->data_read < 12) + pr->firstpkt[pr->data_read++] = (unsigned char) (len--, *data++); + if (pr->data_read < 12) + return 0; + + /* + * If we have not allocated the auth_protocol and auth_data + * strings, do so now. + */ + if (!pr->auth_protocol) { + pr->auth_plen = GET_16BIT(pr->firstpkt[0], pr->firstpkt + 6); + pr->auth_dlen = GET_16BIT(pr->firstpkt[0], pr->firstpkt + 8); + pr->auth_psize = (pr->auth_plen + 3) & ~3; + pr->auth_dsize = (pr->auth_dlen + 3) & ~3; + /* Leave room for a terminating zero, to make our lives easier. */ + pr->auth_protocol = snewn(pr->auth_psize + 1, char); + pr->auth_data = snewn(pr->auth_dsize, unsigned char); + } + + /* + * Read the auth_protocol and auth_data strings. + */ + while (len > 0 && pr->data_read < 12 + pr->auth_psize) + pr->auth_protocol[pr->data_read++ - 12] = (len--, *data++); + while (len > 0 && pr->data_read < 12 + pr->auth_psize + pr->auth_dsize) + pr->auth_data[pr->data_read++ - 12 - + pr->auth_psize] = (unsigned char) (len--, *data++); + if (pr->data_read < 12 + pr->auth_psize + pr->auth_dsize) + return 0; + + /* + * If we haven't verified the authorisation, do so now. + */ + if (!pr->verified) { + char *err; + + pr->auth_protocol[pr->auth_plen] = '\0'; /* ASCIZ */ + err = x11_verify(pr->peer_ip, pr->peer_port, + pr->disp, pr->auth_protocol, + pr->auth_data, pr->auth_dlen); + + /* + * If authorisation failed, construct and send an error + * packet, then terminate the connection. + */ + if (err) { + char *message; + int msglen, msgsize; + unsigned char *reply; + + message = dupprintf("%s X11 proxy: %s", appname, err); + msglen = strlen(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, pr->firstpkt + 2, 4); /* major/minor proto vsn */ + PUT_16BIT(pr->firstpkt[0], reply + 6, msgsize >> 2);/* data len */ + memset(reply + 8, 0, msgsize); + memcpy(reply + 8, message, msglen); + sshfwd_write(pr->c, (char *)reply, 8 + msgsize); + sshfwd_close(pr->c); + x11_close(s); + sfree(reply); + sfree(message); + return 0; + } + + /* + * Now we know we're going to accept the connection. Strip + * the fake auth data, and optionally put real auth data in + * instead. + */ + { + char realauthdata[64]; + int realauthlen = 0; + int authstrlen = strlen(x11_authnames[pr->disp->localauthproto]); + int buflen = 0; /* initialise to placate optimiser */ + static const char zeroes[4] = { 0,0,0,0 }; + void *buf; + + if (pr->disp->localauthproto == X11_MIT) { + assert(pr->disp->localauthdatalen <= lenof(realauthdata)); + realauthlen = pr->disp->localauthdatalen; + memcpy(realauthdata, pr->disp->localauthdata, realauthlen); + } else if (pr->disp->localauthproto == X11_XDM && + pr->disp->localauthdatalen == 16 && + ((buf = sk_getxdmdata(s, &buflen))!=0)) { + time_t t; + realauthlen = (buflen+12+7) & ~7; + assert(realauthlen <= lenof(realauthdata)); + memset(realauthdata, 0, realauthlen); + memcpy(realauthdata, pr->disp->localauthdata, 8); + memcpy(realauthdata+8, buf, buflen); + t = time(NULL); + PUT_32BIT_MSB_FIRST(realauthdata+8+buflen, t); + des_encrypt_xdmauth(pr->disp->localauthdata+9, + (unsigned char *)realauthdata, + realauthlen); + sfree(buf); + } + /* implement other auth methods here if required */ + + PUT_16BIT(pr->firstpkt[0], pr->firstpkt + 6, authstrlen); + PUT_16BIT(pr->firstpkt[0], pr->firstpkt + 8, realauthlen); + + sk_write(s, (char *)pr->firstpkt, 12); + + if (authstrlen) { + sk_write(s, x11_authnames[pr->disp->localauthproto], + authstrlen); + sk_write(s, zeroes, 3 & (-authstrlen)); + } + if (realauthlen) { + sk_write(s, realauthdata, realauthlen); + sk_write(s, zeroes, 3 & (-realauthlen)); + } + } + pr->verified = 1; + } + + /* + * After initialisation, just copy data simply. + */ + + return sk_write(s, data, len); +} diff --git a/putty/dllmain.c b/putty/dllmain.c new file mode 100644 index 0000000..e46a72a --- /dev/null +++ b/putty/dllmain.c @@ -0,0 +1,19 @@ +// dllmain.cpp : DLL ƒAƒvƒŠƒP[ƒVƒ‡ƒ“‚̃Gƒ“ƒgƒŠ ƒ|ƒCƒ“ƒg‚ð’è‹`‚µ‚Ü‚·B +#include "stdafx.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/putty/stdafx.h b/putty/stdafx.h new file mode 100644 index 0000000..d5cdd1f --- /dev/null +++ b/putty/stdafx.h @@ -0,0 +1,16 @@ +// stdafx.h : •W€‚̃VƒXƒeƒ€ ƒCƒ“ƒNƒ‹[ƒh ƒtƒ@ƒCƒ‹‚̃Cƒ“ƒNƒ‹[ƒh ƒtƒ@ƒCƒ‹A‚Ü‚½‚Í +// ŽQÆ‰ñ”‚ª‘½‚­A‚©‚‚ ‚Ü‚è•ÏX‚³‚ê‚È‚¢AƒvƒƒWƒFƒNƒgê—p‚̃Cƒ“ƒNƒ‹[ƒh ƒtƒ@ƒCƒ‹ +// ‚ð‹Lq‚µ‚Ü‚·B +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Windows ƒwƒbƒ_[‚©‚çŽg—p‚³‚ê‚Ä‚¢‚È‚¢•”•ª‚ðœŠO‚µ‚Ü‚·B +// Windows ƒwƒbƒ_[ ƒtƒ@ƒCƒ‹: +#include + + + +// TODO: ƒvƒƒOƒ‰ƒ€‚É•K—v‚ȒljÁƒwƒbƒ_[‚ð‚±‚±‚ÅŽQÆ‚µ‚Ä‚­‚¾‚³‚¢B diff --git a/putty/targetver.h b/putty/targetver.h new file mode 100644 index 0000000..2481509 --- /dev/null +++ b/putty/targetver.h @@ -0,0 +1,24 @@ +#pragma once + +// ˆÈ‰º‚̃}ƒNƒ‚́AÅ’áŒÀ•K—v‚ȃvƒ‰ƒbƒgƒtƒH[ƒ€‚ð’è‹`‚µ‚Ü‚·BÅ’áŒÀ•K—v‚ȃvƒ‰ƒbƒgƒtƒH[ƒ€‚Ƃ́A +// ƒAƒvƒŠƒP[ƒVƒ‡ƒ“‚ðŽÀs‚·‚邽‚ß‚É•K—v‚È‹@”\‚ð”õ‚¦‚½Å‚àŒÃ‚¢ƒo[ƒWƒ‡ƒ“‚Ì Windows ‚â Internet Explorer ‚È‚Ç +// ‚ð‚¢‚¢‚Ü‚·B‚±‚ê‚ç‚̃}ƒNƒ‚́AŽw’肵‚½ƒo[ƒWƒ‡ƒ“‚ƁA‚»‚êˆÈ‘O‚̃o[ƒWƒ‡ƒ“‚̃vƒ‰ƒbƒgƒtƒH[ƒ€ã‚Å—˜—p‚Å‚«‚é‚·‚ׂĂ̋@”\‚ð—LŒø‚É‚·‚邱‚Æ‚É‚æ‚Á‚Ä +// “®ì‚µ‚Ü‚·B + +// ‰º‚ÅŽw’肳‚ꂽ’è‹`‚Ì‘O‚ɑΏۃvƒ‰ƒbƒgƒtƒH[ƒ€‚ðŽw’肵‚È‚¯‚ê‚΂Ȃç‚È‚¢ê‡AˆÈ‰º‚Ì’è‹`‚ð•ÏX‚µ‚Ä‚­‚¾‚³‚¢B +// ˆÙ‚È‚éƒvƒ‰ƒbƒgƒtƒH[ƒ€‚ɑΉž‚·‚é’l‚ÉŠÖ‚·‚éÅVî•ñ‚ɂ‚¢‚ẮAMSDN ‚ðŽQÆ‚µ‚Ä‚­‚¾‚³‚¢B +#ifndef WINVER // Å’áŒÀ•K—v‚ȃvƒ‰ƒbƒgƒtƒH[ƒ€‚ª Windows Vista ‚Å‚ ‚邱‚Æ‚ðŽw’肵‚Ü‚·B +#define WINVER 0x0600 // ‚±‚ê‚ð Windows ‚Ì‘¼‚̃o[ƒWƒ‡ƒ“Œü‚¯‚É“KØ‚È’l‚ɕύX‚µ‚Ä‚­‚¾‚³‚¢B +#endif + +#ifndef _WIN32_WINNT // Å’áŒÀ•K—v‚ȃvƒ‰ƒbƒgƒtƒH[ƒ€‚ª Windows Vista ‚Å‚ ‚邱‚Æ‚ðŽw’肵‚Ü‚·B +#define _WIN32_WINNT 0x0600 // ‚±‚ê‚ð Windows ‚Ì‘¼‚̃o[ƒWƒ‡ƒ“Œü‚¯‚É“KØ‚È’l‚ɕύX‚µ‚Ä‚­‚¾‚³‚¢B +#endif + +#ifndef _WIN32_WINDOWS // Å’áŒÀ•K—v‚ȃvƒ‰ƒbƒgƒtƒH[ƒ€‚ª Windows 98 ‚Å‚ ‚邱‚Æ‚ðŽw’肵‚Ü‚·B +#define _WIN32_WINDOWS 0x0410 // ‚±‚ê‚ð Windows Me ‚Ü‚½‚Í‚»‚êˆÈ~‚̃o[ƒWƒ‡ƒ“Œü‚¯‚É“KØ‚È’l‚ɕύX‚µ‚Ä‚­‚¾‚³‚¢B +#endif + +#ifndef _WIN32_IE // Å’áŒÀ•K—v‚ȃvƒ‰ƒbƒgƒtƒH[ƒ€‚ª Internet Explorer 7.0 ‚Å‚ ‚邱‚Æ‚ðŽw’肵‚Ü‚·B +#define _WIN32_IE 0x0700 // ‚±‚ê‚ð IE ‚Ì‘¼‚̃o[ƒWƒ‡ƒ“Œü‚¯‚É“KØ‚È’l‚ɕύX‚µ‚Ä‚­‚¾‚³‚¢B +#endif