Приложение D: PuTTY hacking guide
- D.1 Cross-OS portability
- D.2 Multiple backends treated equally
- D.3 Multiple sessions per process on some platforms
- D.4 C, not C++
- D.5 Security-conscious coding
- D.6 Independence of specific compiler
- D.7 Small code size
- D.8 Single-threaded code
- D.9 Keystrokes sent to the server wherever possible
- D.10 640×480 friendliness in configuration panels
- D.11 Automatically generated
- D.12 Coroutines in the SSH code
- D.13 Single compilation of each source file
- D.14 Do as we say, not as we do
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.
D.1 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
ssh.c is not acceptable. We went to great lengths to 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
unix subdirectory; the Mac-specific modules are in the
mac subdirectory; the Windows-specific modules are in the
All the modules in the main source directory - notably all of the code for the various back ends - are platform-generic. We want to keep them that way.
This also means you should stick to the C semantics guaranteed by the C standard: try not to make assumptions about the precise size of basic types such as
long int; don't use pointer casts to do endianness-dependent operations, and so on.
(Even within a platform front end you should still be careful of some of these portability issues. The Windows front end compiles on both 32- and 64-bit x86 and also Arm.)
Our current choice of C standards version is mostly C99. With a couple of exceptions, you can assume that C99 features are available (in particular
inline), but you shouldn't use things that are new in C11 (such as
The exceptions to that rule are due to the need for Visual Studio compatibility:
- Don't use variable-length arrays. Visual Studio doesn't support them even now that it's adopted the rest of C99. We use
-Wvlawhen building with gcc and clang, to make it easier to avoid accidentally breaking that rule.
- For historical reasons, we still build with one older VS version which lacks
<inttypes.h>. So that file is included centrally in
defs.h, and has a set of workaround definitions for the
PRIx64-type macros we use. If you need to use another one of those macros, you need to add a workaround definition in
defs.h, and don't casually re-include
<inttypes.h>anywhere else in the source file.
Here are a few portability assumptions that we do currently allow (because we'd already have to thoroughly vet the existing code if they ever needed to change, and it doesn't seem worth doing that unless we really have to):
- You can assume
intis at least 32 bits wide. (We've never tried to port PuTTY to a platform with 16-bit
int, and it doesn't look likely to be necessary in future.)
- Similarly, you can assume
charis exactly 8 bits. (Exceptions to that are even less likely to be relevant to us than short
- You can assume that using
memsetto write zero bytes over a whole structure will have the effect of setting all its pointer fields to
NULL. (The standard itself guarantees this for integer fields, but not for pointers.)
- You can assume that
time_thas POSIX semantics, i.e. that it represents an integer number of non-leap seconds since 1970-01-01 00:00:00 UTC. (Times in this format are used in X authorisation, but we could work around that by carefully distinguishing local
time_tfrom time values used in the wire protocol; but these semantics of
time_tare also baked into the shared library API used by the GSSAPI authentication code, which would be much harder to change.)
- You can assume that the execution character encoding is a superset of the printable characters of ASCII. (In particular, it's fine to do arithmetic on a
charvalue representing a Latin alphabetic character, without bothering to allow for EBCDIC or other non-consecutive encodings of the alphabet.)
On the other hand, here are some particular things not to assume:
- Don't assume anything about the signedness of
char. In particular, you must cast
unsigned charbefore passing them to any
<ctype.h>function (because those expect a non-negative character value, or
EOF). If you need a particular signedness, explicitly specify
unsigned char, or use C99
- From past experience with MacOS, we're still a bit nervous about
'\r'potentially having unusual meanings on a given platform. So it's fine to say
\nin a string you're passing to
printf, but in any context where those characters appear in a standardised wire protocol or a binary file format, they should be spelled
D.2 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
ssh.c. Clearly this is completely wrong: the
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 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.
D.3 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:
flags defines properties that are expected to apply equally to all the sessions run by a single PuTTY process, the random number state in
sshrand.c and the timer list in
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 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.
D.4 C, not C++
PuTTY is written entirely in C, not in C++.
We have made some effort to make it easy to compile our code using a C++ compiler: notably, our
sresize macros explicitly cast the return values of
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
// 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.
D.5 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
dupprintf, which dynamically allocate buffers of the right size for the string they construct. Use these wherever possible.
D.6 Independence of specific compiler
Windows PuTTY can currently be compiled with any of three Windows compilers: MS Visual C, the Cygwin /
mingw32 GNU tools, and
clang (in MS compatibility mode).
This is a really useful property of PuTTY, because it means people who want to contribute to the coding don't depend on having a specific compiler; so they don't have to fork out money for MSVC if they don't already have it, but on the other hand if they do have it they also don't have to spend effort installing
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 (
_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.
_snprintf in particular should be unnecessary, since we provide
dupprintf; see section D.5.)
Compiler independence should apply on all platforms, of course, not just on Windows.
D.7 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 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 could still just install the one PuTTY binary, or just the DLLs you really needed, and it would still work fine.
Depending on 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.
D.8 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
WSAAsyncSelect(), so if you call
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
MessageBox() message loop.
Also, the front ends (in particular Windows Plink) can use multiple threads if they like. However, Windows Plink keeps very tight control of its auxiliary threads, and uses them pretty much exclusively as a form of
select(). Pretty much all the code outside
windows/winplink.c is 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
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 ‘everything’ may include managing more than one login session (section D.3), 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 almost none of the PuTTY code can safely block.
D.9 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 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.
D.10 640×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×1200 desktop. 640×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×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, don't. Split it into more panels instead.
D.11 Automatically generated
PuTTY is intended to compile on multiple platforms, and with multiple compilers. It would be horrifying to try to maintain a single
Makefile which handled all possible situations, and just as painful to try to directly maintain a set of matching
Makefiles for each different compilation environment.
Therefore, we have moved the problem up by one level. In the PuTTY source archive is a file called
Recipe, which lists which source files combine to produce which binaries; and there is also a script called
mkfiles.pl, which reads
Recipe and writes out the real
Makefiles. (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
make depend phase.)
You should never edit any of the PuTTY
Makefiles directly. They are not stored in our source repository at all. They are automatically generated by
mkfiles.pl from the file
If you need to add a new object file to a particular binary, the right thing to do is to edit
Recipe and re-run
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
Makefile to which it is relevant, and to get all the dependency data right.
If you send us a patch that modifies one of the
Makefiles, you just waste our time, because we will have to convert it into a change to
Recipe. If you send us a patch that modifies all of the
Makefiles, you will have wasted a lot of your time as well!
(There is a comment at the top of every
Makefile in the PuTTY source archive saying this, but many people don't seem to read it, so it's worth repeating here.)
D.12 Coroutines in the SSH code
Large parts of the code in the various SSH modules (in fact most of the protocol layers) are structured using a set of macros that implement (something close to) Donald Knuth's ‘coroutines’ concept in C.
Essentially, the purpose of these macros are to arrange that a function can call
crReturn() to return to its caller, and the next time it is called control will resume from just after that
This means that any local (automatic) variables declared in such a function will be corrupted every time you call
crReturn. If you need a variable to persist for longer than that, you must make it a field in some appropriate structure containing the persistent state of the coroutine – typically the main state structure for an SSH protocol layer.
See https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html for a more in-depth discussion of what these macros are for and how they work.
Another caveat: most of these coroutines are not guaranteed to run to completion, because the SSH connection (or whatever) that they're part of might be interrupted at any time by an unexpected network event or user action. So whenever a coroutine-managed variable refers to a resource that needs releasing, you should also ensure that the cleanup function for its containing state structure can reliably release it even if the coroutine is aborted at an arbitrary point.
For example, if an SSH packet protocol layer has to have a field that sometimes points to a piece of allocated memory, then you should ensure that when you free that memory you reset the pointer field to
NULL. Then, no matter when the protocol layer's cleanup function is called, it can reliably free the memory if there is any, and not crash if there isn't.
D.13 Single compilation of each source file
The PuTTY build system for any given platform works on the following very simple model:
- Each source file is compiled precisely once, to produce a single object file.
- 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 wrong way to do it is by adding
#ifdefs in (say)
proxy.c. This would require separate compilation of
proxy.c for PuTTY and PuTTYtel, which means that the entire
Makefile-generation architecture (see section D.11) would have to be significantly redesigned. Unless you are prepared to do that redesign yourself, and guarantee that it will still port to any future platforms we might decide to run on, you should not attempt this!
The 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 (
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 between platforms; so it is allowable to use
#ifdef to select between platforms. This happens in
puttyps.h (choosing which of the platform-specific include files to use), and also in
misc.c (the Windows-specific ‘Minefield’ memory diagnostic system). It should be used sparingly, though, if at all.
D.14 Do as we say, not as we do
The current PuTTY code probably does not conform strictly to 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!