work/0000775000000000000000000000000014714222463006735 5ustar work/.gitignore0000664000000000000000000000104614714222463010726 0ustar *.o *.pyc *.vcxproj *.vcxproj.filters .ninja_deps .ninja_log build.ninja /CMakeCache.txt CMakeFiles/ Debug/ Win32/ cmake_install.cmake /shipped.txt *.dir/ *.lib .dirstamp .deps .DS_Store /*.pdb /*.ilk /*.res /*.RES /*.pch /*.rsp /*.obj /*.exe /*.ncb /*.plg /*.dsw /*.opt /*.dsp /*.sln /*.tds /*.td2 /*.map /*.log /*.GID /xtruss /build.log /build.out /empty.h Makefile /compile *.a /doc/*.html /doc/*.1 /unix/empty.h /unix/plink /unix/pterm /unix/putty /unix/puttytel /unix/psftp /unix/pscp /unix/puttygen /unix/stamp-h1 /unix/*.log /unix/.deps obj-* work/Buildscr0000664000000000000000000000237014714222463010431 0ustar # -*- sh -*- # # bob script to build the xtruss tarball. module xtruss set Version $(!builddate).$(vcsid) # use perl to avoid inconsistent behaviour of echo '\v' in xtruss do perl -e 'print "\n\\versionid xtruss version $$ARGV[0]\n"' $(Version) >> doc/man-xtruss.but in xtruss do perl -e 'print "$#define PACKAGE_VERSION \"$$ARGV[0]\"\n"' $(Version) > version.h # In cmake/gitcommit.cmake, replace the default output "unavailable" # with the commit string generated by bob, so that people rebuilding # the source archive will still get a useful value. in xtruss do sed -i '/set(DEFAULT_COMMIT/s/unavailable/$(vcsfullid)/' cmake/gitcommit.cmake # Build the man page. in . do mkdir build-doc in build-doc do cmake ../xtruss/doc in build-doc do make -j$(nproc) in build-doc do cp xtruss.1 ../xtruss # Test-build the program itself, in STRICT mode. We should ensure this # works before being willing to ship any tarball based on it. in . do mkdir build-test in build-test do cmake ../xtruss -DSTRICT=ON in build-test do make -j$(nproc) in . do cp -R xtruss xtruss-$(Version) in . do tar chzvf xtruss-$(Version).tar.gz xtruss-$(Version) in xtruss/doc do halibut --html=manpage.html man-xtruss.but deliver xtruss-$(Version).tar.gz $@ deliver xtruss/doc/manpage.html $@ work/CMakeLists.txt0000664000000000000000000000140114714222463011471 0ustar cmake_minimum_required(VERSION 3.12) project(xtruss LANGUAGES C) include(cmake/setup.cmake) # Scan the docs directory first, so that when we start calling # installed_program(), we'll know if we have man pages available add_subdirectory(doc) add_compile_definitions(HAVE_CMAKE_H) add_library(utils STATIC ${GENERATED_COMMIT_C}) add_dependencies(utils cmake_commit_c) add_subdirectory(utils) add_library(eventloop STATIC callback.c timing.c) add_library(console STATIC console.c) add_library(crypto STATIC) add_subdirectory(crypto) add_library(network STATIC nullplug.c errsock.c noproxy.c x11disp.c) foreach(subdir ${platform} ${extra_dirs}) add_subdirectory(${subdir}) endforeach() configure_file(cmake/cmake.h.in ${GENERATED_SOURCES_DIR}/cmake.h) work/LICENCE0000664000000000000000000000325114714222463007723 0ustar xtruss is copyright 1997-2021 Simon Tatham. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, Colin Watson, Christopher Staite, Lorenz Diener, Christian Brabandt, Jeff Smith, Pavel Kryukov, Maxim Kuznetsov, Svyatoslav Kuzmich, Nico Williams, Viktor Dukhovni, Josh Dersch, Lars Brinkhoff, and CORE SDI S.A. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name of the X Consortium shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the X Consortium. work/README0000664000000000000000000000430214714222463007614 0ustar README for xtruss ----------------- This is the README for xtruss, an X11 protocol tracer. xtruss is built using CMake . To compile in the simplest way, run these commands in the source directory: cmake . cmake --build . The 'man' page is built using Halibut, from source in the `doc' subdirectory. If you aren't using one of our source snapshots, you'll need to do this yourself. Halibut can be found at . The xtruss home web site is https://www.chiark.greenend.org.uk/~sgtatham/xtruss/ See the file LICENCE for the licence conditions. Source code organisation ------------------------ Most source files in this project are lifted almost without change from the PuTTY source tree, because that already contained nearly all the code necessary to handle proxying of an X11 server, checking the authorisation in the incoming connections, and substituting the right authorisation for the outgoing connection to the master server; it only took about fifty lines of new code to make all of that talk to a local listening socket in place of incoming X forwarding channels of an SSH connection, and I had a ready-made X proxy to which all I had to do was add (a lot of) logging code. The files taken from PuTTY are edited as little as possible, to reduce merge conflicts when bringing in further updates from later versions of PuTTY: files are completely unmodified whenever possible, and failing that, modified in a very minimal way, ideally by just inserting '#if 0' to remove parts of a file which xtruss doesn't use and which would otherwise be difficult to get to compile. So you'll often find comments in the PuTTY-derived parts of the code that refer to functions, variables and entire files that are not actually here. This is a deliberate tradeoff to keep maintenance easy. If you're interested in seeing what all those things really are, you can find them in the PuTTY code base (perhaps by rewinding to the most recent PuTTY commit id cited in the xtruss git history). The xtruss-specific code lives in source files with 'xtruss' in the name: xtruss*.c and xtruss*.h in the top-level directory, and unix/uxxtruss.c for the Unix-specific part. work/callback.c0000664000000000000000000000625314714222463010643 0ustar /* * Facility for queueing callback functions to be run from the * top-level event loop after the current top-level activity finishes. */ #include #include "putty.h" struct callback { struct callback *next; toplevel_callback_fn_t fn; void *ctx; }; static struct callback *cbcurr = NULL, *cbhead = NULL, *cbtail = NULL; static toplevel_callback_notify_fn_t notify_frontend = NULL; static void *notify_ctx = NULL; void request_callback_notifications(toplevel_callback_notify_fn_t fn, void *ctx) { notify_frontend = fn; notify_ctx = ctx; } static void run_idempotent_callback(void *ctx) { struct IdempotentCallback *ic = (struct IdempotentCallback *)ctx; ic->queued = false; ic->fn(ic->ctx); } void queue_idempotent_callback(struct IdempotentCallback *ic) { if (ic->queued) return; ic->queued = true; queue_toplevel_callback(run_idempotent_callback, ic); } void delete_callbacks_for_context(void *ctx) { struct callback *newhead, *newtail; newhead = newtail = NULL; while (cbhead) { struct callback *cb = cbhead; cbhead = cbhead->next; if (cb->ctx == ctx || (cb->fn == run_idempotent_callback && ((struct IdempotentCallback *)cb->ctx)->ctx == ctx)) { sfree(cb); } else { if (!newhead) newhead = cb; else newtail->next = cb; newtail = cb; } } cbhead = newhead; cbtail = newtail; if (newtail) newtail->next = NULL; } void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) { struct callback *cb; cb = snew(struct callback); cb->fn = fn; cb->ctx = ctx; /* * If the front end has requested notification of pending * callbacks, and we didn't already have one queued, let it know * we do have one now. * * If cbcurr is non-NULL, i.e. we are actually in the middle of * executing a callback right now, then we count that as the queue * already having been non-empty. That saves the front end getting * a constant stream of needless re-notifications if the last * callback keeps re-scheduling itself. */ if (notify_frontend && !cbhead && !cbcurr) notify_frontend(notify_ctx); if (cbtail) cbtail->next = cb; else cbhead = cb; cbtail = cb; cb->next = NULL; } bool run_toplevel_callbacks(void) { bool done_something = false; if (cbhead) { /* * Transfer the head callback into cbcurr to indicate that * it's being executed. Then operations which transform the * queue, like delete_callbacks_for_context, can proceed as if * it's not there. */ cbcurr = cbhead; cbhead = cbhead->next; if (!cbhead) cbtail = NULL; /* * Now run the callback, and then clear it out of cbcurr. */ cbcurr->fn(cbcurr->ctx); sfree(cbcurr); cbcurr = NULL; done_something = true; } return done_something; } bool toplevel_callback_pending(void) { return cbcurr != NULL || cbhead != NULL; } work/cmake/0000775000000000000000000000000014714222463010015 5ustar work/cmake/cmake.h.in0000664000000000000000000000014614714222463011654 0ustar #cmakedefine01 HAVE_CLOCK_MONOTONIC #cmakedefine01 HAVE_CLOCK_GETTIME #cmakedefine01 HAVE_SO_PEERCRED work/cmake/gitcommit.cmake0000664000000000000000000000347014714222463013017 0ustar # Pure cmake script to write out cmake_commit.h set(DEFAULT_COMMIT "unavailable") set(commit "${DEFAULT_COMMIT}") set(TOPLEVEL_SOURCE_DIR ${CMAKE_SOURCE_DIR}) execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse --show-toplevel OUTPUT_VARIABLE git_worktree ERROR_VARIABLE stderr RESULT_VARIABLE status) string(REGEX REPLACE "\n$" "" git_worktree "${git_worktree}") if(status EQUAL 0) if(git_worktree STREQUAL CMAKE_SOURCE_DIR) execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse HEAD OUTPUT_VARIABLE git_commit ERROR_VARIABLE stderr RESULT_VARIABLE status) if(status EQUAL 0) string(REGEX REPLACE "\n$" "" commit "${git_commit}") else() if(commit STREQUAL "unavailable") message("Unable to determine git commit: 'git rev-parse HEAD' returned status ${status} and error output:\n${stderr}\n") endif() endif() else() if(commit STREQUAL "unavailable") message("Unable to determine git commit: top-level source dir ${CMAKE_SOURCE_DIR} is not the root of a repository") endif() endif() else() if(commit STREQUAL "unavailable") message("Unable to determine git commit: 'git rev-parse --show-toplevel' returned status ${status} and error output:\n${stderr}\n") endif() endif() if(OUTPUT_TYPE STREQUAL header) file(WRITE "${OUTPUT_FILE}" "\ /* * cmake_commit.h - string literal giving the source git commit, if known. * * Generated by cmake/gitcommit.cmake. */ const char commitid[] = \"${commit}\"; ") elseif(OUTPUT_TYPE STREQUAL halibut) if(commit STREQUAL "unavailable") file(WRITE "${OUTPUT_FILE}" "\ \\versionid no version information available ") else() file(WRITE "${OUTPUT_FILE}" "\ \\versionid built from git commit ${commit} ") endif() else() message(FATAL_ERROR "Set OUTPUT_TYPE when running this script") endif() work/cmake/licence.cmake0000664000000000000000000000247514714222463012431 0ustar # Pure cmake script to generate licence.h from LICENCE file(READ "${LICENCE_FILE}" LICENCE_TEXT) function(c_string_escape outvar value) string(REPLACE "\\" "\\\\" value "${value}") string(REPLACE "\"" "\\\"" value "${value}") set("${outvar}" "${value}" PARENT_SCOPE) endfunction() set(copyright_regex "PuTTY is copyright ([0-9]+-[0-9]+ [^\n]*[^\n.])\\.?\n") string(REGEX MATCH "${copyright_regex}" COPYRIGHT_NOTICE "${LICENCE_TEXT}") string(REGEX REPLACE "${copyright_regex}" "\\1" COPYRIGHT_NOTICE "${COPYRIGHT_NOTICE}") c_string_escape(COPYRIGHT_NOTICE "${COPYRIGHT_NOTICE}") string(REGEX REPLACE "\n$" "" LICENCE_TEXT "${LICENCE_TEXT}") string(REPLACE "\r" "" LICENCE_TEXT "${LICENCE_TEXT}") string(REPLACE "\n\n" "\r" LICENCE_TEXT "${LICENCE_TEXT}") string(REPLACE "\n" " " LICENCE_TEXT "${LICENCE_TEXT}") string(REPLACE "\r" "\n" LICENCE_TEXT "${LICENCE_TEXT}") c_string_escape(LICENCE_TEXT "${LICENCE_TEXT}") string(REPLACE "\n" "\" \\\n parsep \\\n \"" LICENCE_TEXT "${LICENCE_TEXT}") file(WRITE "${OUTPUT_FILE}" "\ /* * licence.h - macro definitions for the PuTTY licence. * * Generated by cmake/licence.cmake from ./LICENCE. * You should edit those files rather than editing this one. */ #define LICENCE_TEXT(parsep) \\ \"${LICENCE_TEXT}\" #define SHORT_COPYRIGHT_DETAILS \"${COPYRIGHT_NOTICE}\" ") work/cmake/platforms/0000775000000000000000000000000014714222463012024 5ustar work/cmake/platforms/unix.cmake0000664000000000000000000000353214714222463014014 0ustar include(CheckIncludeFile) include(CheckLibraryExists) include(CheckSymbolExists) include(CheckCSourceCompiles) include(GNUInstallDirs) set(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -D_DEFAULT_SOURCE -D_GNU_SOURCE) check_symbol_exists(CLOCK_MONOTONIC "time.h" HAVE_CLOCK_MONOTONIC) check_symbol_exists(clock_gettime "time.h" HAVE_CLOCK_GETTIME) check_c_source_compiles(" #define _GNU_SOURCE #include #include int main(int argc, char **argv) { struct ucred cr; socklen_t crlen = sizeof(cr); return getsockopt(0, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) + cr.pid + cr.uid + cr.gid; }" HAVE_SO_PEERCRED) function(add_optional_system_lib library testfn) check_library_exists(${library} ${testfn} "" HAVE_LIB${library}) if (HAVE_LIB${library}) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES};-l${library}) link_libraries(-l${library}) endif() endfunction() add_optional_system_lib(m pow) add_optional_system_lib(rt clock_gettime) add_optional_system_lib(xnet socket) if(STRICT AND (CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla") endif() function(installed_program target) if(CMAKE_VERSION VERSION_LESS 3.14) # CMake 3.13 and earlier required an explicit install destination. install(TARGETS ${target} RUNTIME DESTINATION bin) else() # 3.14 and above selects a sensible default, which we should avoid # overriding here so that end users can override it using # CMAKE_INSTALL_BINDIR. install(TARGETS ${target}) endif() if(HAVE_MANPAGE_${target}_1) install(FILES ${CMAKE_BINARY_DIR}/doc/${target}.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) else() message(WARNING "Could not build man page ${target}.1") endif() endfunction() work/cmake/setup.cmake0000664000000000000000000000537114714222463012165 0ustar # Forcibly re-enable assertions, even if we're building in release # mode. This is a security project - assertions may be enforcing # security-critical constraints. A backstop #ifdef in defs.h should # give a #error if this manoeuvre doesn't do what it needs to. string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") set(XTRUSS_DEBUG OFF CACHE BOOL "Build xtruss with debug() statements enabled") set(XTRUSS_COVERAGE OFF CACHE BOOL "Build xtruss binaries suitable for code coverage analysis") set(STRICT OFF CACHE BOOL "Enable extra compiler warnings and make them errors") include(FindGit) set(GENERATED_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}) set(GENERATED_LICENCE_H ${GENERATED_SOURCES_DIR}/licence.h) set(INTERMEDIATE_LICENCE_H ${GENERATED_LICENCE_H}.tmp) add_custom_command(OUTPUT ${INTERMEDIATE_LICENCE_H} COMMAND ${CMAKE_COMMAND} -DLICENCE_FILE=${CMAKE_SOURCE_DIR}/LICENCE -DOUTPUT_FILE=${INTERMEDIATE_LICENCE_H} -P ${CMAKE_SOURCE_DIR}/cmake/licence.cmake DEPENDS ${CMAKE_SOURCE_DIR}/cmake/licence.cmake ${CMAKE_SOURCE_DIR}/LICENCE) add_custom_target(generated_licence_h BYPRODUCTS ${GENERATED_LICENCE_H} COMMAND ${CMAKE_COMMAND} -E copy_if_different ${INTERMEDIATE_LICENCE_H} ${GENERATED_LICENCE_H} DEPENDS ${INTERMEDIATE_LICENCE_H} COMMENT "Updating licence.h") set(GENERATED_COMMIT_C ${GENERATED_SOURCES_DIR}/cmake_commit.c) set(INTERMEDIATE_COMMIT_C ${GENERATED_COMMIT_C}.tmp) add_custom_target(check_git_commit BYPRODUCTS ${INTERMEDIATE_COMMIT_C} COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DOUTPUT_FILE=${INTERMEDIATE_COMMIT_C} -DOUTPUT_TYPE=header -P ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake DEPENDS ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Checking current git commit") add_custom_target(cmake_commit_c BYPRODUCTS ${GENERATED_COMMIT_C} COMMAND ${CMAKE_COMMAND} -E copy_if_different ${INTERMEDIATE_COMMIT_C} ${GENERATED_COMMIT_C} DEPENDS check_git_commit ${INTERMEDIATE_COMMIT_C} COMMENT "Updating cmake_commit.c") function(add_sources_from_current_dir target) set(sources ${ARGN}) list(TRANSFORM sources PREPEND ${CMAKE_CURRENT_SOURCE_DIR}/) target_sources(${target} PRIVATE ${sources}) endfunction() set(extra_dirs) # Here we could insert additional platform support if needed set(platform unix) include(cmake/platforms/${platform}.cmake) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${GENERATED_SOURCES_DIR} ${platform} ${extra_dirs}) if(XTRUSS_DEBUG) add_compile_definitions(DEBUG) endif() if(XTRUSS_COVERAGE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage -g ") endif() work/console.c0000664000000000000000000000700514714222463010545 0ustar /* * Common pieces between the platform console frontend modules. */ #include #include #include "putty.h" #include "misc.h" #include "console.h" const char hk_absentmsg_common_fmt[] = "The server's host key is not cached. You have no guarantee\n" "that the server is the computer you think it is.\n" "The server's %s key fingerprint is:\n" "%s\n"; const char hk_absentmsg_interactive_intro[] = "If you trust this host, enter \"y\" to add the key to\n" "PuTTY's cache and carry on connecting.\n" "If you want to carry on connecting just once, without\n" "adding the key to the cache, enter \"n\".\n" "If you do not trust this host, press Return to abandon the\n" "connection.\n"; const char hk_absentmsg_interactive_prompt[] = "Store key in cache? (y/n, Return cancels connection, " "i for more info) "; const char hk_wrongmsg_common_fmt[] = "WARNING - POTENTIAL SECURITY BREACH!\n" "The server's host key does not match the one PuTTY has\n" "cached. This means that either the server administrator\n" "has changed the host key, or you have actually connected\n" "to another computer pretending to be the server.\n" "The new %s key fingerprint is:\n" "%s\n"; const char hk_wrongmsg_interactive_intro[] = "If you were expecting this change and trust the new key,\n" "enter \"y\" to update PuTTY's cache and continue connecting.\n" "If you want to carry on connecting but without updating\n" "the cache, enter \"n\".\n" "If you want to abandon the connection completely, press\n" "Return to cancel. Pressing Return is the ONLY guaranteed\n" "safe choice.\n"; const char hk_wrongmsg_interactive_prompt[] = "Update cached key? (y/n, Return cancels connection, " "i for more info) "; const char weakcrypto_msg_common_fmt[] = "The first %s supported by the server is\n" "%s, which is below the configured warning threshold.\n"; const char weakhk_msg_common_fmt[] = "The first host key type we have stored for this server\n" "is %s, which is below the configured warning threshold.\n" "The server also provides the following types of host key\n" "above the threshold, which we do not have stored:\n" "%s\n"; const char console_continue_prompt[] = "Continue with connection? (y/n) "; const char console_abandoned_msg[] = "Connection abandoned.\n"; bool console_batch_mode = false; /* * Error message and/or fatal exit functions, all based on * console_print_error_msg which the platform front end provides. */ void console_print_error_msg_fmt_v( const char *prefix, const char *fmt, va_list ap) { char *msg = dupvprintf(fmt, ap); console_print_error_msg(prefix, msg); sfree(msg); } void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...) { va_list ap; va_start(ap, fmt); console_print_error_msg_fmt_v(prefix, fmt, ap); va_end(ap); } void modalfatalbox(const char *fmt, ...) { va_list ap; va_start(ap, fmt); console_print_error_msg_fmt_v("FATAL ERROR", fmt, ap); va_end(ap); cleanup_exit(1); } void nonfatal(const char *fmt, ...) { va_list ap; va_start(ap, fmt); console_print_error_msg_fmt_v("ERROR", fmt, ap); va_end(ap); } void console_connection_fatal(Seat *seat, const char *msg) { console_print_error_msg("FATAL ERROR", msg); cleanup_exit(1); } /* * Console front ends redo their select() or equivalent every time, so * they don't need separate timer handling. */ void timer_change_notify(unsigned long next) { } work/console.h0000664000000000000000000000104614714222463010551 0ustar /* * Common pieces between the platform console frontend modules. */ extern const char hk_absentmsg_common_fmt[]; extern const char hk_absentmsg_interactive_intro[]; extern const char hk_absentmsg_interactive_prompt[]; extern const char hk_wrongmsg_common_fmt[]; extern const char hk_wrongmsg_interactive_intro[]; extern const char hk_wrongmsg_interactive_prompt[]; extern const char weakcrypto_msg_common_fmt[]; extern const char weakhk_msg_common_fmt[]; extern const char console_continue_prompt[]; extern const char console_abandoned_msg[]; work/crypto/0000775000000000000000000000000014714222463010255 5ustar work/crypto/CMakeLists.txt0000664000000000000000000000007114714222463013013 0ustar add_sources_from_current_dir(crypto des.c xdmauth.c) work/crypto/des.c0000664000000000000000000011220014714222463011170 0ustar /* * Implementation of DES. */ /* * Background * ---------- * * The basic structure of DES is a Feistel network: the 64-bit cipher * block is divided into two 32-bit halves L and R, and in each round, * a mixing function is applied to one of them, the result is XORed * into the other, and then the halves are swapped so that the other * one will be the input to the mixing function next time. (This * structure guarantees reversibility no matter whether the mixing * function itself is bijective.) * * The mixing function for DES goes like this: * + Extract eight contiguous 6-bit strings from the 32-bit word. * They start at positions 4 bits apart, so each string overlaps * the next one by one bit. At least one has to wrap cyclically * round the end of the word. * + XOR each of those strings with 6 bits of data from the key * schedule (which consists of 8 x 6-bit strings per round). * + Use the resulting 6-bit numbers as the indices into eight * different lookup tables ('S-boxes'), each of which delivers a * 4-bit output. * + Concatenate those eight 4-bit values into a 32-bit word. * + Finally, apply a fixed permutation P to that word. * * DES adds one more wrinkle on top of this structure, which is to * conjugate it by a bitwise permutation of the cipher block. That is, * before starting the main cipher rounds, the input bits are permuted * according to a 64-bit permutation called IP, and after the rounds * are finished, the output bits are permuted back again by applying * the inverse of IP. * * This gives a lot of leeway to redefine the components of the cipher * without actually changing the input and output. You could permute * the bits in the output of any or all of the S-boxes, or reorder the * S-boxes among themselves, and adjust the following permutation P to * compensate. And you could adjust IP by post-composing a rotation of * each 32-bit half, and adjust the starting offsets of the 6-bit * S-box indices to compensate. * * test/desref.py demonstrates this by providing two equivalent forms * of the cipher, called DES and SGTDES, which give the same output. * DES is the form described in the original spec: if you make it * print diagnostic output during the cipher and check it against the * original, you should recognise the S-box outputs as matching the * ones you expect. But SGTDES, which I egotistically name after * myself, is much closer to the form implemented here: I've changed * the permutation P to suit my implementation strategy and * compensated by permuting the S-boxes, and also I've added a * rotation right by 1 bit to IP so that only one S-box index has to * wrap round the word and also so that the indices are nicely aligned * for the constant-time selection system I'm using. */ #include #include "ssh.h" #include "mpint_i.h" /* we reuse the BignumInt system */ /* If you compile with -DDES_DIAGNOSTICS, intermediate results will be * sent to debug() (so you also need to compile with -DDEBUG). * Otherwise this ifdef will condition away all the debug() calls. */ #ifndef DES_DIAGNOSTICS #undef debug #define debug(...) ((void)0) #endif /* * General utility functions. */ static inline uint32_t rol(uint32_t x, unsigned c) { return (x << (31 & c)) | (x >> (31 & -c)); } static inline uint32_t ror(uint32_t x, unsigned c) { return rol(x, -c); } /* * The hard part of doing DES in constant time is the S-box lookup. * * My strategy is to iterate over the whole lookup table! That's slow, * but I don't see any way to avoid _something_ along those lines: in * every round, every entry in every S-box is potentially needed, and * if you can't change your memory access pattern based on the input * data, it follows that you have to read a quantity of information * equal to the size of all the S-boxes. (Unless they were to turn out * to be significantly compressible, but I for one couldn't show them * to be.) * * In more detail, I construct a sort of counter-based 'selection * gadget', which is 15 bits wide and starts off with the top bit * zero, the next eight bits all 1, and the bottom six set to the * input S-box index: * * 011111111xxxxxx * * Now if you add 1 in the lowest bit position, then either it carries * into the top section (resetting it to 100000000), or it doesn't do * that yet. If you do that 64 times, then it will _guarantee_ to have * ticked over into 100000000. In between those increments, the eight * bits that started off as 11111111 will have stayed that way for * some number of iterations and then become 00000000, and exactly how * many iterations depends on the input index. * * The purpose of the 0 bit at the top is to absorb the carry when the * switch happens, which means you can pack more than one gadget into * the same machine word and have them all work in parallel without * each one intefering with the next. * * The next step is to use each of those 8-bit segments as a bit mask: * each one is ANDed with a lookup table entry, and all the results * are XORed together. So you end up with the bitwise XOR of some * initial segment of the table entries. And the stored S-box tables * are transformed in such a way that the real S-box values are given * not by the individual entries, but by the cumulative XORs * constructed in this way. * * A refinement is that I increment each gadget by 2 rather than 1 * each time, so I only iterate 32 times instead of 64. That's why * there are 8 selection bits instead of 4: each gadget selects enough * bits to reconstruct _two_ S-box entries, for a pair of indices * (2n,2n+1), and then finally I use the low bit of the index to do a * parallel selection between each of those pairs. * * The selection gadget is not quite 16 bits wide. So you can fit four * of them across a 64-bit word at 16-bit intervals, which is also * convenient because the place the S-box indices are coming from also * has pairs of them separated by 16-bit distances, so it's easy to * copy them into the gadgets in the first place. */ /* * The S-box data. Each pair of nonzero columns here describes one of * the S-boxes, corresponding to the SGTDES tables in test/desref.py, * under the following transformation. * * Take S-box #3 as an example. Its values in successive rows of this * table are eb,e8,54,3d, ... So the cumulative XORs of initial * sequences of those values are eb,(eb^e8),(eb^e8^54), ... which * comes to eb,03,57,... Of _those_ values, the top nibble (e,0,5,...) * gives the even-numbered entries in the S-box, in _reverse_ order * (because a lower input index selects the XOR of a longer * subsequence). The odd-numbered entries are given by XORing the two * digits together: (e^b),(0^3),(5^7),... = 5,3,2,... And indeed, if * you check SGTDES.sboxes[3] you find it ends ... 52 03 e5. */ #define SBOX_ITERATION(X) \ /* 66 22 44 00 77 33 55 11 */ \ X(0xf600970083008500, 0x0e00eb007b002e00) \ X(0xda00e4009000e000, 0xad00e800a700b400) \ X(0x1a009d003f003600, 0xf60054004300cd00) \ X(0xaf00c500e900a900, 0x63003d00f2005900) \ X(0xf300750079001400, 0x80005000a2008900) \ X(0xa100d400d6007b00, 0xd3009000d300e100) \ X(0x450087002600ac00, 0xae003c0031009c00) \ X(0xd000b100b6003600, 0x3e006f0092005900) \ X(0x4d008a0026001000, 0x89007a00b8004a00) \ X(0xca00f5003f00ac00, 0x6f00f0003c009400) \ X(0x92008d0090001000, 0x8c00c600ce004a00) \ X(0xe2005900e9006d00, 0x790078007800fa00) \ X(0x1300b10090008d00, 0xa300170027001800) \ X(0xc70058005f006a00, 0x9c00c100e0006300) \ X(0x9b002000f000f000, 0xf70057001600f900) \ X(0xeb00b0009000af00, 0xa9006300b0005800) \ X(0xa2001d00cf000000, 0x3800b00066000000) \ X(0xf100da007900d000, 0xbc00790094007900) \ X(0x570015001900ad00, 0x6f00ef005100cb00) \ X(0xc3006100e9006d00, 0xc000b700f800f200) \ X(0x1d005800b600d000, 0x67004d00cd002c00) \ X(0xf400b800d600e000, 0x5e00a900b000e700) \ X(0x5400d1003f009c00, 0xc90069002c005300) \ X(0xe200e50060005900, 0x6a00b800c500f200) \ X(0xdf0047007900d500, 0x7000ec004c00ea00) \ X(0x7100d10060009c00, 0x3f00b10095005e00) \ X(0x82008200f0002000, 0x87001d00cd008000) \ X(0xd0007000af00c000, 0xe200be006100f200) \ X(0x8000930060001000, 0x36006e0081001200) \ X(0x6500a300d600ac00, 0xcf003d007d00c000) \ X(0x9000700060009800, 0x62008100ad009200) \ X(0xe000e4003f00f400, 0x5a00ed009000f200) \ /* end of list */ /* * The S-box mapping function. Expects two 32-bit input words: si6420 * contains the table indices for S-boxes 0,2,4,6 with their low bits * starting at position 2 (for S-box 0) and going up in steps of 8. * si7531 has indices 1,3,5,7 in the same bit positions. */ static inline uint32_t des_S(uint32_t si6420, uint32_t si7531) { debug("sindices: %02x %02x %02x %02x %02x %02x %02x %02x\n", 0x3F & (si6420 >> 2), 0x3F & (si7531 >> 2), 0x3F & (si6420 >> 10), 0x3F & (si7531 >> 10), 0x3F & (si6420 >> 18), 0x3F & (si7531 >> 18), 0x3F & (si6420 >> 26), 0x3F & (si7531 >> 26)); #ifdef SIXTY_FOUR_BIT /* * On 64-bit machines, we store the table in exactly the form * shown above, and make two 64-bit words containing four * selection gadgets each. */ /* Set up the gadgets. The 'cNNNN' variables will be gradually * incremented, and the bits in positions FF00FF00FF00FF00 will * act as selectors for the words in the table. * * A side effect of moving the input indices further apart is that * they change order, because it's easier to keep a pair that were * originally 16 bits apart still 16 bits apart, which now makes * them adjacent instead of separated by one. So the fact that * si6420 turns into c6240 (with the 2,4 reversed) is not a typo! * This will all be undone when we rebuild the output word later. */ uint64_t c6240 = ((si6420 | ((uint64_t)si6420 << 24)) & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00; uint64_t c7351 = ((si7531 | ((uint64_t)si7531 << 24)) & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00; debug("S in: c6240=%016"PRIx64" c7351=%016"PRIx64"\n", c6240, c7351); /* Iterate over the table. The 'sNNNN' variables accumulate the * XOR of all the table entries not masked out. */ static const struct tbl { uint64_t t6240, t7351; } tbl[32] = { #define TABLE64(a, b) { a, b }, SBOX_ITERATION(TABLE64) #undef TABLE64 }; uint64_t s6240 = 0, s7351 = 0; for (const struct tbl *t = tbl, *limit = tbl + 32; t < limit; t++) { s6240 ^= c6240 & t->t6240; c6240 += 0x0008000800080008; s7351 ^= c7351 & t->t7351; c7351 += 0x0008000800080008; } debug("S out: s6240=%016"PRIx64" s7351=%016"PRIx64"\n", s6240, s7351); /* Final selection between each even/odd pair: mask off the low * bits of all the input indices (which haven't changed throughout * the iteration), and multiply by a bit mask that will turn each * set bit into a mask covering the upper nibble of the selected * pair. Then use those masks to control which set of lower * nibbles is XORed into the upper nibbles. */ s6240 ^= (s6240 << 4) & ((0xf000/0x004) * (c6240 & 0x0004000400040004)); s7351 ^= (s7351 << 4) & ((0xf000/0x004) * (c7351 & 0x0004000400040004)); /* Now the eight final S-box outputs are in the upper nibble of * each selection position. Mask away the rest of the clutter. */ s6240 &= 0xf000f000f000f000; s7351 &= 0xf000f000f000f000; debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n", (unsigned)(0xF & (s6240 >> 12)), (unsigned)(0xF & (s7351 >> 12)), (unsigned)(0xF & (s6240 >> 44)), (unsigned)(0xF & (s7351 >> 44)), (unsigned)(0xF & (s6240 >> 28)), (unsigned)(0xF & (s7351 >> 28)), (unsigned)(0xF & (s6240 >> 60)), (unsigned)(0xF & (s7351 >> 60))); /* Combine them all into a single 32-bit output word, which will * come out in the order 76543210. */ uint64_t combined = (s6240 >> 12) | (s7351 >> 8); return combined | (combined >> 24); #else /* SIXTY_FOUR_BIT */ /* * For 32-bit platforms, we do the same thing but in four 32-bit * words instead of two 64-bit ones, so the CPU doesn't have to * waste time propagating carries or shifted bits between the two * halves of a uint64 that weren't needed anyway. */ /* Set up the gadgets */ uint32_t c40 = ((si6420 ) & 0x00FC00FC) | 0xFF00FF00; uint32_t c62 = ((si6420 >> 8) & 0x00FC00FC) | 0xFF00FF00; uint32_t c51 = ((si7531 ) & 0x00FC00FC) | 0xFF00FF00; uint32_t c73 = ((si7531 >> 8) & 0x00FC00FC) | 0xFF00FF00; debug("S in: c40=%08"PRIx32" c62=%08"PRIx32 " c51=%08"PRIx32" c73=%08"PRIx32"\n", c40, c62, c51, c73); /* Iterate over the table */ static const struct tbl { uint32_t t40, t62, t51, t73; } tbl[32] = { #define TABLE32(a, b) { ((uint32_t)a), (a>>32), ((uint32_t)b), (b>>32) }, SBOX_ITERATION(TABLE32) #undef TABLE32 }; uint32_t s40 = 0, s62 = 0, s51 = 0, s73 = 0; for (const struct tbl *t = tbl, *limit = tbl + 32; t < limit; t++) { s40 ^= c40 & t->t40; c40 += 0x00080008; s62 ^= c62 & t->t62; c62 += 0x00080008; s51 ^= c51 & t->t51; c51 += 0x00080008; s73 ^= c73 & t->t73; c73 += 0x00080008; } debug("S out: s40=%08"PRIx32" s62=%08"PRIx32 " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73); /* Final selection within each pair */ s40 ^= (s40 << 4) & ((0xf000/0x004) * (c40 & 0x00040004)); s62 ^= (s62 << 4) & ((0xf000/0x004) * (c62 & 0x00040004)); s51 ^= (s51 << 4) & ((0xf000/0x004) * (c51 & 0x00040004)); s73 ^= (s73 << 4) & ((0xf000/0x004) * (c73 & 0x00040004)); /* Clean up the clutter */ s40 &= 0xf000f000; s62 &= 0xf000f000; s51 &= 0xf000f000; s73 &= 0xf000f000; debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n", (unsigned)(0xF & (s40 >> 12)), (unsigned)(0xF & (s51 >> 12)), (unsigned)(0xF & (s62 >> 12)), (unsigned)(0xF & (s73 >> 12)), (unsigned)(0xF & (s40 >> 28)), (unsigned)(0xF & (s51 >> 28)), (unsigned)(0xF & (s62 >> 28)), (unsigned)(0xF & (s73 >> 28))); /* Recombine and return */ return (s40 >> 12) | (s62 >> 4) | (s51 >> 8) | (s73); #endif /* SIXTY_FOUR_BIT */ } /* * Now for the permutation P. The basic strategy here is to use a * Benes network: in each stage, the bit at position i is allowed to * either stay where it is or swap with i ^ D, where D is a power of 2 * that varies with each phase. (So when D=1, pairs of the form * {2n,2n+1} can swap; when D=2, the pairs are {4n+j,4n+j+2} for * j={0,1}, and so on.) * * You can recursively construct a Benes network for an arbitrary * permutation, in which the values of D iterate across all the powers * of 2 less than the permutation size and then go back again. For * example, the typical presentation for 32 bits would have D iterate * over 16,8,4,2,1,2,4,8,16, and there's an easy algorithm that can * express any permutation in that form by deciding which pairs of * bits to swap in the outer pair of stages and then recursing to do * all the stages in between. * * Actually implementing the swaps is easy when they're all between * bits at the same separation: make the value x ^ (x >> D), mask out * just the bits in the low position of a pair that needs to swap, and * then use the resulting value y to make x ^ y ^ (y << D) which is * the swapped version. * * In this particular case, I processed the bit indices in the other * order (going 1,2,4,8,16,8,4,2,1), which makes no significant * difference to the construction algorithm (it's just a relabelling), * but it now means that the first two steps only permute entries * within the output of each S-box - and therefore we can leave them * completely out, in favour of just defining the S-boxes so that * those permutation steps are already applied. Furthermore, by * exhaustive search over the rest of the possible bit-orders for each * S-box, I was able to find a version of P which could be represented * in such a way that two further phases had all their control bits * zero and could be skipped. So the number of swap stages is reduced * to 5 from the 9 that might have been needed. */ static inline uint32_t des_benes_step(uint32_t v, unsigned D, uint32_t mask) { uint32_t diff = (v ^ (v >> D)) & mask; return v ^ diff ^ (diff << D); } static inline uint32_t des_P(uint32_t v_orig) { uint32_t v = v_orig; /* initial stages with distance 1,2 are part of the S-box data table */ v = des_benes_step(v, 4, 0x07030702); v = des_benes_step(v, 8, 0x004E009E); v = des_benes_step(v, 16, 0x0000D9D3); /* v = des_benes_step(v, 8, 0x00000000); no-op, so we can skip it */ v = des_benes_step(v, 4, 0x05040004); /* v = des_benes_step(v, 2, 0x00000000); no-op, so we can skip it */ v = des_benes_step(v, 1, 0x04045015); debug("P(%08"PRIx32") = %08"PRIx32"\n", v_orig, v); return v; } /* * Putting the S and P functions together, and adding in the round key * as well, gives us the full mixing function f. */ static inline uint32_t des_f(uint32_t R, uint32_t K7531, uint32_t K6420) { uint32_t s7531 = R ^ K7531, s6420 = rol(R, 4) ^ K6420; return des_P(des_S(s6420, s7531)); } /* * The key schedule, and the function to set it up. */ typedef struct des_keysched des_keysched; struct des_keysched { uint32_t k7531[16], k6420[16]; }; /* * Simplistic function to select an arbitrary sequence of bits from * one value and glue them together into another value. bitnums[] * gives the sequence of bit indices of the input, from the highest * output bit downwards. An index of -1 means that output bit is left * at zero. * * This function is only used during key setup, so it doesn't need to * be highly optimised. */ static inline uint64_t bitsel( uint64_t input, const int8_t *bitnums, size_t size) { uint64_t ret = 0; while (size-- > 0) { int bitpos = *bitnums++; ret <<= 1; if (bitpos >= 0) ret |= 1 & (input >> bitpos); } return ret; } static void des_key_setup(uint64_t key, des_keysched *sched) { static const int8_t PC1[] = { 7, 15, 23, 31, 39, 47, 55, 63, 6, 14, 22, 30, 38, 46, 54, 62, 5, 13, 21, 29, 37, 45, 53, 61, 4, 12, 20, 28, -1, -1, -1, -1, 1, 9, 17, 25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42, 50, 58, 3, 11, 19, 27, 35, 43, 51, 59, 36, 44, 52, 60, }; static const int8_t PC2_7531[] = { 46, 43, 49, 36, 59, 55, -1, -1, /* index into S-box 7 */ 37, 41, 48, 56, 34, 52, -1, -1, /* index into S-box 5 */ 15, 4, 25, 19, 9, 1, -1, -1, /* index into S-box 3 */ 12, 7, 17, 0, 22, 3, -1, -1, /* index into S-box 1 */ }; static const int8_t PC2_6420[] = { 57, 32, 45, 54, 39, 50, -1, -1, /* index into S-box 6 */ 44, 53, 33, 40, 47, 58, -1, -1, /* index into S-box 4 */ 26, 16, 5, 11, 23, 8, -1, -1, /* index into S-box 2 */ 10, 14, 6, 20, 27, 24, -1, -1, /* index into S-box 0 */ }; static const int leftshifts[] = {1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1}; /* Select 56 bits from the 64-bit input key integer (the low bit * of each input byte is unused), into a word consisting of two * 28-bit integers starting at bits 0 and 32. */ uint64_t CD = bitsel(key, PC1, lenof(PC1)); for (size_t i = 0; i < 16; i++) { /* Rotate each 28-bit half of CD left by 1 or 2 bits (varying * between rounds) */ CD <<= leftshifts[i]; CD = (CD & 0x0FFFFFFF0FFFFFFF) | ((CD & 0xF0000000F0000000) >> 28); /* Select key bits from the rotated word to use during the * actual cipher */ sched->k7531[i] = bitsel(CD, PC2_7531, lenof(PC2_7531)); sched->k6420[i] = bitsel(CD, PC2_6420, lenof(PC2_6420)); } } /* * Helper routines for dealing with 64-bit blocks in the form of an L * and R word. */ typedef struct LR LR; struct LR { uint32_t L, R; }; static inline LR des_load_lr(const void *vp) { const uint8_t *p = (const uint8_t *)vp; LR out; out.L = GET_32BIT_MSB_FIRST(p); out.R = GET_32BIT_MSB_FIRST(p+4); return out; } static inline void des_store_lr(void *vp, LR lr) { uint8_t *p = (uint8_t *)vp; PUT_32BIT_MSB_FIRST(p, lr.L); PUT_32BIT_MSB_FIRST(p+4, lr.R); } static inline LR des_xor_lr(LR a, LR b) { a.L ^= b.L; a.R ^= b.R; return a; } static inline LR des_swap_lr(LR in) { LR out; out.L = in.R; out.R = in.L; return out; } /* * The initial and final permutations of official DES are in a * restricted form, in which the 'before' and 'after' positions of a * given data bit are derived from each other by permuting the bits of * the _index_ and flipping some of them. This allows the permutation * to be performed effectively by a method that looks rather like * _half_ of a general Benes network, because the restricted form * means only half of it is actually needed. * * _Our_ initial and final permutations include a rotation by 1 bit, * but it's still easier to just suffix that to the standard IP/FP * than to regenerate everything using a more general method. * * Because we're permuting 64 bits in this case, between two 32-bit * words, there's a separate helper function for this code that * doesn't look quite like des_benes_step() above. */ static inline void des_bitswap_IP_FP(uint32_t *L, uint32_t *R, unsigned D, uint32_t mask) { uint32_t diff = mask & ((*R >> D) ^ *L); *R ^= diff << D; *L ^= diff; } static inline LR des_IP(LR lr) { des_bitswap_IP_FP(&lr.R, &lr.L, 4, 0x0F0F0F0F); des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF); des_bitswap_IP_FP(&lr.L, &lr.R, 2, 0x33333333); des_bitswap_IP_FP(&lr.L, &lr.R, 8, 0x00FF00FF); des_bitswap_IP_FP(&lr.R, &lr.L, 1, 0x55555555); lr.L = ror(lr.L, 1); lr.R = ror(lr.R, 1); return lr; } static inline LR des_FP(LR lr) { lr.L = rol(lr.L, 1); lr.R = rol(lr.R, 1); des_bitswap_IP_FP(&lr.R, &lr.L, 1, 0x55555555); des_bitswap_IP_FP(&lr.L, &lr.R, 8, 0x00FF00FF); des_bitswap_IP_FP(&lr.L, &lr.R, 2, 0x33333333); des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF); des_bitswap_IP_FP(&lr.R, &lr.L, 4, 0x0F0F0F0F); return lr; } /* * The main cipher functions, which are identical except that they use * the key schedule in opposite orders. * * We provide a version without the initial and final permutations, * for use in triple-DES mode (no sense undoing and redoing it in * between the phases). */ static inline LR des_round(LR in, const des_keysched *sched, size_t round) { LR out; out.L = in.R; out.R = in.L ^ des_f(in.R, sched->k7531[round], sched->k6420[round]); return out; } static inline LR des_inner_cipher(LR lr, const des_keysched *sched, size_t start, size_t step) { lr = des_round(lr, sched, start+0x0*step); lr = des_round(lr, sched, start+0x1*step); lr = des_round(lr, sched, start+0x2*step); lr = des_round(lr, sched, start+0x3*step); lr = des_round(lr, sched, start+0x4*step); lr = des_round(lr, sched, start+0x5*step); lr = des_round(lr, sched, start+0x6*step); lr = des_round(lr, sched, start+0x7*step); lr = des_round(lr, sched, start+0x8*step); lr = des_round(lr, sched, start+0x9*step); lr = des_round(lr, sched, start+0xa*step); lr = des_round(lr, sched, start+0xb*step); lr = des_round(lr, sched, start+0xc*step); lr = des_round(lr, sched, start+0xd*step); lr = des_round(lr, sched, start+0xe*step); lr = des_round(lr, sched, start+0xf*step); return des_swap_lr(lr); } static inline LR des_full_cipher(LR lr, const des_keysched *sched, size_t start, size_t step) { lr = des_IP(lr); lr = des_inner_cipher(lr, sched, start, step); lr = des_FP(lr); return lr; } /* * Parameter pairs for the start,step arguments to the cipher routines * above, causing them to use the same key schedule in opposite orders. */ #define ENCIPHER 0, 1 /* for encryption */ #define DECIPHER 15, -1 /* for decryption */ /* ---------------------------------------------------------------------- * Single-DES */ struct des_cbc_ctx { des_keysched sched; LR iv; ssh_cipher ciph; }; static ssh_cipher *des_cbc_new(const ssh_cipheralg *alg) { struct des_cbc_ctx *ctx = snew(struct des_cbc_ctx); ctx->ciph.vt = alg; return &ctx->ciph; } static void des_cbc_free(ssh_cipher *ciph) { struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); smemclr(ctx, sizeof(*ctx)); sfree(ctx); } static void des_cbc_setkey(ssh_cipher *ciph, const void *vkey) { struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); const uint8_t *key = (const uint8_t *)vkey; des_key_setup(GET_64BIT_MSB_FIRST(key), &ctx->sched); } static void des_cbc_setiv(ssh_cipher *ciph, const void *iv) { struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); ctx->iv = des_load_lr(iv); } static void des_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) { struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); uint8_t *data = (uint8_t *)vdata; for (; len > 0; len -= 8, data += 8) { LR plaintext = des_load_lr(data); LR cipher_in = des_xor_lr(plaintext, ctx->iv); LR ciphertext = des_full_cipher(cipher_in, &ctx->sched, ENCIPHER); des_store_lr(data, ciphertext); ctx->iv = ciphertext; } } static void des_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) { struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); uint8_t *data = (uint8_t *)vdata; for (; len > 0; len -= 8, data += 8) { LR ciphertext = des_load_lr(data); LR cipher_out = des_full_cipher(ciphertext, &ctx->sched, DECIPHER); LR plaintext = des_xor_lr(cipher_out, ctx->iv); des_store_lr(data, plaintext); ctx->iv = ciphertext; } } const ssh_cipheralg ssh_des = { .new = des_cbc_new, .free = des_cbc_free, .setiv = des_cbc_setiv, .setkey = des_cbc_setkey, .encrypt = des_cbc_encrypt, .decrypt = des_cbc_decrypt, .ssh2_id = "des-cbc", .blksize = 8, .real_keybits = 56, .padded_keybytes = 8, .flags = SSH_CIPHER_IS_CBC, .text_name = "single-DES CBC", }; const ssh_cipheralg ssh_des_sshcom_ssh2 = { /* Same as ssh_des_cbc, but with a different SSH-2 ID */ .new = des_cbc_new, .free = des_cbc_free, .setiv = des_cbc_setiv, .setkey = des_cbc_setkey, .encrypt = des_cbc_encrypt, .decrypt = des_cbc_decrypt, .ssh2_id = "des-cbc@ssh.com", .blksize = 8, .real_keybits = 56, .padded_keybytes = 8, .flags = SSH_CIPHER_IS_CBC, .text_name = "single-DES CBC", }; static const ssh_cipheralg *const des_list[] = { &ssh_des, &ssh_des_sshcom_ssh2 }; const ssh2_ciphers ssh2_des = { lenof(des_list), des_list }; /* ---------------------------------------------------------------------- * Triple-DES CBC, SSH-2 style. The CBC mode treats the three * invocations of DES as a single unified cipher, and surrounds it * with just one layer of CBC, so only one IV is needed. */ struct des3_cbc1_ctx { des_keysched sched[3]; LR iv; ssh_cipher ciph; }; static ssh_cipher *des3_cbc1_new(const ssh_cipheralg *alg) { struct des3_cbc1_ctx *ctx = snew(struct des3_cbc1_ctx); ctx->ciph.vt = alg; return &ctx->ciph; } static void des3_cbc1_free(ssh_cipher *ciph) { struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); smemclr(ctx, sizeof(*ctx)); sfree(ctx); } static void des3_cbc1_setkey(ssh_cipher *ciph, const void *vkey) { struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); const uint8_t *key = (const uint8_t *)vkey; for (size_t i = 0; i < 3; i++) des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); } static void des3_cbc1_setiv(ssh_cipher *ciph, const void *iv) { struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); ctx->iv = des_load_lr(iv); } static void des3_cbc1_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) { struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); uint8_t *data = (uint8_t *)vdata; for (; len > 0; len -= 8, data += 8) { LR plaintext = des_load_lr(data); LR cipher_in = des_xor_lr(plaintext, ctx->iv); /* Run three copies of the cipher, without undoing and redoing * IP/FP in between. */ LR lr = des_IP(cipher_in); lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER); lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); LR ciphertext = des_FP(lr); des_store_lr(data, ciphertext); ctx->iv = ciphertext; } } static void des3_cbc1_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) { struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); uint8_t *data = (uint8_t *)vdata; for (; len > 0; len -= 8, data += 8) { LR ciphertext = des_load_lr(data); /* Similarly to encryption, but with the order reversed. */ LR lr = des_IP(ciphertext); lr = des_inner_cipher(lr, &ctx->sched[2], DECIPHER); lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER); lr = des_inner_cipher(lr, &ctx->sched[0], DECIPHER); LR cipher_out = des_FP(lr); LR plaintext = des_xor_lr(cipher_out, ctx->iv); des_store_lr(data, plaintext); ctx->iv = ciphertext; } } const ssh_cipheralg ssh_3des_ssh2 = { .new = des3_cbc1_new, .free = des3_cbc1_free, .setiv = des3_cbc1_setiv, .setkey = des3_cbc1_setkey, .encrypt = des3_cbc1_cbc_encrypt, .decrypt = des3_cbc1_cbc_decrypt, .ssh2_id = "3des-cbc", .blksize = 8, .real_keybits = 168, .padded_keybytes = 24, .flags = SSH_CIPHER_IS_CBC, .text_name = "triple-DES CBC", }; /* ---------------------------------------------------------------------- * Triple-DES in SDCTR mode. Again, the three DES instances are * treated as one big cipher, with a single counter encrypted through * all three. */ #define SDCTR_WORDS (8 / BIGNUM_INT_BYTES) struct des3_sdctr_ctx { des_keysched sched[3]; BignumInt counter[SDCTR_WORDS]; ssh_cipher ciph; }; static ssh_cipher *des3_sdctr_new(const ssh_cipheralg *alg) { struct des3_sdctr_ctx *ctx = snew(struct des3_sdctr_ctx); ctx->ciph.vt = alg; return &ctx->ciph; } static void des3_sdctr_free(ssh_cipher *ciph) { struct des3_sdctr_ctx *ctx = container_of( ciph, struct des3_sdctr_ctx, ciph); smemclr(ctx, sizeof(*ctx)); sfree(ctx); } static void des3_sdctr_setkey(ssh_cipher *ciph, const void *vkey) { struct des3_sdctr_ctx *ctx = container_of( ciph, struct des3_sdctr_ctx, ciph); const uint8_t *key = (const uint8_t *)vkey; for (size_t i = 0; i < 3; i++) des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); } static void des3_sdctr_setiv(ssh_cipher *ciph, const void *viv) { struct des3_sdctr_ctx *ctx = container_of( ciph, struct des3_sdctr_ctx, ciph); const uint8_t *iv = (const uint8_t *)viv; /* Import the initial counter value into the internal representation */ for (unsigned i = 0; i < SDCTR_WORDS; i++) ctx->counter[i] = GET_BIGNUMINT_MSB_FIRST( iv + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES); } static void des3_sdctr_encrypt_decrypt(ssh_cipher *ciph, void *vdata, int len) { struct des3_sdctr_ctx *ctx = container_of( ciph, struct des3_sdctr_ctx, ciph); uint8_t *data = (uint8_t *)vdata; uint8_t iv_buf[8]; for (; len > 0; len -= 8, data += 8) { /* Format the counter value into the buffer. */ for (unsigned i = 0; i < SDCTR_WORDS; i++) PUT_BIGNUMINT_MSB_FIRST( iv_buf + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES, ctx->counter[i]); /* Increment the counter. */ BignumCarry carry = 1; for (unsigned i = 0; i < SDCTR_WORDS; i++) BignumADC(ctx->counter[i], carry, ctx->counter[i], 0, carry); /* Triple-encrypt the counter value from the IV. */ LR lr = des_IP(des_load_lr(iv_buf)); lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER); lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); LR keystream = des_FP(lr); LR input = des_load_lr(data); LR output = des_xor_lr(input, keystream); des_store_lr(data, output); } smemclr(iv_buf, sizeof(iv_buf)); } const ssh_cipheralg ssh_3des_ssh2_ctr = { .new = des3_sdctr_new, .free = des3_sdctr_free, .setiv = des3_sdctr_setiv, .setkey = des3_sdctr_setkey, .encrypt = des3_sdctr_encrypt_decrypt, .decrypt = des3_sdctr_encrypt_decrypt, .ssh2_id = "3des-ctr", .blksize = 8, .real_keybits = 168, .padded_keybytes = 24, .flags = 0, .text_name = "triple-DES SDCTR", }; static const ssh_cipheralg *const des3_list[] = { &ssh_3des_ssh2_ctr, &ssh_3des_ssh2 }; const ssh2_ciphers ssh2_3des = { lenof(des3_list), des3_list }; /* ---------------------------------------------------------------------- * Triple-DES, SSH-1 style. SSH-1 replicated the whole CBC structure * three times, so there have to be three separate IVs, one in each * layer. */ struct des3_cbc3_ctx { des_keysched sched[3]; LR iv[3]; ssh_cipher ciph; }; static ssh_cipher *des3_cbc3_new(const ssh_cipheralg *alg) { struct des3_cbc3_ctx *ctx = snew(struct des3_cbc3_ctx); ctx->ciph.vt = alg; return &ctx->ciph; } static void des3_cbc3_free(ssh_cipher *ciph) { struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); smemclr(ctx, sizeof(*ctx)); sfree(ctx); } static void des3_cbc3_setkey(ssh_cipher *ciph, const void *vkey) { struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); const uint8_t *key = (const uint8_t *)vkey; for (size_t i = 0; i < 3; i++) des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); } static void des3_cbc3_setiv(ssh_cipher *ciph, const void *viv) { struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); /* * In principle, we ought to provide an interface for the user to * input 24 instead of 8 bytes of IV. But that would make this an * ugly exception to the otherwise universal rule that IV size = * cipher block size, and there's really no need to violate that * rule given that this is a historical one-off oddity and SSH-1 * always initialises all three IVs to zero anyway. So we fudge it * by just setting all the IVs to the same value. */ LR iv = des_load_lr(viv); /* But we store the IVs in permuted form, so that we can handle * all three CBC layers without having to do IP/FP in between. */ iv = des_IP(iv); for (size_t i = 0; i < 3; i++) ctx->iv[i] = iv; } static void des3_cbc3_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) { struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); uint8_t *data = (uint8_t *)vdata; for (; len > 0; len -= 8, data += 8) { /* Load and IP the input. */ LR plaintext = des_IP(des_load_lr(data)); LR lr = plaintext; /* Do three passes of CBC, with the middle one inverted. */ lr = des_xor_lr(lr, ctx->iv[0]); lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); ctx->iv[0] = lr; LR ciphertext = lr; lr = des_inner_cipher(ciphertext, &ctx->sched[1], DECIPHER); lr = des_xor_lr(lr, ctx->iv[1]); ctx->iv[1] = ciphertext; lr = des_xor_lr(lr, ctx->iv[2]); lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); ctx->iv[2] = lr; des_store_lr(data, des_FP(lr)); } } static void des3_cbc3_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) { struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); uint8_t *data = (uint8_t *)vdata; for (; len > 0; len -= 8, data += 8) { /* Load and IP the input */ LR lr = des_IP(des_load_lr(data)); LR ciphertext; /* Do three passes of CBC, with the middle one inverted. */ ciphertext = lr; lr = des_inner_cipher(ciphertext, &ctx->sched[2], DECIPHER); lr = des_xor_lr(lr, ctx->iv[2]); ctx->iv[2] = ciphertext; lr = des_xor_lr(lr, ctx->iv[1]); lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER); ctx->iv[1] = lr; ciphertext = lr; lr = des_inner_cipher(ciphertext, &ctx->sched[0], DECIPHER); lr = des_xor_lr(lr, ctx->iv[0]); ctx->iv[0] = ciphertext; des_store_lr(data, des_FP(lr)); } } const ssh_cipheralg ssh_3des_ssh1 = { .new = des3_cbc3_new, .free = des3_cbc3_free, .setiv = des3_cbc3_setiv, .setkey = des3_cbc3_setkey, .encrypt = des3_cbc3_cbc_encrypt, .decrypt = des3_cbc3_cbc_decrypt, .blksize = 8, .real_keybits = 168, .padded_keybytes = 24, .flags = SSH_CIPHER_IS_CBC, .text_name = "triple-DES inner-CBC", }; work/crypto/mpint_i.h0000664000000000000000000003172214714222463012072 0ustar /* * mpint_i.h: definitions used internally by the bignum code, and * also a few other vaguely-bignum-like places. */ /* ---------------------------------------------------------------------- * The assorted conditional definitions of BignumInt and multiply * macros used throughout the bignum code to treat numbers as arrays * of the most conveniently sized word for the target machine. * Exported so that other code (e.g. poly1305) can use it too. * * This code must export, in whatever ifdef branch it ends up in: * * - two types: 'BignumInt' and 'BignumCarry'. BignumInt is an * unsigned integer type which will be used as the base word size * for all bignum operations. BignumCarry is an unsigned integer * type used to hold the carry flag taken as input and output by * the BignumADC macro (see below). * * - five constant macros: * + BIGNUM_INT_BITS, the number of bits in BignumInt, * + BIGNUM_INT_BYTES, the number of bytes that works out to * + BIGNUM_TOP_BIT, the BignumInt value consisting of only the top bit * + BIGNUM_INT_MASK, the BignumInt value with all bits set * + BIGNUM_INT_BITS_BITS, log to the base 2 of BIGNUM_INT_BITS. * * - four statement macros: BignumADC, BignumMUL, BignumMULADD, * BignumMULADD2. These do various kinds of multi-word arithmetic, * and all produce two output values. * * BignumADC(ret,retc,a,b,c) takes input BignumInt values a,b * and a BignumCarry c, and outputs a BignumInt ret = a+b+c and * a BignumCarry retc which is the carry off the top of that * addition. * * BignumMUL(rh,rl,a,b) returns the two halves of the * double-width product a*b. * * BignumMULADD(rh,rl,a,b,addend) returns the two halves of the * double-width value a*b + addend. * * BignumMULADD2(rh,rl,a,b,addend1,addend2) returns the two * halves of the double-width value a*b + addend1 + addend2. * * Every branch of the main ifdef below defines the type BignumInt and * the value BIGNUM_INT_BITS_BITS. The other constant macros are * filled in by common code further down. * * Most branches also define a macro DEFINE_BIGNUMDBLINT containing a * typedef statement which declares a type _twice_ the length of a * BignumInt. This causes the common code further down to produce a * default implementation of the four statement macros in terms of * that double-width type, and also to defined BignumCarry to be * BignumInt. * * However, if a particular compile target does not have a type twice * the length of the BignumInt you want to use but it does provide * some alternative means of doing add-with-carry and double-word * multiply, then the ifdef branch in question can just define * BignumCarry and the four statement macros itself, and that's fine * too. */ /* You can lower the BignumInt size by defining BIGNUM_OVERRIDE on the * command line to be your chosen max value of BIGNUM_INT_BITS_BITS */ #if defined BIGNUM_OVERRIDE #define BB_OK(b) ((b) <= BIGNUM_OVERRIDE) #else #define BB_OK(b) (1) #endif #if defined __SIZEOF_INT128__ && BB_OK(6) /* * 64-bit BignumInt using gcc/clang style 128-bit BignumDblInt. * * gcc and clang both provide a __uint128_t type on 64-bit targets * (and, when they do, indicate its presence by the above macro), * using the same 'two machine registers' kind of code generation * that 32-bit targets use for 64-bit ints. */ typedef unsigned long long BignumInt; #define BIGNUM_INT_BITS_BITS 6 #define DEFINE_BIGNUMDBLINT typedef __uint128_t BignumDblInt #elif defined _MSC_VER && defined _M_AMD64 && BB_OK(6) /* * 64-bit BignumInt, using Visual Studio x86-64 compiler intrinsics. * * 64-bit Visual Studio doesn't provide very much in the way of help * here: there's no int128 type, and also no inline assembler giving * us direct access to the x86-64 MUL or ADC instructions. However, * there are compiler intrinsics giving us that access, so we can * use those - though it turns out we have to be a little careful, * since they seem to generate wrong code if their pointer-typed * output parameters alias their inputs. Hence all the internal temp * variables inside the macros. */ #include typedef unsigned char BignumCarry; /* the type _addcarry_u64 likes to use */ typedef unsigned __int64 BignumInt; #define BIGNUM_INT_BITS_BITS 6 #define BignumADC(ret, retc, a, b, c) do \ { \ BignumInt ADC_tmp; \ (retc) = _addcarry_u64(c, a, b, &ADC_tmp); \ (ret) = ADC_tmp; \ } while (0) #define BignumMUL(rh, rl, a, b) do \ { \ BignumInt MULADD_hi; \ (rl) = _umul128(a, b, &MULADD_hi); \ (rh) = MULADD_hi; \ } while (0) #define BignumMULADD(rh, rl, a, b, addend) do \ { \ BignumInt MULADD_lo, MULADD_hi; \ MULADD_lo = _umul128(a, b, &MULADD_hi); \ MULADD_hi += _addcarry_u64(0, MULADD_lo, (addend), &(rl)); \ (rh) = MULADD_hi; \ } while (0) #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \ { \ BignumInt MULADD_lo1, MULADD_lo2, MULADD_hi; \ MULADD_lo1 = _umul128(a, b, &MULADD_hi); \ MULADD_hi += _addcarry_u64(0, MULADD_lo1, (addend1), &MULADD_lo2); \ MULADD_hi += _addcarry_u64(0, MULADD_lo2, (addend2), &(rl)); \ (rh) = MULADD_hi; \ } while (0) #elif (defined __GNUC__ || defined _LLP64 || __STDC__ >= 199901L) && BB_OK(5) /* 32-bit BignumInt, using C99 unsigned long long as BignumDblInt */ typedef unsigned int BignumInt; #define BIGNUM_INT_BITS_BITS 5 #define DEFINE_BIGNUMDBLINT typedef unsigned long long BignumDblInt #elif defined _MSC_VER && BB_OK(5) /* 32-bit BignumInt, using Visual Studio __int64 as BignumDblInt */ typedef unsigned int BignumInt; #define BIGNUM_INT_BITS_BITS 5 #define DEFINE_BIGNUMDBLINT typedef unsigned __int64 BignumDblInt #elif defined _LP64 && BB_OK(5) /* * 32-bit BignumInt, using unsigned long itself as BignumDblInt. * * Only for platforms where long is 64 bits, of course. */ typedef unsigned int BignumInt; #define BIGNUM_INT_BITS_BITS 5 #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt #elif BB_OK(4) /* * 16-bit BignumInt, using unsigned long as BignumDblInt. * * This is the final fallback for real emergencies: C89 guarantees * unsigned short/long to be at least the required sizes, so this * should work on any C implementation at all. But it'll be * noticeably slow, so if you find yourself in this case you * probably want to move heaven and earth to find an alternative! */ typedef unsigned short BignumInt; #define BIGNUM_INT_BITS_BITS 4 #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt #else /* Should only get here if BB_OK(4) evaluated false, i.e. the * command line defined BIGNUM_OVERRIDE to an absurdly small * value. */ #error Must define BIGNUM_OVERRIDE to at least 4 #endif #undef BB_OK /* * Common code across all branches of that ifdef: define all the * easy constant macros in terms of BIGNUM_INT_BITS_BITS. */ #define BIGNUM_INT_BITS (1 << BIGNUM_INT_BITS_BITS) #define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8) #define BIGNUM_TOP_BIT (((BignumInt)1) << (BIGNUM_INT_BITS-1)) #define BIGNUM_INT_MASK (BIGNUM_TOP_BIT | (BIGNUM_TOP_BIT-1)) /* * Just occasionally, we might need a GET_nnBIT_xSB_FIRST macro to * operate on whatever BignumInt is. */ #if BIGNUM_INT_BITS_BITS == 4 #define GET_BIGNUMINT_MSB_FIRST GET_16BIT_MSB_FIRST #define GET_BIGNUMINT_LSB_FIRST GET_16BIT_LSB_FIRST #define PUT_BIGNUMINT_MSB_FIRST PUT_16BIT_MSB_FIRST #define PUT_BIGNUMINT_LSB_FIRST PUT_16BIT_LSB_FIRST #elif BIGNUM_INT_BITS_BITS == 5 #define GET_BIGNUMINT_MSB_FIRST GET_32BIT_MSB_FIRST #define GET_BIGNUMINT_LSB_FIRST GET_32BIT_LSB_FIRST #define PUT_BIGNUMINT_MSB_FIRST PUT_32BIT_MSB_FIRST #define PUT_BIGNUMINT_LSB_FIRST PUT_32BIT_LSB_FIRST #elif BIGNUM_INT_BITS_BITS == 6 #define GET_BIGNUMINT_MSB_FIRST GET_64BIT_MSB_FIRST #define GET_BIGNUMINT_LSB_FIRST GET_64BIT_LSB_FIRST #define PUT_BIGNUMINT_MSB_FIRST PUT_64BIT_MSB_FIRST #define PUT_BIGNUMINT_LSB_FIRST PUT_64BIT_LSB_FIRST #else #error Ran out of options for GET_BIGNUMINT_xSB_FIRST #endif /* * Common code across _most_ branches of the ifdef: define a set of * statement macros in terms of the BignumDblInt type provided. In * this case, we also define BignumCarry to be the same thing as * BignumInt, for simplicity. */ #ifdef DEFINE_BIGNUMDBLINT typedef BignumInt BignumCarry; #define BignumADC(ret, retc, a, b, c) do \ { \ DEFINE_BIGNUMDBLINT; \ BignumDblInt ADC_temp = (BignumInt)(a); \ ADC_temp += (BignumInt)(b); \ ADC_temp += (c); \ (ret) = (BignumInt)ADC_temp; \ (retc) = (BignumCarry)(ADC_temp >> BIGNUM_INT_BITS); \ } while (0) #define BignumMUL(rh, rl, a, b) do \ { \ DEFINE_BIGNUMDBLINT; \ BignumDblInt MUL_temp = (BignumInt)(a); \ MUL_temp *= (BignumInt)(b); \ (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ (rl) = (BignumInt)(MUL_temp); \ } while (0) #define BignumMULADD(rh, rl, a, b, addend) do \ { \ DEFINE_BIGNUMDBLINT; \ BignumDblInt MUL_temp = (BignumInt)(a); \ MUL_temp *= (BignumInt)(b); \ MUL_temp += (BignumInt)(addend); \ (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ (rl) = (BignumInt)(MUL_temp); \ } while (0) #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \ { \ DEFINE_BIGNUMDBLINT; \ BignumDblInt MUL_temp = (BignumInt)(a); \ MUL_temp *= (BignumInt)(b); \ MUL_temp += (BignumInt)(addend1); \ MUL_temp += (BignumInt)(addend2); \ (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ (rl) = (BignumInt)(MUL_temp); \ } while (0) #endif /* DEFINE_BIGNUMDBLINT */ /* ---------------------------------------------------------------------- * Data structures used inside bignum.c. */ struct mp_int { size_t nw; BignumInt *w; }; struct MontyContext { /* * The actual modulus. */ mp_int *m; /* * Montgomery multiplication works by selecting a value r > m, * coprime to m, which is really easy to divide by. In binary * arithmetic, that means making it a power of 2; in fact we make * it a whole number of BignumInt. * * We don't store r directly as an mp_int (there's no need). But * its value is 2^rbits; we also store rw = rbits/BIGNUM_INT_BITS * (the corresponding word offset within an mp_int). * * pw is the number of words needed to store an mp_int you're * doing reduction on: it has to be big enough to hold the sum of * an input value up to m^2 plus an extra addend up to m*r. */ size_t rbits, rw, pw; /* * The key step in Montgomery reduction requires the inverse of -m * mod r. */ mp_int *minus_minv_mod_r; /* * r^1, r^2 and r^3 mod m, which are used for various purposes. * * (Annoyingly, this is one of the rare cases where it would have * been nicer to have a Pascal-style 1-indexed array. I couldn't * _quite_ bring myself to put a gratuitous zero element in here. * So you just have to live with getting r^k by taking the [k-1]th * element of this array.) */ mp_int *powers_of_r_mod_m[3]; /* * Persistent scratch space from which monty_* functions can * allocate storage for intermediate values. */ mp_int *scratch; }; /* Functions shared between mpint.c and mpunsafe.c */ mp_int *mp_make_sized(size_t nw); work/crypto/xdmauth.c0000664000000000000000000000252214714222463012074 0ustar /* * Convenience functions to encrypt and decrypt the cookies used in * XDM-AUTHORIZATION-1. */ #include "ssh.h" static ssh_cipher *des_xdmauth_cipher(const void *vkeydata) { /* * XDM-AUTHORIZATION-1 uses single-DES, but packs the key into 7 * bytes, so here we have to repack it manually into the canonical * form where it occupies 8 bytes each with the low bit unused. */ const unsigned char *keydata = (const unsigned char *)vkeydata; unsigned char key[8]; int i, nbits, j; unsigned int bits; bits = 0; nbits = 0; j = 0; for (i = 0; i < 8; i++) { if (nbits < 7) { bits = (bits << 8) | keydata[j]; nbits += 8; j++; } key[i] = (bits >> (nbits - 7)) << 1; bits &= ~(0x7F << (nbits - 7)); nbits -= 7; } ssh_cipher *c = ssh_cipher_new(&ssh_des); ssh_cipher_setkey(c, key); smemclr(key, sizeof(key)); ssh_cipher_setiv(c, key); return c; } void des_encrypt_xdmauth(const void *keydata, void *blk, int len) { ssh_cipher *c = des_xdmauth_cipher(keydata); ssh_cipher_encrypt(c, blk, len); ssh_cipher_free(c); } void des_decrypt_xdmauth(const void *keydata, void *blk, int len) { ssh_cipher *c = des_xdmauth_cipher(keydata); ssh_cipher_decrypt(c, blk, len); ssh_cipher_free(c); } work/debian/0000775000000000000000000000000014714222463010157 5ustar work/debian/.gitignore0000664000000000000000000000007714714222463012153 0ustar .debhelper debhelper-build-stamp files xtruss xtruss.substvars work/debian/README.source0000664000000000000000000000071214714222463012336 0ustar Maintained in Debian in git on salsa, according to the dgit-maint-merge(7) git workflow. NMUs welcome - but please upload with dgit if you can. The source package is 1.0 (native). Upstream doesn't make releases of any kind. We would prefer to use 3.0 (native) but dpkg hates that with a non-native version number, like this package has, despite the Technical Commitee ruling that it is OK: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1007717#384 work/debian/changelog0000664000000000000000000000030614714222463012030 0ustar xtruss (0.0~git20241011.27fafffe-1) unstable; urgency=medium * Initial upload to Debian. * Closes: #1087205 -- Ian Jackson Sun, 10 Nov 2024 21:27:15 +0000 work/debian/control0000664000000000000000000000103314714222463011557 0ustar Source: xtruss Homepage: https://www.chiark.greenend.org.uk/~sgtatham/xtruss/ Build-Depends: debhelper-compat (=13), cmake, halibut Maintainer: Ian Jackson Vcs-Git: https://salsa.debian.org/debian/xtruss/ Priority: optional Section: devel Standards-Version: 4.7.0.1 Package: xtruss Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends} Description: X11 protocol trace utility Like strace, but for the X Window System: dumps X protocol requests and replies. Works by proxying the X connection. work/debian/copyright0000664000000000000000000000405014714222463012111 0ustar Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: xtruss Upstream-Contact: Simon Tatham Source: https://git.tartarus.org/?p=simon/xtruss.git Files: * Copyright: xtruss is copyright 1997-2021 Simon Tatham. . Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, Colin Watson, Christopher Staite, Lorenz Diener, Christian Brabandt, Jeff Smith, Pavel Kryukov, Maxim Kuznetsov, Svyatoslav Kuzmich, Nico Williams, Viktor Dukhovni, Josh Dersch, Lars Brinkhoff, and CORE SDI S.A. . (Note that much code is taken from PuTTY, where the above authorship information comes from. See README.) License: Expat 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. . Except as contained in this notice, the name of the X Consortium shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the X Consortium. work/debian/rules0000775000000000000000000000003614714222463011236 0ustar #!/usr/bin/make -f %: dh $@ work/debian/source/0000775000000000000000000000000014714222463011457 5ustar work/debian/source/format0000664000000000000000000000000414714222463012664 0ustar 1.0 work/debian/source/lintian-overrides0000664000000000000000000000004314714222463015035 0ustar malformed-debian-changelog-version work/debian/tests/0000775000000000000000000000000014714222463011321 5ustar work/debian/tests/control0000664000000000000000000000021114714222463012716 0ustar Depends: @, x11-utils, xvfb, xauth Tests: xwininfo Restrictions: allow-stderr Depends: @, man Tests: manpage Restrictions: allow-stderr work/debian/tests/manpage0000775000000000000000000000033014714222463012653 0ustar #!/bin/bash set -ex # Various past bugs have involved non-installation of manpages, or # busted manpages. LC_ALL=C.UTF-8 man xtruss \ | grep 'xtruss - trace X protocol exchanges, in the manner of strace' echo ok. work/debian/tests/xwininfo0000775000000000000000000000061714714222463013114 0ustar #!/bin/bash set -e if [ "$AUTOPKGTEST_ARTIFACTS" ]; then cd "$AUTOPKGTEST_ARTIFACTS" elif [ "$AUTOPKGTEST_TMP" ]; then cd "$AUTOPKGTEST_TMP" else echo 'Writing xtruss log to .' fi : ${XTRUSS:=xtruss} set -x xvfb-run -a \ $XTRUSS -o xtruss.out \ xwininfo -root grep '^GetGeometry' xtruss.out grep -P '^QueryTree\(window=w\#\w+\) = \{root=.*, parent=None\}$' xtruss.out echo ok. work/debian/xtruss.install0000664000000000000000000000003414714222463013114 0ustar README usr/share/doc/xtruss work/defs.h0000664000000000000000000001471414714222463010036 0ustar /* * defs.h: initial definitions for PuTTY. * * The rule about this header file is that it can't depend on any * other header file in this code base. This is where we define * things, as much as we can, that other headers will want to refer * to, such as opaque structure types and their associated typedefs, * or macros that are used by other headers. */ #ifndef PUTTY_DEFS_H #define PUTTY_DEFS_H #ifdef NDEBUG /* * PuTTY is a security project, so assertions are important - if an * assumption is violated, proceeding anyway may have far worse * consequences than simple program termination. This check and #error * should arrange that we don't ever accidentally compile assertions * out. */ #error Do not compile this code base with NDEBUG defined! #endif #if HAVE_CMAKE_H #include "cmake.h" #endif #include #include #include /* for __MINGW_PRINTF_FORMAT */ #include #if defined _MSC_VER && _MSC_VER < 1800 /* Work around lack of inttypes.h and strtoumax in older MSVC */ #define PRIx32 "x" #define PRIu32 "u" #define PRIu64 "I64u" #define PRIdMAX "I64d" #define PRIXMAX "I64X" #define SCNu64 "I64u" #define SIZEx "Ix" #define SIZEu "Iu" uintmax_t strtoumax(const char *nptr, char **endptr, int base); #else #include /* Because we still support older MSVC libraries which don't recognise the * standard C "z" modifier for size_t-sized integers, we must use an * inttypes.h-style macro for those */ #define SIZEx "zx" #define SIZEu "zu" #endif #if defined __GNUC__ || defined __clang__ /* * On MinGW, the correct compiler format checking for vsnprintf() etc * can depend on compile-time flags; these control whether you get * ISO C or Microsoft's non-standard format strings. * We sometimes use __attribute__ ((format)) for our own printf-like * functions, which are ultimately interpreted by the toolchain-chosen * printf, so we need to take that into account to get correct warnings. */ #ifdef __MINGW_PRINTF_FORMAT #define PRINTF_LIKE(fmt_index, ellipsis_index) \ __attribute__ ((format (__MINGW_PRINTF_FORMAT, fmt_index, ellipsis_index))) #else #define PRINTF_LIKE(fmt_index, ellipsis_index) \ __attribute__ ((format (printf, fmt_index, ellipsis_index))) #endif #else /* __GNUC__ */ #define PRINTF_LIKE(fmt_index, ellipsis_index) #endif /* __GNUC__ */ typedef struct conf_tag Conf; typedef struct terminal_tag Terminal; typedef struct term_utf8_decode term_utf8_decode; typedef struct Filename Filename; typedef struct FontSpec FontSpec; typedef struct bufchain_tag bufchain; typedef struct strbuf strbuf; typedef struct LoadedFile LoadedFile; typedef struct RSAKey RSAKey; typedef struct BinarySink BinarySink; typedef struct BinarySource BinarySource; typedef struct stdio_sink stdio_sink; typedef struct bufchain_sink bufchain_sink; typedef struct handle_sink handle_sink; typedef struct IdempotentCallback IdempotentCallback; typedef struct SockAddr SockAddr; typedef struct Socket Socket; typedef struct Plug Plug; typedef struct SocketPeerInfo SocketPeerInfo; typedef struct Backend Backend; typedef struct BackendVtable BackendVtable; typedef struct Ldisc_tag Ldisc; typedef struct LogContext LogContext; typedef struct LogPolicy LogPolicy; typedef struct LogPolicyVtable LogPolicyVtable; typedef struct Seat Seat; typedef struct SeatVtable SeatVtable; typedef struct TermWin TermWin; typedef struct TermWinVtable TermWinVtable; typedef struct Ssh Ssh; typedef struct mp_int mp_int; typedef struct MontyContext MontyContext; typedef struct WeierstrassCurve WeierstrassCurve; typedef struct WeierstrassPoint WeierstrassPoint; typedef struct MontgomeryCurve MontgomeryCurve; typedef struct MontgomeryPoint MontgomeryPoint; typedef struct EdwardsCurve EdwardsCurve; typedef struct EdwardsPoint EdwardsPoint; typedef struct SshServerConfig SshServerConfig; typedef struct SftpServer SftpServer; typedef struct SftpServerVtable SftpServerVtable; typedef struct Channel Channel; typedef struct SshChannel SshChannel; typedef struct mainchan mainchan; typedef struct ssh_sharing_state ssh_sharing_state; typedef struct ssh_sharing_connstate ssh_sharing_connstate; typedef struct share_channel share_channel; typedef struct PortFwdManager PortFwdManager; typedef struct PortFwdRecord PortFwdRecord; typedef struct ConnectionLayer ConnectionLayer; typedef struct prng prng; typedef struct ssh_hashalg ssh_hashalg; typedef struct ssh_hash ssh_hash; typedef struct ssh_kex ssh_kex; typedef struct ssh_kexes ssh_kexes; typedef struct ssh_keyalg ssh_keyalg; typedef struct ssh_key ssh_key; typedef struct ssh_compressor ssh_compressor; typedef struct ssh_decompressor ssh_decompressor; typedef struct ssh_compression_alg ssh_compression_alg; typedef struct ssh2_userkey ssh2_userkey; typedef struct ssh2_macalg ssh2_macalg; typedef struct ssh2_mac ssh2_mac; typedef struct ssh_cipheralg ssh_cipheralg; typedef struct ssh_cipher ssh_cipher; typedef struct ssh2_ciphers ssh2_ciphers; typedef struct dh_ctx dh_ctx; typedef struct ecdh_key ecdh_key; typedef struct dlgparam dlgparam; typedef struct settings_w settings_w; typedef struct settings_r settings_r; typedef struct settings_e settings_e; typedef struct SessionSpecial SessionSpecial; typedef struct StripCtrlChars StripCtrlChars; /* * A small structure wrapping up a (pointer, length) pair so that it * can be conveniently passed to or from a function. */ typedef struct ptrlen { const void *ptr; size_t len; } ptrlen; typedef struct logblank_t logblank_t; typedef struct BinaryPacketProtocol BinaryPacketProtocol; typedef struct PacketProtocolLayer PacketProtocolLayer; /* Do a compile-time type-check of 'to_check' (without evaluating it), * as a side effect of returning the value 'to_return'. Note that * although this macro double-*expands* to_return, it always * *evaluates* exactly one copy of it, so it's side-effect safe. */ #define TYPECHECK(to_check, to_return) \ (sizeof(to_check) ? (to_return) : (to_return)) /* Return a pointer to the object of structure type 'type' whose field * with name 'field' is pointed at by 'object'. */ #define container_of(object, type, field) \ TYPECHECK(object == &((type *)0)->field, \ ((type *)(((char *)(object)) - offsetof(type, field)))) #if defined __GNUC__ || defined __clang__ #define NORETURN __attribute__((__noreturn__)) #elif defined _MSC_VER #define NORETURN __declspec(noreturn) #else #define NORETURN #endif #endif /* PUTTY_DEFS_H */ work/doc/0000775000000000000000000000000014714222463007502 5ustar work/doc/CMakeLists.txt0000664000000000000000000000423314714222463012244 0ustar cmake_minimum_required(VERSION 3.12) project(putty-documentation LANGUAGES) # This build script can be run standalone, or included as a # subdirectory of the main PuTTY cmake build system. If the latter, a # couple of things change: it has to set variables telling the rest of # the build system what manpages are available to be installed, and it # will change whether the 'make doc' target is included in 'make all'. include(FindGit) include(FindPerl) find_program(HALIBUT halibut) set(doc_outputs) set(manpage_outputs) macro(register_manpage title section) list(APPEND manpage_outputs ${title}.${section}) if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) # Only set this variable if there _is_ a parent scope. set(HAVE_MANPAGE_${title}_${section} ON PARENT_SCOPE) endif() endmacro() macro(manpage title section) if(HALIBUT) add_custom_command(OUTPUT ${title}.${section} COMMAND ${HALIBUT} --man=${title}.${section} ${CMAKE_CURRENT_SOURCE_DIR}/mancfg.but ${CMAKE_CURRENT_SOURCE_DIR}/man-${title}.but DEPENDS mancfg.but man-${title}.but) register_manpage(${title} ${section}) elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section}) add_custom_command(OUTPUT ${title}.${section} COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section} ${title}.${section} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section}) register_manpage(${title} ${section}) endif() endmacro() manpage(xtruss 1) add_custom_target(manpages ALL DEPENDS ${manpage_outputs}) add_custom_target(doc DEPENDS ${doc_outputs} manpages) if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) # If we're doing a cmake from just the doc subdir, we expect the # user to want to make all the documentation, including HTML and so # forth. (What else would be the point?) # # But if we're included from the main makefile, then by default we # only make the man pages (which are necessary for 'make install'), # and we leave everything else to a separate 'make doc' target which # the user can invoke if they need to. add_custom_target(doc-default ALL DEPENDS doc) endif() work/doc/man-xtruss.but0000664000000000000000000002034214714222463012340 0ustar \cfg{man-identity}{xtruss}{1}{2009-05-02}{PuTTY spinoffs}{PuTTY spinoffs} \define{dash} \u2013{-} \title Man page for \cw{xtruss} \U NAME \cw{xtruss} \dash trace X protocol exchanges, in the manner of \cw{strace} \U SYNOPSIS \c xtruss [ options ] command [ command-arguments ] \e bbbbbb iiiiiii iiiiiii iiiiiiiiiiiiiiiii \c xtruss [ options ] -p X-resource-ID \e bbbbbb iiiiiii bb iiiiiiiiiiiii \c xtruss [ options ] -p - \e bbbbbb iiiiiii bb b \c xtruss [ options ] -P \e bbbbbb iiiiiii bb \U DESCRIPTION \cw{xtruss} is a utility which logs everything that passes between the X server and one or more X client programs. In this it is similar to \cw{xmon}(1), but intended to combine \cw{xmon}'s basic functionality with an interface much more similar to \cw{strace}(1). Like \cw{xmon}, \cw{xtruss} in its default mode works by setting up a proxy X server, waiting for connections to that, and forwarding them on to the real X server. However, unlike \cw{xmon}, you don't have to deal with any of that by hand: there's no need to start the trace utility in one terminal and manually attach processes to it from another, unless you really want to (in which case the \cw{-P} option will do that). The principal mode of use is just to type \cw{xtruss} followed by the command line of your X program; \cw{xtruss} will automatically take care of adjusting the new program's environment to point at its proxy server, and (also unlike \cw{xmon}) it will also take care of X authorisation automatically. As an alternative mode of use, you can also attach \cw{xtruss} to an already-running X application, if you didn't realise you were going to want to trace it until it had already been started. This mode requires cooperation from the X server \dash specifically, it can't work unless the server supports the \cw{RECORD} protocol extension \dash but since modern X.Org servers do provide that, it's often useful. The logging format of \cw{xtruss} is much more compact than that of \cw{xmon}, and resembles \cw{strace} in that it's written to look like a series of function calls some of which return values. For instance, where \cw{xmon} would print \c ............REQUEST: GetSelectionOwner \c sequence number: 000f \c request length: 0002 \c selection: \c ..............REPLY: GetSelectionOwner \c sequence number: 000f \c reply length: 00000000 \c owner: WIN 02c0002b \c ............REQUEST: InternAtom \c sequence number: 0010 \c only-if-exists: False \c request length: 0005 \c length of name: 000c \c name: "VT_SELECTION" \c ..............REPLY: InternAtom \c sequence number: 0010 \c reply length: 00000000 \c atom: ATM 000002bf \cw{xtruss} will instead print \c GetSelectionOwner(selection=a#1) = {owner=w#02C0002B} \c InternAtom(name="VT_SELECTION", only-if-exists=False) = {atom=a#703} Note that not only has each request been condensed on to one line (though most lines will be long enough to wrap, at least on a standard 80-column terminal), but also each request and reply have been printed on the \e{same} line. That last is not always possible, of course: sometimes an application will queue multiple requests before receiving the reply to the first one (in fact, this is generally good behaviour since it minimises network round-trip delays), in which case \cw{xtruss}'s output will look \dash again mimicking \cw{strace} \dash something like this: \c InternAtom(name="TARGETS", only-if-exists=False) = \c InternAtom(name="TIMESTAMP", only-if-exists=False) = \c ... InternAtom(name="TARGETS", only-if-exists=False) = {atom=a#378} \c ... InternAtom(name="TIMESTAMP", only-if-exists=False) = {atom=a#379} \U OPTIONS These options change the mode of operation of \cw{xtruss} away from the default of acting as a wrapper on a single command: \dt \cw{-p} \e{resource-ID} \dd Attach to the X client owning the given resource, using the X \cw{RECORD} extension (which the server must support for this to work). The resource ID can be a decimal integer or a hex integer preceded with \cq{0x}. It typically names an X window, but can name another type of resource instead (e.g. a pixmap or cursor) or just specify the resource base of the client connection. If you don't know any of those things, you can give the resource ID as just \cq{-}, in which case \cw{xtruss} will allow you to interactively select a window by clicking with the mouse (similarly to \cw{xkill}(1), \cw{xwininfo}(1) and \cw{xprop}(1)) and will trace the client that owns the window you select. \dt \cw{-P} \dd Set up a logging X proxy as in the normal mode, but instead of spawning a subprocess to connect to that proxy, just wait for connections. This turns \cw{xtruss} into a tool more similar to \cw{xmon}: you start it in one terminal window, and then from another terminal window you can configure selected processes to connect to the proxy server and be logged. \cw{xtruss} will print on standard output the environment variables you need to set up to connect other processes to the proxy (in both \cw{sh} and \cw{csh} syntax). The following options apply to all modes of operation: \dt \cw{-s} \e{length} \dd Limit the length of output lines by eliding most of the contents of long arrays, strings and blocks of data. \cw{xtruss} will begin to shorten lines at the specified length (any line shorter than that should not be interfered with), but lines cannot always be chopped to the exact length and continue to make sense, so the line length is approximate only. Specifying zero or \cq{unlimited} will remove all restriction, so that \cw{xtruss} will display the full contents of every request it understands, no matter how big. Default is 256. \dt \cw{-o} \e{filename} \dd Send the trace output to the specified file, or to standard output if \e{filename} is just \cq{-}. The default is to log to standard error. \dt \cw{-e} [\e{class}\cw{=}][\cw{!}]\e{item}[\cw{,}\e{item}...] \dd Specify a subset of X requests or X events to log. \e{class} can be either \cq{requests} or \cq{events}; if the class is omitted, \cq{requests} is assumed. The list of \e{item} gives a list of X request names or X event names (respectively) to be logged; all other requests or events are omitted. If the list of items is prefixed with \cw{!}, it is instead treated as a list of requests or events \e{not} to be logged, and anything not in the list is printed. Reply and error packets are not separately filtered: they are logged if and only if the request they respond to was logged. \lcont{ For example, to log only \cw{ImageText8} and \cw{ImageText16} requests, you might say \q{\cw{xtruss -e requests=ImageText8,ImageText16} \e{command}} or just \q{\cw{xtruss -e ImageText8,ImageText16} \e{command}}. To inhibit the display of \cw{FocusIn} and \cw{FocusOut} events, you might say \q{\cw{xtruss -e events=!FocusIn,FocusOut} \e{command}}. (Note that the \cw{!} character might be treated specially by your shell, so you may need to escape it.) } \dt \cw{-I} \dd Log the initialisation message sent by the X server at the start of the connection. This is omitted by default because it's particularly long and ugly. \dt \cw{-R} \dd As well as translating the X protocol, also give a raw hex dump of all the data transferred over the connection. (Probably most useful to include in a bug report about \cw{xtruss} itself!) \dt \cw{-C} \dd Prefix every output line with the X client id (resource base) of the client connection it came from. By default \cw{xtruss} only starts to do this if it's tracing more than one X client; before then, lines are unprefixed. This option makes prefixing unconditional from the start of the run. \U BUGS Many commonly used X protocol extensions are not currently decoded. A lot of this program has been only minimally tested. \U LICENCE \cw{xtruss} is free software, distributed under the MIT/X11 licence. Type \cw{xtruss --licence} to see the full licence text. work/doc/mancfg.but0000664000000000000000000000006714714222463011454 0ustar \# This file was used in PuTTY, but is blank in xtruss work/errsock.c0000664000000000000000000000266714714222463010564 0ustar /* * A dummy Socket implementation which just holds an error message. */ #include #include #include "tree234.h" #include "putty.h" #include "network.h" typedef struct { char *error; Plug *plug; Socket sock; } ErrorSocket; static Plug *sk_error_plug(Socket *s, Plug *p) { ErrorSocket *es = container_of(s, ErrorSocket, sock); Plug *ret = es->plug; if (p) es->plug = p; return ret; } static void sk_error_close(Socket *s) { ErrorSocket *es = container_of(s, ErrorSocket, sock); sfree(es->error); sfree(es); } static const char *sk_error_socket_error(Socket *s) { ErrorSocket *es = container_of(s, ErrorSocket, sock); return es->error; } static SocketPeerInfo *sk_error_peer_info(Socket *s) { return NULL; } static const SocketVtable ErrorSocket_sockvt = { .plug = sk_error_plug, .close = sk_error_close, .socket_error = sk_error_socket_error, .peer_info = sk_error_peer_info, /* other methods are NULL */ }; Socket *new_error_socket_consume_string(Plug *plug, char *errmsg) { ErrorSocket *es = snew(ErrorSocket); es->sock.vt = &ErrorSocket_sockvt; es->plug = plug; es->error = errmsg; return &es->sock; } Socket *new_error_socket_fmt(Plug *plug, const char *fmt, ...) { va_list ap; char *msg; va_start(ap, fmt); msg = dupvprintf(fmt, ap); va_end(ap); return new_error_socket_consume_string(plug, msg); } work/marshal.h0000664000000000000000000003432414714222463010543 0ustar #ifndef PUTTY_MARSHAL_H #define PUTTY_MARSHAL_H #include "defs.h" #include /* * A sort of 'abstract base class' or 'interface' or 'trait' which is * the common feature of all types that want to accept data formatted * using the SSH binary conventions of uint32, string, mpint etc. */ struct BinarySink { void (*write)(BinarySink *sink, const void *data, size_t len); BinarySink *binarysink_; }; /* * To define a structure type as a valid target for binary formatted * data, put 'BinarySink_IMPLEMENTATION' in its declaration, and when * an instance is set up, use 'BinarySink_INIT' to initialise the * 'base class' state, providing a function pointer to be the * implementation of the write() call above. */ #define BinarySink_IMPLEMENTATION BinarySink binarysink_[1] #define BinarySink_INIT(obj, writefn) \ ((obj)->binarysink_->write = (writefn), \ (obj)->binarysink_->binarysink_ = (obj)->binarysink_) /* * To define a larger structure type as a valid BinarySink in such a * way that it will delegate the write method to some other object, * put 'BinarySink_DELEGATE_IMPLEMENTATION' in its declaration, and * when an instance is set up, use 'BinarySink_DELEGATE_INIT' to point * at the object it wants to delegate to. * * In such a delegated structure, you might sometimes want to have the * delegation stop being valid (e.g. it might be delegating to an * object that only sometimes exists). You can null out the delegate * pointer using BinarySink_DELEGATE_CLEAR. */ #define BinarySink_DELEGATE_IMPLEMENTATION BinarySink *binarysink_ #define BinarySink_DELEGATE_INIT(obj, othersink) \ ((obj)->binarysink_ = BinarySink_UPCAST(othersink)) #define BinarySink_DELEGATE_CLEAR(obj) ((obj)->binarysink_ = NULL) /* * The implementing type's write function will want to downcast its * 'BinarySink *' parameter back to the more specific type. Also, * sometimes you'll want to upcast a pointer to a particular * implementing type into an abstract 'BinarySink *' to pass to * generic subroutines not defined in this file. These macros do that * job. * * Importantly, BinarySink_UPCAST can also be applied to a BinarySink * * itself (and leaves it unchanged). That's achieved by a small * piece of C trickery: implementing structures and the BinarySink * structure itself both contain a field called binarysink_, but in * implementing objects it's a BinarySink[1] whereas in the abstract * type it's a 'BinarySink *' pointing back to the same structure, * meaning that you can say 'foo->binarysink_' in either case and get * a pointer type by different methods. */ #define BinarySink_DOWNCAST(object, type) \ TYPECHECK((object) == ((type *)0)->binarysink_, \ ((type *)(((char *)(object)) - offsetof(type, binarysink_)))) #define BinarySink_UPCAST(object) \ TYPECHECK((object)->binarysink_ == (BinarySink *)0, \ (object)->binarysink_) /* * If you structure-copy an object that's implementing BinarySink, * then that tricky self-pointer in its trait subobject will point to * the wrong place. You could call BinarySink_INIT again, but this * macro is terser and does all that's needed to fix up the copied * object. */ #define BinarySink_COPIED(obj) \ ((obj)->binarysink_->binarysink_ = (obj)->binarysink_) /* * The put_* macros are the main client to this system. Any structure * which implements the BinarySink 'trait' is valid for use as the * first parameter of any of these put_* macros. */ /* Basic big-endian integer types. */ #define put_byte(bs, val) \ BinarySink_put_byte(BinarySink_UPCAST(bs), val) #define put_uint16(bs, val) \ BinarySink_put_uint16(BinarySink_UPCAST(bs), val) #define put_uint32(bs, val) \ BinarySink_put_uint32(BinarySink_UPCAST(bs), val) #define put_uint64(bs, val) \ BinarySink_put_uint64(BinarySink_UPCAST(bs), val) /* SSH booleans, encoded as a single byte storing either 0 or 1. */ #define put_bool(bs, val) \ BinarySink_put_bool(BinarySink_UPCAST(bs), val) /* SSH strings, with a leading uint32 length field. 'stringz' is a * convenience function that takes an ordinary C zero-terminated * string as input. 'stringsb' takes a strbuf * as input, and * finalises it as a side effect (handy for multi-level marshalling in * which you use these same functions to format an inner blob of data * that then gets wrapped into a string container in an outer one). */ #define put_string(bs, val, len) \ BinarySink_put_string(BinarySink_UPCAST(bs),val,len) #define put_stringpl(bs, ptrlen) \ BinarySink_put_stringpl(BinarySink_UPCAST(bs),ptrlen) #define put_stringz(bs, val) \ BinarySink_put_stringz(BinarySink_UPCAST(bs), val) #define put_stringsb(bs, val) \ BinarySink_put_stringsb(BinarySink_UPCAST(bs), val) /* Other string outputs: 'asciz' emits the string data directly into * the output including the terminating \0, and 'pstring' emits the * string in Pascal style with a leading _one_-byte length field. * pstring can fail if the string is too long. */ #define put_asciz(bs, val) \ BinarySink_put_asciz(BinarySink_UPCAST(bs), val) #define put_pstring(bs, val) \ BinarySink_put_pstring(BinarySink_UPCAST(bs), val) /* Multiprecision integers, in both the SSH-1 and SSH-2 formats. */ #define put_mp_ssh1(bs, val) \ BinarySink_put_mp_ssh1(BinarySink_UPCAST(bs), val) #define put_mp_ssh2(bs, val) \ BinarySink_put_mp_ssh2(BinarySink_UPCAST(bs), val) /* Padding with a specified byte. */ #define put_padding(bs, len, padbyte) \ BinarySink_put_padding(BinarySink_UPCAST(bs), len, padbyte) /* Fallback: just emit raw data bytes, using a syntax that matches the * rest of these macros. */ #define put_data(bs, val, len) \ BinarySink_put_data(BinarySink_UPCAST(bs), val, len) #define put_datapl(bs, pl) \ BinarySink_put_datapl(BinarySink_UPCAST(bs), pl) /* * The underlying real C functions that implement most of those * macros. Generally you won't want to call these directly, because * they have such cumbersome names; you call the wrapper macros above * instead. * * A few functions whose wrapper macros are defined above are actually * declared in other headers, so as to guarantee that the * declaration(s) of their other parameter type(s) are in scope. */ void BinarySink_put_data(BinarySink *, const void *data, size_t len); void BinarySink_put_datapl(BinarySink *, ptrlen); void BinarySink_put_padding(BinarySink *, size_t len, unsigned char padbyte); void BinarySink_put_byte(BinarySink *, unsigned char); void BinarySink_put_bool(BinarySink *, bool); void BinarySink_put_uint16(BinarySink *, unsigned long); void BinarySink_put_uint32(BinarySink *, unsigned long); void BinarySink_put_uint64(BinarySink *, uint64_t); void BinarySink_put_string(BinarySink *, const void *data, size_t len); void BinarySink_put_stringpl(BinarySink *, ptrlen); void BinarySink_put_stringz(BinarySink *, const char *str); struct strbuf; void BinarySink_put_stringsb(BinarySink *, struct strbuf *); void BinarySink_put_asciz(BinarySink *, const char *str); bool BinarySink_put_pstring(BinarySink *, const char *str); void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x); void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x); /* ---------------------------------------------------------------------- */ /* * A complementary trait structure for _un_-marshalling. * * This structure contains client-visible data fields rather than * methods, because that seemed more useful than leaving it totally * opaque. But it's still got the self-pointer system that will allow * the set of get_* macros to target one of these itself or any other * type that 'derives' from it. So, for example, an SSH packet * structure can act as a BinarySource while also having additional * fields like the packet type. */ typedef enum BinarySourceError { BSE_NO_ERROR, BSE_OUT_OF_DATA, BSE_INVALID } BinarySourceError; struct BinarySource { /* * (data, len) is the data block being decoded. pos is the current * position within the block. */ const void *data; size_t pos, len; /* * 'err' indicates whether a decoding error has happened at any * point. Once this has been set to something other than * BSE_NO_ERROR, it shouldn't be changed by any unmarshalling * function. So you can safely do a long sequence of get_foo() * operations and then test err just once at the end, rather than * having to conditionalise every single get. * * The unmarshalling functions should always return some value, * even if a decoding error occurs. Generally on error they'll * return zero (if numeric) or the empty string (if string-based), * or some other appropriate default value for more complicated * types. * * If the usual return value is dynamically allocated (e.g. a * bignum, or a normal C 'char *' string), then the error value is * also dynamic in the same way. So you have to free exactly the * same set of things whether or not there was a decoding error, * which simplifies exit paths - for example, you could call a big * pile of get_foo functions, then put the actual handling of the * results under 'if (!get_err(src))', and then free everything * outside that if. */ BinarySourceError err; /* * Self-pointer for the implicit derivation trick, same as * BinarySink above. */ BinarySource *binarysource_; }; /* * Implementation macros, similar to BinarySink. */ #define BinarySource_IMPLEMENTATION BinarySource binarysource_[1] static inline void BinarySource_INIT__(BinarySource *src, ptrlen data) { src->data = data.ptr; src->len = data.len; src->pos = 0; src->err = BSE_NO_ERROR; src->binarysource_ = src; } #define BinarySource_BARE_INIT_PL(obj, pl) \ TYPECHECK(&(obj)->binarysource_ == (BinarySource **)0, \ BinarySource_INIT__(obj, pl)) #define BinarySource_BARE_INIT(obj, data_, len_) \ BinarySource_BARE_INIT_PL(obj, make_ptrlen(data_, len_)) #define BinarySource_INIT_PL(obj, pl) \ TYPECHECK(&(obj)->binarysource_ == (BinarySource (*)[1])0, \ BinarySource_INIT__(BinarySource_UPCAST(obj), pl)) #define BinarySource_INIT(obj, data_, len_) \ BinarySource_INIT_PL(obj, make_ptrlen(data_, len_)) #define BinarySource_DOWNCAST(object, type) \ TYPECHECK((object) == ((type *)0)->binarysource_, \ ((type *)(((char *)(object)) - offsetof(type, binarysource_)))) #define BinarySource_UPCAST(object) \ TYPECHECK((object)->binarysource_ == (BinarySource *)0, \ (object)->binarysource_) #define BinarySource_COPIED(obj) \ ((obj)->binarysource_->binarysource_ = (obj)->binarysource_) #define BinarySource_REWIND_TO(src, pos) \ BinarySource_REWIND_TO__((src)->binarysource_, pos) #define BinarySource_REWIND(src) \ BinarySource_REWIND_TO__((src)->binarysource_, 0) #define get_data(src, len) \ BinarySource_get_data(BinarySource_UPCAST(src), len) #define get_byte(src) \ BinarySource_get_byte(BinarySource_UPCAST(src)) #define get_bool(src) \ BinarySource_get_bool(BinarySource_UPCAST(src)) #define get_uint16(src) \ BinarySource_get_uint16(BinarySource_UPCAST(src)) #define get_uint32(src) \ BinarySource_get_uint32(BinarySource_UPCAST(src)) #define get_uint64(src) \ BinarySource_get_uint64(BinarySource_UPCAST(src)) #define get_string(src) \ BinarySource_get_string(BinarySource_UPCAST(src)) #define get_asciz(src) \ BinarySource_get_asciz(BinarySource_UPCAST(src)) #define get_chars(src, include) \ BinarySource_get_chars(BinarySource_UPCAST(src), include) #define get_nonchars(src, exclude) \ BinarySource_get_nonchars(BinarySource_UPCAST(src), exclude) #define get_chomped_line(src) \ BinarySource_get_chomped_line(BinarySource_UPCAST(src)) #define get_pstring(src) \ BinarySource_get_pstring(BinarySource_UPCAST(src)) #define get_mp_ssh1(src) \ BinarySource_get_mp_ssh1(BinarySource_UPCAST(src)) #define get_mp_ssh2(src) \ BinarySource_get_mp_ssh2(BinarySource_UPCAST(src)) #define get_rsa_ssh1_pub(src, rsa, order) \ BinarySource_get_rsa_ssh1_pub(BinarySource_UPCAST(src), rsa, order) #define get_rsa_ssh1_priv(src, rsa) \ BinarySource_get_rsa_ssh1_priv(BinarySource_UPCAST(src), rsa) #define get_rsa_ssh1_priv_agent(src) \ BinarySource_get_rsa_ssh1_priv_agent(BinarySource_UPCAST(src)) #define get_err(src) (BinarySource_UPCAST(src)->err) #define get_avail(src) (BinarySource_UPCAST(src)->len - \ BinarySource_UPCAST(src)->pos) #define get_ptr(src) \ ((const void *)( \ (const unsigned char *)(BinarySource_UPCAST(src)->data) + \ BinarySource_UPCAST(src)->pos)) ptrlen BinarySource_get_data(BinarySource *, size_t); unsigned char BinarySource_get_byte(BinarySource *); bool BinarySource_get_bool(BinarySource *); unsigned BinarySource_get_uint16(BinarySource *); unsigned long BinarySource_get_uint32(BinarySource *); uint64_t BinarySource_get_uint64(BinarySource *); ptrlen BinarySource_get_string(BinarySource *); const char *BinarySource_get_asciz(BinarySource *); ptrlen BinarySource_get_chars(BinarySource *, const char *include_set); ptrlen BinarySource_get_nonchars(BinarySource *, const char *exclude_set); ptrlen BinarySource_get_chomped_line(BinarySource *); ptrlen BinarySource_get_pstring(BinarySource *); mp_int *BinarySource_get_mp_ssh1(BinarySource *src); mp_int *BinarySource_get_mp_ssh2(BinarySource *src); void BinarySource_REWIND_TO__(BinarySource *src, size_t pos); /* * A couple of useful standard BinarySink implementations, which live * as sensibly here as anywhere else: one that makes a BinarySink * whose effect is to write to a stdio stream, and one whose effect is * to append to a bufchain. */ struct stdio_sink { FILE *fp; BinarySink_IMPLEMENTATION; }; struct bufchain_sink { bufchain *ch; BinarySink_IMPLEMENTATION; }; void stdio_sink_init(stdio_sink *sink, FILE *fp); void bufchain_sink_init(bufchain_sink *sink, bufchain *ch); #endif /* PUTTY_MARSHAL_H */ work/misc.h0000664000000000000000000004054314714222463010047 0ustar /* * Header for misc.c. */ #ifndef PUTTY_MISC_H #define PUTTY_MISC_H #include "defs.h" #include "puttymem.h" #include "marshal.h" #include /* for FILE * */ #include /* for va_list */ #include /* for abort */ #include /* for struct tm */ #include /* for INT_MAX/MIN */ #include /* for assert (obviously) */ unsigned long parse_blocksize(const char *bs); char ctrlparse(char *s, char **next); size_t host_strcspn(const char *s, const char *set); char *host_strchr(const char *s, int c); char *host_strrchr(const char *s, int c); char *host_strduptrim(const char *s); char *dupstr(const char *s); char *dupcat_fn(const char *s1, ...); #define dupcat(...) dupcat_fn(__VA_ARGS__, (const char *)NULL) char *dupprintf(const char *fmt, ...) PRINTF_LIKE(1, 2); char *dupvprintf(const char *fmt, va_list ap); void burnstr(char *string); /* * The visible part of a strbuf structure. There's a surrounding * implementation struct in misc.c, which isn't exposed to client * code. */ struct strbuf { char *s; unsigned char *u; size_t len; BinarySink_IMPLEMENTATION; }; /* strbuf constructors: strbuf_new_nm and strbuf_new differ in that a * strbuf constructed using the _nm version will resize itself by * alloc/copy/smemclr/free instead of realloc. Use that version for * data sensitive enough that it's worth costing performance to * avoid copies of it lingering in process memory. */ strbuf *strbuf_new(void); strbuf *strbuf_new_nm(void); void strbuf_free(strbuf *buf); void *strbuf_append(strbuf *buf, size_t len); void strbuf_shrink_to(strbuf *buf, size_t new_len); void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove); char *strbuf_to_str(strbuf *buf); /* does free buf, but you must free result */ void strbuf_catf(strbuf *buf, const char *fmt, ...) PRINTF_LIKE(2, 3); void strbuf_catfv(strbuf *buf, const char *fmt, va_list ap); static inline void strbuf_clear(strbuf *buf) { strbuf_shrink_to(buf, 0); } bool strbuf_chomp(strbuf *buf, char char_to_remove); strbuf *strbuf_new_for_agent_query(void); void strbuf_finalise_agent_query(strbuf *buf); /* String-to-Unicode converters that auto-allocate the destination and * work around the rather deficient interface of mb_to_wc. * * These actually live in miscucs.c, not misc.c (the distinction being * that the former is only linked into tools that also have the main * Unicode support). */ wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len); wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string); static inline int toint(unsigned u) { /* * Convert an unsigned to an int, without running into the * undefined behaviour which happens by the strict C standard if * the value overflows. You'd hope that sensible compilers would * do the sensible thing in response to a cast, but actually I * don't trust modern compilers not to do silly things like * assuming that _obviously_ you wouldn't have caused an overflow * and so they can elide an 'if (i < 0)' test immediately after * the cast. * * Sensible compilers ought of course to optimise this entire * function into 'just return the input value', and since it's * also declared inline, elide it completely in their output. */ if (u <= (unsigned)INT_MAX) return (int)u; else if (u >= (unsigned)INT_MIN) /* wrap in cast _to_ unsigned is OK */ return INT_MIN + (int)(u - (unsigned)INT_MIN); else return INT_MIN; /* fallback; should never occur on binary machines */ } char *fgetline(FILE *fp); bool read_file_into(BinarySink *bs, FILE *fp); char *chomp(char *str); bool strstartswith(const char *s, const char *t); bool strendswith(const char *s, const char *t); void base64_encode_atom(const unsigned char *data, int n, char *out); int base64_decode_atom(const char *atom, unsigned char *out); struct bufchain_granule; struct bufchain_tag { struct bufchain_granule *head, *tail; size_t buffersize; /* current amount of buffered data */ void (*queue_idempotent_callback)(IdempotentCallback *ic); IdempotentCallback *ic; }; void bufchain_init(bufchain *ch); void bufchain_clear(bufchain *ch); size_t bufchain_size(bufchain *ch); void bufchain_add(bufchain *ch, const void *data, size_t len); ptrlen bufchain_prefix(bufchain *ch); void bufchain_consume(bufchain *ch, size_t len); void bufchain_fetch(bufchain *ch, void *data, size_t len); void bufchain_fetch_consume(bufchain *ch, void *data, size_t len); bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len); size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len); void bufchain_set_callback_inner( bufchain *ch, IdempotentCallback *ic, void (*queue_idempotent_callback)(IdempotentCallback *ic)); static inline void bufchain_set_callback(bufchain *ch, IdempotentCallback *ic) { extern void queue_idempotent_callback(struct IdempotentCallback *ic); /* Wrapper that puts in the standard queue_idempotent_callback * function. Lives here rather than in utils.c so that standalone * programs can use the bufchain facility without this optional * callback feature and not need to provide a stub of * queue_idempotent_callback. */ bufchain_set_callback_inner(ch, ic, queue_idempotent_callback); } bool validate_manual_hostkey(char *key); struct tm ltime(void); /* * Special form of strcmp which can cope with NULL inputs. NULL is * defined to sort before even the empty string. */ int nullstrcmp(const char *a, const char *b); static inline ptrlen make_ptrlen(const void *ptr, size_t len) { ptrlen pl; pl.ptr = ptr; pl.len = len; return pl; } static inline ptrlen ptrlen_from_asciz(const char *str) { return make_ptrlen(str, strlen(str)); } static inline ptrlen ptrlen_from_strbuf(strbuf *sb) { return make_ptrlen(sb->u, sb->len); } bool ptrlen_eq_string(ptrlen pl, const char *str); bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2); int ptrlen_strcmp(ptrlen pl1, ptrlen pl2); /* ptrlen_startswith and ptrlen_endswith write through their 'tail' * argument if and only if it is non-NULL and they return true. Hence * you can write ptrlen_startswith(thing, prefix, &thing), writing * back to the same ptrlen it read from, to remove a prefix if present * and say whether it did so. */ bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail); bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail); ptrlen ptrlen_get_word(ptrlen *input, const char *separators); char *mkstr(ptrlen pl); int string_length_for_printf(size_t); /* Derive two printf arguments from a ptrlen, suitable for "%.*s" */ #define PTRLEN_PRINTF(pl) \ string_length_for_printf((pl).len), (const char *)(pl).ptr /* Make a ptrlen out of a compile-time string literal. We try to * enforce that it _is_ a string literal by token-pasting "" on to it, * which should provoke a compile error if it's any other kind of * string. */ #define PTRLEN_LITERAL(stringlit) \ TYPECHECK("" stringlit "", make_ptrlen(stringlit, sizeof(stringlit)-1)) /* Make a ptrlen out of a compile-time string literal in a way that * allows you to declare the ptrlen itself as a compile-time initialiser. */ #define PTRLEN_DECL_LITERAL(stringlit) \ { TYPECHECK("" stringlit "", stringlit), sizeof(stringlit)-1 } /* Make a ptrlen out of a constant byte array. */ #define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a)) /* Wipe sensitive data out of memory that's about to be freed. Simpler * than memset because we don't need the fill char parameter; also * attempts (by fiddly use of volatile) to inhibit the compiler from * over-cleverly trying to optimise the memset away because it knows * the variable is going out of scope. */ void smemclr(void *b, size_t len); /* Compare two fixed-length chunks of memory for equality, without * data-dependent control flow (so an attacker with a very accurate * stopwatch can't try to guess where the first mismatching byte was). * Returns false for mismatch or true for equality (unlike memcmp), * hinted at by the 'eq' in the name. */ bool smemeq(const void *av, const void *bv, size_t len); /* Encode a single UTF-8 character. Assumes that illegal characters * (such as things in the surrogate range, or > 0x10FFFF) have already * been removed. */ size_t encode_utf8(void *output, unsigned long ch); /* Write a string out in C string-literal format. */ void write_c_string_literal(FILE *fp, ptrlen str); char *buildinfo(const char *newline); /* * A function you can put at points in the code where execution should * never reach in the first place. Better than assert(false), or even * assert(false && "some explanatory message"), because some compilers * don't interpret assert(false) as a declaration of unreachability, * so they may still warn about pointless things like some variable * not being initialised on the unreachable code path. * * I follow the assertion with a call to abort() just in case someone * compiles with -DNDEBUG, and I wrap that abort inside my own * function labelled NORETURN just in case some unusual kind of system * header wasn't foresighted enough to label abort() itself that way. */ static inline NORETURN void unreachable_internal(void) { abort(); } #define unreachable(msg) (assert(false && msg), unreachable_internal()) /* * Debugging functions. * * Output goes to debug.log * * debug() is like printf(). * * dmemdump() and dmemdumpl() both do memory dumps. The difference * is that dmemdumpl() is more suited for when the memory address is * important (say because you'll be recording pointer values later * on). dmemdump() is more concise. */ #ifdef DEBUG void debug_printf(const char *fmt, ...) PRINTF_LIKE(1, 2); void debug_memdump(const void *buf, int len, bool L); #define debug(...) (debug_printf(__VA_ARGS__)) #define dmemdump(buf,len) (debug_memdump(buf, len, false)) #define dmemdumpl(buf,len) (debug_memdump(buf, len, true)) #else #define debug(...) ((void)0) #define dmemdump(buf,len) ((void)0) #define dmemdumpl(buf,len) ((void)0) #endif #ifndef lenof #define lenof(x) ( (sizeof((x))) / (sizeof(*(x)))) #endif #ifndef min #define min(x,y) ( (x) < (y) ? (x) : (y) ) #endif #ifndef max #define max(x,y) ( (x) > (y) ? (x) : (y) ) #endif static inline uint64_t GET_64BIT_LSB_FIRST(const void *vp) { const uint8_t *p = (const uint8_t *)vp; return (((uint64_t)p[0] ) | ((uint64_t)p[1] << 8) | ((uint64_t)p[2] << 16) | ((uint64_t)p[3] << 24) | ((uint64_t)p[4] << 32) | ((uint64_t)p[5] << 40) | ((uint64_t)p[6] << 48) | ((uint64_t)p[7] << 56)); } static inline void PUT_64BIT_LSB_FIRST(void *vp, uint64_t value) { uint8_t *p = (uint8_t *)vp; p[0] = (uint8_t)(value); p[1] = (uint8_t)(value >> 8); p[2] = (uint8_t)(value >> 16); p[3] = (uint8_t)(value >> 24); p[4] = (uint8_t)(value >> 32); p[5] = (uint8_t)(value >> 40); p[6] = (uint8_t)(value >> 48); p[7] = (uint8_t)(value >> 56); } static inline uint32_t GET_32BIT_LSB_FIRST(const void *vp) { const uint8_t *p = (const uint8_t *)vp; return (((uint32_t)p[0] ) | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24)); } static inline void PUT_32BIT_LSB_FIRST(void *vp, uint32_t value) { uint8_t *p = (uint8_t *)vp; p[0] = (uint8_t)(value); p[1] = (uint8_t)(value >> 8); p[2] = (uint8_t)(value >> 16); p[3] = (uint8_t)(value >> 24); } static inline uint16_t GET_16BIT_LSB_FIRST(const void *vp) { const uint8_t *p = (const uint8_t *)vp; return (((uint16_t)p[0] ) | ((uint16_t)p[1] << 8)); } static inline void PUT_16BIT_LSB_FIRST(void *vp, uint16_t value) { uint8_t *p = (uint8_t *)vp; p[0] = (uint8_t)(value); p[1] = (uint8_t)(value >> 8); } static inline uint64_t GET_64BIT_MSB_FIRST(const void *vp) { const uint8_t *p = (const uint8_t *)vp; return (((uint64_t)p[7] ) | ((uint64_t)p[6] << 8) | ((uint64_t)p[5] << 16) | ((uint64_t)p[4] << 24) | ((uint64_t)p[3] << 32) | ((uint64_t)p[2] << 40) | ((uint64_t)p[1] << 48) | ((uint64_t)p[0] << 56)); } static inline void PUT_64BIT_MSB_FIRST(void *vp, uint64_t value) { uint8_t *p = (uint8_t *)vp; p[7] = (uint8_t)(value); p[6] = (uint8_t)(value >> 8); p[5] = (uint8_t)(value >> 16); p[4] = (uint8_t)(value >> 24); p[3] = (uint8_t)(value >> 32); p[2] = (uint8_t)(value >> 40); p[1] = (uint8_t)(value >> 48); p[0] = (uint8_t)(value >> 56); } static inline uint32_t GET_32BIT_MSB_FIRST(const void *vp) { const uint8_t *p = (const uint8_t *)vp; return (((uint32_t)p[3] ) | ((uint32_t)p[2] << 8) | ((uint32_t)p[1] << 16) | ((uint32_t)p[0] << 24)); } static inline void PUT_32BIT_MSB_FIRST(void *vp, uint32_t value) { uint8_t *p = (uint8_t *)vp; p[3] = (uint8_t)(value); p[2] = (uint8_t)(value >> 8); p[1] = (uint8_t)(value >> 16); p[0] = (uint8_t)(value >> 24); } static inline uint16_t GET_16BIT_MSB_FIRST(const void *vp) { const uint8_t *p = (const uint8_t *)vp; return (((uint16_t)p[1] ) | ((uint16_t)p[0] << 8)); } static inline void PUT_16BIT_MSB_FIRST(void *vp, uint16_t value) { uint8_t *p = (uint8_t *)vp; p[1] = (uint8_t)(value); p[0] = (uint8_t)(value >> 8); } /* For use in X11-related applications, an endianness-variable form of * {GET,PUT}_16BIT which expects 'endian' to be either 'B' or 'l' */ static inline uint16_t GET_16BIT_X11(char endian, const void *p) { return endian == 'B' ? GET_16BIT_MSB_FIRST(p) : GET_16BIT_LSB_FIRST(p); } static inline void PUT_16BIT_X11(char endian, void *p, uint16_t value) { if (endian == 'B') PUT_16BIT_MSB_FIRST(p, value); else PUT_16BIT_LSB_FIRST(p, value); } /* Replace NULL with the empty string, permitting an idiom in which we * get a string (pointer,length) pair that might be NULL,0 and can * then safely say things like printf("%.*s", length, NULLTOEMPTY(ptr)) */ static inline const char *NULLTOEMPTY(const char *s) { return s ? s : ""; } /* StripCtrlChars, defined in stripctrl.c: an adapter you can put on * the front of one BinarySink and which functions as one in turn. * Interprets its input as a stream of multibyte characters in the * system locale, and removes any that are not either printable * characters or newlines. */ struct StripCtrlChars { BinarySink_IMPLEMENTATION; /* and this is contained in a larger structure */ }; StripCtrlChars *stripctrl_new( BinarySink *bs_out, bool permit_cr, wchar_t substitution); StripCtrlChars *stripctrl_new_term_fn( BinarySink *bs_out, bool permit_cr, wchar_t substitution, Terminal *term, unsigned long (*translate)( Terminal *, term_utf8_decode *, unsigned char)); #define stripctrl_new_term(bs, cr, sub, term) \ stripctrl_new_term_fn(bs, cr, sub, term, term_translate) void stripctrl_retarget(StripCtrlChars *sccpub, BinarySink *new_bs_out); void stripctrl_reset(StripCtrlChars *sccpub); void stripctrl_free(StripCtrlChars *sanpub); void stripctrl_enable_line_limiting(StripCtrlChars *sccpub); char *stripctrl_string_ptrlen(StripCtrlChars *sccpub, ptrlen str); static inline char *stripctrl_string(StripCtrlChars *sccpub, const char *str) { return stripctrl_string_ptrlen(sccpub, ptrlen_from_asciz(str)); } /* * A mechanism for loading a file from disk into a memory buffer where * it can be picked apart as a BinarySource. */ struct LoadedFile { char *data; size_t len, max_size; BinarySource_IMPLEMENTATION; }; typedef enum { LF_OK, /* file loaded successfully */ LF_TOO_BIG, /* file didn't fit in buffer */ LF_ERROR, /* error from stdio layer */ } LoadFileStatus; LoadedFile *lf_new(size_t max_size); void lf_free(LoadedFile *lf); LoadFileStatus lf_load_fp(LoadedFile *lf, FILE *fp); LoadFileStatus lf_load(LoadedFile *lf, const Filename *filename); static inline ptrlen ptrlen_from_lf(LoadedFile *lf) { return make_ptrlen(lf->data, lf->len); } /* Set the memory block of 'size' bytes at 'out' to the bitwise XOR of * the two blocks of the same size at 'in1' and 'in2'. * * 'out' may point to exactly the same address as one of the inputs, * but if the input and output blocks overlap in any other way, the * result of this function is not guaranteed. No memmove-style effort * is made to handle difficult overlap cases. */ void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size); #endif work/network.h0000664000000000000000000002765414714222463010615 0ustar /* * Networking abstraction in PuTTY. * * The way this works is: a back end can choose to open any number * of sockets - including zero, which might be necessary in some. * It can register a bunch of callbacks (most notably for when * data is received) for each socket, and it can call the networking * abstraction to send data without having to worry about blocking. * The stuff behind the abstraction takes care of selects and * nonblocking writes and all that sort of painful gubbins. */ #ifndef PUTTY_NETWORK_H #define PUTTY_NETWORK_H #include "defs.h" typedef struct SocketVtable SocketVtable; typedef struct PlugVtable PlugVtable; struct Socket { const struct SocketVtable *vt; }; struct SocketVtable { Plug *(*plug) (Socket *s, Plug *p); /* use a different plug (return the old one) */ /* if p is NULL, it doesn't change the plug */ /* but it does return the one it's using */ void (*close) (Socket *s); size_t (*write) (Socket *s, const void *data, size_t len); size_t (*write_oob) (Socket *s, const void *data, size_t len); void (*write_eof) (Socket *s); void (*set_frozen) (Socket *s, bool is_frozen); /* ignored by tcp, but vital for ssl */ const char *(*socket_error) (Socket *s); SocketPeerInfo *(*peer_info) (Socket *s); }; typedef union { void *p; int i; } accept_ctx_t; typedef Socket *(*accept_fn_t)(accept_ctx_t ctx, Plug *plug); struct Plug { const struct PlugVtable *vt; }; typedef enum PlugLogType { PLUGLOG_CONNECT_TRYING, PLUGLOG_CONNECT_FAILED, PLUGLOG_CONNECT_SUCCESS, PLUGLOG_PROXY_MSG, } PlugLogType; struct PlugVtable { void (*log)(Plug *p, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code); /* * Passes the client progress reports on the process of setting * up the connection. * * - PLUGLOG_CONNECT_TRYING means we are about to try to connect * to address `addr' (error_msg and error_code are ignored) * * - PLUGLOG_CONNECT_FAILED means we have failed to connect to * address `addr' (error_msg and error_code are supplied). This * is not a fatal error - we may well have other candidate * addresses to fall back to. When it _is_ fatal, the closing() * function will be called. * * - PLUGLOG_CONNECT_SUCCESS means we have succeeded in * connecting to address `addr'. * * - PLUGLOG_PROXY_MSG means that error_msg contains a line of * logging information from whatever the connection is being * proxied through. This will typically be a wodge of * standard-error output from a local proxy command, so the * receiver should probably prefix it to indicate this. */ void (*closing) (Plug *p, const char *error_msg, int error_code, bool calling_back); /* error_msg is NULL iff it is not an error (ie it closed normally) */ /* calling_back != 0 iff there is a Plug function */ /* currently running (would cure the fixme in try_send()) */ void (*receive) (Plug *p, int urgent, const char *data, size_t len); /* * - urgent==0. `data' points to `len' bytes of perfectly * ordinary data. * * - urgent==1. `data' points to `len' bytes of data, * which were read from before an Urgent pointer. * * - urgent==2. `data' points to `len' bytes of data, * the first of which was the one at the Urgent mark. */ void (*sent) (Plug *p, size_t bufsize); /* * The `sent' function is called when the pending send backlog * on a socket is cleared or partially cleared. The new backlog * size is passed in the `bufsize' parameter. */ int (*accepting)(Plug *p, accept_fn_t constructor, accept_ctx_t ctx); /* * `accepting' is called only on listener-type sockets, and is * passed a constructor function+context that will create a fresh * Socket describing the connection. It returns nonzero if it * doesn't want the connection for some reason, or 0 on success. */ }; /* proxy indirection layer */ /* NB, control of 'addr' is passed via new_connection, which takes * responsibility for freeing it */ Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug, Conf *conf); Socket *new_listener(const char *srcaddr, int port, Plug *plug, bool local_host_only, Conf *conf, int addressfamily); SockAddr *name_lookup(const char *host, int port, char **canonicalname, Conf *conf, int addressfamily, LogContext *logctx, const char *lookup_reason_for_logging); /* platform-dependent callback from new_connection() */ /* (same caveat about addr as new_connection()) */ Socket *platform_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug, Conf *conf); /* callback for SSH jump-host proxying */ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug, Conf *conf); /* socket functions */ void sk_init(void); /* called once at program startup */ void sk_cleanup(void); /* called just before program exit */ SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_family); SockAddr *sk_nonamelookup(const char *host); void sk_getaddr(SockAddr *addr, char *buf, int buflen); bool sk_addr_needs_port(SockAddr *addr); bool sk_hostname_is_local(const char *name); bool sk_address_is_local(SockAddr *addr); bool sk_address_is_special_local(SockAddr *addr); int sk_addrtype(SockAddr *addr); void sk_addrcopy(SockAddr *addr, char *buf); void sk_addr_free(SockAddr *addr); /* sk_addr_dup generates another SockAddr which contains the same data * as the original one and can be freed independently. May not actually * physically _duplicate_ it: incrementing a reference count so that * one more free is required before it disappears is an acceptable * implementation. */ SockAddr *sk_addr_dup(SockAddr *addr); /* NB, control of 'addr' is passed via sk_new, which takes responsibility * for freeing it, as for new_connection() */ Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *p); Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, bool local_host_only, int address_family); static inline Plug *sk_plug(Socket *s, Plug *p) { return s->vt->plug(s, p); } static inline void sk_close(Socket *s) { s->vt->close(s); } static inline size_t sk_write(Socket *s, const void *data, size_t len) { return s->vt->write(s, data, len); } static inline size_t sk_write_oob(Socket *s, const void *data, size_t len) { return s->vt->write_oob(s, data, len); } static inline void sk_write_eof(Socket *s) { s->vt->write_eof(s); } static inline void plug_log( Plug *p, int type, SockAddr *addr, int port, const char *msg, int code) { p->vt->log(p, type, addr, port, msg, code); } static inline void plug_closing( Plug *p, const char *msg, int code, bool calling_back) { p->vt->closing(p, msg, code, calling_back); } static inline void plug_receive(Plug *p, int urg, const char *data, size_t len) { p->vt->receive(p, urg, data, len); } static inline void plug_sent (Plug *p, size_t bufsize) { p->vt->sent(p, bufsize); } static inline int plug_accepting(Plug *p, accept_fn_t cons, accept_ctx_t ctx) { return p->vt->accepting(p, cons, ctx); } /* * Special error values are returned from sk_namelookup and sk_new * if there's a problem. These functions extract an error message, * or return NULL if there's no problem. */ const char *sk_addr_error(SockAddr *addr); static inline const char *sk_socket_error(Socket *s) { return s->vt->socket_error(s); } /* * Set the `frozen' flag on a socket. A frozen socket is one in * which all READABLE notifications are ignored, so that data is * not accepted from the peer until the socket is unfrozen. This * exists for two purposes: * * - Port forwarding: when a local listening port receives a * connection, we do not want to receive data from the new * socket until we have somewhere to send it. Hence, we freeze * the socket until its associated SSH channel is ready; then we * unfreeze it and pending data is delivered. * * - Socket buffering: if an SSH channel (or the whole connection) * backs up or presents a zero window, we must freeze the * associated local socket in order to avoid unbounded buffer * growth. */ static inline void sk_set_frozen(Socket *s, bool is_frozen) { s->vt->set_frozen(s, is_frozen); } /* * Return a structure giving some information about the other end of * the socket. May be NULL, if nothing is available at all. If it is * not NULL, then it is dynamically allocated, and should be freed by * a call to sk_free_peer_info(). See below for the definition. */ static inline SocketPeerInfo *sk_peer_info(Socket *s) { return s->vt->peer_info(s); } /* * The structure returned from sk_peer_info, and a function to free * one (in misc.c). */ struct SocketPeerInfo { int addressfamily; /* * Text form of the IPv4 or IPv6 address of the other end of the * socket, if available, in the standard text representation. */ const char *addr_text; /* * Binary form of the same address. Filled in if and only if * addr_text is not NULL. You can tell which branch of the union * is used by examining 'addressfamily'. */ union { unsigned char ipv6[16]; unsigned char ipv4[4]; } addr_bin; /* * Remote port number, or -1 if not available. */ int port; /* * Free-form text suitable for putting in log messages. For IP * sockets, repeats the address and port information from above. * But it can be completely different, e.g. for Unix-domain * sockets it gives information about the uid, gid and pid of the * connecting process. */ const char *log_text; }; void sk_free_peer_info(SocketPeerInfo *pi); /* * Simple wrapper on getservbyname(), needed by ssh.c. Returns the * port number, in host byte order (suitable for printf and so on). * Returns 0 on failure. Any platform not supporting getservbyname * can just return 0 - this function is not required to handle * numeric port specifications. */ int net_service_lookup(char *service); /* * Look up the local hostname; return value needs freeing. * May return NULL. */ char *get_hostname(void); /* * Trivial socket implementation which just stores an error. Found in * errsock.c. * * The consume_string variant takes an already-formatted dynamically * allocated string, and takes over ownership of that string. */ Socket *new_error_socket_fmt(Plug *plug, const char *fmt, ...) PRINTF_LIKE(2, 3); Socket *new_error_socket_consume_string(Plug *plug, char *errmsg); /* * Trivial plug that does absolutely nothing. Found in nullplug.c. */ extern Plug *const nullplug; /* ---------------------------------------------------------------------- * Functions defined outside the network code, which have to be * declared in this header file rather than the main putty.h because * they use types defined here. */ /* * Exports from be_misc.c. */ void backend_socket_log(Seat *seat, LogContext *logctx, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code, Conf *conf, bool session_started); typedef struct ProxyStderrBuf { char buf[8192]; size_t size; } ProxyStderrBuf; void psb_init(ProxyStderrBuf *psb); void log_proxy_stderr( Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len); #endif work/noproxy.c0000664000000000000000000000224114714222463010616 0ustar /* * noproxy.c: an alternative to proxy.c, for use by auxiliary programs * that need to make network connections but don't want to include all * the full-on support for endless network proxies (and its * configuration requirements). Implements the primary APIs of * proxy.c, but maps them straight to the underlying network layer. */ #include "putty.h" #include "network.h" #include "proxy.h" SockAddr *name_lookup(const char *host, int port, char **canonicalname, Conf *conf, int addressfamily, LogContext *logctx, const char *reason) { return sk_namelookup(host, canonicalname, addressfamily); } Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug, Conf *conf) { return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); } Socket *new_listener(const char *srcaddr, int port, Plug *plug, bool local_host_only, Conf *conf, int addressfamily) { return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily); } work/norand.c0000664000000000000000000000054314714222463010364 0ustar /* * Stub implementations of RNG functions for applications without an RNG. */ #include "putty.h" void random_read(void *out, size_t size) { unreachable("Random numbers are not available in this application"); } void random_save_seed(void) { } void random_destroy_seed(void) { } void noise_ultralight(NoiseSourceId id, unsigned long data) { } work/nullplug.c0000664000000000000000000000215114714222463010742 0ustar /* * nullplug.c: provide a null implementation of the Plug vtable which * ignores all calls. Occasionally useful in cases where we want to * make a network connection just to see if it works, but not do * anything with it afterwards except close it again. */ #include "putty.h" static void nullplug_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, const char *err_msg, int err_code) { } static void nullplug_closing(Plug *plug, const char *error_msg, int error_code, bool calling_back) { } static void nullplug_receive( Plug *plug, int urgent, const char *data, size_t len) { } static void nullplug_sent(Plug *plug, size_t bufsize) { } static const PlugVtable nullplug_plugvt = { .log = nullplug_socket_log, .closing = nullplug_closing, .receive = nullplug_receive, .sent = nullplug_sent, }; static Plug nullplug_plug = { &nullplug_plugvt }; /* * There's a singleton instance of nullplug, because it's not * interesting enough to worry about making more than one of them. */ Plug *const nullplug = &nullplug_plug; work/proxy.h0000664000000000000000000000601414714222463010270 0ustar /* * Network proxy abstraction in PuTTY * * A proxy layer, if necessary, wedges itself between the * network code and the higher level backend. * * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5 */ #ifndef PUTTY_PROXY_H #define PUTTY_PROXY_H #define PROXY_ERROR_GENERAL 8000 #define PROXY_ERROR_UNEXPECTED 8001 typedef struct ProxySocket ProxySocket; struct ProxySocket { const char *error; Socket *sub_socket; Plug *plug; SockAddr *remote_addr; int remote_port; bufchain pending_output_data; bufchain pending_oob_output_data; bufchain pending_input_data; bool pending_eof; #define PROXY_STATE_NEW -1 #define PROXY_STATE_ACTIVE 0 int state; /* proxy states greater than 0 are implementation * dependent, but represent various stages/states * of the initialization/setup/negotiation with the * proxy server. */ bool freeze; /* should we freeze the underlying socket when * we are done with the proxy negotiation? this * simply caches the value of sk_set_frozen calls. */ #define PROXY_CHANGE_NEW -1 #define PROXY_CHANGE_CLOSING 0 #define PROXY_CHANGE_SENT 1 #define PROXY_CHANGE_RECEIVE 2 #define PROXY_CHANGE_ACCEPTING 3 /* something has changed (a call from the sub socket * layer into our Proxy Plug layer, or we were just * created, etc), so the proxy layer needs to handle * this change (the type of which is the second argument) * and further the proxy negotiation process. */ int (*negotiate) (ProxySocket * /* this */, int /* change type */); /* current arguments of plug handlers * (for use by proxy's negotiate function) */ /* closing */ const char *closing_error_msg; int closing_error_code; bool closing_calling_back; /* receive */ bool receive_urgent; const char *receive_data; int receive_len; /* accepting */ accept_fn_t accepting_constructor; accept_ctx_t accepting_ctx; /* configuration, used to look up proxy settings */ Conf *conf; /* CHAP transient data */ int chap_num_attributes; int chap_num_attributes_processed; int chap_current_attribute; int chap_current_datalen; Socket sock; Plug plugimpl; }; extern void proxy_activate (ProxySocket *); extern int proxy_http_negotiate (ProxySocket *, int); extern int proxy_telnet_negotiate (ProxySocket *, int); extern int proxy_socks4_negotiate (ProxySocket *, int); extern int proxy_socks5_negotiate (ProxySocket *, int); /* * This may be reused by local-command proxies on individual * platforms. */ char *format_telnet_command(SockAddr *addr, int port, Conf *conf); /* * These are implemented in cproxy.c or nocproxy.c, depending on * whether encrypted proxy authentication is available. */ extern void proxy_socks5_offerencryptedauth(BinarySink *); extern int proxy_socks5_handlechap (ProxySocket *); extern int proxy_socks5_selectchap(ProxySocket *); #endif work/putty.h0000664000000000000000000030404514714222463010301 0ustar #ifndef PUTTY_PUTTY_H #define PUTTY_PUTTY_H #include /* for wchar_t */ #include /* for INT_MAX */ #include "defs.h" #include "platform.h" #include "network.h" #include "misc.h" #include "marshal.h" /* * We express various time intervals in unsigned long minutes, but may need to * clip some values so that the resulting number of ticks does not overflow an * integer value. */ #define MAX_TICK_MINS (INT_MAX / (60 * TICKSPERSEC)) /* * Fingerprints of the current and previous PGP master keys, to * establish a trust path between an executable and other files. */ #define PGP_MASTER_KEY_YEAR "2021" #define PGP_MASTER_KEY_DETAILS "RSA, 3072-bit" #define PGP_MASTER_KEY_FP \ "A872 D42F 1660 890F 0E05 223E DD43 55EA AC11 19DE" #define PGP_PREV_MASTER_KEY_YEAR "2018" #define PGP_PREV_MASTER_KEY_DETAILS "RSA, 4096-bit" #define PGP_PREV_MASTER_KEY_FP \ "24E1 B1C5 75EA 3C9F F752 A922 76BC 7FE4 EBFD 2D9E" /* * Definitions of three separate indexing schemes for colour palette * entries. * * Why three? Because history, sorry. * * Two of the colour indexings are used in escape sequences. The * Linux-console style OSC P sequences for setting the palette use an * indexing in which the eight standard ANSI SGR colours come first, * then their bold versions, and then six extra colours for default * fg/bg and the terminal cursor. And the xterm OSC 4 sequences for * querying the palette use a related indexing in which the six extra * colours are pushed up to indices 256 and onwards, with the previous * 16 being the first part of the xterm 256-colour space, and 240 * additional terminal-accessible colours inserted in the middle. * * The third indexing is the order that the colours appear in the * PuTTY configuration panel, and also the order in which they're * described in the saved session files. This order specifies the same * set of colours as the OSC P encoding, but in a different order, * with the default fg/bg colours (which users are most likely to want * to reconfigure) at the start, and the ANSI SGR colours coming * later. * * So all three indices really are needed, because all three appear in * protocols or file formats outside the PuTTY binary. (Changing the * saved-session encoding would have a backwards-compatibility impact; * also, if we ever do, it would be better to replace the numeric * indices with descriptive keywords.) * * Since the OSC 4 encoding contains the full set of colours used in * the terminal display, that's the encoding used by front ends to * store any actual data associated with their palette entries. So the * TermWin palette_set and palette_get_overrides methods use that * encoding, and so does the bitwise encoding of attribute words used * in terminal redraw operations. * * The Conf encoding, of course, is used by config.c and settings.c. * * The aim is that those two sections of the code should never need to * come directly into contact, and the only module that should have to * deal directly with the mapping between these colour encodings - or * to deal _at all_ with the intermediate OSC P encoding - is * terminal.c itself. */ #define CONF_NCOLOURS 22 /* 16 + 6 special ones */ #define OSCP_NCOLOURS 22 /* same as CONF, but different order */ #define OSC4_NCOLOURS 262 /* 256 + the same 6 special ones */ /* The list macro for the conf colours also gives the textual names * used in the GUI configurer */ #define CONF_COLOUR_LIST(X) \ X(fg, "Default Foreground") \ X(fg_bold, "Default Bold Foreground") \ X(bg, "Default Background") \ X(bg_bold, "Default Bold Background") \ X(cursor_fg, "Cursor Text") \ X(cursor_bg, "Cursor Colour") \ X(black, "ANSI Black") \ X(black_bold, "ANSI Black Bold") \ X(red, "ANSI Red") \ X(red_bold, "ANSI Red Bold") \ X(green, "ANSI Green") \ X(green_bold, "ANSI Green Bold") \ X(yellow, "ANSI Yellow") \ X(yellow_bold, "ANSI Yellow Bold") \ X(blue, "ANSI Blue") \ X(blue_bold, "ANSI Blue Bold") \ X(magenta, "ANSI Magenta") \ X(magenta_bold, "ANSI Magenta Bold") \ X(cyan, "ANSI Cyan") \ X(cyan_bold, "ANSI Cyan Bold") \ X(white, "ANSI White") \ X(white_bold, "ANSI White Bold") \ /* end of list */ #define OSCP_COLOUR_LIST(X) \ X(black) \ X(red) \ X(green) \ X(yellow) \ X(blue) \ X(magenta) \ X(cyan) \ X(white) \ X(black_bold) \ X(red_bold) \ X(green_bold) \ X(yellow_bold) \ X(blue_bold) \ X(magenta_bold) \ X(cyan_bold) \ X(white_bold) \ /* * In the OSC 4 indexing, this is where the extra 240 colours go. * They consist of: * * - 216 colours forming a 6x6x6 cube, with R the most * significant colour and G the least. In other words, these * occupy the space of indices 16 <= i < 232, with each * individual colour found as i = 16 + 36*r + 6*g + b, for all * 0 <= r,g,b <= 5. * * - The remaining indices, 232 <= i < 256, consist of a uniform * series of grey shades running between black and white (but * not including either, since actual black and white are * already provided in the previous colour cube). * * After that, we have the remaining 6 special colours: */ \ X(fg) \ X(fg_bold) \ X(bg) \ X(bg_bold) \ X(cursor_fg) \ X(cursor_bg) \ /* end of list */ /* Enumerations of the colour lists. These are available everywhere in * the code. The OSC P encoding shouldn't be used outside terminal.c, * but the easiest way to define the OSC 4 enum is to have the OSC P * one available to compute with. */ enum { #define ENUM_DECL(id,name) CONF_COLOUR_##id, CONF_COLOUR_LIST(ENUM_DECL) #undef ENUM_DECL }; enum { #define ENUM_DECL(id) OSCP_COLOUR_##id, OSCP_COLOUR_LIST(ENUM_DECL) #undef ENUM_DECL }; enum { #define ENUM_DECL(id) OSC4_COLOUR_##id = \ OSCP_COLOUR_##id + (OSCP_COLOUR_##id >= 16 ? 240 : 0), OSCP_COLOUR_LIST(ENUM_DECL) #undef ENUM_DECL }; /* Mapping tables defined in terminal.c */ extern const int colour_indices_conf_to_oscp[CONF_NCOLOURS]; extern const int colour_indices_conf_to_osc4[CONF_NCOLOURS]; extern const int colour_indices_oscp_to_osc4[OSCP_NCOLOURS]; /* Three attribute types: * The ATTRs (normal attributes) are stored with the characters in * the main display arrays * * The TATTRs (temporary attributes) are generated on the fly, they * can overlap with characters but not with normal attributes. * * The LATTRs (line attributes) are an entirely disjoint space of * flags. * * The DATTRs (display attributes) are internal to terminal.c (but * defined here because their values have to match the others * here); they reuse the TATTR_* space but are always masked off * before sending to the front end. * * ATTR_INVALID is an illegal colour combination. */ #define TATTR_ACTCURS 0x40000000UL /* active cursor (block) */ #define TATTR_PASCURS 0x20000000UL /* passive cursor (box) */ #define TATTR_RIGHTCURS 0x10000000UL /* cursor-on-RHS */ #define TATTR_COMBINING 0x80000000UL /* combining characters */ #define DATTR_STARTRUN 0x80000000UL /* start of redraw run */ #define TDATTR_MASK 0xF0000000UL #define TATTR_MASK (TDATTR_MASK) #define DATTR_MASK (TDATTR_MASK) #define LATTR_NORM 0x00000000UL #define LATTR_WIDE 0x00000001UL #define LATTR_TOP 0x00000002UL #define LATTR_BOT 0x00000003UL #define LATTR_MODE 0x00000003UL #define LATTR_WRAPPED 0x00000010UL /* this line wraps to next */ #define LATTR_WRAPPED2 0x00000020UL /* with WRAPPED: CJK wide character wrapped to next line, so last single-width cell is empty */ #define ATTR_INVALID 0x03FFFFU /* Use the DC00 page for direct to font. */ #define CSET_OEMCP 0x0000DC00UL /* OEM Codepage DTF */ #define CSET_ACP 0x0000DD00UL /* Ansi Codepage DTF */ /* These are internal use overlapping with the UTF-16 surrogates */ #define CSET_ASCII 0x0000D800UL /* normal ASCII charset ESC ( B */ #define CSET_LINEDRW 0x0000D900UL /* line drawing charset ESC ( 0 */ #define CSET_SCOACS 0x0000DA00UL /* SCO Alternate charset */ #define CSET_GBCHR 0x0000DB00UL /* UK variant charset ESC ( A */ #define CSET_MASK 0xFFFFFF00UL /* Character set mask */ #define DIRECT_CHAR(c) ((c&0xFFFFFC00)==0xD800) #define DIRECT_FONT(c) ((c&0xFFFFFE00)==0xDC00) #define UCSERR (CSET_LINEDRW|'a') /* UCS Format error character. */ /* * UCSWIDE is a special value used in the terminal data to signify * the character cell containing the right-hand half of a CJK wide * character. We use 0xDFFF because it's part of the surrogate * range and hence won't be used for anything else (it's impossible * to input it via UTF-8 because our UTF-8 decoder correctly * rejects surrogates). */ #define UCSWIDE 0xDFFF #define ATTR_NARROW 0x0800000U #define ATTR_WIDE 0x0400000U #define ATTR_BOLD 0x0040000U #define ATTR_UNDER 0x0080000U #define ATTR_REVERSE 0x0100000U #define ATTR_BLINK 0x0200000U #define ATTR_FGMASK 0x00001FFU /* stores a colour in OSC 4 indexing */ #define ATTR_BGMASK 0x003FE00U /* stores a colour in OSC 4 indexing */ #define ATTR_COLOURS 0x003FFFFU #define ATTR_DIM 0x1000000U #define ATTR_STRIKE 0x2000000U #define ATTR_FGSHIFT 0 #define ATTR_BGSHIFT 9 #define ATTR_DEFFG (OSC4_COLOUR_fg << ATTR_FGSHIFT) #define ATTR_DEFBG (OSC4_COLOUR_bg << ATTR_BGSHIFT) #define ATTR_DEFAULT (ATTR_DEFFG | ATTR_DEFBG) struct sesslist { int nsessions; const char **sessions; char *buffer; /* so memory can be freed later */ }; struct unicode_data { char **uni_tbl; bool dbcs_screenfont; int font_codepage; int line_codepage; wchar_t unitab_scoacs[256]; wchar_t unitab_line[256]; wchar_t unitab_font[256]; wchar_t unitab_xterm[256]; wchar_t unitab_oemcp[256]; unsigned char unitab_ctrl[256]; }; #define LGXF_OVR 1 /* existing logfile overwrite */ #define LGXF_APN 0 /* existing logfile append */ #define LGXF_ASK -1 /* existing logfile ask */ #define LGTYP_NONE 0 /* logmode: no logging */ #define LGTYP_ASCII 1 /* logmode: pure ascii */ #define LGTYP_DEBUG 2 /* logmode: all chars of traffic */ #define LGTYP_PACKETS 3 /* logmode: SSH data packets */ #define LGTYP_SSHRAW 4 /* logmode: SSH raw data */ /* * Enumeration of 'special commands' that can be sent during a * session, separately from the byte stream of ordinary session data. */ typedef enum { /* * Commands that are generally useful in multiple backends. */ SS_BRK, /* serial-line break */ SS_EOF, /* end-of-file on session input */ SS_NOP, /* transmit data with no effect */ SS_PING, /* try to keep the session alive (probably, but not * necessarily, implemented as SS_NOP) */ /* * Commands specific to Telnet. */ SS_AYT, /* Are You There */ SS_SYNCH, /* Synch */ SS_EC, /* Erase Character */ SS_EL, /* Erase Line */ SS_GA, /* Go Ahead */ SS_ABORT, /* Abort Process */ SS_AO, /* Abort Output */ SS_IP, /* Interrupt Process */ SS_SUSP, /* Suspend Process */ SS_EOR, /* End Of Record */ SS_EOL, /* Telnet end-of-line sequence (CRLF, as opposed to CR * NUL that escapes a literal CR) */ /* * Commands specific to SSH. */ SS_REKEY, /* trigger an immediate repeat key exchange */ SS_XCERT, /* cross-certify another host key ('arg' indicates which) */ #if 0 /* removed in xtruss */ /* * Send a POSIX-style signal. (Useful in SSH and also pterm.) * * We use the master list in ssh/signal-list.h to define these enum * values, which will come out looking like names of the form * SS_SIGABRT, SS_SIGINT etc. */ #define SIGNAL_MAIN(name, text) SS_SIG ## name, #define SIGNAL_SUB(name) SS_SIG ## name, #include "ssh/signal-list.h" #undef SIGNAL_MAIN #undef SIGNAL_SUB #endif /* removed in xtruss */ /* * These aren't really special commands, but they appear in the * enumeration because the list returned from * backend_get_specials() will use them to specify the structure * of the GUI specials menu. */ SS_SEP, /* Separator */ SS_SUBMENU, /* Start a new submenu with specified name */ SS_EXITMENU, /* Exit current submenu, or end of entire specials list */ } SessionSpecialCode; /* * The structure type returned from backend_get_specials. */ struct SessionSpecial { const char *name; SessionSpecialCode code; int arg; }; /* Needed by both ssh/channel.h and ssh/ppl.h */ typedef void (*add_special_fn_t)( void *ctx, const char *text, SessionSpecialCode code, int arg); typedef enum { MBT_NOTHING, MBT_LEFT, MBT_MIDDLE, MBT_RIGHT, /* `raw' button designations */ MBT_SELECT, MBT_EXTEND, MBT_PASTE, /* `cooked' button designations */ MBT_WHEEL_UP, MBT_WHEEL_DOWN /* mouse wheel */ } Mouse_Button; typedef enum { MA_NOTHING, MA_CLICK, MA_2CLK, MA_3CLK, MA_DRAG, MA_RELEASE } Mouse_Action; /* Keyboard modifiers -- keys the user is actually holding down */ #define PKM_SHIFT 0x01 #define PKM_CONTROL 0x02 #define PKM_META 0x04 #define PKM_ALT 0x08 /* Keyboard flags that aren't really modifiers */ #define PKF_CAPSLOCK 0x10 #define PKF_NUMLOCK 0x20 #define PKF_REPEAT 0x40 /* Stand-alone keysyms for function keys */ typedef enum { PK_NULL, /* No symbol for this key */ /* Main keypad keys */ PK_ESCAPE, PK_TAB, PK_BACKSPACE, PK_RETURN, PK_COMPOSE, /* Editing keys */ PK_HOME, PK_INSERT, PK_DELETE, PK_END, PK_PAGEUP, PK_PAGEDOWN, /* Cursor keys */ PK_UP, PK_DOWN, PK_RIGHT, PK_LEFT, PK_REST, /* Numeric keypad */ /* Real one looks like: */ PK_PF1, PK_PF2, PK_PF3, PK_PF4, /* PF1 PF2 PF3 PF4 */ PK_KPCOMMA, PK_KPMINUS, PK_KPDECIMAL, /* 7 8 9 - */ PK_KP0, PK_KP1, PK_KP2, PK_KP3, PK_KP4, /* 4 5 6 , */ PK_KP5, PK_KP6, PK_KP7, PK_KP8, PK_KP9, /* 1 2 3 en- */ PK_KPBIGPLUS, PK_KPENTER, /* 0 . ter */ /* Top row */ PK_F1, PK_F2, PK_F3, PK_F4, PK_F5, PK_F6, PK_F7, PK_F8, PK_F9, PK_F10, PK_F11, PK_F12, PK_F13, PK_F14, PK_F15, PK_F16, PK_F17, PK_F18, PK_F19, PK_F20, PK_PAUSE } Key_Sym; #define PK_ISEDITING(k) ((k) >= PK_HOME && (k) <= PK_PAGEDOWN) #define PK_ISCURSOR(k) ((k) >= PK_UP && (k) <= PK_REST) #define PK_ISKEYPAD(k) ((k) >= PK_PF1 && (k) <= PK_KPENTER) #define PK_ISFKEY(k) ((k) >= PK_F1 && (k) <= PK_F20) enum { VT_XWINDOWS, VT_OEMANSI, VT_OEMONLY, VT_POORMAN, VT_UNICODE }; enum { /* * SSH-2 key exchange algorithms */ KEX_WARN, KEX_DHGROUP1, KEX_DHGROUP14, KEX_DHGEX, KEX_RSA, KEX_ECDH, KEX_MAX }; enum { /* * SSH-2 host key algorithms */ HK_WARN, HK_RSA, HK_DSA, HK_ECDSA, HK_ED25519, HK_ED448, HK_MAX }; enum { /* * SSH ciphers (both SSH-1 and SSH-2) */ CIPHER_WARN, /* pseudo 'cipher' */ CIPHER_3DES, CIPHER_BLOWFISH, CIPHER_AES, /* (SSH-2 only) */ CIPHER_DES, CIPHER_ARCFOUR, CIPHER_CHACHA20, CIPHER_MAX /* no. ciphers (inc warn) */ }; enum TriState { /* * Several different bits of the PuTTY configuration seem to be * three-way settings whose values are `always yes', `always * no', and `decide by some more complex automated means'. This * is true of line discipline options (local echo and line * editing), proxy DNS, proxy terminal logging, Close On Exit, and * SSH server bug workarounds. Accordingly I supply a single enum * here to deal with them all. */ FORCE_ON, FORCE_OFF, AUTO }; enum { /* * Proxy types. */ PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5, PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_SSH, PROXY_FUZZ }; enum { /* * Line discipline options which the backend might try to control. */ LD_EDIT, /* local line editing */ LD_ECHO, /* local echo */ LD_N_OPTIONS }; enum { /* Actions on remote window title query */ TITLE_NONE, TITLE_EMPTY, TITLE_REAL }; enum { /* SUPDUP character set options */ SUPDUP_CHARSET_ASCII, SUPDUP_CHARSET_ITS, SUPDUP_CHARSET_WAITS }; enum { /* Protocol back ends. (CONF_protocol) */ PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH, PROT_SSHCONN, /* PROT_SERIAL is supported on a subset of platforms, but it doesn't * hurt to define it globally. */ PROT_SERIAL, /* PROT_SUPDUP is the historical RFC 734 protocol. */ PROT_SUPDUP, PROTOCOL_LIMIT, /* upper bound on number of protocols */ }; enum { /* Bell settings (CONF_beep) */ BELL_DISABLED, BELL_DEFAULT, BELL_VISUAL, BELL_WAVEFILE, BELL_PCSPEAKER }; enum { /* Taskbar flashing indication on bell (CONF_beep_ind) */ B_IND_DISABLED, B_IND_FLASH, B_IND_STEADY }; enum { /* Resize actions (CONF_resize_action) */ RESIZE_TERM, RESIZE_DISABLED, RESIZE_FONT, RESIZE_EITHER }; enum { /* Function key types (CONF_funky_type) */ FUNKY_TILDE, FUNKY_LINUX, FUNKY_XTERM, FUNKY_VT400, FUNKY_VT100P, FUNKY_SCO }; enum { FQ_DEFAULT, FQ_ANTIALIASED, FQ_NONANTIALIASED, FQ_CLEARTYPE }; enum { SER_PAR_NONE, SER_PAR_ODD, SER_PAR_EVEN, SER_PAR_MARK, SER_PAR_SPACE }; enum { SER_FLOW_NONE, SER_FLOW_XONXOFF, SER_FLOW_RTSCTS, SER_FLOW_DSRDTR }; /* * Tables of string <-> enum value mappings used in settings.c. * Defined here so that backends can export their GSS library tables * to the cross-platform settings code. */ struct keyvalwhere { /* * Two fields which define a string and enum value to be * equivalent to each other. */ const char *s; int v; /* * The next pair of fields are used by gprefs() in settings.c to * arrange that when it reads a list of strings representing a * preference list and translates it into the corresponding list * of integers, strings not appearing in the list are entered in a * configurable position rather than uniformly at the end. */ /* * 'vrel' indicates which other value in the list to place this * element relative to. It should be a value that has occurred in * a 'v' field of some other element of the array, or -1 to * indicate that we simply place relative to one or other end of * the list. * * gprefs will try to process the elements in an order which makes * this field work (i.e. so that the element referenced has been * added before processing this one). */ int vrel; /* * 'where' indicates whether to place the new value before or * after the one referred to by vrel. -1 means before; +1 means * after. * * When vrel is -1, this also implicitly indicates which end of * the array to use. So vrel=-1, where=-1 means to place _before_ * some end of the list (hence, at the last element); vrel=-1, * where=+1 means to place _after_ an end (hence, at the first). */ int where; }; #ifndef NO_GSSAPI extern const int ngsslibs; extern const char *const gsslibnames[]; /* for displaying in configuration */ extern const struct keyvalwhere gsslibkeywords[]; /* for settings.c */ #endif extern const char *const ttymodes[]; enum { /* * Network address types. Used for specifying choice of IPv4/v6 * in config; also used in proxy.c to indicate whether a given * host name has already been resolved or will be resolved at * the proxy end. */ ADDRTYPE_UNSPEC, ADDRTYPE_IPV4, ADDRTYPE_IPV6, ADDRTYPE_LOCAL, /* e.g. Unix domain socket, or Windows named pipe */ ADDRTYPE_NAME /* SockAddr storing an unresolved host name */ }; /* Backend flags */ #define BACKEND_RESIZE_FORBIDDEN 0x01 /* Backend does not allow resizing terminal */ #define BACKEND_NEEDS_TERMINAL 0x02 /* Backend must have terminal */ #define BACKEND_SUPPORTS_NC_HOST 0x04 /* Backend can honour CONF_ssh_nc_host */ /* In (no)sshproxy.c */ extern const bool ssh_proxy_supported; struct Backend { const BackendVtable *vt; }; struct BackendVtable { char *(*init) (const BackendVtable *vt, Seat *seat, Backend **backend_out, LogContext *logctx, Conf *conf, const char *host, int port, char **realhost, bool nodelay, bool keepalive); void (*free) (Backend *be); /* Pass in a replacement configuration. */ void (*reconfig) (Backend *be, Conf *conf); /* send() returns the current amount of buffered data. */ size_t (*send) (Backend *be, const char *buf, size_t len); /* sendbuffer() does the same thing but without attempting a send */ size_t (*sendbuffer) (Backend *be); void (*size) (Backend *be, int width, int height); void (*special) (Backend *be, SessionSpecialCode code, int arg); const SessionSpecial *(*get_specials) (Backend *be); bool (*connected) (Backend *be); int (*exitcode) (Backend *be); /* If back->sendok() returns false, the backend doesn't currently * want input data, so the frontend should avoid acquiring any if * possible (passing back-pressure on to its sender). */ bool (*sendok) (Backend *be); bool (*ldisc_option_state) (Backend *be, int); void (*provide_ldisc) (Backend *be, Ldisc *ldisc); /* Tells the back end that the front end buffer is clearing. */ void (*unthrottle) (Backend *be, size_t bufsize); int (*cfg_info) (Backend *be); /* Only implemented in the SSH protocol: check whether a * connection-sharing upstream exists for a given configuration. */ bool (*test_for_upstream)(const char *host, int port, Conf *conf); /* Special-purpose function to return additional information to put * in a "are you sure you want to close this session" dialog; * return NULL if no such info, otherwise caller must free. * Only implemented in the SSH protocol, to warn about downstream * connections that would be lost if this one were terminated. */ char *(*close_warn_text)(Backend *be); /* 'id' is a machine-readable name for the backend, used in * saved-session storage. 'displayname' is a human-readable name * for error messages. */ const char *id, *displayname; int protocol; int default_port; unsigned flags; /* Only relevant for the serial protocol: bit masks of which * parity and flow control settings are supported. */ unsigned serial_parity_mask, serial_flow_mask; }; static inline char *backend_init( const BackendVtable *vt, Seat *seat, Backend **out, LogContext *logctx, Conf *conf, const char *host, int port, char **rhost, bool nd, bool ka) { return vt->init(vt, seat, out, logctx, conf, host, port, rhost, nd, ka); } static inline void backend_free(Backend *be) { be->vt->free(be); } static inline void backend_reconfig(Backend *be, Conf *conf) { be->vt->reconfig(be, conf); } static inline size_t backend_send(Backend *be, const char *buf, size_t len) { return be->vt->send(be, buf, len); } static inline size_t backend_sendbuffer(Backend *be) { return be->vt->sendbuffer(be); } static inline void backend_size(Backend *be, int width, int height) { be->vt->size(be, width, height); } static inline void backend_special( Backend *be, SessionSpecialCode code, int arg) { be->vt->special(be, code, arg); } static inline const SessionSpecial *backend_get_specials(Backend *be) { return be->vt->get_specials(be); } static inline bool backend_connected(Backend *be) { return be->vt->connected(be); } static inline int backend_exitcode(Backend *be) { return be->vt->exitcode(be); } static inline bool backend_sendok(Backend *be) { return be->vt->sendok(be); } static inline bool backend_ldisc_option_state(Backend *be, int state) { return be->vt->ldisc_option_state(be, state); } static inline void backend_provide_ldisc(Backend *be, Ldisc *ldisc) { be->vt->provide_ldisc(be, ldisc); } static inline void backend_unthrottle(Backend *be, size_t bufsize) { be->vt->unthrottle(be, bufsize); } static inline int backend_cfg_info(Backend *be) { return be->vt->cfg_info(be); } extern const struct BackendVtable *const backends[]; /* * In programs with a config UI, only the first few members of * backends[] will be displayed at the top-level; the others will be * relegated to a drop-down. */ extern const size_t n_ui_backends; /* * Suggested default protocol provided by the backend link module. * The application is free to ignore this. */ extern const int be_default_protocol; /* * Name of this particular application, for use in the config box * and other pieces of text. */ extern const char *const appname; /* * Mechanism for getting text strings such as usernames and passwords * from the front-end. * The fields are mostly modelled after SSH's keyboard-interactive auth. * FIXME We should probably mandate a character set/encoding (probably UTF-8). * * Since many of the pieces of text involved may be chosen by the server, * the caller must take care to ensure that the server can't spoof locally- * generated prompts such as key passphrase prompts. Some ground rules: * - If the front-end needs to truncate a string, it should lop off the * end. * - The front-end should filter out any dangerous characters and * generally not trust the strings. (But \n is required to behave * vaguely sensibly, at least in `instruction', and ideally in * `prompt[]' too.) */ typedef struct { char *prompt; bool echo; strbuf *result; } prompt_t; typedef struct { /* * Indicates whether the information entered is to be used locally * (for instance a key passphrase prompt), or is destined for the wire. * This is a hint only; the front-end is at liberty not to use this * information (so the caller should ensure that the supplied text is * sufficient). */ bool to_server; /* * Indicates whether the prompts originated _at_ the server, so * that the front end can display some kind of trust sigil that * distinguishes (say) a legit private-key passphrase prompt from * a fake one sent by a malicious server. */ bool from_server; char *name; /* Short description, perhaps for dialog box title */ bool name_reqd; /* Display of `name' required or optional? */ char *instruction; /* Long description, maybe with embedded newlines */ bool instr_reqd; /* Display of `instruction' required or optional? */ size_t n_prompts; /* May be zero (in which case display the foregoing, * if any, and return success) */ size_t prompts_size; /* allocated storage capacity for prompts[] */ prompt_t **prompts; void *data; /* slot for housekeeping data, managed by * seat_get_userpass_input(); initially NULL */ } prompts_t; prompts_t *new_prompts(void); void add_prompt(prompts_t *p, char *promptstr, bool echo); void prompt_set_result(prompt_t *pr, const char *newstr); char *prompt_get_result(prompt_t *pr); const char *prompt_get_result_ref(prompt_t *pr); void free_prompts(prompts_t *p); /* * Data type definitions for true-colour terminal display. * 'optionalrgb' describes a single RGB colour, which overrides the * other colour settings if 'enabled' is nonzero, and is ignored * otherwise. 'truecolour' contains a pair of those for foreground and * background. */ typedef struct optionalrgb { bool enabled; unsigned char r, g, b; } optionalrgb; extern const optionalrgb optionalrgb_none; typedef struct truecolour { optionalrgb fg, bg; } truecolour; #define optionalrgb_equal(r1,r2) ( \ (r1).enabled==(r2).enabled && \ (r1).r==(r2).r && (r1).g==(r2).g && (r1).b==(r2).b) #define truecolour_equal(c1,c2) ( \ optionalrgb_equal((c1).fg, (c2).fg) && \ optionalrgb_equal((c1).bg, (c2).bg)) /* * Enumeration of clipboards. We provide some standard ones cross- * platform, and then permit each platform to extend this enumeration * further by defining PLATFORM_CLIPBOARDS in its own header file. * * CLIP_NULL is a non-clipboard, writes to which are ignored and reads * from which return no data. * * CLIP_LOCAL refers to a buffer within terminal.c, which * unconditionally saves the last data selected in the terminal. In * configurations where a system clipboard is not written * automatically on selection but instead by an explicit UI action, * this is where the code responding to that action can find the data * to write to the clipboard in question. */ #define CROSS_PLATFORM_CLIPBOARDS(X) \ X(CLIP_NULL, "null clipboard") \ X(CLIP_LOCAL, "last text selected in terminal") \ /* end of list */ #define ALL_CLIPBOARDS(X) \ CROSS_PLATFORM_CLIPBOARDS(X) \ PLATFORM_CLIPBOARDS(X) \ /* end of list */ #define CLIP_ID(id,name) id, enum { ALL_CLIPBOARDS(CLIP_ID) N_CLIPBOARDS }; #undef CLIP_ID /* Hint from backend to frontend about time-consuming operations, used * by seat_set_busy_status. Initial state is assumed to be * BUSY_NOT. */ typedef enum BusyStatus { BUSY_NOT, /* Not busy, all user interaction OK */ BUSY_WAITING, /* Waiting for something; local event loops still running so some local interaction (e.g. menus) OK, but network stuff is suspended */ BUSY_CPU /* Locally busy (e.g. crypto); user interaction * suspended */ } BusyStatus; typedef enum SeatInteractionContext { SIC_BANNER, SIC_KI_PROMPTS } SeatInteractionContext; /* * Data type 'Seat', which is an API intended to contain essentially * everything that a back end might need to talk to its client for: * session output, password prompts, SSH warnings about host keys and * weak cryptography, notifications of events like the remote process * exiting or the GUI specials menu needing an update. */ struct Seat { const struct SeatVtable *vt; }; struct SeatVtable { /* * Provide output from the remote session. 'is_stderr' indicates * that the output should be sent to a separate error message * channel, if the seat has one. But combining both channels into * one is OK too; that's what terminal-window based seats do. * * The return value is the current size of the output backlog. */ size_t (*output)(Seat *seat, bool is_stderr, const void *data, size_t len); /* * Called when the back end wants to indicate that EOF has arrived * on the server-to-client stream. Returns false to indicate that * we intend to keep the session open in the other direction, or * true to indicate that if they're closing so are we. */ bool (*eof)(Seat *seat); /* * Called by the back end to notify that the output backlog has * changed size. A front end in control of the event loop won't * necessarily need this (they can just keep checking it via * backend_sendbuffer at every opportunity), but one buried in the * depths of something else (like an SSH proxy) will need to be * proactively notified that the amount of buffered data has * become smaller. */ void (*sent)(Seat *seat, size_t new_sendbuffer); /* * Try to get answers from a set of interactive login prompts. The * prompts are provided in 'p'; the bufchain 'input' holds the * data currently outstanding in the session's normal standard- * input channel. Seats may implement this function by consuming * data from 'input' (e.g. password prompts in GUI PuTTY, * displayed in the same terminal as the subsequent session), or * by doing something entirely different (e.g. directly * interacting with standard I/O, or putting up a dialog box). * * A positive return value means that all prompts have had answers * filled in. A zero return means that the user performed a * deliberate 'cancel' UI action. A negative return means that no * answer can be given yet but please try again later. * * (FIXME: it would be nice to distinguish two classes of cancel * action, so the user could specify 'I want to abandon this * entire attempt to start a session' or the milder 'I want to * abandon this particular form of authentication and fall back to * a different one' - e.g. if you turn out not to be able to * remember your private key passphrase then perhaps you'd rather * fall back to password auth rather than aborting the whole * session.) * * (Also FIXME: currently, backends' only response to the 'try * again later' is to try again when more input data becomes * available, because they assume that a seat is returning that * value because it's consuming keyboard input. But a seat that * handled this function by putting up a dialog box might want to * put it up non-modally, and therefore would want to proactively * notify the backend to retry once the dialog went away. So if I * ever do want to move password prompts into a dialog box, I'll * want a backend method for sending that notification.) */ int (*get_userpass_input)(Seat *seat, prompts_t *p, bufchain *input); /* * Notify the seat that the process running at the other end of * the connection has finished. */ void (*notify_remote_exit)(Seat *seat); /* * Notify the seat that the whole connection has finished. * (Distinct from notify_remote_exit, e.g. in the case where you * have port forwardings still active when the main foreground * session goes away: then you'd get notify_remote_exit when the * foreground session dies, but notify_remote_disconnect when the * last forwarding vanishes and the network connection actually * closes.) * * This function might be called multiple times by accident; seats * should be prepared to cope. * * More precisely: this function notifies the seat that * backend_connected() might now return false where previously it * returned true. (Note the 'might': an accidental duplicate call * might happen when backend_connected() was already returning * false. Or even, in weird situations, when it hadn't stopped * returning true yet. The point is, when you get this * notification, all it's really telling you is that it's worth * _checking_ backend_connected, if you weren't already.) */ void (*notify_remote_disconnect)(Seat *seat); /* * Notify the seat that the connection has suffered a fatal error. */ void (*connection_fatal)(Seat *seat, const char *message); /* * Notify the seat that the list of special commands available * from backend_get_specials() has changed, so that it might want * to call that function to repopulate its menu. * * Seats are not expected to call backend_get_specials() * proactively; they may start by assuming that the backend * provides no special commands at all, so if the backend does * provide any, then it should use this notification at startup * time. Of course it can also invoke it later if the set of * special commands changes. * * It does not need to invoke it at session shutdown. */ void (*update_specials_menu)(Seat *seat); /* * Get the seat's preferred value for an SSH terminal mode * setting. Returning NULL indicates no preference (i.e. the SSH * connection will not attempt to set the mode at all). * * The returned value is dynamically allocated, and the caller * should free it. */ char *(*get_ttymode)(Seat *seat, const char *mode); /* * Tell the seat whether the backend is currently doing anything * CPU-intensive (typically a cryptographic key exchange). See * BusyStatus enumeration above. */ void (*set_busy_status)(Seat *seat, BusyStatus status); /* * Ask the seat whether a given SSH host key should be accepted. * This may return immediately after checking saved configuration * or command-line options, or it may have to present a prompt to * the user and return asynchronously later. * * Return values: * * - +1 means `key was OK' (either already known or the user just * approved it) `so continue with the connection' * * - 0 means `key was not OK, abandon the connection' * * - -1 means `I've initiated enquiries, please wait to be called * back via the provided function with a result that's either 0 * or +1'. */ int (*verify_ssh_host_key)( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, void (*callback)(void *ctx, int result), void *ctx); /* * Check with the seat whether it's OK to use a cryptographic * primitive from below the 'warn below this line' threshold in * the input Conf. Return values are the same as * verify_ssh_host_key above. */ int (*confirm_weak_crypto_primitive)( Seat *seat, const char *algtype, const char *algname, void (*callback)(void *ctx, int result), void *ctx); /* * Variant form of confirm_weak_crypto_primitive, which prints a * slightly different message but otherwise has the same * semantics. * * This form is used in the case where we're using a host key * below the warning threshold because that's the best one we have * cached, but at least one host key algorithm *above* the * threshold is available that we don't have cached. 'betteralgs' * lists the better algorithm(s). */ int (*confirm_weak_cached_hostkey)( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, int result), void *ctx); /* * Indicates whether the seat is expecting to interact with the * user in the UTF-8 character set. (Affects e.g. visual erase * handling in local line editing.) */ bool (*is_utf8)(Seat *seat); /* * Notify the seat that the back end, and/or the ldisc between * them, have changed their idea of whether they currently want * local echo and/or local line editing enabled. */ void (*echoedit_update)(Seat *seat, bool echoing, bool editing); /* * Return the local X display string relevant to a seat, or NULL * if there isn't one or if the concept is meaningless. */ const char *(*get_x_display)(Seat *seat); /* * Return the X11 id of the X terminal window relevant to a seat, * by returning true and filling in the output pointer. Return * false if there isn't one or if the concept is meaningless. */ bool (*get_windowid)(Seat *seat, long *id_out); /* * Return the size of the terminal window in pixels. If the * concept is meaningless or the information is unavailable, * return false; otherwise fill in the output pointers and return * true. */ bool (*get_window_pixel_size)(Seat *seat, int *width, int *height); /* * Return a StripCtrlChars appropriate for sanitising untrusted * terminal data (e.g. SSH banners, prompts) being sent to the * user of this seat. May return NULL if no sanitisation is * needed. */ StripCtrlChars *(*stripctrl_new)( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); /* * Set the seat's current idea of where output is coming from. * True means that output is being generated by our own code base * (and hence, can be trusted if it's asking you for secrets such * as your passphrase); false means output is coming from the * server. * * Returns true if the seat has a way to indicate this * distinction. Returns false if not, in which case the backend * should use a fallback defence against spoofing of PuTTY's local * prompts by malicious servers. */ bool (*set_trust_status)(Seat *seat, bool trusted); /* * Ask the seat whether it would like verbose messages. */ bool (*verbose)(Seat *seat); /* * Ask the seat whether it's an interactive program. */ bool (*interactive)(Seat *seat); /* * Return the seat's current idea of where the output cursor is. * * Returns true if the seat has a cursor. Returns false if not. */ bool (*get_cursor_position)(Seat *seat, int *x, int *y); }; static inline size_t seat_output( Seat *seat, bool err, const void *data, size_t len) { return seat->vt->output(seat, err, data, len); } static inline bool seat_eof(Seat *seat) { return seat->vt->eof(seat); } static inline void seat_sent(Seat *seat, size_t bufsize) { seat->vt->sent(seat, bufsize); } static inline int seat_get_userpass_input( Seat *seat, prompts_t *p, bufchain *input) { return seat->vt->get_userpass_input(seat, p, input); } static inline void seat_notify_remote_exit(Seat *seat) { seat->vt->notify_remote_exit(seat); } static inline void seat_notify_remote_disconnect(Seat *seat) { seat->vt->notify_remote_disconnect(seat); } static inline void seat_update_specials_menu(Seat *seat) { seat->vt->update_specials_menu(seat); } static inline char *seat_get_ttymode(Seat *seat, const char *mode) { return seat->vt->get_ttymode(seat, mode); } static inline void seat_set_busy_status(Seat *seat, BusyStatus status) { seat->vt->set_busy_status(seat, status); } static inline int seat_verify_ssh_host_key( Seat *seat, const char *h, int p, const char *ktyp, char *kstr, const char *kdsp, char **fps, void (*cb)(void *ctx, int result), void *ctx) { return seat->vt->verify_ssh_host_key(seat, h, p, ktyp, kstr, kdsp, fps, cb, ctx); } static inline int seat_confirm_weak_crypto_primitive( Seat *seat, const char *atyp, const char *aname, void (*cb)(void *ctx, int result), void *ctx) { return seat->vt->confirm_weak_crypto_primitive(seat, atyp, aname, cb, ctx); } static inline int seat_confirm_weak_cached_hostkey( Seat *seat, const char *aname, const char *better, void (*cb)(void *ctx, int result), void *ctx) { return seat->vt->confirm_weak_cached_hostkey(seat, aname, better, cb, ctx); } static inline bool seat_is_utf8(Seat *seat) { return seat->vt->is_utf8(seat); } static inline void seat_echoedit_update(Seat *seat, bool ec, bool ed) { seat->vt->echoedit_update(seat, ec, ed); } static inline const char *seat_get_x_display(Seat *seat) { return seat->vt->get_x_display(seat); } static inline bool seat_get_windowid(Seat *seat, long *id_out) { return seat->vt->get_windowid(seat, id_out); } static inline bool seat_get_window_pixel_size(Seat *seat, int *w, int *h) { return seat->vt->get_window_pixel_size(seat, w, h); } static inline StripCtrlChars *seat_stripctrl_new( Seat *seat, BinarySink *bs, SeatInteractionContext sic) { return seat->vt->stripctrl_new(seat, bs, sic); } static inline bool seat_set_trust_status(Seat *seat, bool trusted) { return seat->vt->set_trust_status(seat, trusted); } static inline bool seat_verbose(Seat *seat) { return seat->vt->verbose(seat); } static inline bool seat_interactive(Seat *seat) { return seat->vt->interactive(seat); } static inline bool seat_get_cursor_position(Seat *seat, int *x, int *y) { return seat->vt->get_cursor_position(seat, x, y); } /* Unlike the seat's actual method, the public entry point * seat_connection_fatal is a wrapper function with a printf-like API, * defined in misc.c. */ void seat_connection_fatal(Seat *seat, const char *fmt, ...) PRINTF_LIKE(2, 3); /* Handy aliases for seat_output which set is_stderr to a fixed value. */ static inline size_t seat_stdout(Seat *seat, const void *data, size_t len) { return seat_output(seat, false, data, len); } static inline size_t seat_stdout_pl(Seat *seat, ptrlen data) { return seat_output(seat, false, data.ptr, data.len); } static inline size_t seat_stderr(Seat *seat, const void *data, size_t len) { return seat_output(seat, true, data, len); } static inline size_t seat_stderr_pl(Seat *seat, ptrlen data) { return seat_output(seat, true, data.ptr, data.len); } /* * Stub methods for seat implementations that want to use the obvious * null handling for a given method. * * These are generally obvious, except for is_utf8, where you might * plausibly want to return either fixed answer 'no' or 'yes'. */ size_t nullseat_output( Seat *seat, bool is_stderr, const void *data, size_t len); bool nullseat_eof(Seat *seat); void nullseat_sent(Seat *seat, size_t bufsize); int nullseat_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); void nullseat_notify_remote_exit(Seat *seat); void nullseat_notify_remote_disconnect(Seat *seat); void nullseat_connection_fatal(Seat *seat, const char *message); void nullseat_update_specials_menu(Seat *seat); char *nullseat_get_ttymode(Seat *seat, const char *mode); void nullseat_set_busy_status(Seat *seat, BusyStatus status); int nullseat_verify_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, void (*callback)(void *ctx, int result), void *ctx); int nullseat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, void (*callback)(void *ctx, int result), void *ctx); int nullseat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, int result), void *ctx); bool nullseat_is_never_utf8(Seat *seat); bool nullseat_is_always_utf8(Seat *seat); void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing); const char *nullseat_get_x_display(Seat *seat); bool nullseat_get_windowid(Seat *seat, long *id_out); bool nullseat_get_window_pixel_size(Seat *seat, int *width, int *height); StripCtrlChars *nullseat_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); bool nullseat_set_trust_status(Seat *seat, bool trusted); bool nullseat_set_trust_status_vacuously(Seat *seat, bool trusted); bool nullseat_verbose_no(Seat *seat); bool nullseat_verbose_yes(Seat *seat); bool nullseat_interactive_no(Seat *seat); bool nullseat_interactive_yes(Seat *seat); bool nullseat_get_cursor_position(Seat *seat, int *x, int *y); /* * Seat functions provided by the platform's console-application * support module (wincons.c, uxcons.c). */ void console_connection_fatal(Seat *seat, const char *message); int console_verify_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, void (*callback)(void *ctx, int result), void *ctx); int console_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, void (*callback)(void *ctx, int result), void *ctx); int console_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, int result), void *ctx); StripCtrlChars *console_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); bool console_set_trust_status(Seat *seat, bool trusted); /* * Other centralised seat functions. */ int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); bool cmdline_seat_verbose(Seat *seat); typedef struct rgb { uint8_t r, g, b; } rgb; /* * Data type 'TermWin', which is a vtable encapsulating all the * functionality that Terminal expects from its containing terminal * window. */ struct TermWin { const struct TermWinVtable *vt; }; struct TermWinVtable { /* * All functions listed here between setup_draw_ctx and * free_draw_ctx expect to be _called_ between them too, so that * the TermWin has a drawing context currently available. * * (Yes, even char_width, because e.g. the Windows implementation * of TermWin handles it by loading the currently configured font * into the HDC and doing a GDI query.) */ bool (*setup_draw_ctx)(TermWin *); /* Draw text in the window, during a painting operation */ void (*draw_text)(TermWin *, int x, int y, wchar_t *text, int len, unsigned long attrs, int line_attrs, truecolour tc); /* Draw the visible cursor. Expects you to have called do_text * first (because it might just draw an underline over a character * presumed to exist already), but also expects you to pass in all * the details of the character under the cursor (because it might * redraw it in different colours). */ void (*draw_cursor)(TermWin *, int x, int y, wchar_t *text, int len, unsigned long attrs, int line_attrs, truecolour tc); /* Draw the sigil indicating that a line of text has come from * PuTTY itself rather than the far end (defence against end-of- * authentication spoofing) */ void (*draw_trust_sigil)(TermWin *, int x, int y); int (*char_width)(TermWin *, int uc); void (*free_draw_ctx)(TermWin *); void (*set_cursor_pos)(TermWin *, int x, int y); /* set_raw_mouse_mode instructs the front end to start sending mouse events * in raw mode suitable for translating into mouse-tracking terminal data * (e.g. include scroll-wheel events and don't bother to identify double- * and triple-clicks). set_raw_mouse_mode_pointer instructs the front end * to change the mouse pointer shape to *indicate* raw mouse mode. */ void (*set_raw_mouse_mode)(TermWin *, bool enable); void (*set_raw_mouse_mode_pointer)(TermWin *, bool enable); void (*set_scrollbar)(TermWin *, int total, int start, int page); void (*bell)(TermWin *, int mode); void (*clip_write)(TermWin *, int clipboard, wchar_t *text, int *attrs, truecolour *colours, int len, bool must_deselect); void (*clip_request_paste)(TermWin *, int clipboard); void (*refresh)(TermWin *); void (*request_resize)(TermWin *, int w, int h); void (*set_title)(TermWin *, const char *title); void (*set_icon_title)(TermWin *, const char *icontitle); /* set_minimised and set_maximised are assumed to set two * independent settings, rather than a single three-way * {min,normal,max} switch. The idea is that when you un-minimise * the window it remembers whether to go back to normal or * maximised. */ void (*set_minimised)(TermWin *, bool minimised); void (*set_maximised)(TermWin *, bool maximised); void (*move)(TermWin *, int x, int y); void (*set_zorder)(TermWin *, bool top); /* Set the colour palette that the TermWin will use to display * text. One call to this function sets 'ncolours' consecutive * colours in the OSC 4 sequence, starting at 'start'. */ void (*palette_set)(TermWin *, unsigned start, unsigned ncolours, const rgb *colours); /* Query the front end for any OS-local overrides to the default * colours stored in Conf. The front end should set any it cares * about by calling term_palette_override. * * The Terminal object is passed in as a parameter, because this * can be called as a callback from term_init(). So the TermWin * itself won't yet have been told where to find its Terminal * object, because that doesn't happen until term_init * returns. */ void (*palette_get_overrides)(TermWin *, Terminal *); }; static inline bool win_setup_draw_ctx(TermWin *win) { return win->vt->setup_draw_ctx(win); } static inline void win_draw_text( TermWin *win, int x, int y, wchar_t *text, int len, unsigned long attrs, int line_attrs, truecolour tc) { win->vt->draw_text(win, x, y, text, len, attrs, line_attrs, tc); } static inline void win_draw_cursor( TermWin *win, int x, int y, wchar_t *text, int len, unsigned long attrs, int line_attrs, truecolour tc) { win->vt->draw_cursor(win, x, y, text, len, attrs, line_attrs, tc); } static inline void win_draw_trust_sigil(TermWin *win, int x, int y) { win->vt->draw_trust_sigil(win, x, y); } static inline int win_char_width(TermWin *win, int uc) { return win->vt->char_width(win, uc); } static inline void win_free_draw_ctx(TermWin *win) { win->vt->free_draw_ctx(win); } static inline void win_set_cursor_pos(TermWin *win, int x, int y) { win->vt->set_cursor_pos(win, x, y); } static inline void win_set_raw_mouse_mode(TermWin *win, bool enable) { win->vt->set_raw_mouse_mode(win, enable); } static inline void win_set_raw_mouse_mode_pointer(TermWin *win, bool enable) { win->vt->set_raw_mouse_mode_pointer(win, enable); } static inline void win_set_scrollbar(TermWin *win, int t, int s, int p) { win->vt->set_scrollbar(win, t, s, p); } static inline void win_bell(TermWin *win, int mode) { win->vt->bell(win, mode); } static inline void win_clip_write( TermWin *win, int clipboard, wchar_t *text, int *attrs, truecolour *colours, int len, bool deselect) { win->vt->clip_write(win, clipboard, text, attrs, colours, len, deselect); } static inline void win_clip_request_paste(TermWin *win, int clipboard) { win->vt->clip_request_paste(win, clipboard); } static inline void win_refresh(TermWin *win) { win->vt->refresh(win); } static inline void win_request_resize(TermWin *win, int w, int h) { win->vt->request_resize(win, w, h); } static inline void win_set_title(TermWin *win, const char *title) { win->vt->set_title(win, title); } static inline void win_set_icon_title(TermWin *win, const char *icontitle) { win->vt->set_icon_title(win, icontitle); } static inline void win_set_minimised(TermWin *win, bool minimised) { win->vt->set_minimised(win, minimised); } static inline void win_set_maximised(TermWin *win, bool maximised) { win->vt->set_maximised(win, maximised); } static inline void win_move(TermWin *win, int x, int y) { win->vt->move(win, x, y); } static inline void win_set_zorder(TermWin *win, bool top) { win->vt->set_zorder(win, top); } static inline void win_palette_set( TermWin *win, unsigned start, unsigned ncolours, const rgb *colours) { win->vt->palette_set(win, start, ncolours, colours); } static inline void win_palette_get_overrides(TermWin *win, Terminal *term) { win->vt->palette_get_overrides(win, term); } /* * Global functions not specific to a connection instance. */ void nonfatal(const char *, ...) PRINTF_LIKE(1, 2); NORETURN void modalfatalbox(const char *, ...) PRINTF_LIKE(1, 2); NORETURN void cleanup_exit(int); /* * Exports from conf.c, and a big enum (via parametric macro) of * configuration option keys. */ #define CONFIG_OPTIONS(X) \ /* X(value-type, subkey-type, keyword) */ \ X(STR, NONE, host) \ X(INT, NONE, port) \ X(INT, NONE, protocol) /* PROT_SSH, PROT_TELNET etc */ \ X(INT, NONE, addressfamily) /* ADDRTYPE_IPV[46] or ADDRTYPE_UNSPEC */ \ X(INT, NONE, close_on_exit) /* FORCE_ON, FORCE_OFF, AUTO */ \ X(BOOL, NONE, warn_on_close) \ X(INT, NONE, ping_interval) /* in seconds */ \ X(BOOL, NONE, tcp_nodelay) \ X(BOOL, NONE, tcp_keepalives) \ X(STR, NONE, loghost) /* logical host being contacted, for host key check */ \ /* Proxy options */ \ X(STR, NONE, proxy_exclude_list) \ X(INT, NONE, proxy_dns) /* FORCE_ON, FORCE_OFF, AUTO */ \ X(BOOL, NONE, even_proxy_localhost) \ X(INT, NONE, proxy_type) /* PROXY_NONE, PROXY_SOCKS4, ... */ \ X(STR, NONE, proxy_host) \ X(INT, NONE, proxy_port) \ X(STR, NONE, proxy_username) \ X(STR, NONE, proxy_password) \ X(STR, NONE, proxy_telnet_command) \ X(INT, NONE, proxy_log_to_term) /* FORCE_ON, FORCE_OFF, AUTO */ \ /* SSH options */ \ X(STR, NONE, remote_cmd) \ X(STR, NONE, remote_cmd2) /* fallback if remote_cmd fails; never loaded or saved */ \ X(BOOL, NONE, nopty) \ X(BOOL, NONE, compression) \ X(INT, INT, ssh_kexlist) \ X(INT, INT, ssh_hklist) \ X(BOOL, NONE, ssh_prefer_known_hostkeys) \ X(INT, NONE, ssh_rekey_time) /* in minutes */ \ X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \ X(BOOL, NONE, tryagent) \ X(BOOL, NONE, agentfwd) \ X(BOOL, NONE, change_username) /* allow username switching in SSH-2 */ \ X(INT, INT, ssh_cipherlist) \ X(FILENAME, NONE, keyfile) \ /* \ * Which SSH protocol to use. \ * For historical reasons, the current legal values for CONF_sshprot \ * are: \ * 0 = SSH-1 only \ * 3 = SSH-2 only \ * We used to also support \ * 1 = SSH-1 with fallback to SSH-2 \ * 2 = SSH-2 with fallback to SSH-1 \ * and we continue to use 0/3 in storage formats rather than the more \ * obvious 1/2 to avoid surprises if someone saves a session and later \ * downgrades PuTTY. So it's easier to use these numbers internally too. \ */ \ X(INT, NONE, sshprot) \ X(BOOL, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \ X(BOOL, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \ X(BOOL, NONE, ssh_no_trivial_userauth) /* disable trivial types of auth */ \ X(BOOL, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \ X(BOOL, NONE, try_tis_auth) \ X(BOOL, NONE, try_ki_auth) \ X(BOOL, NONE, try_gssapi_auth) /* attempt gssapi auth via ssh userauth */ \ X(BOOL, NONE, try_gssapi_kex) /* attempt gssapi auth via ssh kex */ \ X(BOOL, NONE, gssapifwd) /* forward tgt via gss */ \ X(INT, NONE, gssapirekey) /* KEXGSS refresh interval (mins) */ \ X(INT, INT, ssh_gsslist) /* preference order for local GSS libs */ \ X(FILENAME, NONE, ssh_gss_custom) \ X(BOOL, NONE, ssh_subsys) /* run a subsystem rather than a command */ \ X(BOOL, NONE, ssh_subsys2) /* fallback to go with remote_cmd_ptr2 */ \ X(BOOL, NONE, ssh_no_shell) /* avoid running a shell */ \ X(STR, NONE, ssh_nc_host) /* host to connect to in `nc' mode */ \ X(INT, NONE, ssh_nc_port) /* port to connect to in `nc' mode */ \ /* Telnet options */ \ X(STR, NONE, termtype) \ X(STR, NONE, termspeed) \ X(STR, STR, ttymodes) /* values are "Vvalue" or "A" */ \ X(STR, STR, environmt) \ X(STR, NONE, username) \ X(BOOL, NONE, username_from_env) \ X(STR, NONE, localusername) \ X(BOOL, NONE, rfc_environ) \ X(BOOL, NONE, passive_telnet) \ /* Serial port options */ \ X(STR, NONE, serline) \ X(INT, NONE, serspeed) \ X(INT, NONE, serdatabits) \ X(INT, NONE, serstopbits) \ X(INT, NONE, serparity) /* SER_PAR_NONE, SER_PAR_ODD, ... */ \ X(INT, NONE, serflow) /* SER_FLOW_NONE, SER_FLOW_XONXOFF, ... */ \ /* Supdup options */ \ X(STR, NONE, supdup_location) \ X(INT, NONE, supdup_ascii_set) \ X(BOOL, NONE, supdup_more) \ X(BOOL, NONE, supdup_scroll) \ /* Keyboard options */ \ X(BOOL, NONE, bksp_is_delete) \ X(BOOL, NONE, rxvt_homeend) \ X(INT, NONE, funky_type) /* FUNKY_XTERM, FUNKY_LINUX, ... */ \ X(BOOL, NONE, no_applic_c) /* totally disable app cursor keys */ \ X(BOOL, NONE, no_applic_k) /* totally disable app keypad */ \ X(BOOL, NONE, no_mouse_rep) /* totally disable mouse reporting */ \ X(BOOL, NONE, no_remote_resize) /* disable remote resizing */ \ X(BOOL, NONE, no_alt_screen) /* disable alternate screen */ \ X(BOOL, NONE, no_remote_wintitle) /* disable remote retitling */ \ X(BOOL, NONE, no_remote_clearscroll) /* disable ESC[3J */ \ X(BOOL, NONE, no_dbackspace) /* disable destructive backspace */ \ X(BOOL, NONE, no_remote_charset) /* disable remote charset config */ \ X(INT, NONE, remote_qtitle_action) /* remote win title query action * (TITLE_NONE, TITLE_EMPTY, ...) */ \ X(BOOL, NONE, app_cursor) \ X(BOOL, NONE, app_keypad) \ X(BOOL, NONE, nethack_keypad) \ X(BOOL, NONE, telnet_keyboard) \ X(BOOL, NONE, telnet_newline) \ X(BOOL, NONE, alt_f4) /* is it special? */ \ X(BOOL, NONE, alt_space) /* is it special? */ \ X(BOOL, NONE, alt_only) /* is it special? */ \ X(INT, NONE, localecho) /* FORCE_ON, FORCE_OFF, AUTO */ \ X(INT, NONE, localedit) /* FORCE_ON, FORCE_OFF, AUTO */ \ X(BOOL, NONE, alwaysontop) \ X(BOOL, NONE, fullscreenonaltenter) \ X(BOOL, NONE, scroll_on_key) \ X(BOOL, NONE, scroll_on_disp) \ X(BOOL, NONE, erase_to_scrollback) \ X(BOOL, NONE, compose_key) \ X(BOOL, NONE, ctrlaltkeys) \ X(BOOL, NONE, osx_option_meta) \ X(BOOL, NONE, osx_command_meta) \ X(STR, NONE, wintitle) /* initial window title */ \ /* Terminal options */ \ X(INT, NONE, savelines) \ X(BOOL, NONE, dec_om) \ X(BOOL, NONE, wrap_mode) \ X(BOOL, NONE, lfhascr) \ X(INT, NONE, cursor_type) /* 0=block 1=underline 2=vertical */ \ X(BOOL, NONE, blink_cur) \ X(INT, NONE, beep) /* BELL_DISABLED, BELL_DEFAULT, ... */ \ X(INT, NONE, beep_ind) /* B_IND_DISABLED, B_IND_FLASH, ... */ \ X(BOOL, NONE, bellovl) /* bell overload protection active? */ \ X(INT, NONE, bellovl_n) /* number of bells to cause overload */ \ X(INT, NONE, bellovl_t) /* time interval for overload (seconds) */ \ X(INT, NONE, bellovl_s) /* period of silence to re-enable bell (s) */ \ X(FILENAME, NONE, bell_wavefile) \ X(BOOL, NONE, scrollbar) \ X(BOOL, NONE, scrollbar_in_fullscreen) \ X(INT, NONE, resize_action) /* RESIZE_TERM, RESIZE_DISABLED, ... */ \ X(BOOL, NONE, bce) \ X(BOOL, NONE, blinktext) \ X(BOOL, NONE, win_name_always) \ X(INT, NONE, width) \ X(INT, NONE, height) \ X(FONT, NONE, font) \ X(INT, NONE, font_quality) /* FQ_DEFAULT, FQ_ANTIALIASED, ... */ \ X(FILENAME, NONE, logfilename) \ X(INT, NONE, logtype) /* LGTYP_NONE, LGTYPE_ASCII, ... */ \ X(INT, NONE, logxfovr) /* LGXF_OVR, LGXF_APN, LGXF_ASK */ \ X(BOOL, NONE, logflush) \ X(BOOL, NONE, logheader) \ X(BOOL, NONE, logomitpass) \ X(BOOL, NONE, logomitdata) \ X(BOOL, NONE, hide_mouseptr) \ X(BOOL, NONE, sunken_edge) \ X(INT, NONE, window_border) /* in pixels */ \ X(STR, NONE, answerback) \ X(STR, NONE, printer) \ X(BOOL, NONE, no_arabicshaping) \ X(BOOL, NONE, no_bidi) \ /* Colour options */ \ X(BOOL, NONE, ansi_colour) \ X(BOOL, NONE, xterm_256_colour) \ X(BOOL, NONE, true_colour) \ X(BOOL, NONE, system_colour) \ X(BOOL, NONE, try_palette) \ X(INT, NONE, bold_style) /* 1=font 2=colour (3=both) */ \ X(INT, INT, colours) /* indexed by the CONF_COLOUR_* enum encoding */ \ /* Selection options */ \ X(INT, NONE, mouse_is_xterm) /* 0=compromise 1=xterm 2=Windows */ \ X(BOOL, NONE, rect_select) \ X(BOOL, NONE, paste_controls) \ X(BOOL, NONE, rawcnp) \ X(BOOL, NONE, utf8linedraw) \ X(BOOL, NONE, rtf_paste) \ X(BOOL, NONE, mouse_override) \ X(INT, INT, wordness) \ X(BOOL, NONE, mouseautocopy) \ X(INT, NONE, mousepaste) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ X(INT, NONE, ctrlshiftins) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ X(INT, NONE, ctrlshiftcv) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ X(STR, NONE, mousepaste_custom) \ X(STR, NONE, ctrlshiftins_custom) \ X(STR, NONE, ctrlshiftcv_custom) \ /* translations */ \ X(INT, NONE, vtmode) /* VT_XWINDOWS, VT_OEMANSI, ... */ \ X(STR, NONE, line_codepage) \ X(BOOL, NONE, cjk_ambig_wide) \ X(BOOL, NONE, utf8_override) \ X(BOOL, NONE, xlat_capslockcyr) \ /* X11 forwarding */ \ X(BOOL, NONE, x11_forward) \ X(STR, NONE, x11_display) \ X(INT, NONE, x11_auth) /* X11_NO_AUTH, X11_MIT, X11_XDM */ \ X(FILENAME, NONE, xauthfile) \ /* port forwarding */ \ X(BOOL, NONE, lport_acceptall) /* accept conns from hosts other than localhost */ \ X(BOOL, NONE, rport_acceptall) /* same for remote forwarded ports (SSH-2 only) */ \ /* \ * Subkeys for 'portfwd' can have the following forms: \ * \ * [LR]localport \ * [LR]localaddr:localport \ * \ * Dynamic forwardings are indicated by an 'L' key, and the \ * special value "D". For all other forwardings, the value \ * should be of the form 'host:port'. \ */ \ X(STR, STR, portfwd) \ /* SSH bug compatibility modes. All FORCE_ON/FORCE_OFF/AUTO */ \ X(INT, NONE, sshbug_ignore1) \ X(INT, NONE, sshbug_plainpw1) \ X(INT, NONE, sshbug_rsa1) \ X(INT, NONE, sshbug_hmac2) \ X(INT, NONE, sshbug_derivekey2) \ X(INT, NONE, sshbug_rsapad2) \ X(INT, NONE, sshbug_pksessid2) \ X(INT, NONE, sshbug_rekey2) \ X(INT, NONE, sshbug_maxpkt2) \ X(INT, NONE, sshbug_ignore2) \ X(INT, NONE, sshbug_oldgex2) \ X(INT, NONE, sshbug_winadj) \ X(INT, NONE, sshbug_chanreq) \ X(INT, NONE, sshbug_dropstart) \ /* \ * ssh_simple means that we promise never to open any channel \ * other than the main one, which means it can safely use a very \ * large window in SSH-2. \ */ \ X(BOOL, NONE, ssh_simple) \ X(BOOL, NONE, ssh_connection_sharing) \ X(BOOL, NONE, ssh_connection_sharing_upstream) \ X(BOOL, NONE, ssh_connection_sharing_downstream) \ /* * ssh_manual_hostkeys is conceptually a set rather than a * dictionary: the string subkeys are the important thing, and the * actual values to which those subkeys map are all "". */ \ X(STR, STR, ssh_manual_hostkeys) \ /* Options for pterm. Should split out into platform-dependent part. */ \ X(BOOL, NONE, stamp_utmp) \ X(BOOL, NONE, login_shell) \ X(BOOL, NONE, scrollbar_on_left) \ X(BOOL, NONE, shadowbold) \ X(FONT, NONE, boldfont) \ X(FONT, NONE, widefont) \ X(FONT, NONE, wideboldfont) \ X(INT, NONE, shadowboldoffset) /* in pixels */ \ X(BOOL, NONE, crhaslf) \ X(STR, NONE, winclass) \ /* end of list */ /* Now define the actual enum of option keywords using that macro. */ #define CONF_ENUM_DEF(valtype, keytype, keyword) CONF_ ## keyword, enum config_primary_key { CONFIG_OPTIONS(CONF_ENUM_DEF) N_CONFIG_OPTIONS }; #undef CONF_ENUM_DEF /* Functions handling configuration structures. */ Conf *conf_new(void); /* create an empty configuration */ void conf_free(Conf *conf); Conf *conf_copy(Conf *oldconf); void conf_copy_into(Conf *dest, Conf *src); /* Mandatory accessor functions: enforce by assertion that keys exist. */ bool conf_get_bool(Conf *conf, int key); int conf_get_int(Conf *conf, int key); int conf_get_int_int(Conf *conf, int key, int subkey); char *conf_get_str(Conf *conf, int key); /* result still owned by conf */ char *conf_get_str_str(Conf *conf, int key, const char *subkey); Filename *conf_get_filename(Conf *conf, int key); FontSpec *conf_get_fontspec(Conf *conf, int key); /* still owned by conf */ /* Optional accessor function: return NULL if key does not exist. */ char *conf_get_str_str_opt(Conf *conf, int key, const char *subkey); /* Accessor function to step through a string-subkeyed list. * Returns the next subkey after the provided one, or the first if NULL. * Returns NULL if there are none left. * Both the return value and *subkeyout are still owned by conf. */ char *conf_get_str_strs(Conf *conf, int key, char *subkeyin, char **subkeyout); /* Return the nth string subkey in a list. Owned by conf. NULL if beyond end */ char *conf_get_str_nthstrkey(Conf *conf, int key, int n); /* Functions to set entries in configuration. Always copy their inputs. */ void conf_set_bool(Conf *conf, int key, bool value); void conf_set_int(Conf *conf, int key, int value); void conf_set_int_int(Conf *conf, int key, int subkey, int value); void conf_set_str(Conf *conf, int key, const char *value); void conf_set_str_str(Conf *conf, int key, const char *subkey, const char *val); void conf_del_str_str(Conf *conf, int key, const char *subkey); void conf_set_filename(Conf *conf, int key, const Filename *val); void conf_set_fontspec(Conf *conf, int key, const FontSpec *val); /* Serialisation functions for Duplicate Session */ void conf_serialise(BinarySink *bs, Conf *conf); bool conf_deserialise(Conf *conf, BinarySource *src);/*returns true on success*/ /* * Functions to copy, free, serialise and deserialise FontSpecs. * Provided per-platform, to go with the platform's idea of a * FontSpec's contents. */ FontSpec *fontspec_copy(const FontSpec *f); void fontspec_free(FontSpec *f); void fontspec_serialise(BinarySink *bs, FontSpec *f); FontSpec *fontspec_deserialise(BinarySource *src); /* * Exports from noise.c. */ typedef enum NoiseSourceId { NOISE_SOURCE_TIME, NOISE_SOURCE_IOID, NOISE_SOURCE_IOLEN, NOISE_SOURCE_KEY, NOISE_SOURCE_MOUSEBUTTON, NOISE_SOURCE_MOUSEPOS, NOISE_SOURCE_MEMINFO, NOISE_SOURCE_STAT, NOISE_SOURCE_RUSAGE, NOISE_SOURCE_FGWINDOW, NOISE_SOURCE_CAPTURE, NOISE_SOURCE_CLIPBOARD, NOISE_SOURCE_QUEUE, NOISE_SOURCE_CURSORPOS, NOISE_SOURCE_THREADTIME, NOISE_SOURCE_PROCTIME, NOISE_SOURCE_PERFCOUNT, NOISE_MAX_SOURCES } NoiseSourceId; void noise_get_heavy(void (*func) (void *, int)); void noise_get_light(void (*func) (void *, int)); void noise_regular(void); void noise_ultralight(NoiseSourceId id, unsigned long data); void random_save_seed(void); void random_destroy_seed(void); /* * Exports from settings.c. * * load_settings() and do_defaults() return false if the provided * session name didn't actually exist. But they still fill in the * provided Conf with _something_. */ const struct BackendVtable *backend_vt_from_name(const char *name); const struct BackendVtable *backend_vt_from_proto(int proto); char *get_remote_username(Conf *conf); /* dynamically allocated */ char *save_settings(const char *section, Conf *conf); void save_open_settings(settings_w *sesskey, Conf *conf); bool load_settings(const char *section, Conf *conf); void load_open_settings(settings_r *sesskey, Conf *conf); void get_sesslist(struct sesslist *, bool allocate); bool do_defaults(const char *, Conf *); void registry_cleanup(void); void settings_set_default_protocol(int); void settings_set_default_port(int); /* * Functions used by settings.c to provide platform-specific * default settings. * * (The integer one is expected to return `def' if it has no clear * opinion of its own. This is because there's no integer value * which I can reliably set aside to indicate `nil'. The string * function is perfectly all right returning NULL, of course. The * Filename and FontSpec functions are _not allowed_ to fail to * return, since these defaults _must_ be per-platform.) * * The 'Filename *' returned by platform_default_filename, and the * 'FontSpec *' returned by platform_default_fontspec, have ownership * transferred to the caller, and must be freed. */ char *platform_default_s(const char *name); bool platform_default_b(const char *name, bool def); int platform_default_i(const char *name, int def); Filename *platform_default_filename(const char *name); FontSpec *platform_default_fontspec(const char *name); /* * Exports from terminal.c. */ Terminal *term_init(Conf *, struct unicode_data *, TermWin *); void term_free(Terminal *); void term_size(Terminal *, int, int, int); void term_paint(Terminal *, int, int, int, int, bool); void term_scroll(Terminal *, int, int); void term_scroll_to_selection(Terminal *, int); void term_pwron(Terminal *, bool); void term_clrsb(Terminal *); void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action, int, int, bool, bool, bool); void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int, unsigned int); void term_lost_clipboard_ownership(Terminal *, int clipboard); void term_update(Terminal *); void term_invalidate(Terminal *); void term_blink(Terminal *, bool set_cursor); void term_do_paste(Terminal *, const wchar_t *, int); void term_nopaste(Terminal *); void term_copyall(Terminal *, const int *, int); void term_pre_reconfig(Terminal *, Conf *); void term_reconfig(Terminal *, Conf *); void term_request_copy(Terminal *, const int *clipboards, int n_clipboards); void term_request_paste(Terminal *, int clipboard); void term_seen_key_event(Terminal *); size_t term_data(Terminal *, bool is_stderr, const void *data, size_t len); void term_provide_backend(Terminal *term, Backend *backend); void term_provide_logctx(Terminal *term, LogContext *logctx); void term_set_focus(Terminal *term, bool has_focus); char *term_get_ttymode(Terminal *term, const char *mode); int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input); void term_set_trust_status(Terminal *term, bool trusted); void term_keyinput(Terminal *, int codepage, const void *buf, int len); void term_keyinputw(Terminal *, const wchar_t * widebuf, int len); void term_get_cursor_position(Terminal *term, int *x, int *y); void term_setup_window_titles(Terminal *term, const char *title_hostname); void term_notify_minimised(Terminal *term, bool minimised); void term_notify_palette_changed(Terminal *term); void term_notify_window_pos(Terminal *term, int x, int y); void term_notify_window_size_pixels(Terminal *term, int x, int y); void term_palette_override(Terminal *term, unsigned osc4_index, rgb rgb); typedef enum SmallKeypadKey { SKK_HOME, SKK_END, SKK_INSERT, SKK_DELETE, SKK_PGUP, SKK_PGDN, } SmallKeypadKey; int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl); int format_function_key(char *buf, Terminal *term, int key_number, bool shift, bool ctrl); int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key); int format_numeric_keypad_key(char *buf, Terminal *term, char key, bool shift, bool ctrl); /* * Exports from logging.c. */ struct LogPolicyVtable { /* * Pass Event Log entries on from LogContext to the front end, * which might write them to standard error or save them for a GUI * list box or other things. */ void (*eventlog)(LogPolicy *lp, const char *event); /* * Ask what to do about the specified output log file already * existing. Can return four values: * * - 2 means overwrite the log file * - 1 means append to the log file * - 0 means cancel logging for this session * - -1 means please wait, and callback() will be called with one * of those options. */ int (*askappend)(LogPolicy *lp, Filename *filename, void (*callback)(void *ctx, int result), void *ctx); /* * Emergency logging when the log file itself can't be opened, * which typically means we want to shout about it more loudly * than a mere Event Log entry. * * One reasonable option is to send it to the same place that * stderr output from the main session goes (so, either a console * tool's actual stderr, or a terminal window). In many cases this * is unlikely to cause this error message to turn up * embarrassingly in a log file of real server output, because the * whole point is that we haven't managed to open any such log * file :-) */ void (*logging_error)(LogPolicy *lp, const char *event); /* * Ask whether extra verbose log messages are required. */ bool (*verbose)(LogPolicy *lp); }; struct LogPolicy { const LogPolicyVtable *vt; }; static inline void lp_eventlog(LogPolicy *lp, const char *event) { lp->vt->eventlog(lp, event); } static inline int lp_askappend( LogPolicy *lp, Filename *filename, void (*callback)(void *ctx, int result), void *ctx) { return lp->vt->askappend(lp, filename, callback, ctx); } static inline void lp_logging_error(LogPolicy *lp, const char *event) { lp->vt->logging_error(lp, event); } static inline bool lp_verbose(LogPolicy *lp) { return lp->vt->verbose(lp); } /* Defined in conscli.c, used in several console command-line tools */ extern LogPolicy console_cli_logpolicy[]; int console_askappend(LogPolicy *lp, Filename *filename, void (*callback)(void *ctx, int result), void *ctx); void console_logging_error(LogPolicy *lp, const char *string); void console_eventlog(LogPolicy *lp, const char *string); bool null_lp_verbose_yes(LogPolicy *lp); bool null_lp_verbose_no(LogPolicy *lp); bool cmdline_lp_verbose(LogPolicy *lp); LogContext *log_init(LogPolicy *lp, Conf *conf); void log_free(LogContext *logctx); void log_reconfig(LogContext *logctx, Conf *conf); void logfopen(LogContext *logctx); void logfclose(LogContext *logctx); void logtraffic(LogContext *logctx, unsigned char c, int logmode); void logflush(LogContext *logctx); void logevent(LogContext *logctx, const char *event); void logeventf(LogContext *logctx, const char *fmt, ...) PRINTF_LIKE(2, 3); void logeventvf(LogContext *logctx, const char *fmt, va_list ap); /* * Pass a dynamically allocated string to logevent and immediately * free it. Intended for use by wrapper macros which pass the return * value of dupprintf straight to this. */ void logevent_and_free(LogContext *logctx, char *event); enum { PKT_INCOMING, PKT_OUTGOING }; enum { PKTLOG_EMIT, PKTLOG_BLANK, PKTLOG_OMIT }; struct logblank_t { int offset; int len; int type; }; void log_packet(LogContext *logctx, int direction, int type, const char *texttype, const void *data, size_t len, int n_blanks, const struct logblank_t *blanks, const unsigned long *sequence, unsigned downstream_id, const char *additional_log_text); /* * Exports from testback.c */ extern const struct BackendVtable null_backend; extern const struct BackendVtable loop_backend; /* * Exports from raw.c. */ extern const struct BackendVtable raw_backend; /* * Exports from rlogin.c. */ extern const struct BackendVtable rlogin_backend; /* * Exports from telnet.c. */ extern const struct BackendVtable telnet_backend; /* * Exports from ssh/ssh.c. */ extern const struct BackendVtable ssh_backend; extern const struct BackendVtable sshconn_backend; /* * Exports from supdup.c. */ extern const struct BackendVtable supdup_backend; /* * Exports from ldisc.c. */ Ldisc *ldisc_create(Conf *, Terminal *, Backend *, Seat *); void ldisc_configure(Ldisc *, Conf *); void ldisc_free(Ldisc *); void ldisc_send(Ldisc *, const void *buf, int len, bool interactive); void ldisc_echoedit_update(Ldisc *); /* * Exports from sshrand.c. */ void random_add_noise(NoiseSourceId source, const void *noise, int length); void random_read(void *buf, size_t size); void random_get_savedata(void **data, int *len); extern int random_active; /* The random number subsystem is activated if at least one other entity * within the program expresses an interest in it. So each SSH session * calls random_ref on startup and random_unref on shutdown. */ void random_ref(void); void random_unref(void); /* random_clear is equivalent to calling random_unref as many times as * necessary to shut down the global PRNG instance completely. It's * not needed in normal applications, but the command-line PuTTYgen * test finds it useful to clean up after each invocation of the * logical main() no matter whether it needed random numbers or * not. */ void random_clear(void); /* random_setup_custom sets up the process-global random number * generator specially, with a hash function of your choice. */ void random_setup_custom(const ssh_hashalg *hash); /* random_setup_special() is a macro wrapper on that, which makes an * extra-big one based on the largest hash function we have. It's * defined this way to avoid what would otherwise be an unnecessary * module dependency from sshrand.c to a hash function implementation. */ #define random_setup_special() random_setup_custom(&ssh_shake256_114bytes) /* Manually drop a random seed into the random number generator, e.g. * just before generating a key. */ void random_reseed(ptrlen seed); /* Limit on how much entropy is worth putting into the generator (bits). */ size_t random_seed_bits(void); /* * Exports from pinger.c. */ typedef struct Pinger Pinger; Pinger *pinger_new(Conf *conf, Backend *backend); void pinger_reconfig(Pinger *, Conf *oldconf, Conf *newconf); void pinger_free(Pinger *); /* * Exports from misc.c. */ #include "misc.h" bool conf_launchable(Conf *conf); char const *conf_dest(Conf *conf); /* * Exports from sessprep.c. */ void prepare_session(Conf *conf); /* * Exports from sercfg.c. */ void ser_setup_config_box(struct controlbox *b, bool midsession, int parity_mask, int flow_mask); /* * Exports from version.c. */ extern const char ver[]; extern const char commitid[]; /* * Exports from unicode.c. */ #ifndef CP_UTF8 #define CP_UTF8 65001 #endif /* void init_ucs(void); -- this is now in platform-specific headers */ bool is_dbcs_leadbyte(int codepage, char byte); int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, wchar_t *wcstr, int wclen); int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, char *mbstr, int mblen, const char *defchr, struct unicode_data *ucsdata); wchar_t xlat_uskbd2cyrllic(int ch); int check_compose(int first, int second); int decode_codepage(char *cp_name); const char *cp_enumerate (int index); const char *cp_name(int codepage); void get_unitab(int codepage, wchar_t * unitab, int ftype); /* * Exports from wcwidth.c */ int mk_wcwidth(unsigned int ucs); int mk_wcswidth(const unsigned int *pwcs, size_t n); int mk_wcwidth_cjk(unsigned int ucs); int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n); /* * Exports from pageantc.c. * * agent_query returns NULL for here's-a-response, and non-NULL for * query-in- progress. In the latter case there will be a call to * `callback' at some future point, passing callback_ctx as the first * parameter and the actual reply data as the second and third. * * The response may be a NULL pointer (in either of the synchronous * or asynchronous cases), which indicates failure to receive a * response. * * When the return from agent_query is not NULL, it identifies the * in-progress query in case it needs to be cancelled. If * agent_cancel_query is called, then the pending query is destroyed * and the callback will not be called. (E.g. if you're going to throw * away the thing you were using as callback_ctx.) * * Passing a null pointer as callback forces agent_query to behave * synchronously, i.e. it will block if necessary, and guarantee to * return NULL. The wrapper function agent_query_synchronous() makes * this easier. */ typedef struct agent_pending_query agent_pending_query; agent_pending_query *agent_query( strbuf *in, void **out, int *outlen, void (*callback)(void *, void *, int), void *callback_ctx); void agent_cancel_query(agent_pending_query *); void agent_query_synchronous(strbuf *in, void **out, int *outlen); bool agent_exists(void); /* For stream-oriented agent connections, if available. */ Socket *agent_connect(Plug *plug); /* * Exports from wildcard.c */ const char *wc_error(int value); int wc_match_pl(const char *wildcard, ptrlen target); int wc_match(const char *wildcard, const char *target); bool wc_unescape(char *output, const char *wildcard); /* * Exports from frontend (windlg.c etc) */ void pgp_fingerprints(void); /* * have_ssh_host_key() just returns true if a key of that type is * already cached and false otherwise. */ bool have_ssh_host_key(const char *host, int port, const char *keytype); /* * Exports from console frontends (wincons.c, uxcons.c) * that aren't equivalents to things in windlg.c et al. */ extern bool console_batch_mode, console_antispoof_prompt; int console_get_userpass_input(prompts_t *p); bool is_interactive(void); void console_print_error_msg(const char *prefix, const char *msg); void console_print_error_msg_fmt_v( const char *prefix, const char *fmt, va_list ap); void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...) PRINTF_LIKE(2, 3); /* * Exports from printing.c. */ typedef struct printer_enum_tag printer_enum; typedef struct printer_job_tag printer_job; printer_enum *printer_start_enum(int *nprinters); char *printer_get_name(printer_enum *, int); void printer_finish_enum(printer_enum *); printer_job *printer_start_job(char *printer); void printer_job_data(printer_job *, const void *, size_t); void printer_finish_job(printer_job *); /* * Exports from cmdline.c (and also cmdline_error(), which is * defined differently in various places and required _by_ * cmdline.c). * * Note that cmdline_process_param takes a const option string, but a * writable argument string. That's not a mistake - that's so it can * zero out password arguments in the hope of not having them show up * avoidably in Unix 'ps'. */ int cmdline_process_param(const char *, char *, int, Conf *); void cmdline_run_saved(Conf *); void cmdline_cleanup(void); int cmdline_get_passwd_input(prompts_t *p); bool cmdline_host_ok(Conf *); bool cmdline_verbose(void); bool cmdline_loaded_session(void); /* * Here we have a flags word provided by each tool, which describes * the capabilities of that tool that cmdline.c needs to know about. * It will refuse certain command-line options if a particular tool * inherently can't do anything sensible. For example, the file * transfer tools (psftp, pscp) can't do a great deal with protocol * selections (ever tried running scp over telnet?) or with port * forwarding (even if it wasn't a hideously bad idea, they don't have * the select/poll infrastructure to make them work). */ extern const unsigned cmdline_tooltype; /* Bit flags for the above */ #define TOOLTYPE_LIST(X) \ X(TOOLTYPE_FILETRANSFER) \ X(TOOLTYPE_NONNETWORK) \ X(TOOLTYPE_HOST_ARG) \ X(TOOLTYPE_HOST_ARG_CAN_BE_SESSION) \ X(TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX) \ X(TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD) \ X(TOOLTYPE_PORT_ARG) \ X(TOOLTYPE_NO_VERBOSE_OPTION) \ /* end of list */ #define BITFLAG_INDEX(val) val ## _bitflag_index, enum { TOOLTYPE_LIST(BITFLAG_INDEX) }; #define BITFLAG_DEF(val) val = 1U << (val ## _bitflag_index), enum { TOOLTYPE_LIST(BITFLAG_DEF) }; void cmdline_error(const char *, ...) PRINTF_LIKE(1, 2); /* * Exports from config.c. */ struct controlbox; union control; void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg, void *data, int event); #define CHECKBOX_INVERT (1<<30) void conf_checkbox_handler(union control *ctrl, dlgparam *dlg, void *data, int event); void conf_editbox_handler(union control *ctrl, dlgparam *dlg, void *data, int event); void conf_filesel_handler(union control *ctrl, dlgparam *dlg, void *data, int event); void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, void *data, int event); void setup_config_box(struct controlbox *b, bool midsession, int protocol, int protcfginfo); /* * Exports from minibidi.c. */ #define BIDI_CHAR_INDEX_NONE ((unsigned short)-1) typedef struct bidi_char { unsigned int origwc, wc; unsigned short index, nchars; } bidi_char; int do_bidi(bidi_char *line, int count); int do_shape(bidi_char *line, bidi_char *to, int count); bool is_rtl(int c); /* * X11 auth mechanisms we know about. */ enum { X11_NO_AUTH, X11_MIT, /* MIT-MAGIC-COOKIE-1 */ X11_XDM, /* XDM-AUTHORIZATION-1 */ X11_NAUTHS }; extern const char *const x11_authnames[X11_NAUTHS]; /* * An enum for the copy-paste UI action configuration. */ enum { CLIPUI_NONE, /* UI action has no copy/paste effect */ CLIPUI_IMPLICIT, /* use the default clipboard implicit in mouse actions */ CLIPUI_EXPLICIT, /* use the default clipboard for explicit Copy/Paste */ CLIPUI_CUSTOM, /* use a named clipboard (on systems that support it) */ }; /* * Miscellaneous exports from the platform-specific code. * * filename_serialise and filename_deserialise have the same semantics * as fontspec_serialise and fontspec_deserialise above. */ Filename *filename_from_str(const char *string); const char *filename_to_str(const Filename *fn); bool filename_equal(const Filename *f1, const Filename *f2); bool filename_is_null(const Filename *fn); Filename *filename_copy(const Filename *fn); void filename_free(Filename *fn); void filename_serialise(BinarySink *bs, const Filename *f); Filename *filename_deserialise(BinarySource *src); char *get_username(void); /* return value needs freeing */ char *get_random_data(int bytes, const char *device); /* used in cmdgen.c */ char filename_char_sanitise(char c); /* rewrite special pathname chars */ bool open_for_write_would_lose_data(const Filename *fn); /* * Exports and imports from timing.c. * * schedule_timer() asks the front end to schedule a callback to a * timer function in a given number of ticks. The returned value is * the time (in ticks since an arbitrary offset) at which the * callback can be expected. This value will also be passed as the * `now' parameter to the callback function. Hence, you can (for * example) schedule an event at a particular time by calling * schedule_timer() and storing the return value in your context * structure as the time when that event is due. The first time a * callback function gives you that value or more as `now', you do * the thing. * * expire_timer_context() drops all current timers associated with * a given value of ctx (for when you're about to free ctx). * * run_timers() is called from the front end when it has reason to * think some timers have reached their moment, or when it simply * needs to know how long to wait next. We pass it the time we * think it is. It returns true and places the time when the next * timer needs to go off in `next', or alternatively it returns * false if there are no timers at all pending. * * timer_change_notify() must be supplied by the front end; it * notifies the front end that a new timer has been added to the * list which is sooner than any existing ones. It provides the * time when that timer needs to go off. * * *** FRONT END IMPLEMENTORS NOTE: * * There's an important subtlety in the front-end implementation of * the timer interface. When a front end is given a `next' value, * either returned from run_timers() or via timer_change_notify(), * it should ensure that it really passes _that value_ as the `now' * parameter to its next run_timers call. It should _not_ simply * call GETTICKCOUNT() to get the `now' parameter when invoking * run_timers(). * * The reason for this is that an OS's system clock might not agree * exactly with the timing mechanisms it supplies to wait for a * given interval. I'll illustrate this by the simple example of * Unix Plink, which uses timeouts to poll() in a way which for * these purposes can simply be considered to be a wait() function. * Suppose, for the sake of argument, that this wait() function * tends to return early by 1%. Then a possible sequence of actions * is: * * - run_timers() tells the front end that the next timer firing * is 10000ms from now. * - Front end calls wait(10000ms), but according to * GETTICKCOUNT() it has only waited for 9900ms. * - Front end calls run_timers() again, passing time T-100ms as * `now'. * - run_timers() does nothing, and says the next timer firing is * still 100ms from now. * - Front end calls wait(100ms), which only waits for 99ms. * - Front end calls run_timers() yet again, passing time T-1ms. * - run_timers() says there's still 1ms to wait. * - Front end calls wait(1ms). * * If you're _lucky_ at this point, wait(1ms) will actually wait * for 1ms and you'll only have woken the program up three times. * If you're unlucky, wait(1ms) might do nothing at all due to * being below some minimum threshold, and you might find your * program spends the whole of the last millisecond tight-looping * between wait() and run_timers(). * * Instead, what you should do is to _save_ the precise `next' * value provided by run_timers() or via timer_change_notify(), and * use that precise value as the input to the next run_timers() * call. So: * * - run_timers() tells the front end that the next timer firing * is at time T, 10000ms from now. * - Front end calls wait(10000ms). * - Front end then immediately calls run_timers() and passes it * time T, without stopping to check GETTICKCOUNT() at all. * * This guarantees that the program wakes up only as many times as * there are actual timer actions to be taken, and that the timing * mechanism will never send it into a tight loop. * * (It does also mean that the timer action in the above example * will occur 100ms early, but this is not generally critical. And * the hypothetical 1% error in wait() will be partially corrected * for anyway when, _after_ run_timers() returns, you call * GETTICKCOUNT() and compare the result with the returned `next' * value to find out how long you have to make your next wait().) */ typedef void (*timer_fn_t)(void *ctx, unsigned long now); unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx); void expire_timer_context(void *ctx); bool run_timers(unsigned long now, unsigned long *next); void timer_change_notify(unsigned long next); unsigned long timing_last_clock(void); /* * Exports from callback.c. * * This provides a method of queuing function calls to be run at the * earliest convenience from the top-level event loop. Use it if * you're deep in a nested chain of calls and want to trigger an * action which will probably lead to your function being re-entered * recursively if you just call the initiating function the normal * way. * * Most front ends run the queued callbacks by simply calling * run_toplevel_callbacks() after handling each event in their * top-level event loop. However, if a front end doesn't have control * over its own event loop (e.g. because it's using GTK) then it can * instead request notifications when a callback is available, so that * it knows to ask its delegate event loop to do the same thing. Also, * if a front end needs to know whether a callback is pending without * actually running it (e.g. so as to put a zero timeout on a poll() * call) then it can call toplevel_callback_pending(), which will * return true if at least one callback is in the queue. * * run_toplevel_callbacks() returns true if it ran any actual code. * This can be used as a means of speculatively terminating a poll * loop, as in PSFTP, for example - if a callback has run then perhaps * it might have done whatever the loop's caller was waiting for. */ typedef void (*toplevel_callback_fn_t)(void *ctx); void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx); bool run_toplevel_callbacks(void); bool toplevel_callback_pending(void); void delete_callbacks_for_context(void *ctx); /* * Another facility in callback.c deals with 'idempotent' callbacks, * defined as those which never need to be scheduled again if they are * already scheduled and have not yet run. (An example would be one * which, when called, empties a queue of data completely: when data * is added to the queue, you must ensure a run of the queue-consuming * function has been scheduled, but if one is already pending, you * don't need to schedule a second one.) */ struct IdempotentCallback { toplevel_callback_fn_t fn; void *ctx; bool queued; }; void queue_idempotent_callback(struct IdempotentCallback *ic); typedef void (*toplevel_callback_notify_fn_t)(void *ctx); void request_callback_notifications(toplevel_callback_notify_fn_t notify, void *ctx); /* * Define no-op macros for the jump list functions, on platforms that * don't support them. (This is a bit of a hack, and it'd be nicer to * localise even the calls to those functions into the Windows front * end, but it'll do for the moment.) */ #ifndef JUMPLIST_SUPPORTED #define add_session_to_jumplist(x) ((void)0) #define remove_session_from_jumplist(x) ((void)0) #endif /* SURROGATE PAIR */ #ifndef HIGH_SURROGATE_START /* in some toolchains defines these */ #define HIGH_SURROGATE_START 0xd800 #define HIGH_SURROGATE_END 0xdbff #define LOW_SURROGATE_START 0xdc00 #define LOW_SURROGATE_END 0xdfff #endif /* These macros exist in the Windows API, so the environment may * provide them. If not, define them in terms of the above. */ #ifndef IS_HIGH_SURROGATE #define IS_HIGH_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && \ ((wch) <= HIGH_SURROGATE_END)) #define IS_LOW_SURROGATE(wch) (((wch) >= LOW_SURROGATE_START) && \ ((wch) <= LOW_SURROGATE_END)) #define IS_SURROGATE_PAIR(hs, ls) (IS_HIGH_SURROGATE(hs) && \ IS_LOW_SURROGATE(ls)) #endif #define IS_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && \ ((wch) <= LOW_SURROGATE_END)) #define HIGH_SURROGATE_OF(codept) \ (HIGH_SURROGATE_START + (((codept) - 0x10000) >> 10)) #define LOW_SURROGATE_OF(codept) \ (LOW_SURROGATE_START + (((codept) - 0x10000) & 0x3FF)) #define FROM_SURROGATES(wch1, wch2) \ (0x10000 + (((wch1) & 0x3FF) << 10) + ((wch2) & 0x3FF)) #endif work/puttymem.h0000664000000000000000000001251714714222463011000 0ustar /* * PuTTY memory-handling header. */ #ifndef PUTTY_PUTTYMEM_H #define PUTTY_PUTTYMEM_H #include /* for size_t */ #include /* for memcpy() */ #include "defs.h" #define smalloc(z) safemalloc(z,1,0) #define snmalloc safemalloc #define srealloc(y,z) saferealloc(y,z,1) #define snrealloc saferealloc #define sfree safefree void *safemalloc(size_t factor1, size_t factor2, size_t addend); void *saferealloc(void *, size_t, size_t); void safefree(void *); /* * Direct use of smalloc within the code should be avoided where * possible, in favour of these type-casting macros which ensure you * don't mistakenly allocate enough space for one sort of structure * and assign it to a different sort of pointer. sresize also uses * TYPECHECK to verify that the _input_ pointer is a pointer to the * correct type. */ #define snew(type) ((type *)snmalloc(1, sizeof(type), 0)) #define snewn(n, type) ((type *)snmalloc((n), sizeof(type), 0)) #define sresize(ptr, n, type) TYPECHECK((type *)0 == (ptr), \ ((type *)snrealloc((ptr), (n), sizeof(type)))) /* * For cases where you want to allocate a struct plus a subsidiary * data buffer in one step, this macro lets you add a constant to the * amount malloced. * * Since the return value is already cast to the struct type, a * pointer to that many bytes of extra data can be conveniently * obtained by simply adding 1 to the returned pointer! * snew_plus_get_aux is a handy macro that does that and casts the * result to void *, so you can assign it straight to wherever you * wanted it. */ #define snew_plus(type, extra) ((type *)snmalloc(1, sizeof(type), (extra))) #define snew_plus_get_aux(ptr) ((void *)((ptr) + 1)) /* * Helper macros to deal with the common use case of growing an array. * * The common setup is that 'array' is a pointer to the first element * of a dynamic array of some type, and 'size' represents the current * allocated size of that array (in elements). Both of those macro * parameters are implicitly written back to. * * Then sgrowarray(array, size, n) means: make sure the nth element of * the array exists (i.e. the size is at least n+1). You call that * before writing to the nth element, if you're looping round * appending to the array. * * If you need to grow the array by more than one element, you can * instead call sgrowarrayn(array, size, n, m), which will ensure the * size of the array is at least n+m. (So sgrowarray is just the * special case of that in which m == 1.) * * It's common to call sgrowarrayn with one of n,m equal to the * previous logical length of the array, and the other equal to the * new number of logical entries you want to add, so that n <= size on * entry. But that's not actually a mandatory precondition: the two * length parameters are just arbitrary integers that get added * together with an initial check for overflow, and the semantics are * simply 'make sure the array is big enough to take their sum, no * matter how big it was to start with'.) * * Another occasionally useful idiom is to call sgrowarray with n == * size, i.e. sgrowarray(array, size, size). That just means: make * array bigger by _some_ amount, I don't particularly mind how much. * You might use that style if you were repeatedly calling an API * function outside your control, which would either fill your buffer * and return success, or else return a 'too big' error without * telling you how much bigger it needed to be. * * The _nm variants of the macro set the 'private' flag in the * underlying function, which forces array resizes to be done by a * manual allocate/copy/free instead of realloc, with careful clearing * of the previous memory block before we free it. This costs * performance, but if the block contains important secrets such as * private keys or passwords, it avoids the risk that a realloc that * moves the memory block might leave a copy of the data visible in * the freed memory at the previous location. */ void *safegrowarray(void *array, size_t *size, size_t eltsize, size_t oldlen, size_t extralen, bool private); /* The master macro wrapper, of which all others are special cases */ #define sgrowarray_general(array, size, n, m, priv) \ ((array) = safegrowarray(array, &(size), sizeof(*array), n, m, priv)) /* The special-case macros that are easier to use in most situations */ #define sgrowarrayn( a, s, n, m) sgrowarray_general(a, s, n, m, false) #define sgrowarray( a, s, n ) sgrowarray_general(a, s, n, 1, false) #define sgrowarrayn_nm(a, s, n, m) sgrowarray_general(a, s, n, m, true ) #define sgrowarray_nm( a, s, n ) sgrowarray_general(a, s, n, 1, true ) /* * This function is called by the innermost safemalloc/saferealloc * functions when allocation fails. Usually it's provided by misc.c * which ties it into an application's existing modalfatalbox() * system, but standalone test applications can reimplement it some * other way if they prefer. */ NORETURN void out_of_memory(void); #ifdef MINEFIELD /* * Definitions for Minefield, PuTTY's own Windows-specific malloc * debugger in the style of Electric Fence. Implemented in winmisc.c, * and referred to by the main malloc wrappers in memory.c. */ void *minefield_c_malloc(size_t size); void minefield_c_free(void *p); void *minefield_c_realloc(void *p, size_t size); #endif #endif work/ssh/0000775000000000000000000000000014714222463007532 5ustar work/ssh/channel.h0000664000000000000000000003407714714222463011326 0ustar /* * Abstraction of the various ways to handle the local end of an SSH * connection-layer channel. */ #ifndef PUTTY_SSHCHAN_H #define PUTTY_SSHCHAN_H typedef struct ChannelVtable ChannelVtable; struct ChannelVtable { void (*free)(Channel *); /* Called for channel types that were created at the same time as * we sent an outgoing CHANNEL_OPEN, when the confirmation comes * back from the server indicating that the channel has been * opened, or the failure message indicating that it hasn't, * respectively. In the latter case, this must _not_ free the * Channel structure - the client will call the free method * separately. But it might do logging or other local cleanup. */ void (*open_confirmation)(Channel *); void (*open_failed)(Channel *, const char *error_text); size_t (*send)(Channel *, bool is_stderr, const void *buf, size_t len); void (*send_eof)(Channel *); void (*set_input_wanted)(Channel *, bool wanted); char *(*log_close_msg)(Channel *); bool (*want_close)(Channel *, bool sent_local_eof, bool rcvd_remote_eof); /* A method for every channel request we know of. All of these * return true for success or false for failure. */ bool (*rcvd_exit_status)(Channel *, int status); bool (*rcvd_exit_signal)( Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg); bool (*rcvd_exit_signal_numeric)( Channel *chan, int signum, bool core_dumped, ptrlen msg); bool (*run_shell)(Channel *chan); bool (*run_command)(Channel *chan, ptrlen command); bool (*run_subsystem)(Channel *chan, ptrlen subsys); bool (*enable_x11_forwarding)( Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, unsigned screen_number); bool (*enable_agent_forwarding)(Channel *chan); bool (*allocate_pty)( Channel *chan, ptrlen termtype, unsigned width, unsigned height, unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); bool (*set_env)(Channel *chan, ptrlen var, ptrlen value); bool (*send_break)(Channel *chan, unsigned length); bool (*send_signal)(Channel *chan, ptrlen signame); bool (*change_window_size)( Channel *chan, unsigned width, unsigned height, unsigned pixwidth, unsigned pixheight); /* A method for signalling success/failure responses to channel * requests initiated from the SshChannel vtable with want_reply * true. */ void (*request_response)(Channel *, bool success); }; struct Channel { const struct ChannelVtable *vt; unsigned initial_fixed_window_size; }; static inline void chan_free(Channel *ch) { ch->vt->free(ch); } static inline void chan_open_confirmation(Channel *ch) { ch->vt->open_confirmation(ch); } static inline void chan_open_failed(Channel *ch, const char *err) { ch->vt->open_failed(ch, err); } static inline size_t chan_send( Channel *ch, bool err, const void *buf, size_t len) { return ch->vt->send(ch, err, buf, len); } static inline void chan_send_eof(Channel *ch) { ch->vt->send_eof(ch); } static inline void chan_set_input_wanted(Channel *ch, bool wanted) { ch->vt->set_input_wanted(ch, wanted); } static inline char *chan_log_close_msg(Channel *ch) { return ch->vt->log_close_msg(ch); } static inline bool chan_want_close(Channel *ch, bool leof, bool reof) { return ch->vt->want_close(ch, leof, reof); } static inline bool chan_rcvd_exit_status(Channel *ch, int status) { return ch->vt->rcvd_exit_status(ch, status); } static inline bool chan_rcvd_exit_signal( Channel *ch, ptrlen sig, bool core, ptrlen msg) { return ch->vt->rcvd_exit_signal(ch, sig, core, msg); } static inline bool chan_rcvd_exit_signal_numeric( Channel *ch, int sig, bool core, ptrlen msg) { return ch->vt->rcvd_exit_signal_numeric(ch, sig, core, msg); } static inline bool chan_run_shell(Channel *ch) { return ch->vt->run_shell(ch); } static inline bool chan_run_command(Channel *ch, ptrlen cmd) { return ch->vt->run_command(ch, cmd); } static inline bool chan_run_subsystem(Channel *ch, ptrlen subsys) { return ch->vt->run_subsystem(ch, subsys); } static inline bool chan_enable_x11_forwarding( Channel *ch, bool oneshot, ptrlen ap, ptrlen ad, unsigned scr) { return ch->vt->enable_x11_forwarding(ch, oneshot, ap, ad, scr); } static inline bool chan_enable_agent_forwarding(Channel *ch) { return ch->vt->enable_agent_forwarding(ch); } static inline bool chan_allocate_pty( Channel *ch, ptrlen termtype, unsigned w, unsigned h, unsigned pw, unsigned ph, struct ssh_ttymodes modes) { return ch->vt->allocate_pty(ch, termtype, w, h, pw, ph, modes); } static inline bool chan_set_env(Channel *ch, ptrlen var, ptrlen value) { return ch->vt->set_env(ch, var, value); } static inline bool chan_send_break(Channel *ch, unsigned length) { return ch->vt->send_break(ch, length); } static inline bool chan_send_signal(Channel *ch, ptrlen signame) { return ch->vt->send_signal(ch, signame); } static inline bool chan_change_window_size( Channel *ch, unsigned w, unsigned h, unsigned pw, unsigned ph) { return ch->vt->change_window_size(ch, w, h, pw, ph); } static inline void chan_request_response(Channel *ch, bool success) { ch->vt->request_response(ch, success); } /* * Reusable methods you can put in vtables to give default handling of * some of those functions. */ /* open_confirmation / open_failed for any channel it doesn't apply to */ void chan_remotely_opened_confirmation(Channel *chan); void chan_remotely_opened_failure(Channel *chan, const char *errtext); /* want_close for any channel that wants the default behaviour of not * closing until both directions have had an EOF */ bool chan_default_want_close(Channel *, bool, bool); /* default implementations that refuse all the channel requests */ bool chan_no_exit_status(Channel *, int); bool chan_no_exit_signal(Channel *, ptrlen, bool, ptrlen); bool chan_no_exit_signal_numeric(Channel *, int, bool, ptrlen); bool chan_no_run_shell(Channel *chan); bool chan_no_run_command(Channel *chan, ptrlen command); bool chan_no_run_subsystem(Channel *chan, ptrlen subsys); bool chan_no_enable_x11_forwarding( Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, unsigned screen_number); bool chan_no_enable_agent_forwarding(Channel *chan); bool chan_no_allocate_pty( Channel *chan, ptrlen termtype, unsigned width, unsigned height, unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value); bool chan_no_send_break(Channel *chan, unsigned length); bool chan_no_send_signal(Channel *chan, ptrlen signame); bool chan_no_change_window_size( Channel *chan, unsigned width, unsigned height, unsigned pixwidth, unsigned pixheight); /* default implementation that never expects to receive a response */ void chan_no_request_response(Channel *, bool); /* * Constructor for a trivial do-nothing implementation of * ChannelVtable. Used for 'zombie' channels, i.e. channels whose * proper local source of data has been shut down or otherwise stopped * existing, but the SSH side is still there and needs some kind of a * Channel implementation to talk to. In particular, the want_close * method for this channel always returns 'yes, please close this * channel asap', regardless of whether local and/or remote EOF have * been sent - indeed, even if _neither_ has. */ Channel *zombiechan_new(void); /* ---------------------------------------------------------------------- * This structure is owned by an SSH connection layer, and identifies * the connection layer's end of the channel, for the Channel * implementation to talk back to. */ typedef struct SshChannelVtable SshChannelVtable; struct SshChannelVtable { size_t (*write)(SshChannel *c, bool is_stderr, const void *, size_t); void (*write_eof)(SshChannel *c); void (*initiate_close)(SshChannel *c, const char *err); void (*unthrottle)(SshChannel *c, size_t bufsize); Conf *(*get_conf)(SshChannel *c); void (*window_override_removed)(SshChannel *c); void (*x11_sharing_handover)(SshChannel *c, ssh_sharing_connstate *share_cs, share_channel *share_chan, const char *peer_addr, int peer_port, int endian, int protomajor, int protominor, const void *initial_data, int initial_len); /* * All the outgoing channel requests we support. Each one has a * want_reply flag, which will cause a callback to * chan_request_response when the result is available. * * The ones that return 'bool' use it to indicate that the SSH * protocol in use doesn't support this request at all. * * (It's also intentional that not all of them have a want_reply * flag: the ones that don't are because SSH-1 has no method for * signalling success or failure of that request, or because we * wouldn't do anything usefully different with the reply in any * case.) */ void (*send_exit_status)(SshChannel *c, int status); void (*send_exit_signal)( SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg); void (*send_exit_signal_numeric)( SshChannel *c, int signum, bool core_dumped, ptrlen msg); void (*request_x11_forwarding)( SshChannel *c, bool want_reply, const char *authproto, const char *authdata, int screen_number, bool oneshot); void (*request_agent_forwarding)( SshChannel *c, bool want_reply); void (*request_pty)( SshChannel *c, bool want_reply, Conf *conf, int w, int h); bool (*send_env_var)( SshChannel *c, bool want_reply, const char *var, const char *value); void (*start_shell)( SshChannel *c, bool want_reply); void (*start_command)( SshChannel *c, bool want_reply, const char *command); bool (*start_subsystem)( SshChannel *c, bool want_reply, const char *subsystem); bool (*send_serial_break)( SshChannel *c, bool want_reply, int length); /* length=0 for default */ bool (*send_signal)( SshChannel *c, bool want_reply, const char *signame); void (*send_terminal_size_change)( SshChannel *c, int w, int h); void (*hint_channel_is_simple)(SshChannel *c); }; struct SshChannel { const struct SshChannelVtable *vt; ConnectionLayer *cl; }; static inline size_t sshfwd_write_ext( SshChannel *c, bool is_stderr, const void *data, size_t len) { return c->vt->write(c, is_stderr, data, len); } static inline size_t sshfwd_write(SshChannel *c, const void *data, size_t len) { return sshfwd_write_ext(c, false, data, len); } static inline void sshfwd_write_eof(SshChannel *c) { c->vt->write_eof(c); } static inline void sshfwd_initiate_close(SshChannel *c, const char *err) { c->vt->initiate_close(c, err); } static inline void sshfwd_unthrottle(SshChannel *c, size_t bufsize) { c->vt->unthrottle(c, bufsize); } static inline Conf *sshfwd_get_conf(SshChannel *c) { return c->vt->get_conf(c); } static inline void sshfwd_window_override_removed(SshChannel *c) { c->vt->window_override_removed(c); } static inline void sshfwd_x11_sharing_handover( SshChannel *c, ssh_sharing_connstate *cs, share_channel *sch, const char *addr, int port, int endian, int maj, int min, const void *idata, int ilen) { c->vt->x11_sharing_handover(c, cs, sch, addr, port, endian, maj, min, idata, ilen); } static inline void sshfwd_send_exit_status(SshChannel *c, int status) { c->vt->send_exit_status(c, status); } static inline void sshfwd_send_exit_signal( SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg) { c->vt->send_exit_signal(c, signame, core_dumped, msg); } static inline void sshfwd_send_exit_signal_numeric( SshChannel *c, int signum, bool core_dumped, ptrlen msg) { c->vt->send_exit_signal_numeric(c, signum, core_dumped, msg); } static inline void sshfwd_request_x11_forwarding( SshChannel *c, bool want_reply, const char *proto, const char *data, int scr, bool once) { c->vt->request_x11_forwarding(c, want_reply, proto, data, scr, once); } static inline void sshfwd_request_agent_forwarding( SshChannel *c, bool want_reply) { c->vt->request_agent_forwarding(c, want_reply); } static inline void sshfwd_request_pty( SshChannel *c, bool want_reply, Conf *conf, int w, int h) { c->vt->request_pty(c, want_reply, conf, w, h); } static inline bool sshfwd_send_env_var( SshChannel *c, bool want_reply, const char *var, const char *value) { return c->vt->send_env_var(c, want_reply, var, value); } static inline void sshfwd_start_shell( SshChannel *c, bool want_reply) { c->vt->start_shell(c, want_reply); } static inline void sshfwd_start_command( SshChannel *c, bool want_reply, const char *command) { c->vt->start_command(c, want_reply, command); } static inline bool sshfwd_start_subsystem( SshChannel *c, bool want_reply, const char *subsystem) { return c->vt->start_subsystem(c, want_reply, subsystem); } static inline bool sshfwd_send_serial_break( SshChannel *c, bool want_reply, int length) { return c->vt->send_serial_break(c, want_reply, length); } static inline bool sshfwd_send_signal( SshChannel *c, bool want_reply, const char *signame) { return c->vt->send_signal(c, want_reply, signame); } static inline void sshfwd_send_terminal_size_change( SshChannel *c, int w, int h) { c->vt->send_terminal_size_change(c, w, h); } static inline void sshfwd_hint_channel_is_simple(SshChannel *c) { c->vt->hint_channel_is_simple(c); } /* ---------------------------------------------------------------------- * The 'main' or primary channel of the SSH connection is special, * because it's the one that's connected directly to parts of the * frontend such as the terminal and the specials menu. So it exposes * a richer API. */ mainchan *mainchan_new( PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, int term_width, int term_height, bool is_simple, SshChannel **sc_out); void mainchan_get_specials( mainchan *mc, add_special_fn_t add_special, void *ctx); void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg); void mainchan_terminal_size(mainchan *mc, int width, int height); #endif /* PUTTY_SSHCHAN_H */ work/ssh/server.h0000664000000000000000000001332714714222463011217 0ustar typedef struct AuthPolicy AuthPolicy; struct SshServerConfig { const char *application_name; const char *session_starting_dir; RSAKey *rsa_kex_key; /* * In all of these ptrlens, setting the 'ptr' member to NULL means * that we're not overriding the default configuration. */ ptrlen banner; /* default here is 'no banner' */ ptrlen kex_override[NKEXLIST]; bool exit_signal_numeric; /* mimic an old server bug */ unsigned long ssh1_cipher_mask; bool ssh1_allow_compression; bool bare_connection; bool stunt_pretend_to_accept_any_pubkey; bool stunt_open_unconditional_agent_socket; bool stunt_allow_none_auth; bool stunt_allow_trivial_ki_auth; bool stunt_return_success_to_pubkey_offer; }; Plug *ssh_server_plug( Conf *conf, const SshServerConfig *ssc, ssh_key *const *hostkeys, int nhostkeys, RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy, const SftpServerVtable *sftpserver_vt); void ssh_server_start(Plug *plug, Socket *socket); void server_instance_terminated(LogPolicy *logpolicy); void platform_logevent(const char *msg); #define AUTHMETHODS(X) \ X(NONE) \ X(PASSWORD) \ X(PUBLICKEY) \ X(KBDINT) \ X(TIS) \ X(CRYPTOCARD) \ /* end of list */ #define AUTHMETHOD_BIT_INDEX(name) AUTHMETHOD_BIT_INDEX_##name, enum { AUTHMETHODS(AUTHMETHOD_BIT_INDEX) AUTHMETHOD_BIT_INDEX_dummy }; #define AUTHMETHOD_BIT_VALUE(name) \ AUTHMETHOD_##name = 1 << AUTHMETHOD_BIT_INDEX_##name, enum { AUTHMETHODS(AUTHMETHOD_BIT_VALUE) AUTHMETHOD_BIT_VALUE_dummy }; typedef struct AuthKbdInt AuthKbdInt; typedef struct AuthKbdIntPrompt AuthKbdIntPrompt; struct AuthKbdInt { char *title, *instruction; /* both need freeing */ int nprompts; AuthKbdIntPrompt *prompts; /* the array itself needs freeing */ }; struct AuthKbdIntPrompt { char *prompt; /* needs freeing */ bool echo; }; unsigned auth_methods(AuthPolicy *); bool auth_none(AuthPolicy *, ptrlen username); int auth_password(AuthPolicy *, ptrlen username, ptrlen password, ptrlen *opt_new_password); /* auth_password returns 1 for 'accepted', 0 for 'rejected', and 2 for * 'ok but now you need to change your password' */ bool auth_publickey(AuthPolicy *, ptrlen username, ptrlen public_blob); /* auth_publickey_ssh1 must return the whole public key given the modulus, * because the SSH-1 client never transmits the exponent over the wire. * The key remains owned by the AuthPolicy. */ AuthKbdInt *auth_kbdint_prompts(AuthPolicy *, ptrlen username); /* auth_kbdint_prompts returns NULL to trigger auth failure */ int auth_kbdint_responses(AuthPolicy *, const ptrlen *responses); /* auth_kbdint_responses returns >0 for success, <0 for failure, and 0 * to indicate that we haven't decided yet and further prompts are * coming */ /* The very similar SSH-1 TIS and CryptoCard methods are combined into * a single API for AuthPolicy, which takes a method argument */ char *auth_ssh1int_challenge(AuthPolicy *, unsigned method, ptrlen username); bool auth_ssh1int_response(AuthPolicy *, ptrlen response); RSAKey *auth_publickey_ssh1( AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus); /* auth_successful returns false if further authentication is needed */ bool auth_successful(AuthPolicy *, ptrlen username, unsigned method); PacketProtocolLayer *ssh2_userauth_server_new( PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy, const SshServerConfig *ssc); void ssh2_userauth_server_set_transport_layer( PacketProtocolLayer *userauth, PacketProtocolLayer *transport); void ssh2connection_server_configure( PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt, const SshServerConfig *ssc); void ssh1connection_server_configure( PacketProtocolLayer *ppl, const SshServerConfig *ssc); PacketProtocolLayer *ssh1_login_server_new( PacketProtocolLayer *successor_layer, RSAKey *hostkey, AuthPolicy *authpolicy, const SshServerConfig *ssc); Channel *sesschan_new(SshChannel *c, LogContext *logctx, const SftpServerVtable *sftpserver_vt, const SshServerConfig *ssc); Backend *pty_backend_create( Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd, struct ssh_ttymodes ttymodes, bool pipes_instead_of_pty, const char *dir, const char *const *env_vars_to_unset); int pty_backend_exit_signum(Backend *be); ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg); /* * Establish a listening X server. Return value is the _number_ of * Sockets that it established pointing at the given Plug. (0 * indicates complete failure.) The socket pointers themselves are * written into sockets[], up to a possible total of MAX_X11_SOCKETS. * * The supplied Conf has necessary environment variables written into * it. (And is also used to open the port listeners, though that * shouldn't affect anything.) */ #define MAX_X11_SOCKETS 2 int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, const char *screen_number_suffix, ptrlen authproto, ptrlen authdata, Socket **sockets, Conf *conf); Conf *make_ssh_server_conf(void); /* Provided by Unix front end programs to uxsftpserver.c */ void make_unix_sftp_filehandle_key(void *data, size_t size); typedef struct agentfwd agentfwd; agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out); void agentfwd_free(agentfwd *agent); work/ssh.h0000664000000000000000000021444314714222463007713 0ustar #include #include #include "puttymem.h" #include "tree234.h" #include "network.h" #include "misc.h" struct ssh_channel; /* * Buffer management constants. There are several of these for * various different purposes: * * - SSH1_BUFFER_LIMIT is the amount of backlog that must build up * on a local data stream before we throttle the whole SSH * connection (in SSH-1 only). Throttling the whole connection is * pretty drastic so we set this high in the hope it won't * happen very often. * * - SSH_MAX_BACKLOG is the amount of backlog that must build up * on the SSH connection itself before we defensively throttle * _all_ local data streams. This is pretty drastic too (though * thankfully unlikely in SSH-2 since the window mechanism should * ensure that the server never has any need to throttle its end * of the connection), so we set this high as well. * * - OUR_V2_WINSIZE is the default window size we present on SSH-2 * channels. * * - OUR_V2_BIGWIN is the window size we advertise for the only * channel in a simple connection. It must be <= INT_MAX. * * - OUR_V2_MAXPKT is the official "maximum packet size" we send * to the remote side. This actually has nothing to do with the * size of the _packet_, but is instead a limit on the amount * of data we're willing to receive in a single SSH2 channel * data message. * * - OUR_V2_PACKETLIMIT is actually the maximum size of SSH * _packet_ we're prepared to cope with. It must be a multiple * of the cipher block size, and must be at least 35000. */ #define SSH1_BUFFER_LIMIT 32768 #define SSH_MAX_BACKLOG 32768 #define OUR_V2_WINSIZE 16384 #define OUR_V2_BIGWIN 0x7fffffff #define OUR_V2_MAXPKT 0x4000UL #define OUR_V2_PACKETLIMIT 0x9000UL typedef struct PacketQueueNode PacketQueueNode; struct PacketQueueNode { PacketQueueNode *next, *prev; size_t formal_size; /* contribution to PacketQueueBase's total_size */ bool on_free_queue; /* is this packet scheduled for freeing? */ }; typedef struct PktIn { int type; unsigned long sequence; /* SSH-2 incoming sequence number */ PacketQueueNode qnode; /* for linking this packet on to a queue */ BinarySource_IMPLEMENTATION; } PktIn; typedef struct PktOut { size_t prefix; /* bytes up to and including type field */ size_t length; /* total bytes, including prefix */ int type; size_t minlen; /* SSH-2: ensure wire length is at least this */ unsigned char *data; /* allocated storage */ size_t maxlen; /* amount of storage allocated for `data' */ /* Extra metadata used in SSH packet logging mode, allowing us to * log in the packet header line that the packet came from a * connection-sharing downstream and what if anything unusual was * done to it. The additional_log_text field is expected to be a * static string - it will not be freed. */ unsigned downstream_id; const char *additional_log_text; PacketQueueNode qnode; /* for linking this packet on to a queue */ BinarySink_IMPLEMENTATION; } PktOut; typedef struct PacketQueueBase { PacketQueueNode end; size_t total_size; /* sum of all formal_size fields on the queue */ struct IdempotentCallback *ic; } PacketQueueBase; typedef struct PktInQueue { PacketQueueBase pqb; PktIn *(*after)(PacketQueueBase *, PacketQueueNode *prev, bool pop); } PktInQueue; typedef struct PktOutQueue { PacketQueueBase pqb; PktOut *(*after)(PacketQueueBase *, PacketQueueNode *prev, bool pop); } PktOutQueue; void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node); void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node); void pq_base_concatenate(PacketQueueBase *dest, PacketQueueBase *q1, PacketQueueBase *q2); void pq_in_init(PktInQueue *pq); void pq_out_init(PktOutQueue *pq); void pq_in_clear(PktInQueue *pq); void pq_out_clear(PktOutQueue *pq); #define pq_push(pq, pkt) \ TYPECHECK((pq)->after(&(pq)->pqb, NULL, false) == pkt, \ pq_base_push(&(pq)->pqb, &(pkt)->qnode)) #define pq_push_front(pq, pkt) \ TYPECHECK((pq)->after(&(pq)->pqb, NULL, false) == pkt, \ pq_base_push_front(&(pq)->pqb, &(pkt)->qnode)) #define pq_peek(pq) ((pq)->after(&(pq)->pqb, &(pq)->pqb.end, false)) #define pq_pop(pq) ((pq)->after(&(pq)->pqb, &(pq)->pqb.end, true)) #define pq_concatenate(dst, q1, q2) \ TYPECHECK((q1)->after(&(q1)->pqb, NULL, false) == \ (dst)->after(&(dst)->pqb, NULL, false) && \ (q2)->after(&(q2)->pqb, NULL, false) == \ (dst)->after(&(dst)->pqb, NULL, false), \ pq_base_concatenate(&(dst)->pqb, &(q1)->pqb, &(q2)->pqb)) #define pq_first(pq) pq_peek(pq) #define pq_next(pq, pkt) ((pq)->after(&(pq)->pqb, &(pkt)->qnode, false)) /* * Packet type contexts, so that ssh2_pkt_type can correctly decode * the ambiguous type numbers back into the correct type strings. */ typedef enum { SSH2_PKTCTX_NOKEX, SSH2_PKTCTX_DHGROUP, SSH2_PKTCTX_DHGEX, SSH2_PKTCTX_ECDHKEX, SSH2_PKTCTX_GSSKEX, SSH2_PKTCTX_RSAKEX } Pkt_KCtx; typedef enum { SSH2_PKTCTX_NOAUTH, SSH2_PKTCTX_PUBLICKEY, SSH2_PKTCTX_PASSWORD, SSH2_PKTCTX_GSSAPI, SSH2_PKTCTX_KBDINTER } Pkt_ACtx; typedef struct PacketLogSettings { bool omit_passwords, omit_data; Pkt_KCtx kctx; Pkt_ACtx actx; } PacketLogSettings; #define MAX_BLANKS 4 /* no packet needs more censored sections than this */ int ssh1_censor_packet( const PacketLogSettings *pls, int type, bool sender_is_client, ptrlen pkt, logblank_t *blanks); int ssh2_censor_packet( const PacketLogSettings *pls, int type, bool sender_is_client, ptrlen pkt, logblank_t *blanks); PktOut *ssh_new_packet(void); void ssh_free_pktout(PktOut *pkt); Socket *ssh_connection_sharing_init( const char *host, int port, Conf *conf, LogContext *logctx, Plug *sshplug, ssh_sharing_state **state); void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, ConnectionLayer *cl); bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf); void share_got_pkt_from_server(ssh_sharing_connstate *ctx, int type, const void *pkt, int pktlen); void share_activate(ssh_sharing_state *sharestate, const char *server_verstring); void sharestate_free(ssh_sharing_state *state); int share_ndownstreams(ssh_sharing_state *state); void ssh_connshare_log(Ssh *ssh, int event, const char *logtext, const char *ds_err, const char *us_err); void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, unsigned upstream_id, unsigned server_id, unsigned server_currwin, unsigned server_maxpkt, unsigned client_adjusted_window, const char *peer_addr, int peer_port, int endian, int protomajor, int protominor, const void *initial_data, int initial_len); /* Per-application overrides for what roles we can take in connection * sharing, regardless of user configuration (e.g. pscp will never be * an upstream) */ extern const bool share_can_be_downstream; extern const bool share_can_be_upstream; struct X11Display; struct X11FakeAuth; /* Structure definition centralised here because the SSH-1 and SSH-2 * connection layers both use it. But the client module (portfwd.c) * should not try to look inside here. */ struct ssh_rportfwd { unsigned sport, dport; char *shost, *dhost; int addressfamily; char *log_description; /* name of remote listening port, for logging */ ssh_sharing_connstate *share_ctx; PortFwdRecord *pfr; }; void free_rportfwd(struct ssh_rportfwd *rpf); typedef struct ConnectionLayerVtable ConnectionLayerVtable; struct ConnectionLayerVtable { /* Allocate and free remote-to-local port forwardings, called by * PortFwdManager or by connection sharing */ struct ssh_rportfwd *(*rportfwd_alloc)( ConnectionLayer *cl, const char *shost, int sport, const char *dhost, int dport, int addressfamily, const char *log_description, PortFwdRecord *pfr, ssh_sharing_connstate *share_ctx); void (*rportfwd_remove)(ConnectionLayer *cl, struct ssh_rportfwd *rpf); /* Open a local-to-remote port forwarding channel, called by * PortFwdManager */ SshChannel *(*lportfwd_open)( ConnectionLayer *cl, const char *hostname, int port, const char *description, const SocketPeerInfo *peerinfo, Channel *chan); /* Initiate opening of a 'session'-type channel */ SshChannel *(*session_open)(ConnectionLayer *cl, Channel *chan); /* Open outgoing channels for X and agent forwarding. (Used in the * SSH server.) */ SshChannel *(*serverside_x11_open)(ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi); SshChannel *(*serverside_agent_open)(ConnectionLayer *cl, Channel *chan); /* Add an X11 display for ordinary X forwarding */ struct X11FakeAuth *(*add_x11_display)( ConnectionLayer *cl, int authtype, struct X11Display *x11disp); /* Add and remove X11 displays for connection sharing downstreams */ struct X11FakeAuth *(*add_sharing_x11_display)( ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, share_channel *share_chan); void (*remove_sharing_x11_display)( ConnectionLayer *cl, struct X11FakeAuth *auth); /* Pass through an outgoing SSH packet from a downstream */ void (*send_packet_from_downstream)( ConnectionLayer *cl, unsigned id, int type, const void *pkt, int pktlen, const char *additional_log_text); /* Allocate/free an upstream channel number associated with a * sharing downstream */ unsigned (*alloc_sharing_channel)(ConnectionLayer *cl, ssh_sharing_connstate *connstate); void (*delete_sharing_channel)(ConnectionLayer *cl, unsigned localid); /* Indicate that a downstream has sent a global request with the * want-reply flag, so that when a reply arrives it will be passed * back to that downstrean */ void (*sharing_queue_global_request)( ConnectionLayer *cl, ssh_sharing_connstate *connstate); /* Indicate that the last downstream has disconnected */ void (*sharing_no_more_downstreams)(ConnectionLayer *cl); /* Query whether the connection layer is doing agent forwarding */ bool (*agent_forwarding_permitted)(ConnectionLayer *cl); /* Set the size of the main terminal window (if any) */ void (*terminal_size)(ConnectionLayer *cl, int width, int height); /* Indicate that the backlog on standard output has cleared */ void (*stdout_unthrottle)(ConnectionLayer *cl, size_t bufsize); /* Query the size of the backlog on standard _input_ */ size_t (*stdin_backlog)(ConnectionLayer *cl); /* Tell the connection layer that the SSH connection itself has * backed up, so it should tell all currently open channels to * cease reading from their local input sources if they can. (Or * tell it that that state of affairs has gone away again.) */ void (*throttle_all_channels)(ConnectionLayer *cl, bool throttled); /* Ask the connection layer about its current preference for * line-discipline options. */ bool (*ldisc_option)(ConnectionLayer *cl, int option); /* Communicate _to_ the connection layer (from the main session * channel) what its preference for line-discipline options is. */ void (*set_ldisc_option)(ConnectionLayer *cl, int option, bool value); /* Communicate to the connection layer whether X forwarding was * successfully enabled (for purposes of knowing whether to accept * subsequent channel-opens). */ void (*enable_x_fwd)(ConnectionLayer *cl); /* Communicate to the connection layer whether the main session * channel currently wants user input. */ void (*set_wants_user_input)(ConnectionLayer *cl, bool wanted); }; struct ConnectionLayer { LogContext *logctx; const struct ConnectionLayerVtable *vt; }; static inline struct ssh_rportfwd *ssh_rportfwd_alloc( ConnectionLayer *cl, const char *sh, int sp, const char *dh, int dp, int af, const char *log, PortFwdRecord *pfr, ssh_sharing_connstate *cs) { return cl->vt->rportfwd_alloc(cl, sh, sp, dh, dp, af, log, pfr, cs); } static inline void ssh_rportfwd_remove( ConnectionLayer *cl, struct ssh_rportfwd *rpf) { cl->vt->rportfwd_remove(cl, rpf); } static inline SshChannel *ssh_lportfwd_open( ConnectionLayer *cl, const char *host, int port, const char *desc, const SocketPeerInfo *pi, Channel *chan) { return cl->vt->lportfwd_open(cl, host, port, desc, pi, chan); } static inline SshChannel *ssh_session_open(ConnectionLayer *cl, Channel *chan) { return cl->vt->session_open(cl, chan); } static inline SshChannel *ssh_serverside_x11_open( ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) { return cl->vt->serverside_x11_open(cl, chan, pi); } static inline SshChannel *ssh_serverside_agent_open( ConnectionLayer *cl, Channel *chan) { return cl->vt->serverside_agent_open(cl, chan); } static inline struct X11FakeAuth *ssh_add_x11_display( ConnectionLayer *cl, int authtype, struct X11Display *x11disp) { return cl->vt->add_x11_display(cl, authtype, x11disp); } static inline struct X11FakeAuth *ssh_add_sharing_x11_display( ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, share_channel *share_chan) { return cl->vt->add_sharing_x11_display(cl, authtype, share_cs, share_chan); } static inline void ssh_remove_sharing_x11_display( ConnectionLayer *cl, struct X11FakeAuth *auth) { cl->vt->remove_sharing_x11_display(cl, auth); } static inline void ssh_send_packet_from_downstream( ConnectionLayer *cl, unsigned id, int type, const void *pkt, int len, const char *log) { cl->vt->send_packet_from_downstream(cl, id, type, pkt, len, log); } static inline unsigned ssh_alloc_sharing_channel( ConnectionLayer *cl, ssh_sharing_connstate *connstate) { return cl->vt->alloc_sharing_channel(cl, connstate); } static inline void ssh_delete_sharing_channel( ConnectionLayer *cl, unsigned localid) { cl->vt->delete_sharing_channel(cl, localid); } static inline void ssh_sharing_queue_global_request( ConnectionLayer *cl, ssh_sharing_connstate *connstate) { cl->vt->sharing_queue_global_request(cl, connstate); } static inline void ssh_sharing_no_more_downstreams(ConnectionLayer *cl) { cl->vt->sharing_no_more_downstreams(cl); } static inline bool ssh_agent_forwarding_permitted(ConnectionLayer *cl) { return cl->vt->agent_forwarding_permitted(cl); } static inline void ssh_terminal_size(ConnectionLayer *cl, int w, int h) { cl->vt->terminal_size(cl, w, h); } static inline void ssh_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize) { cl->vt->stdout_unthrottle(cl, bufsize); } static inline size_t ssh_stdin_backlog(ConnectionLayer *cl) { return cl->vt->stdin_backlog(cl); } static inline void ssh_throttle_all_channels(ConnectionLayer *cl, bool thr) { cl->vt->throttle_all_channels(cl, thr); } static inline bool ssh_ldisc_option(ConnectionLayer *cl, int option) { return cl->vt->ldisc_option(cl, option); } static inline void ssh_set_ldisc_option(ConnectionLayer *cl, int opt, bool val) { cl->vt->set_ldisc_option(cl, opt, val); } static inline void ssh_enable_x_fwd(ConnectionLayer *cl) { cl->vt->enable_x_fwd(cl); } static inline void ssh_set_wants_user_input(ConnectionLayer *cl, bool wanted) { cl->vt->set_wants_user_input(cl, wanted); } /* Exports from portfwd.c */ PortFwdManager *portfwdmgr_new(ConnectionLayer *cl); void portfwdmgr_free(PortFwdManager *mgr); void portfwdmgr_config(PortFwdManager *mgr, Conf *conf); void portfwdmgr_close(PortFwdManager *mgr, PortFwdRecord *pfr); void portfwdmgr_close_all(PortFwdManager *mgr); char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, char *hostname, int port, SshChannel *c, int addressfamily); bool portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port, const char *keyhost, int keyport, Conf *conf); bool portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port); Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug, bool start_ready); void portfwd_raw_free(Channel *pfchan); void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc); Socket *platform_make_agent_socket(Plug *plug, const char *dirprefix, char **error, char **name); LogContext *ssh_get_logctx(Ssh *ssh); /* Communications back to ssh.c from connection layers */ void ssh_throttle_conn(Ssh *ssh, int adjust); void ssh_got_exitcode(Ssh *ssh, int status); void ssh_ldisc_update(Ssh *ssh); void ssh_got_fallback_cmd(Ssh *ssh); bool ssh_is_bare(Ssh *ssh); /* Communications back to ssh.c from the BPP */ void ssh_conn_processed_data(Ssh *ssh); void ssh_sendbuffer_changed(Ssh *ssh); void ssh_check_frozen(Ssh *ssh); /* Functions to abort the connection, for various reasons. */ void ssh_remote_error(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); void ssh_remote_eof(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); void ssh_proto_error(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); void ssh_user_close(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); /* Bit positions in the SSH-1 cipher protocol word */ #define SSH1_CIPHER_IDEA 1 #define SSH1_CIPHER_DES 2 #define SSH1_CIPHER_3DES 3 #define SSH1_CIPHER_BLOWFISH 6 /* The subset of those that we support, with names for selecting them * on Uppity's command line */ #define SSH1_SUPPORTED_CIPHER_LIST(X) \ X(SSH1_CIPHER_3DES, "3des") \ X(SSH1_CIPHER_BLOWFISH, "blowfish") \ X(SSH1_CIPHER_DES, "des") \ /* end of list */ #define SSH1_CIPHER_LIST_MAKE_MASK(bitpos, name) | (1U << bitpos) #define SSH1_SUPPORTED_CIPHER_MASK \ (0 SSH1_SUPPORTED_CIPHER_LIST(SSH1_CIPHER_LIST_MAKE_MASK)) struct ssh_key { const ssh_keyalg *vt; }; struct RSAKey { int bits; int bytes; mp_int *modulus; mp_int *exponent; mp_int *private_exponent; mp_int *p; mp_int *q; mp_int *iqmp; char *comment; ssh_key sshk; }; struct dsa_key { mp_int *p, *q, *g, *y, *x; ssh_key sshk; }; struct ec_curve; /* Weierstrass form curve */ struct ec_wcurve { WeierstrassCurve *wc; WeierstrassPoint *G; mp_int *G_order; }; /* Montgomery form curve */ struct ec_mcurve { MontgomeryCurve *mc; MontgomeryPoint *G; unsigned log2_cofactor; }; /* Edwards form curve */ struct ec_ecurve { EdwardsCurve *ec; EdwardsPoint *G; mp_int *G_order; unsigned log2_cofactor; }; typedef enum EllipticCurveType { EC_WEIERSTRASS, EC_MONTGOMERY, EC_EDWARDS } EllipticCurveType; struct ec_curve { EllipticCurveType type; /* 'name' is the identifier of the curve when it has to appear in * wire protocol encodings, as it does in e.g. the public key and * signature formats for NIST curves. Curves which do not format * their keys or signatures in this way just have name==NULL. * * 'textname' is non-NULL for all curves, and is a human-readable * identification suitable for putting in log messages. */ const char *name, *textname; size_t fieldBits, fieldBytes; mp_int *p; union { struct ec_wcurve w; struct ec_mcurve m; struct ec_ecurve e; }; }; const ssh_keyalg *ec_alg_by_oid(int len, const void *oid, const struct ec_curve **curve); const unsigned char *ec_alg_oid(const ssh_keyalg *alg, int *oidlen); extern const int ec_nist_curve_lengths[], n_ec_nist_curve_lengths; extern const int ec_ed_curve_lengths[], n_ec_ed_curve_lengths; bool ec_nist_alg_and_curve_by_bits(int bits, const struct ec_curve **curve, const ssh_keyalg **alg); bool ec_ed_alg_and_curve_by_bits(int bits, const struct ec_curve **curve, const ssh_keyalg **alg); struct ecdsa_key { const struct ec_curve *curve; WeierstrassPoint *publicKey; mp_int *privateKey; ssh_key sshk; }; struct eddsa_key { const struct ec_curve *curve; EdwardsPoint *publicKey; mp_int *privateKey; ssh_key sshk; }; WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg); EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg); typedef struct key_components { size_t ncomponents, componentsize; struct { char *name; bool is_mp_int; union { char *text; mp_int *mp; }; } *components; } key_components; key_components *key_components_new(void); void key_components_add_text(key_components *kc, const char *name, const char *value); void key_components_add_mp(key_components *kc, const char *name, mp_int *value); void key_components_free(key_components *kc); /* * SSH-1 never quite decided which order to store the two components * of an RSA key. During connection setup, the server sends its host * and server keys with the exponent first; private key files store * the modulus first. The agent protocol is even more confusing, * because the client specifies a key to the server in one order and * the server lists the keys it knows about in the other order! */ typedef enum { RSA_SSH1_EXPONENT_FIRST, RSA_SSH1_MODULUS_FIRST } RsaSsh1Order; void BinarySource_get_rsa_ssh1_pub( BinarySource *src, RSAKey *result, RsaSsh1Order order); void BinarySource_get_rsa_ssh1_priv( BinarySource *src, RSAKey *rsa); RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src); bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key); mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key); bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key, strbuf *outbuf); char *rsastr_fmt(RSAKey *key); char *rsa_ssh1_fingerprint(RSAKey *key); char **rsa_ssh1_fake_all_fingerprints(RSAKey *key); bool rsa_verify(RSAKey *key); void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, RsaSsh1Order order); int rsa_ssh1_public_blob_len(ptrlen data); void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key); void freersapriv(RSAKey *key); void freersakey(RSAKey *key); key_components *rsa_components(RSAKey *key); uint32_t crc32_rfc1662(ptrlen data); uint32_t crc32_ssh1(ptrlen data); uint32_t crc32_update(uint32_t crc_input, ptrlen data); /* SSH CRC compensation attack detector */ struct crcda_ctx; struct crcda_ctx *crcda_make_context(void); void crcda_free_context(struct crcda_ctx *ctx); bool detect_attack(struct crcda_ctx *ctx, const unsigned char *buf, uint32_t len, const unsigned char *IV); /* * SSH2 RSA key exchange functions */ struct ssh_rsa_kex_extra { int minklen; }; RSAKey *ssh_rsakex_newkey(ptrlen data); void ssh_rsakex_freekey(RSAKey *key); int ssh_rsakex_klen(RSAKey *key); strbuf *ssh_rsakex_encrypt( RSAKey *key, const ssh_hashalg *h, ptrlen plaintext); mp_int *ssh_rsakex_decrypt( RSAKey *key, const ssh_hashalg *h, ptrlen ciphertext); /* * SSH2 ECDH key exchange functions */ const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex); ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex); void ssh_ecdhkex_freekey(ecdh_key *key); void ssh_ecdhkex_getpublic(ecdh_key *key, BinarySink *bs); mp_int *ssh_ecdhkex_getkey(ecdh_key *key, ptrlen remoteKey); /* * Helper function for k generation in DSA, reused in ECDSA */ mp_int *dsa_gen_k(const char *id_string, mp_int *modulus, mp_int *private_key, unsigned char *digest, int digest_len); struct ssh_cipher { const ssh_cipheralg *vt; }; struct ssh_cipheralg { ssh_cipher *(*new)(const ssh_cipheralg *alg); void (*free)(ssh_cipher *); void (*setiv)(ssh_cipher *, const void *iv); void (*setkey)(ssh_cipher *, const void *key); void (*encrypt)(ssh_cipher *, void *blk, int len); void (*decrypt)(ssh_cipher *, void *blk, int len); /* Ignored unless SSH_CIPHER_SEPARATE_LENGTH flag set */ void (*encrypt_length)(ssh_cipher *, void *blk, int len, unsigned long seq); void (*decrypt_length)(ssh_cipher *, void *blk, int len, unsigned long seq); const char *ssh2_id; int blksize; /* real_keybits is the number of bits of entropy genuinely used by * the cipher scheme; it's used for deciding how big a * Diffie-Hellman group is needed to exchange a key for the * cipher. */ int real_keybits; /* padded_keybytes is the number of bytes of key data expected as * input to the setkey function; it's used for deciding how much * data needs to be generated from the post-kex generation of key * material. In a sensible cipher which uses all its key bytes for * real work, this will just be real_keybits/8, but in DES-type * ciphers which ignore one bit in each byte, it'll be slightly * different. */ int padded_keybytes; unsigned int flags; #define SSH_CIPHER_IS_CBC 1 #define SSH_CIPHER_SEPARATE_LENGTH 2 const char *text_name; /* If set, this takes priority over other MAC. */ const ssh2_macalg *required_mac; /* Pointer to any extra data used by a particular implementation. */ const void *extra; }; static inline ssh_cipher *ssh_cipher_new(const ssh_cipheralg *alg) { return alg->new(alg); } static inline void ssh_cipher_free(ssh_cipher *c) { c->vt->free(c); } static inline void ssh_cipher_setiv(ssh_cipher *c, const void *iv) { c->vt->setiv(c, iv); } static inline void ssh_cipher_setkey(ssh_cipher *c, const void *key) { c->vt->setkey(c, key); } static inline void ssh_cipher_encrypt(ssh_cipher *c, void *blk, int len) { c->vt->encrypt(c, blk, len); } static inline void ssh_cipher_decrypt(ssh_cipher *c, void *blk, int len) { c->vt->decrypt(c, blk, len); } static inline void ssh_cipher_encrypt_length( ssh_cipher *c, void *blk, int len, unsigned long seq) { c->vt->encrypt_length(c, blk, len, seq); } static inline void ssh_cipher_decrypt_length( ssh_cipher *c, void *blk, int len, unsigned long seq) { c->vt->decrypt_length(c, blk, len, seq); } static inline const struct ssh_cipheralg *ssh_cipher_alg(ssh_cipher *c) { return c->vt; } struct ssh2_ciphers { int nciphers; const ssh_cipheralg *const *list; }; struct ssh2_mac { const ssh2_macalg *vt; BinarySink_DELEGATE_IMPLEMENTATION; }; struct ssh2_macalg { /* Passes in the cipher context */ ssh2_mac *(*new)(const ssh2_macalg *alg, ssh_cipher *cipher); void (*free)(ssh2_mac *); void (*setkey)(ssh2_mac *, ptrlen key); void (*start)(ssh2_mac *); void (*genresult)(ssh2_mac *, unsigned char *); const char *(*text_name)(ssh2_mac *); const char *name, *etm_name; int len, keylen; /* Pointer to any extra data used by a particular implementation. */ const void *extra; }; static inline ssh2_mac *ssh2_mac_new( const ssh2_macalg *alg, ssh_cipher *cipher) { return alg->new(alg, cipher); } static inline void ssh2_mac_free(ssh2_mac *m) { m->vt->free(m); } static inline void ssh2_mac_setkey(ssh2_mac *m, ptrlen key) { m->vt->setkey(m, key); } static inline void ssh2_mac_start(ssh2_mac *m) { m->vt->start(m); } static inline void ssh2_mac_genresult(ssh2_mac *m, unsigned char *out) { m->vt->genresult(m, out); } static inline const char *ssh2_mac_text_name(ssh2_mac *m) { return m->vt->text_name(m); } static inline const ssh2_macalg *ssh2_mac_alg(ssh2_mac *m) { return m->vt; } /* Centralised 'methods' for ssh2_mac, defined in sshmac.c. These run * the MAC in a specifically SSH-2 style, i.e. taking account of a * packet sequence number as well as the data to be authenticated. */ bool ssh2_mac_verresult(ssh2_mac *, const void *); void ssh2_mac_generate(ssh2_mac *, void *, int, unsigned long seq); bool ssh2_mac_verify(ssh2_mac *, const void *, int, unsigned long seq); /* Use a MAC in its raw form, outside SSH-2 context, to MAC a given * string with a given key in the most obvious way. */ void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output); struct ssh_hash { const ssh_hashalg *vt; BinarySink_DELEGATE_IMPLEMENTATION; }; struct ssh_hashalg { ssh_hash *(*new)(const ssh_hashalg *alg); void (*reset)(ssh_hash *); void (*copyfrom)(ssh_hash *dest, ssh_hash *src); void (*digest)(ssh_hash *, unsigned char *); void (*free)(ssh_hash *); size_t hlen; /* output length in bytes */ size_t blocklen; /* length of the hash's input block, or 0 for N/A */ const char *text_basename; /* the semantic name of the hash */ const char *annotation; /* extra info, e.g. which of multiple impls */ const char *text_name; /* both combined, e.g. "SHA-n (unaccelerated)" */ const void *extra; /* private to the hash implementation */ }; static inline ssh_hash *ssh_hash_new(const ssh_hashalg *alg) { ssh_hash *h = alg->new(alg); if (h) h->vt->reset(h); return h; } static inline ssh_hash *ssh_hash_copy(ssh_hash *orig) { ssh_hash *h = orig->vt->new(orig->vt); h->vt->copyfrom(h, orig); return h; } static inline void ssh_hash_digest(ssh_hash *h, unsigned char *out) { h->vt->digest(h, out); } static inline void ssh_hash_free(ssh_hash *h) { h->vt->free(h); } static inline const ssh_hashalg *ssh_hash_alg(ssh_hash *h) { return h->vt; } /* The reset and copyfrom vtable methods return void. But for call-site * convenience, these wrappers return their input pointer. */ static inline ssh_hash *ssh_hash_reset(ssh_hash *h) { h->vt->reset(h); return h; } static inline ssh_hash *ssh_hash_copyfrom(ssh_hash *dest, ssh_hash *src) { dest->vt->copyfrom(dest, src); return dest; } /* ssh_hash_final emits the digest _and_ frees the ssh_hash */ static inline void ssh_hash_final(ssh_hash *h, unsigned char *out) { h->vt->digest(h, out); h->vt->free(h); } /* ssh_hash_digest_nondestructive generates a finalised hash from the * given object without changing its state, so you can continue * appending data to get a hash of an extended string. */ static inline void ssh_hash_digest_nondestructive(ssh_hash *h, unsigned char *out) { ssh_hash_final(ssh_hash_copy(h), out); } /* Handy macros for defining all those text-name fields at once */ #define HASHALG_NAMES_BARE(base) \ .text_basename = base, .annotation = NULL, .text_name = base #define HASHALG_NAMES_ANNOTATED(base, ann) \ .text_basename = base, .annotation = ann, .text_name = base " (" ann ")" void hash_simple(const ssh_hashalg *alg, ptrlen data, void *output); struct ssh_kex { const char *name, *groupname; enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH, KEXTYPE_GSS } main_type; const ssh_hashalg *hash; const void *extra; /* private to the kex methods */ }; struct ssh_kexes { int nkexes; const ssh_kex *const *list; }; /* Indices of the negotiation strings in the KEXINIT packet */ enum kexlist { KEXLIST_KEX, KEXLIST_HOSTKEY, KEXLIST_CSCIPHER, KEXLIST_SCCIPHER, KEXLIST_CSMAC, KEXLIST_SCMAC, KEXLIST_CSCOMP, KEXLIST_SCCOMP, NKEXLIST }; struct ssh_keyalg { /* Constructors that create an ssh_key */ ssh_key *(*new_pub) (const ssh_keyalg *self, ptrlen pub); ssh_key *(*new_priv) (const ssh_keyalg *self, ptrlen pub, ptrlen priv); ssh_key *(*new_priv_openssh) (const ssh_keyalg *self, BinarySource *); /* Methods that operate on an existing ssh_key */ void (*freekey) (ssh_key *key); char *(*invalid) (ssh_key *key, unsigned flags); void (*sign) (ssh_key *key, ptrlen data, unsigned flags, BinarySink *); bool (*verify) (ssh_key *key, ptrlen sig, ptrlen data); void (*public_blob)(ssh_key *key, BinarySink *); void (*private_blob)(ssh_key *key, BinarySink *); void (*openssh_blob) (ssh_key *key, BinarySink *); char *(*cache_str) (ssh_key *key); key_components *(*components) (ssh_key *key); /* 'Class methods' that don't deal with an ssh_key at all */ int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob); /* Constant data fields giving information about the key type */ const char *ssh_id; /* string identifier in the SSH protocol */ const char *cache_id; /* identifier used in PuTTY's host key cache */ const void *extra; /* private to the public key methods */ const unsigned supported_flags; /* signature-type flags we understand */ }; static inline ssh_key *ssh_key_new_pub(const ssh_keyalg *self, ptrlen pub) { return self->new_pub(self, pub); } static inline ssh_key *ssh_key_new_priv( const ssh_keyalg *self, ptrlen pub, ptrlen priv) { return self->new_priv(self, pub, priv); } static inline ssh_key *ssh_key_new_priv_openssh( const ssh_keyalg *self, BinarySource *src) { return self->new_priv_openssh(self, src); } static inline void ssh_key_free(ssh_key *key) { key->vt->freekey(key); } static inline char *ssh_key_invalid(ssh_key *key, unsigned flags) { return key->vt->invalid(key, flags); } static inline void ssh_key_sign( ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) { key->vt->sign(key, data, flags, bs); } static inline bool ssh_key_verify(ssh_key *key, ptrlen sig, ptrlen data) { return key->vt->verify(key, sig, data); } static inline void ssh_key_public_blob(ssh_key *key, BinarySink *bs) { key->vt->public_blob(key, bs); } static inline void ssh_key_private_blob(ssh_key *key, BinarySink *bs) { key->vt->private_blob(key, bs); } static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *bs) { key->vt->openssh_blob(key, bs); } static inline char *ssh_key_cache_str(ssh_key *key) { return key->vt->cache_str(key); } static inline key_components *ssh_key_components(ssh_key *key) { return key->vt->components(key); } static inline int ssh_key_public_bits(const ssh_keyalg *self, ptrlen blob) { return self->pubkey_bits(self, blob); } static inline const ssh_keyalg *ssh_key_alg(ssh_key *key) { return key->vt; } static inline const char *ssh_key_ssh_id(ssh_key *key) { return key->vt->ssh_id; } static inline const char *ssh_key_cache_id(ssh_key *key) { return key->vt->cache_id; } /* * Enumeration of signature flags from draft-miller-ssh-agent-02 */ #define SSH_AGENT_RSA_SHA2_256 2 #define SSH_AGENT_RSA_SHA2_512 4 struct ssh_compressor { const ssh_compression_alg *vt; }; struct ssh_decompressor { const ssh_compression_alg *vt; }; struct ssh_compression_alg { const char *name; /* For zlib@openssh.com: if non-NULL, this name will be considered once * userauth has completed successfully. */ const char *delayed_name; ssh_compressor *(*compress_new)(void); void (*compress_free)(ssh_compressor *); void (*compress)(ssh_compressor *, const unsigned char *block, int len, unsigned char **outblock, int *outlen, int minlen); ssh_decompressor *(*decompress_new)(void); void (*decompress_free)(ssh_decompressor *); bool (*decompress)(ssh_decompressor *, const unsigned char *block, int len, unsigned char **outblock, int *outlen); const char *text_name; }; static inline ssh_compressor *ssh_compressor_new( const ssh_compression_alg *alg) { return alg->compress_new(); } static inline ssh_decompressor *ssh_decompressor_new( const ssh_compression_alg *alg) { return alg->decompress_new(); } static inline void ssh_compressor_free(ssh_compressor *c) { c->vt->compress_free(c); } static inline void ssh_decompressor_free(ssh_decompressor *d) { d->vt->decompress_free(d); } static inline void ssh_compressor_compress( ssh_compressor *c, const unsigned char *block, int len, unsigned char **outblock, int *outlen, int minlen) { c->vt->compress(c, block, len, outblock, outlen, minlen); } static inline bool ssh_decompressor_decompress( ssh_decompressor *d, const unsigned char *block, int len, unsigned char **outblock, int *outlen) { return d->vt->decompress(d, block, len, outblock, outlen); } static inline const ssh_compression_alg *ssh_compressor_alg( ssh_compressor *c) { return c->vt; } static inline const ssh_compression_alg *ssh_decompressor_alg( ssh_decompressor *d) { return d->vt; } struct ssh2_userkey { ssh_key *key; /* the key itself */ char *comment; /* the key comment */ }; /* Argon2 password hashing function */ typedef enum { Argon2d = 0, Argon2i = 1, Argon2id = 2 } Argon2Flavour; void argon2(Argon2Flavour, uint32_t mem, uint32_t passes, uint32_t parallel, uint32_t taglen, ptrlen P, ptrlen S, ptrlen K, ptrlen X, strbuf *out); void argon2_choose_passes( Argon2Flavour, uint32_t mem, uint32_t milliseconds, uint32_t *passes, uint32_t parallel, uint32_t taglen, ptrlen P, ptrlen S, ptrlen K, ptrlen X, strbuf *out); /* The H' hash defined in Argon2, exposed just for testcrypt */ strbuf *argon2_long_hash(unsigned length, ptrlen data); /* The maximum length of any hash algorithm. (bytes) */ #define MAX_HASH_LEN (114) /* longest is SHAKE256 with 114-byte output */ extern const ssh_cipheralg ssh_3des_ssh1; extern const ssh_cipheralg ssh_blowfish_ssh1; extern const ssh_cipheralg ssh_3des_ssh2_ctr; extern const ssh_cipheralg ssh_3des_ssh2; extern const ssh_cipheralg ssh_des; extern const ssh_cipheralg ssh_des_sshcom_ssh2; extern const ssh_cipheralg ssh_aes256_sdctr; extern const ssh_cipheralg ssh_aes256_sdctr_ni; extern const ssh_cipheralg ssh_aes256_sdctr_neon; extern const ssh_cipheralg ssh_aes256_sdctr_sw; extern const ssh_cipheralg ssh_aes256_cbc; extern const ssh_cipheralg ssh_aes256_cbc_ni; extern const ssh_cipheralg ssh_aes256_cbc_neon; extern const ssh_cipheralg ssh_aes256_cbc_sw; extern const ssh_cipheralg ssh_aes192_sdctr; extern const ssh_cipheralg ssh_aes192_sdctr_ni; extern const ssh_cipheralg ssh_aes192_sdctr_neon; extern const ssh_cipheralg ssh_aes192_sdctr_sw; extern const ssh_cipheralg ssh_aes192_cbc; extern const ssh_cipheralg ssh_aes192_cbc_ni; extern const ssh_cipheralg ssh_aes192_cbc_neon; extern const ssh_cipheralg ssh_aes192_cbc_sw; extern const ssh_cipheralg ssh_aes128_sdctr; extern const ssh_cipheralg ssh_aes128_sdctr_ni; extern const ssh_cipheralg ssh_aes128_sdctr_neon; extern const ssh_cipheralg ssh_aes128_sdctr_sw; extern const ssh_cipheralg ssh_aes128_cbc; extern const ssh_cipheralg ssh_aes128_cbc_ni; extern const ssh_cipheralg ssh_aes128_cbc_neon; extern const ssh_cipheralg ssh_aes128_cbc_sw; extern const ssh_cipheralg ssh_blowfish_ssh2_ctr; extern const ssh_cipheralg ssh_blowfish_ssh2; extern const ssh_cipheralg ssh_arcfour256_ssh2; extern const ssh_cipheralg ssh_arcfour128_ssh2; extern const ssh_cipheralg ssh2_chacha20_poly1305; extern const ssh2_ciphers ssh2_3des; extern const ssh2_ciphers ssh2_des; extern const ssh2_ciphers ssh2_aes; extern const ssh2_ciphers ssh2_blowfish; extern const ssh2_ciphers ssh2_arcfour; extern const ssh2_ciphers ssh2_ccp; extern const ssh_hashalg ssh_md5; extern const ssh_hashalg ssh_sha1; extern const ssh_hashalg ssh_sha1_ni; extern const ssh_hashalg ssh_sha1_neon; extern const ssh_hashalg ssh_sha1_sw; extern const ssh_hashalg ssh_sha256; extern const ssh_hashalg ssh_sha256_ni; extern const ssh_hashalg ssh_sha256_neon; extern const ssh_hashalg ssh_sha256_sw; extern const ssh_hashalg ssh_sha384; extern const ssh_hashalg ssh_sha384_neon; extern const ssh_hashalg ssh_sha384_sw; extern const ssh_hashalg ssh_sha512; extern const ssh_hashalg ssh_sha512_neon; extern const ssh_hashalg ssh_sha512_sw; extern const ssh_hashalg ssh_sha3_224; extern const ssh_hashalg ssh_sha3_256; extern const ssh_hashalg ssh_sha3_384; extern const ssh_hashalg ssh_sha3_512; extern const ssh_hashalg ssh_shake256_114bytes; extern const ssh_hashalg ssh_blake2b; extern const ssh_kexes ssh_diffiehellman_group1; extern const ssh_kexes ssh_diffiehellman_group14; extern const ssh_kexes ssh_diffiehellman_gex; extern const ssh_kexes ssh_gssk5_sha1_kex; extern const ssh_kexes ssh_rsa_kex; extern const ssh_kex ssh_ec_kex_curve25519; extern const ssh_kex ssh_ec_kex_curve448; extern const ssh_kex ssh_ec_kex_nistp256; extern const ssh_kex ssh_ec_kex_nistp384; extern const ssh_kex ssh_ec_kex_nistp521; extern const ssh_kexes ssh_ecdh_kex; extern const ssh_keyalg ssh_dsa; extern const ssh_keyalg ssh_rsa; extern const ssh_keyalg ssh_rsa_sha256; extern const ssh_keyalg ssh_rsa_sha512; extern const ssh_keyalg ssh_ecdsa_ed25519; extern const ssh_keyalg ssh_ecdsa_ed448; extern const ssh_keyalg ssh_ecdsa_nistp256; extern const ssh_keyalg ssh_ecdsa_nistp384; extern const ssh_keyalg ssh_ecdsa_nistp521; extern const ssh2_macalg ssh_hmac_md5; extern const ssh2_macalg ssh_hmac_sha1; extern const ssh2_macalg ssh_hmac_sha1_buggy; extern const ssh2_macalg ssh_hmac_sha1_96; extern const ssh2_macalg ssh_hmac_sha1_96_buggy; extern const ssh2_macalg ssh_hmac_sha256; extern const ssh2_macalg ssh2_poly1305; extern const ssh_compression_alg ssh_zlib; /* Special constructor: BLAKE2b can be instantiated with any hash * length up to 128 bytes */ ssh_hash *blake2b_new_general(unsigned hashlen); /* * On some systems, you have to detect hardware crypto acceleration by * asking the local OS API rather than OS-agnostically asking the CPU * itself. If so, then this function should be implemented in each * platform subdirectory. */ bool platform_aes_neon_available(void); bool platform_sha256_neon_available(void); bool platform_sha1_neon_available(void); bool platform_sha512_neon_available(void); /* * PuTTY version number formatted as an SSH version string. */ extern const char sshver[]; /* * Gross hack: pscp will try to start SFTP but fall back to scp1 if * that fails. This variable is the means by which scp.c can reach * into the SSH code and find out which one it got. */ extern bool ssh_fallback_cmd(Backend *backend); /* * The PRNG type, defined in sshprng.c. Visible data fields are * 'savesize', which suggests how many random bytes you should request * from a particular PRNG instance to write to putty.rnd, and a * BinarySink implementation which you can use to write seed data in * between calling prng_seed_{begin,finish}. */ struct prng { size_t savesize; BinarySink_IMPLEMENTATION; /* (also there's a surrounding implementation struct in sshprng.c) */ }; prng *prng_new(const ssh_hashalg *hashalg); void prng_free(prng *p); void prng_seed_begin(prng *p); void prng_seed_finish(prng *p); void prng_read(prng *p, void *vout, size_t size); void prng_add_entropy(prng *p, unsigned source_id, ptrlen data); size_t prng_seed_bits(prng *p); /* This function must be implemented by the platform, and returns a * timer in milliseconds that the PRNG can use to know whether it's * been reseeded too recently to do it again. * * The PRNG system has its own special timing function not because its * timing needs are unusual in the real applications, but simply so * that testcrypt can mock it to keep the tests deterministic. */ uint64_t prng_reseed_time_ms(void); void random_read(void *out, size_t size); /* Exports from x11fwd.c */ enum { X11_TRANS_IPV4 = 0, X11_TRANS_IPV6 = 6, X11_TRANS_UNIX = 256 }; struct X11Display { /* Broken-down components of the display name itself */ bool unixdomain; char *hostname; int displaynum; int screennum; /* OSX sometimes replaces all the above with a full Unix-socket pathname */ char *unixsocketpath; /* PuTTY networking SockAddr to connect to the display, and associated * gubbins */ SockAddr *addr; int port; char *realhost; /* Our local auth details for talking to the real X display. */ int localauthproto; unsigned char *localauthdata; int localauthdatalen; }; struct X11FakeAuth { /* Auth details we invented for a virtual display on the SSH server. */ int proto; unsigned char *data; int datalen; char *protoname; char *datastring; /* The encrypted form of the first block, in XDM-AUTHORIZATION-1. * Used as part of the key when these structures are organised * into a tree. See x11_invent_fake_auth for explanation. */ unsigned char *xa1_firstblock; /* * Used inside x11fwd.c to remember recently seen * XDM-AUTHORIZATION-1 strings, to avoid replay attacks. */ tree234 *xdmseen; /* * What to do with an X connection matching this auth data. */ struct X11Display *disp; ssh_sharing_connstate *share_cs; share_channel *share_chan; }; void *x11_make_greeting(int endian, int protomajor, int protominor, int auth_proto, const void *auth_data, int auth_len, const char *peer_ip, int peer_port, int *outlen); int x11_authcmp(void *av, void *bv); /* for putting X11FakeAuth in a tree234 */ /* * x11_setup_display() parses the display variable and fills in an * X11Display structure. Some remote auth details are invented; * the supplied authtype parameter configures the preferred * authorisation protocol to use at the remote end. The local auth * details are looked up by calling platform_get_x11_auth. * * If the returned pointer is NULL, then *error_msg will contain a * dynamically allocated error message string. */ extern struct X11Display *x11_setup_display(const char *display, Conf *, char **error_msg); void x11_free_display(struct X11Display *disp); struct X11FakeAuth *x11_invent_fake_auth(tree234 *t, int authtype); void x11_free_fake_auth(struct X11FakeAuth *auth); Channel *x11_new_channel(tree234 *authtree, SshChannel *c, const char *peeraddr, int peerport, bool connection_sharing_possible); char *x11_display(const char *display); /* Platform-dependent X11 functions */ extern void platform_get_x11_auth(struct X11Display *display, Conf *); /* examine a mostly-filled-in X11Display and fill in localauth* */ extern const bool platform_uses_x11_unix_by_default; /* choose default X transport in the absence of a specified one */ SockAddr *platform_get_x11_unix_address(const char *path, int displaynum); /* make up a SockAddr naming the address for displaynum */ char *platform_get_x_display(void); /* allocated local X display string, if any */ /* Callbacks in x11.c usable _by_ platform X11 functions */ /* * This function does the job of platform_get_x11_auth, provided * it is told where to find a normally formatted .Xauthority file: * it opens that file, parses it to find an auth record which * matches the display details in "display", and fills in the * localauth fields. * * It is expected that most implementations of * platform_get_x11_auth() will work by finding their system's * .Xauthority file, adjusting the display details if necessary * for local oddities like Unix-domain socket transport, and * calling this function to do the rest of the work. */ void x11_get_auth_from_authfile(struct X11Display *display, const char *authfilename); void x11_format_auth_for_authfile( BinarySink *bs, SockAddr *addr, int display_no, ptrlen authproto, ptrlen authdata); int x11_identify_auth_proto(ptrlen protoname); void *x11_dehexify(ptrlen hex, int *outlen); bool x11_parse_ip(const char *addr_string, unsigned long *ip); Channel *agentf_new(SshChannel *c); bool dh_is_gex(const ssh_kex *kex); dh_ctx *dh_setup_group(const ssh_kex *kex); dh_ctx *dh_setup_gex(mp_int *pval, mp_int *gval); int dh_modulus_bit_size(const dh_ctx *ctx); void dh_cleanup(dh_ctx *); mp_int *dh_create_e(dh_ctx *, int nbits); const char *dh_validate_f(dh_ctx *, mp_int *f); mp_int *dh_find_K(dh_ctx *, mp_int *f); static inline bool is_base64_char(char c) { return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '+' || c == '/' || c == '='); } extern int base64_decode_atom(const char *atom, unsigned char *out); extern int base64_lines(int datalen); extern void base64_encode_atom(const unsigned char *data, int n, char *out); extern void base64_encode(FILE *fp, const unsigned char *data, int datalen, int cpl); /* ppk_load_* can return this as an error */ extern ssh2_userkey ssh2_wrong_passphrase; #define SSH2_WRONG_PASSPHRASE (&ssh2_wrong_passphrase) bool ppk_encrypted_s(BinarySource *src, char **comment); bool ppk_encrypted_f(const Filename *filename, char **comment); bool rsa1_encrypted_s(BinarySource *src, char **comment); bool rsa1_encrypted_f(const Filename *filename, char **comment); ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase, const char **errorstr); ssh2_userkey *ppk_load_f(const Filename *filename, const char *passphrase, const char **errorstr); int rsa1_load_s(BinarySource *src, RSAKey *key, const char *passphrase, const char **errorstr); int rsa1_load_f(const Filename *filename, RSAKey *key, const char *passphrase, const char **errorstr); typedef struct ppk_save_parameters { unsigned fmt_version; /* currently 2 or 3 */ /* * Parameters for fmt_version == 3 */ Argon2Flavour argon2_flavour; uint32_t argon2_mem; /* in Kbyte */ bool argon2_passes_auto; union { uint32_t argon2_passes; /* if auto == false */ uint32_t argon2_milliseconds; /* if auto == true */ }; uint32_t argon2_parallelism; /* The ability to choose a specific salt is only intended for the * use of the automated test of PuTTYgen. It's a (mild) security * risk to do it with any passphrase you actually care about, * because it invalidates the entire point of having a salt in the * first place. */ const uint8_t *salt; size_t saltlen; } ppk_save_parameters; extern const ppk_save_parameters ppk_save_default_parameters; strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, const ppk_save_parameters *params); bool ppk_save_f(const Filename *filename, ssh2_userkey *key, const char *passphrase, const ppk_save_parameters *params); strbuf *rsa1_save_sb(RSAKey *key, const char *passphrase); bool rsa1_save_f(const Filename *filename, RSAKey *key, const char *passphrase); bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs, char **commentptr, const char **errorstr); bool ppk_loadpub_f(const Filename *filename, char **algorithm, BinarySink *bs, char **commentptr, const char **errorstr); int rsa1_loadpub_s(BinarySource *src, BinarySink *bs, char **commentptr, const char **errorstr); int rsa1_loadpub_f(const Filename *filename, BinarySink *bs, char **commentptr, const char **errorstr); extern const ssh_keyalg *const all_keyalgs[]; extern const size_t n_keyalgs; const ssh_keyalg *find_pubkey_alg(const char *name); const ssh_keyalg *find_pubkey_alg_len(ptrlen name); /* Convenient wrappers on the LoadedFile mechanism suitable for key files */ LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr); LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr); enum { SSH_KEYTYPE_UNOPENABLE, SSH_KEYTYPE_UNKNOWN, SSH_KEYTYPE_SSH1, SSH_KEYTYPE_SSH2, /* * The OpenSSH key types deserve a little explanation. OpenSSH has * two physical formats for private key storage: an old PEM-based * one largely dictated by their use of OpenSSL and full of ASN.1, * and a new one using the same private key formats used over the * wire for talking to ssh-agent. The old format can only support * a subset of the key types, because it needs redesign for each * key type, and after a while they decided to move to the new * format so as not to have to do that. * * On input, key files are identified as either * SSH_KEYTYPE_OPENSSH_PEM or SSH_KEYTYPE_OPENSSH_NEW, describing * accurately which actual format the keys are stored in. * * On output, however, we default to following OpenSSH's own * policy of writing out PEM-style keys for maximum backwards * compatibility if the key type supports it, and otherwise * switching to the new format. So the formats you can select for * output are SSH_KEYTYPE_OPENSSH_NEW (forcing the new format for * any key type), and SSH_KEYTYPE_OPENSSH_AUTO to use the oldest * format supported by whatever key type you're writing out. * * So we have three type codes, but only two of them usable in any * given circumstance. An input key file will never be identified * as AUTO, only PEM or NEW; key export UIs should not be able to * select PEM, only AUTO or NEW. */ SSH_KEYTYPE_OPENSSH_AUTO, SSH_KEYTYPE_OPENSSH_PEM, SSH_KEYTYPE_OPENSSH_NEW, SSH_KEYTYPE_SSHCOM, /* * Public-key-only formats, which we still want to be able to read * for various purposes. */ SSH_KEYTYPE_SSH1_PUBLIC, SSH_KEYTYPE_SSH2_PUBLIC_RFC4716, SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH }; typedef enum { SSH_FPTYPE_MD5, SSH_FPTYPE_SHA256, } FingerprintType; #define SSH_FPTYPE_DEFAULT SSH_FPTYPE_SHA256 #define SSH_N_FPTYPES (SSH_FPTYPE_SHA256 + 1) FingerprintType ssh2_pick_fingerprint(char **fingerprints, FingerprintType preferred_type); FingerprintType ssh2_pick_default_fingerprint(char **fingerprints); char *ssh1_pubkey_str(RSAKey *ssh1key); void ssh1_write_pubkey(FILE *fp, RSAKey *ssh1key); char *ssh2_pubkey_openssh_str(ssh2_userkey *key); void ssh2_write_pubkey(FILE *fp, const char *comment, const void *v_pub_blob, int pub_len, int keytype); char *ssh2_fingerprint_blob(ptrlen, FingerprintType); char *ssh2_fingerprint(ssh_key *key, FingerprintType); char **ssh2_all_fingerprints_for_blob(ptrlen); char **ssh2_all_fingerprints(ssh_key *key); void ssh2_free_all_fingerprints(char **); int key_type(const Filename *filename); int key_type_s(BinarySource *src); const char *key_type_to_str(int type); bool import_possible(int type); int import_target_type(int type); bool import_encrypted(const Filename *filename, int type, char **comment); bool import_encrypted_s(const Filename *filename, BinarySource *src, int type, char **comment); int import_ssh1(const Filename *filename, int type, RSAKey *key, char *passphrase, const char **errmsg_p); int import_ssh1_s(BinarySource *src, int type, RSAKey *key, char *passphrase, const char **errmsg_p); ssh2_userkey *import_ssh2(const Filename *filename, int type, char *passphrase, const char **errmsg_p); ssh2_userkey *import_ssh2_s(BinarySource *src, int type, char *passphrase, const char **errmsg_p); bool export_ssh1(const Filename *filename, int type, RSAKey *key, char *passphrase); bool export_ssh2(const Filename *filename, int type, ssh2_userkey *key, char *passphrase); void des3_decrypt_pubkey(const void *key, void *blk, int len); void des3_encrypt_pubkey(const void *key, void *blk, int len); void des3_decrypt_pubkey_ossh(const void *key, const void *iv, void *blk, int len); void des3_encrypt_pubkey_ossh(const void *key, const void *iv, void *blk, int len); void aes256_encrypt_pubkey(const void *key, const void *iv, void *blk, int len); void aes256_decrypt_pubkey(const void *key, const void *iv, void *blk, int len); void des_encrypt_xdmauth(const void *key, void *blk, int len); void des_decrypt_xdmauth(const void *key, void *blk, int len); void openssh_bcrypt(const char *passphrase, const unsigned char *salt, int saltbytes, int rounds, unsigned char *out, int outbytes); /* * Connection-sharing API provided by platforms. This function must * either: * - return SHARE_NONE and do nothing * - return SHARE_DOWNSTREAM and set *sock to a Socket connected to * downplug * - return SHARE_UPSTREAM and set *sock to a Socket connected to * upplug. */ enum { SHARE_NONE, SHARE_DOWNSTREAM, SHARE_UPSTREAM }; int platform_ssh_share(const char *name, Conf *conf, Plug *downplug, Plug *upplug, Socket **sock, char **logtext, char **ds_err, char **us_err, bool can_upstream, bool can_downstream); void platform_ssh_share_cleanup(const char *name); /* * List macro defining the SSH-1 message type codes. */ #define SSH1_MESSAGE_TYPES(X, y) \ X(y, SSH1_MSG_DISCONNECT, 1) \ X(y, SSH1_SMSG_PUBLIC_KEY, 2) \ X(y, SSH1_CMSG_SESSION_KEY, 3) \ X(y, SSH1_CMSG_USER, 4) \ X(y, SSH1_CMSG_AUTH_RSA, 6) \ X(y, SSH1_SMSG_AUTH_RSA_CHALLENGE, 7) \ X(y, SSH1_CMSG_AUTH_RSA_RESPONSE, 8) \ X(y, SSH1_CMSG_AUTH_PASSWORD, 9) \ X(y, SSH1_CMSG_REQUEST_PTY, 10) \ X(y, SSH1_CMSG_WINDOW_SIZE, 11) \ X(y, SSH1_CMSG_EXEC_SHELL, 12) \ X(y, SSH1_CMSG_EXEC_CMD, 13) \ X(y, SSH1_SMSG_SUCCESS, 14) \ X(y, SSH1_SMSG_FAILURE, 15) \ X(y, SSH1_CMSG_STDIN_DATA, 16) \ X(y, SSH1_SMSG_STDOUT_DATA, 17) \ X(y, SSH1_SMSG_STDERR_DATA, 18) \ X(y, SSH1_CMSG_EOF, 19) \ X(y, SSH1_SMSG_EXIT_STATUS, 20) \ X(y, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, 21) \ X(y, SSH1_MSG_CHANNEL_OPEN_FAILURE, 22) \ X(y, SSH1_MSG_CHANNEL_DATA, 23) \ X(y, SSH1_MSG_CHANNEL_CLOSE, 24) \ X(y, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION, 25) \ X(y, SSH1_SMSG_X11_OPEN, 27) \ X(y, SSH1_CMSG_PORT_FORWARD_REQUEST, 28) \ X(y, SSH1_MSG_PORT_OPEN, 29) \ X(y, SSH1_CMSG_AGENT_REQUEST_FORWARDING, 30) \ X(y, SSH1_SMSG_AGENT_OPEN, 31) \ X(y, SSH1_MSG_IGNORE, 32) \ X(y, SSH1_CMSG_EXIT_CONFIRMATION, 33) \ X(y, SSH1_CMSG_X11_REQUEST_FORWARDING, 34) \ X(y, SSH1_CMSG_AUTH_RHOSTS_RSA, 35) \ X(y, SSH1_MSG_DEBUG, 36) \ X(y, SSH1_CMSG_REQUEST_COMPRESSION, 37) \ X(y, SSH1_CMSG_AUTH_TIS, 39) \ X(y, SSH1_SMSG_AUTH_TIS_CHALLENGE, 40) \ X(y, SSH1_CMSG_AUTH_TIS_RESPONSE, 41) \ X(y, SSH1_CMSG_AUTH_CCARD, 70) \ X(y, SSH1_SMSG_AUTH_CCARD_CHALLENGE, 71) \ X(y, SSH1_CMSG_AUTH_CCARD_RESPONSE, 72) \ /* end of list */ #define SSH1_AUTH_RHOSTS 1 /* 0x1 */ #define SSH1_AUTH_RSA 2 /* 0x2 */ #define SSH1_AUTH_PASSWORD 3 /* 0x3 */ #define SSH1_AUTH_RHOSTS_RSA 4 /* 0x4 */ #define SSH1_AUTH_TIS 5 /* 0x5 */ #define SSH1_AUTH_CCARD 16 /* 0x10 */ #define SSH1_PROTOFLAG_SCREEN_NUMBER 1 /* 0x1 */ /* Mask for protoflags we will echo back to server if seen */ #define SSH1_PROTOFLAGS_SUPPORTED 0 /* 0x1 */ /* * List macro defining SSH-2 message type codes. Some of these depend * on particular contexts (i.e. a previously negotiated kex or auth * method) */ #define SSH2_MESSAGE_TYPES(X, K, A, y) \ X(y, SSH2_MSG_DISCONNECT, 1) \ X(y, SSH2_MSG_IGNORE, 2) \ X(y, SSH2_MSG_UNIMPLEMENTED, 3) \ X(y, SSH2_MSG_DEBUG, 4) \ X(y, SSH2_MSG_SERVICE_REQUEST, 5) \ X(y, SSH2_MSG_SERVICE_ACCEPT, 6) \ X(y, SSH2_MSG_EXT_INFO, 7) \ X(y, SSH2_MSG_KEXINIT, 20) \ X(y, SSH2_MSG_NEWKEYS, 21) \ K(y, SSH2_MSG_KEXDH_INIT, 30, SSH2_PKTCTX_DHGROUP) \ K(y, SSH2_MSG_KEXDH_REPLY, 31, SSH2_PKTCTX_DHGROUP) \ K(y, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, 30, SSH2_PKTCTX_DHGEX) \ K(y, SSH2_MSG_KEX_DH_GEX_REQUEST, 34, SSH2_PKTCTX_DHGEX) \ K(y, SSH2_MSG_KEX_DH_GEX_GROUP, 31, SSH2_PKTCTX_DHGEX) \ K(y, SSH2_MSG_KEX_DH_GEX_INIT, 32, SSH2_PKTCTX_DHGEX) \ K(y, SSH2_MSG_KEX_DH_GEX_REPLY, 33, SSH2_PKTCTX_DHGEX) \ K(y, SSH2_MSG_KEXGSS_INIT, 30, SSH2_PKTCTX_GSSKEX) \ K(y, SSH2_MSG_KEXGSS_CONTINUE, 31, SSH2_PKTCTX_GSSKEX) \ K(y, SSH2_MSG_KEXGSS_COMPLETE, 32, SSH2_PKTCTX_GSSKEX) \ K(y, SSH2_MSG_KEXGSS_HOSTKEY, 33, SSH2_PKTCTX_GSSKEX) \ K(y, SSH2_MSG_KEXGSS_ERROR, 34, SSH2_PKTCTX_GSSKEX) \ K(y, SSH2_MSG_KEXGSS_GROUPREQ, 40, SSH2_PKTCTX_GSSKEX) \ K(y, SSH2_MSG_KEXGSS_GROUP, 41, SSH2_PKTCTX_GSSKEX) \ K(y, SSH2_MSG_KEXRSA_PUBKEY, 30, SSH2_PKTCTX_RSAKEX) \ K(y, SSH2_MSG_KEXRSA_SECRET, 31, SSH2_PKTCTX_RSAKEX) \ K(y, SSH2_MSG_KEXRSA_DONE, 32, SSH2_PKTCTX_RSAKEX) \ K(y, SSH2_MSG_KEX_ECDH_INIT, 30, SSH2_PKTCTX_ECDHKEX) \ K(y, SSH2_MSG_KEX_ECDH_REPLY, 31, SSH2_PKTCTX_ECDHKEX) \ X(y, SSH2_MSG_USERAUTH_REQUEST, 50) \ X(y, SSH2_MSG_USERAUTH_FAILURE, 51) \ X(y, SSH2_MSG_USERAUTH_SUCCESS, 52) \ X(y, SSH2_MSG_USERAUTH_BANNER, 53) \ A(y, SSH2_MSG_USERAUTH_PK_OK, 60, SSH2_PKTCTX_PUBLICKEY) \ A(y, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, 60, SSH2_PKTCTX_PASSWORD) \ A(y, SSH2_MSG_USERAUTH_INFO_REQUEST, 60, SSH2_PKTCTX_KBDINTER) \ A(y, SSH2_MSG_USERAUTH_INFO_RESPONSE, 61, SSH2_PKTCTX_KBDINTER) \ A(y, SSH2_MSG_USERAUTH_GSSAPI_RESPONSE, 60, SSH2_PKTCTX_GSSAPI) \ A(y, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, 61, SSH2_PKTCTX_GSSAPI) \ A(y, SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, 63, SSH2_PKTCTX_GSSAPI) \ A(y, SSH2_MSG_USERAUTH_GSSAPI_ERROR, 64, SSH2_PKTCTX_GSSAPI) \ A(y, SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, 65, SSH2_PKTCTX_GSSAPI) \ A(y, SSH2_MSG_USERAUTH_GSSAPI_MIC, 66, SSH2_PKTCTX_GSSAPI) \ X(y, SSH2_MSG_GLOBAL_REQUEST, 80) \ X(y, SSH2_MSG_REQUEST_SUCCESS, 81) \ X(y, SSH2_MSG_REQUEST_FAILURE, 82) \ X(y, SSH2_MSG_CHANNEL_OPEN, 90) \ X(y, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, 91) \ X(y, SSH2_MSG_CHANNEL_OPEN_FAILURE, 92) \ X(y, SSH2_MSG_CHANNEL_WINDOW_ADJUST, 93) \ X(y, SSH2_MSG_CHANNEL_DATA, 94) \ X(y, SSH2_MSG_CHANNEL_EXTENDED_DATA, 95) \ X(y, SSH2_MSG_CHANNEL_EOF, 96) \ X(y, SSH2_MSG_CHANNEL_CLOSE, 97) \ X(y, SSH2_MSG_CHANNEL_REQUEST, 98) \ X(y, SSH2_MSG_CHANNEL_SUCCESS, 99) \ X(y, SSH2_MSG_CHANNEL_FAILURE, 100) \ /* end of list */ #define DEF_ENUM_UNIVERSAL(y, name, value) name = value, #define DEF_ENUM_CONTEXTUAL(y, name, value, context) name = value, enum { SSH1_MESSAGE_TYPES(DEF_ENUM_UNIVERSAL, y) SSH2_MESSAGE_TYPES(DEF_ENUM_UNIVERSAL, DEF_ENUM_CONTEXTUAL, DEF_ENUM_CONTEXTUAL, y) /* Virtual packet type, for packets too short to even have a type */ SSH_MSG_NO_TYPE_CODE = 256 }; #undef DEF_ENUM_UNIVERSAL #undef DEF_ENUM_CONTEXTUAL /* * SSH-1 agent messages. */ #define SSH1_AGENTC_REQUEST_RSA_IDENTITIES 1 #define SSH1_AGENT_RSA_IDENTITIES_ANSWER 2 #define SSH1_AGENTC_RSA_CHALLENGE 3 #define SSH1_AGENT_RSA_RESPONSE 4 #define SSH1_AGENTC_ADD_RSA_IDENTITY 7 #define SSH1_AGENTC_REMOVE_RSA_IDENTITY 8 #define SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 /* openssh private? */ /* * Messages common to SSH-1 and OpenSSH's SSH-2. */ #define SSH_AGENT_FAILURE 5 #define SSH_AGENT_SUCCESS 6 /* * OpenSSH's SSH-2 agent messages. */ #define SSH2_AGENTC_REQUEST_IDENTITIES 11 #define SSH2_AGENT_IDENTITIES_ANSWER 12 #define SSH2_AGENTC_SIGN_REQUEST 13 #define SSH2_AGENT_SIGN_RESPONSE 14 #define SSH2_AGENTC_ADD_IDENTITY 17 #define SSH2_AGENTC_REMOVE_IDENTITY 18 #define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 #define SSH2_AGENTC_EXTENSION 27 #define SSH_AGENT_EXTENSION_FAILURE 28 /* * Assorted other SSH-related enumerations. */ #define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 /* 0x1 */ #define SSH2_DISCONNECT_PROTOCOL_ERROR 2 /* 0x2 */ #define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 /* 0x3 */ #define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 /* 0x4 */ #define SSH2_DISCONNECT_MAC_ERROR 5 /* 0x5 */ #define SSH2_DISCONNECT_COMPRESSION_ERROR 6 /* 0x6 */ #define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 /* 0x7 */ #define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 /* 0x8 */ #define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 /* 0x9 */ #define SSH2_DISCONNECT_CONNECTION_LOST 10 /* 0xa */ #define SSH2_DISCONNECT_BY_APPLICATION 11 /* 0xb */ #define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 /* 0xc */ #define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 /* 0xd */ #define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 /* 0xe */ #define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 /* 0xf */ #define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 /* 0x1 */ #define SSH2_OPEN_CONNECT_FAILED 2 /* 0x2 */ #define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 /* 0x3 */ #define SSH2_OPEN_RESOURCE_SHORTAGE 4 /* 0x4 */ #define SSH2_EXTENDED_DATA_STDERR 1 /* 0x1 */ enum { #if 0 /* removed in xtruss */ /* TTY modes with opcodes defined consistently in the SSH specs. */ #define TTYMODE_CHAR(name, val, index) SSH_TTYMODE_##name = val, #define TTYMODE_FLAG(name, val, field, mask) SSH_TTYMODE_##name = val, #include "ssh/ttymode-list.h" #undef TTYMODE_CHAR #undef TTYMODE_FLAG #endif /* removed in xtruss */ /* Modes encoded differently between SSH-1 and SSH-2, for which we * make up our own dummy opcodes to avoid confusion. */ TTYMODE_dummy = 255, TTYMODE_ISPEED, TTYMODE_OSPEED, /* Limiting value that we can use as an array bound below */ TTYMODE_LIMIT, /* The real opcodes for terminal speeds. */ TTYMODE_ISPEED_SSH1 = 192, TTYMODE_OSPEED_SSH1 = 193, TTYMODE_ISPEED_SSH2 = 128, TTYMODE_OSPEED_SSH2 = 129, /* And the opcode that ends a list. */ TTYMODE_END_OF_LIST = 0 }; struct ssh_ttymodes { /* A boolean per mode, indicating whether it's set. */ bool have_mode[TTYMODE_LIMIT]; /* The actual value for each mode. */ unsigned mode_val[TTYMODE_LIMIT]; }; struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf); struct ssh_ttymodes read_ttymodes_from_packet( BinarySource *bs, int ssh_version); void write_ttymodes_to_packet(BinarySink *bs, int ssh_version, struct ssh_ttymodes modes); const char *ssh1_pkt_type(int type); const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type); bool ssh2_pkt_type_code_valid(unsigned type); /* * Need this to warn about support for the original SSH-2 keyfile * format. */ void old_keyfile_warning(void); /* * Flags indicating implementation bugs that we know how to mitigate * if we think the other end has them. */ #define SSH_IMPL_BUG_LIST(X) \ X(BUG_CHOKES_ON_SSH1_IGNORE) \ X(BUG_SSH2_HMAC) \ X(BUG_NEEDS_SSH1_PLAIN_PASSWORD) \ X(BUG_CHOKES_ON_RSA) \ X(BUG_SSH2_RSA_PADDING) \ X(BUG_SSH2_DERIVEKEY) \ X(BUG_SSH2_REKEY) \ X(BUG_SSH2_PK_SESSIONID) \ X(BUG_SSH2_MAXPKT) \ X(BUG_CHOKES_ON_SSH2_IGNORE) \ X(BUG_CHOKES_ON_WINADJ) \ X(BUG_SENDS_LATE_REQUEST_REPLY) \ X(BUG_SSH2_OLDGEX) \ /* end of list */ #define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing, enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) }; #undef TMP_DECLARE_LOG2_ENUM #define TMP_DECLARE_REAL_ENUM(thing) thing = 1 << log2_##thing, enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_REAL_ENUM) }; #undef TMP_DECLARE_REAL_ENUM /* Shared system for allocating local SSH channel ids. Expects to be * passed a tree full of structs that have a field called 'localid' of * type unsigned, and will check that! */ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset); #define alloc_channel_id(tree, type) \ TYPECHECK(&((type *)0)->localid == (unsigned *)0, \ alloc_channel_id_general(tree, offsetof(type, localid))) void add_to_commasep(strbuf *buf, const char *data); bool get_commasep_word(ptrlen *list, ptrlen *word); int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key); typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void); void ssh_transient_hostkey_cache_free(ssh_transient_hostkey_cache *thc); void ssh_transient_hostkey_cache_add( ssh_transient_hostkey_cache *thc, ssh_key *key); bool ssh_transient_hostkey_cache_verify( ssh_transient_hostkey_cache *thc, ssh_key *key); bool ssh_transient_hostkey_cache_has( ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg); bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc); work/sshcr.h0000664000000000000000000000644314714222463010237 0ustar /* * Coroutine mechanics used in PuTTY's SSH code. */ #ifndef PUTTY_SSHCR_H #define PUTTY_SSHCR_H /* * If these macros look impenetrable to you, you might find it helpful * to read * * https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html * * which explains the theory behind these macros. * * In particular, if you are getting `case expression not constant' * errors when building with MS Visual Studio, this is because MS's * Edit and Continue debugging feature causes their compiler to * violate ANSI C. To disable Edit and Continue debugging: * * - right-click ssh.c in the FileView * - click Settings * - select the C/C++ tab and the General category * - under `Debug info:', select anything _other_ than `Program * Database for Edit and Continue'. */ #define crBegin(v) do { int *crLine = &v; switch(v) { case 0: #define crBeginState crBegin(s->crLine) #define crStateP(t, v) \ struct t *s; \ if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; } \ s = (v); #define crState(t) crStateP(t, ssh->t) #define crFinish(z) } *crLine = 0; return (z); } while (0) #define crFinishV } *crLine = 0; return; } while (0) #define crFinishFreed(z) } return (z); } while (0) #define crFinishFreedV } return; } while (0) #define crFinishFree(z) } sfree(s); return (z); } while (0) #define crFinishFreeV } sfree(s); return; } while (0) #define crReturn(z) \ do {\ *crLine =__LINE__; return (z); case __LINE__:;\ } while (0) #define crReturnV \ do {\ *crLine=__LINE__; return; case __LINE__:;\ } while (0) #define crStop(z) do{ *crLine = 0; return (z); }while(0) #define crStopV do{ *crLine = 0; return; }while(0) /* * The crMaybeWaitUntil macros could have been more easily written in * terms of the simple crReturn above, by writing things like * * while (!condition) { crReturn(whatever); } * * (or do-while in the case of crWaitUntil). But it's better to do it * directly by writing _once_ to crLine before first testing the * condition, because this way it's robust against the condition check * potentially freeing the entire coroutine state structure as a side * effect (as long as it also evaluates false if it does that), * because we don't write into crLine between the condition evaluating * to false and the 'return' statement. */ #define crMaybeWaitUntil(c) \ do { \ *crLine =__LINE__; \ case __LINE__: if (!(c)) return 0; \ } while (0) #define crMaybeWaitUntilV(c) \ do { \ *crLine =__LINE__; \ case __LINE__: if (!(c)) return; \ } while (0) #define crWaitUntil(c) \ do { \ *crLine =__LINE__; return; \ case __LINE__: if (!(c)) return 0; \ } while (0) #define crWaitUntilV(c) \ do { \ *crLine =__LINE__; return; \ case __LINE__: if (!(c)) return; \ } while (0) #endif /* PUTTY_SSHCR_H */ work/storage.h0000664000000000000000000001020014714222463010543 0ustar /* * storage.h: interface defining functions for storage and recovery * of PuTTY's persistent data. */ #ifndef PUTTY_STORAGE_H #define PUTTY_STORAGE_H /* ---------------------------------------------------------------------- * Functions to save and restore PuTTY sessions. Note that this is * only the low-level code to do the reading and writing. The * higher-level code that translates an internal Conf structure into * a set of (key,value) pairs in their external storage format is * elsewhere, since it doesn't (mostly) change between platforms. */ /* * Write a saved session. The caller is expected to call * open_setting_w() to get a `void *' handle, then pass that to a * number of calls to write_setting_s() and write_setting_i(), and * then close it using close_settings_w(). At the end of this call * sequence the settings should have been written to the PuTTY * persistent storage area. * * A given key will be written at most once while saving a session. * Keys may be up to 255 characters long. String values have no length * limit. * * Any returned error message must be freed after use. */ settings_w *open_settings_w(const char *sessionname, char **errmsg); void write_setting_s(settings_w *handle, const char *key, const char *value); void write_setting_i(settings_w *handle, const char *key, int value); void write_setting_filename(settings_w *handle, const char *key, Filename *value); void write_setting_fontspec(settings_w *handle, const char *key, FontSpec *font); void close_settings_w(settings_w *handle); /* * Read a saved session. The caller is expected to call * open_setting_r() to get a `void *' handle, then pass that to a * number of calls to read_setting_s() and read_setting_i(), and * then close it using close_settings_r(). * * read_setting_s() returns a dynamically allocated string which the * caller must free. read_setting_filename() and * read_setting_fontspec() likewise return dynamically allocated * structures. * * If a particular string setting is not present in the session, * read_setting_s() can return NULL, in which case the caller * should invent a sensible default. If an integer setting is not * present, read_setting_i() returns its provided default. */ settings_r *open_settings_r(const char *sessionname); char *read_setting_s(settings_r *handle, const char *key); int read_setting_i(settings_r *handle, const char *key, int defvalue); Filename *read_setting_filename(settings_r *handle, const char *key); FontSpec *read_setting_fontspec(settings_r *handle, const char *key); void close_settings_r(settings_r *handle); /* * Delete a whole saved session. */ void del_settings(const char *sessionname); /* * Enumerate all saved sessions. */ settings_e *enum_settings_start(void); bool enum_settings_next(settings_e *handle, strbuf *out); void enum_settings_finish(settings_e *handle); /* ---------------------------------------------------------------------- * Functions to access PuTTY's host key database. */ /* * See if a host key matches the database entry. Return values can * be 0 (entry matches database), 1 (entry is absent in database), * or 2 (entry exists in database and is different). */ int verify_host_key(const char *hostname, int port, const char *keytype, const char *key); /* * Write a host key into the database, overwriting any previous * entry that might have been there. */ void store_host_key(const char *hostname, int port, const char *keytype, const char *key); /* ---------------------------------------------------------------------- * Functions to access PuTTY's random number seed file. */ typedef void (*noise_consumer_t) (void *data, int len); /* * Read PuTTY's random seed file and pass its contents to a noise * consumer function. */ void read_random_seed(noise_consumer_t consumer); /* * Write PuTTY's random seed file from a given chunk of noise. */ void write_random_seed(void *data, int len); /* ---------------------------------------------------------------------- * Cleanup function: remove all of PuTTY's persistent state. */ void cleanup_all(void); #endif work/timing.c0000664000000000000000000001371714714222463010401 0ustar /* * timing.c * * This module tracks any timers set up by schedule_timer(). It * keeps all the currently active timers in a list; it informs the * front end of when the next timer is due to go off if that * changes; and, very importantly, it tracks the context pointers * passed to schedule_timer(), so that if a context is freed all * the timers associated with it can be immediately annulled. * * * The problem is that computer clocks aren't perfectly accurate. * The GETTICKCOUNT function returns a 32bit number that normally * increases by about 1000 every second. On windows this uses the PC's * interrupt timer and so is only accurate to around 20ppm. On unix it's * a value that's calculated from the current UTC time and so is in theory * accurate in the long term but may jitter and jump in the short term. * * What PuTTY needs from these timers is simply a way of delaying the * calling of a function for a little while, if it's occasionally called a * little early or late that's not a problem. So to protect against clock * jumps schedule_timer records the time that it was called in the timer * structure. With this information the run_timers function can see when * the current GETTICKCOUNT value is after the time the event should be * fired OR before the time it was set. In the latter case the clock must * have jumped, the former is (probably) just the normal passage of time. * */ #include #include #include "putty.h" #include "tree234.h" struct timer { timer_fn_t fn; void *ctx; unsigned long now; unsigned long when_set; }; static tree234 *timers = NULL; static tree234 *timer_contexts = NULL; static unsigned long now = 0L; static int compare_timers(void *av, void *bv) { struct timer *a = (struct timer *)av; struct timer *b = (struct timer *)bv; long at = a->now - now; long bt = b->now - now; if (at < bt) return -1; else if (at > bt) return +1; /* * Failing that, compare on the other two fields, just so that * we don't get unwanted equality. */ #if defined(__LCC__) || defined(__clang__) /* lcc won't let us compare function pointers. Legal, but annoying. */ { int c = memcmp(&a->fn, &b->fn, sizeof(a->fn)); if (c) return c; } #else if (a->fn < b->fn) return -1; else if (a->fn > b->fn) return +1; #endif if (a->ctx < b->ctx) return -1; else if (a->ctx > b->ctx) return +1; /* * Failing _that_, the two entries genuinely are equal, and we * never have a need to store them separately in the tree. */ return 0; } static int compare_timer_contexts(void *av, void *bv) { char *a = (char *)av; char *b = (char *)bv; if (a < b) return -1; else if (a > b) return +1; return 0; } static void init_timers(void) { if (!timers) { timers = newtree234(compare_timers); timer_contexts = newtree234(compare_timer_contexts); now = GETTICKCOUNT(); } } unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx) { unsigned long when; struct timer *t, *first; init_timers(); now = GETTICKCOUNT(); when = ticks + now; /* * Just in case our various defences against timing skew fail * us: if we try to schedule a timer that's already in the * past, we instead schedule it for the immediate future. */ if (when - now <= 0) when = now + 1; t = snew(struct timer); t->fn = fn; t->ctx = ctx; t->now = when; t->when_set = now; if (t != add234(timers, t)) { sfree(t); /* identical timer already exists */ } else { add234(timer_contexts, t->ctx);/* don't care if this fails */ } first = (struct timer *)index234(timers, 0); if (first == t) { /* * This timer is the very first on the list, so we must * notify the front end. */ timer_change_notify(first->now); } return when; } unsigned long timing_last_clock(void) { /* * Return the last value we stored in 'now'. In particular, * calling this just after schedule_timer returns the value of * 'now' that was used to decide when the timer you just set would * go off. */ return now; } /* * Call to run any timers whose time has reached the present. * Returns the time (in ticks) expected until the next timer after * that triggers. */ bool run_timers(unsigned long anow, unsigned long *next) { struct timer *first; init_timers(); now = GETTICKCOUNT(); while (1) { first = (struct timer *)index234(timers, 0); if (!first) return false; /* no timers remaining */ if (find234(timer_contexts, first->ctx, NULL) == NULL) { /* * This timer belongs to a context that has been * expired. Delete it without running. */ delpos234(timers, 0); sfree(first); } else if (now - (first->when_set - 10) > first->now - (first->when_set - 10)) { /* * This timer is active and has reached its running * time. Run it. */ delpos234(timers, 0); first->fn(first->ctx, first->now); sfree(first); } else { /* * This is the first still-active timer that is in the * future. Return how long it has yet to go. */ *next = first->now; return true; } } } /* * Call to expire all timers associated with a given context. */ void expire_timer_context(void *ctx) { init_timers(); /* * We don't bother to check the return value; if the context * already wasn't in the tree (presumably because no timers * ever actually got scheduled for it) then that's fine and we * simply don't need to do anything. */ del234(timer_contexts, ctx); } work/tree234.h0000664000000000000000000001605714714222463010307 0ustar /* * tree234.h: header defining functions in tree234.c. * * This file is copyright 1999-2001 Simon Tatham. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef TREE234_H #define TREE234_H /* * This typedef is opaque outside tree234.c itself. */ typedef struct tree234_Tag tree234; typedef int (*cmpfn234) (void *, void *); /* * Create a 2-3-4 tree. If `cmp' is NULL, the tree is unsorted, and * lookups by key will fail: you can only look things up by numeric * index, and you have to use addpos234() and delpos234(). */ tree234 *newtree234(cmpfn234 cmp); /* * Free a 2-3-4 tree (not including freeing the elements). */ void freetree234(tree234 * t); /* * Add an element e to a sorted 2-3-4 tree t. Returns e on success, * or if an existing element compares equal, returns that. */ void *add234(tree234 * t, void *e); /* * Add an element e to an unsorted 2-3-4 tree t. Returns e on * success, NULL on failure. (Failure should only occur if the * index is out of range or the tree is sorted.) * * Index range can be from 0 to the tree's current element count, * inclusive. */ void *addpos234(tree234 * t, void *e, int index); /* * Look up the element at a given numeric index in a 2-3-4 tree. * Returns NULL if the index is out of range. * * One obvious use for this function is in iterating over the whole * of a tree (sorted or unsorted): * * for (i = 0; (p = index234(tree, i)) != NULL; i++) consume(p); * * or * * int maxcount = count234(tree); * for (i = 0; i < maxcount; i++) { * p = index234(tree, i); * assert(p != NULL); * consume(p); * } */ void *index234(tree234 * t, int index); /* * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not * found. e is always passed as the first argument to cmp, so cmp * can be an asymmetric function if desired. cmp can also be passed * as NULL, in which case the compare function from the tree proper * will be used. * * Three of these functions are special cases of findrelpos234. The * non-`pos' variants lack the `index' parameter: if the parameter * is present and non-NULL, it must point to an integer variable * which will be filled with the numeric index of the returned * element. * * The non-`rel' variants lack the `relation' parameter. This * parameter allows you to specify what relation the element you * provide has to the element you're looking for. This parameter * can be: * * REL234_EQ - find only an element that compares equal to e * REL234_LT - find the greatest element that compares < e * REL234_LE - find the greatest element that compares <= e * REL234_GT - find the smallest element that compares > e * REL234_GE - find the smallest element that compares >= e * * Non-`rel' variants assume REL234_EQ. * * If `rel' is REL234_GT or REL234_LT, the `e' parameter may be * NULL. In this case, REL234_GT will return the smallest element * in the tree, and REL234_LT will return the greatest. This gives * an alternative means of iterating over a sorted tree, instead of * using index234: * * // to loop forwards * for (p = NULL; (p = findrel234(tree, p, NULL, REL234_GT)) != NULL ;) * consume(p); * * // to loop backwards * for (p = NULL; (p = findrel234(tree, p, NULL, REL234_LT)) != NULL ;) * consume(p); */ enum { REL234_EQ, REL234_LT, REL234_LE, REL234_GT, REL234_GE }; void *find234(tree234 * t, void *e, cmpfn234 cmp); void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation); void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index); void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, int relation, int *index); /* * A more general search type still. Use search234_start() to * initialise one of these state structures; it will fill in * state->element with an element of the tree, and state->index with * the index of that element. If you don't like that element, call * search234_step, with direction == -1 if you want an element earlier * in the tree, or +1 if you want a later one. * * If either function returns state->element == NULL, then you've * narrowed the search to a point between two adjacent elements, so * there are no further elements left to return consistent with the * constraints you've imposed. In this case, state->index tells you * how many elements come before the point you narrowed down to. After * this, you mustn't call search234_step again (unless the state * structure is first reinitialised). * * The use of this search system is that you get both the candidate * element _and_ its index at every stage, so you can use both of them * to make your decision. Also, you can remember element pointers from * earlier in the search. * * The fields beginning with underscores are private to the * implementation, and only exposed so that clients can know how much * space to allocate for the structure as a whole. Don't modify them. * (Except that it's safe to copy the whole structure.) */ typedef struct search234_state { void *element; int index; int _lo, _hi, _last, _base; void *_node; } search234_state; void search234_start(search234_state *state, tree234 *t); void search234_step(search234_state *state, int direction); /* * Delete an element e in a 2-3-4 tree. Does not free the element, * merely removes all links to it from the tree nodes. * * delpos234 deletes the element at a particular tree index: it * works on both sorted and unsorted trees. * * del234 deletes the element passed to it, so it only works on * sorted trees. (It's equivalent to using findpos234 to determine * the index of an element, and then passing that index to * delpos234.) * * Both functions return a pointer to the element they delete, for * the user to free or pass on elsewhere or whatever. If the index * is out of range (delpos234) or the element is already not in the * tree (del234) then they return NULL. */ void *del234(tree234 * t, void *e); void *delpos234(tree234 * t, int index); /* * Return the total element count of a tree234. */ int count234(tree234 * t); #endif /* TREE234_H */ work/unix/0000775000000000000000000000000014714222463007720 5ustar work/unix/CMakeLists.txt0000664000000000000000000000154214714222463012462 0ustar set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_sources_from_current_dir(utils utils/block_signal.c utils/cloexec.c utils/dputs.c utils/filename.c utils/fontspec.c utils/getticks.c utils/nonblock.c utils/pollwrap.c utils/signal.c # We want the ISO C implementation of ltime(), because we don't have # a local better alternative ../utils/ltime.c) add_sources_from_current_dir(eventloop cliloop.c uxsel.c) add_sources_from_current_dir(console console.c) add_sources_from_current_dir(network network.c peerinfo.c x11.c) add_executable(xtruss uxxtruss.c ${CMAKE_SOURCE_DIR}/xtruss.c ${CMAKE_SOURCE_DIR}/xtruss-trace.c ${CMAKE_SOURCE_DIR}/xtruss-proxy.c ${CMAKE_SOURCE_DIR}/xtruss-record.c ${CMAKE_SOURCE_DIR}/norand.c) target_link_libraries(xtruss eventloop console network utils crypto) installed_program(xtruss) work/unix/cliloop.c0000664000000000000000000000673714714222463011542 0ustar #include #include "putty.h" void cli_main_loop(cliloop_pw_setup_t pw_setup, cliloop_pw_check_t pw_check, cliloop_continue_t cont, void *ctx) { unsigned long now = GETTICKCOUNT(); int *fdlist = NULL; size_t fdsize = 0; pollwrapper *pw = pollwrap_new(); while (true) { int rwx; int ret; int fdstate; unsigned long next; pollwrap_clear(pw); if (!pw_setup(ctx, pw)) break; /* our client signalled emergency exit */ /* Count the currently active fds. */ size_t nfds = 0; for (int fd = first_fd(&fdstate, &rwx); fd >= 0; fd = next_fd(&fdstate, &rwx)) nfds++; /* Expand the fdlist buffer if necessary. */ sgrowarray(fdlist, fdsize, nfds); /* * Add all currently open uxsel fds to pw, and store them in * fdlist as well. */ size_t fdcount = 0; for (int fd = first_fd(&fdstate, &rwx); fd >= 0; fd = next_fd(&fdstate, &rwx)) { fdlist[fdcount++] = fd; pollwrap_add_fd_rwx(pw, fd, rwx); } if (toplevel_callback_pending()) { ret = pollwrap_poll_instant(pw); } else if (run_timers(now, &next)) { do { unsigned long then; long ticks; then = now; now = GETTICKCOUNT(); if (now - then > next - then) ticks = 0; else ticks = next - now; bool overflow = false; if (ticks > INT_MAX) { ticks = INT_MAX; overflow = true; } ret = pollwrap_poll_timeout(pw, ticks); if (ret == 0 && !overflow) now = next; else now = GETTICKCOUNT(); } while (ret < 0 && errno == EINTR); } else { ret = pollwrap_poll_endless(pw); } if (ret < 0 && errno == EINTR) continue; if (ret < 0) { perror("poll"); exit(1); } bool found_fd = (ret > 0); for (size_t i = 0; i < fdcount; i++) { int fd = fdlist[i]; int rwx = pollwrap_get_fd_rwx(pw, fd); /* * We must process exceptional notifications before * ordinary readability ones, or we may go straight * past the urgent marker. */ if (rwx & SELECT_X) select_result(fd, SELECT_X); if (rwx & SELECT_R) select_result(fd, SELECT_R); if (rwx & SELECT_W) select_result(fd, SELECT_W); } pw_check(ctx, pw); bool ran_callback = run_toplevel_callbacks(); if (!cont(ctx, found_fd, ran_callback)) break; } pollwrap_free(pw); sfree(fdlist); } bool cliloop_no_pw_setup(void *ctx, pollwrapper *pw) { return true; } void cliloop_no_pw_check(void *ctx, pollwrapper *pw) {} bool cliloop_always_continue(void *ctx, bool fd, bool cb) { return true; } /* * Any application using this main loop doesn't need to do anything * when uxsel adds or removes an fd, because we synchronously re-check * the current list every time we go round the main loop above. */ uxsel_id *uxsel_input_add(int fd, int rwx) { return NULL; } void uxsel_input_remove(uxsel_id *id) { } work/unix/console.c0000664000000000000000000003455214714222463011537 0ustar /* * uxcons.c: various interactive-prompt routines shared between the * Unix console PuTTY tools */ #include #include #include #include #include #include #include #include #include "putty.h" #include "storage.h" #include "ssh.h" #include "console.h" static struct termios orig_termios_stderr; static bool stderr_is_a_tty; void stderr_tty_init() { /* Ensure that if stderr is a tty, we can get it back to a sane state. */ if (isatty(STDERR_FILENO)) { stderr_is_a_tty = true; tcgetattr(STDERR_FILENO, &orig_termios_stderr); } } void premsg(struct termios *cf) { if (stderr_is_a_tty) { tcgetattr(STDERR_FILENO, cf); tcsetattr(STDERR_FILENO, TCSADRAIN, &orig_termios_stderr); } } void postmsg(struct termios *cf) { if (stderr_is_a_tty) tcsetattr(STDERR_FILENO, TCSADRAIN, cf); } void cleanup_exit(int code) { /* * Clean up. */ sk_cleanup(); random_save_seed(); exit(code); } void console_print_error_msg(const char *prefix, const char *msg) { struct termios cf; premsg(&cf); fputs(prefix, stderr); fputs(": ", stderr); fputs(msg, stderr); fputc('\n', stderr); fflush(stderr); postmsg(&cf); } #if 0 /* removed in xtruss */ /* * Wrapper around Unix read(2), suitable for use on a file descriptor * that's been set into nonblocking mode. Handles EAGAIN/EWOULDBLOCK * by means of doing a one-fd poll and then trying again; all other * errors (including errors from poll) are returned to the caller. */ static int block_and_read(int fd, void *buf, size_t len) { int ret; pollwrapper *pw = pollwrap_new(); while ((ret = read(fd, buf, len)) < 0 && ( #ifdef EAGAIN (errno == EAGAIN) || #endif #ifdef EWOULDBLOCK (errno == EWOULDBLOCK) || #endif false)) { pollwrap_clear(pw); pollwrap_add_fd_rwx(pw, fd, SELECT_R); do { ret = pollwrap_poll_endless(pw); } while (ret < 0 && errno == EINTR); assert(ret != 0); if (ret < 0) { pollwrap_free(pw); return ret; } assert(pollwrap_check_fd_rwx(pw, fd, SELECT_R)); } pollwrap_free(pw); return ret; } int console_verify_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **fingerprints, void (*callback)(void *ctx, int result), void *ctx) { int ret; char line[32]; struct termios cf; const char *common_fmt, *intro, *prompt; /* * Verify the key. */ ret = verify_host_key(host, port, keytype, keystr); if (ret == 0) /* success - key matched OK */ return 1; premsg(&cf); if (ret == 2) { /* key was different */ common_fmt = hk_wrongmsg_common_fmt; intro = hk_wrongmsg_interactive_intro; prompt = hk_wrongmsg_interactive_prompt; } else { /* key was absent */ common_fmt = hk_absentmsg_common_fmt; intro = hk_absentmsg_interactive_intro; prompt = hk_absentmsg_interactive_prompt; } FingerprintType fptype_default = ssh2_pick_default_fingerprint(fingerprints); fprintf(stderr, common_fmt, keytype, fingerprints[fptype_default]); if (console_batch_mode) { fputs(console_abandoned_msg, stderr); return 0; } fputs(intro, stderr); fflush(stderr); while (true) { fputs(prompt, stderr); fflush(stderr); struct termios oldmode, newmode; tcgetattr(0, &oldmode); newmode = oldmode; newmode.c_lflag |= ECHO | ISIG | ICANON; tcsetattr(0, TCSANOW, &newmode); line[0] = '\0'; if (block_and_read(0, line, sizeof(line) - 1) <= 0) /* handled below */; tcsetattr(0, TCSANOW, &oldmode); if (line[0] == 'i' || line[0] == 'I') { fprintf(stderr, "Full public key:\n%s\n", keydisp); if (fingerprints[SSH_FPTYPE_SHA256]) fprintf(stderr, "SHA256 key fingerprint:\n%s\n", fingerprints[SSH_FPTYPE_SHA256]); if (fingerprints[SSH_FPTYPE_MD5]) fprintf(stderr, "MD5 key fingerprint:\n%s\n", fingerprints[SSH_FPTYPE_MD5]); } else { break; } } /* In case of misplaced reflexes from another program, also recognise 'q' * as 'abandon connection rather than trust this key' */ if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n' && line[0] != 'q' && line[0] != 'Q') { if (line[0] == 'y' || line[0] == 'Y') store_host_key(host, port, keytype, keystr); postmsg(&cf); return 1; } else { fputs(console_abandoned_msg, stderr); postmsg(&cf); return 0; } } int console_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, void (*callback)(void *ctx, int result), void *ctx) { char line[32]; struct termios cf; premsg(&cf); fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname); if (console_batch_mode) { fputs(console_abandoned_msg, stderr); postmsg(&cf); return 0; } fputs(console_continue_prompt, stderr); fflush(stderr); { struct termios oldmode, newmode; tcgetattr(0, &oldmode); newmode = oldmode; newmode.c_lflag |= ECHO | ISIG | ICANON; tcsetattr(0, TCSANOW, &newmode); line[0] = '\0'; if (block_and_read(0, line, sizeof(line) - 1) <= 0) /* handled below */; tcsetattr(0, TCSANOW, &oldmode); } if (line[0] == 'y' || line[0] == 'Y') { postmsg(&cf); return 1; } else { fputs(console_abandoned_msg, stderr); postmsg(&cf); return 0; } } int console_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, int result), void *ctx) { char line[32]; struct termios cf; premsg(&cf); fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs); if (console_batch_mode) { fputs(console_abandoned_msg, stderr); postmsg(&cf); return 0; } fputs(console_continue_prompt, stderr); fflush(stderr); { struct termios oldmode, newmode; tcgetattr(0, &oldmode); newmode = oldmode; newmode.c_lflag |= ECHO | ISIG | ICANON; tcsetattr(0, TCSANOW, &newmode); line[0] = '\0'; if (block_and_read(0, line, sizeof(line) - 1) <= 0) /* handled below */; tcsetattr(0, TCSANOW, &oldmode); } if (line[0] == 'y' || line[0] == 'Y') { postmsg(&cf); return 1; } else { fputs(console_abandoned_msg, stderr); postmsg(&cf); return 0; } } /* * Ask whether to wipe a session log file before writing to it. * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). */ int console_askappend(LogPolicy *lp, Filename *filename, void (*callback)(void *ctx, int result), void *ctx) { static const char msgtemplate[] = "The session log file \"%.*s\" already exists.\n" "You can overwrite it with a new session log,\n" "append your session log to the end of it,\n" "or disable session logging for this session.\n" "Enter \"y\" to wipe the file, \"n\" to append to it,\n" "or just press Return to disable logging.\n" "Wipe the log file? (y/n, Return cancels logging) "; static const char msgtemplate_batch[] = "The session log file \"%.*s\" already exists.\n" "Logging will not be enabled.\n"; char line[32]; struct termios cf; premsg(&cf); if (console_batch_mode) { fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename->path); fflush(stderr); return 0; } fprintf(stderr, msgtemplate, FILENAME_MAX, filename->path); fflush(stderr); { struct termios oldmode, newmode; tcgetattr(0, &oldmode); newmode = oldmode; newmode.c_lflag |= ECHO | ISIG | ICANON; tcsetattr(0, TCSANOW, &newmode); line[0] = '\0'; if (block_and_read(0, line, sizeof(line) - 1) <= 0) /* handled below */; tcsetattr(0, TCSANOW, &oldmode); } postmsg(&cf); if (line[0] == 'y' || line[0] == 'Y') return 2; else if (line[0] == 'n' || line[0] == 'N') return 1; else return 0; } bool console_antispoof_prompt = true; bool console_set_trust_status(Seat *seat, bool trusted) { if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { /* * In batch mode, we don't need to worry about the server * mimicking our interactive authentication, because the user * already knows not to expect any. * * If standard input isn't connected to a terminal, likewise, * because even if the server did send a spoof authentication * prompt, the user couldn't respond to it via the terminal * anyway. * * We also vacuously return success if the user has purposely * disabled the antispoof prompt. */ return true; } return false; } /* * Warn about the obsolescent key file format. * * Uniquely among these functions, this one does _not_ expect a * frontend handle. This means that if PuTTY is ported to a * platform which requires frontend handles, this function will be * an anomaly. Fortunately, the problem it addresses will not have * been present on that platform, so it can plausibly be * implemented as an empty function. */ void old_keyfile_warning(void) { static const char message[] = "You are loading an SSH-2 private key which has an\n" "old version of the file format. This means your key\n" "file is not fully tamperproof. Future versions of\n" "PuTTY may stop supporting this private key format,\n" "so we recommend you convert your key to the new\n" "format.\n" "\n" "Once the key is loaded into PuTTYgen, you can perform\n" "this conversion simply by saving it again.\n"; struct termios cf; premsg(&cf); fputs(message, stderr); postmsg(&cf); } void console_logging_error(LogPolicy *lp, const char *string) { /* Errors setting up logging are considered important, so they're * displayed to standard error even when not in verbose mode */ struct termios cf; premsg(&cf); fprintf(stderr, "%s\n", string); fflush(stderr); postmsg(&cf); } void console_eventlog(LogPolicy *lp, const char *string) { /* Ordinary Event Log entries are displayed in the same way as * logging errors, but only in verbose mode */ if (lp_verbose(lp)) console_logging_error(lp, string); } StripCtrlChars *console_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) { return stripctrl_new(bs_out, false, 0); } /* * Special functions to read and print to the console for password * prompts and the like. Uses /dev/tty or stdin/stderr, in that order * of preference; also sanitises escape sequences out of the text, on * the basis that it might have been sent by a hostile SSH server * doing malicious keyboard-interactive. */ static void console_open(FILE **outfp, int *infd) { int fd; if ((fd = open("/dev/tty", O_RDWR)) >= 0) { *infd = fd; *outfp = fdopen(*infd, "w"); } else { *infd = 0; *outfp = stderr; } } static void console_close(FILE *outfp, int infd) { if (outfp != stderr) fclose(outfp); /* will automatically close infd too */ } static void console_write(FILE *outfp, ptrlen data) { fwrite(data.ptr, 1, data.len, outfp); fflush(outfp); } int console_get_userpass_input(prompts_t *p) { size_t curr_prompt; FILE *outfp = NULL; int infd; /* * Zero all the results, in case we abort half-way through. */ { int i; for (i = 0; i < p->n_prompts; i++) prompt_set_result(p->prompts[i], ""); } if (p->n_prompts && console_batch_mode) return 0; console_open(&outfp, &infd); /* * Preamble. */ /* We only print the `name' caption if we have to... */ if (p->name_reqd && p->name) { ptrlen plname = ptrlen_from_asciz(p->name); console_write(outfp, plname); if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) console_write(outfp, PTRLEN_LITERAL("\n")); } /* ...but we always print any `instruction'. */ if (p->instruction) { ptrlen plinst = ptrlen_from_asciz(p->instruction); console_write(outfp, plinst); if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) console_write(outfp, PTRLEN_LITERAL("\n")); } for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { struct termios oldmode, newmode; prompt_t *pr = p->prompts[curr_prompt]; tcgetattr(infd, &oldmode); newmode = oldmode; newmode.c_lflag |= ISIG | ICANON; if (!pr->echo) newmode.c_lflag &= ~ECHO; else newmode.c_lflag |= ECHO; tcsetattr(infd, TCSANOW, &newmode); console_write(outfp, ptrlen_from_asciz(pr->prompt)); bool failed = false; while (1) { size_t toread = 65536; size_t prev_result_len = pr->result->len; void *ptr = strbuf_append(pr->result, toread); int ret = read(infd, ptr, toread); if (ret <= 0) { failed = true; break; } strbuf_shrink_to(pr->result, prev_result_len + ret); if (strbuf_chomp(pr->result, '\n')) break; } tcsetattr(infd, TCSANOW, &oldmode); if (!pr->echo) console_write(outfp, PTRLEN_LITERAL("\n")); if (failed) { console_close(outfp, infd); return 0; /* failure due to read error */ } } console_close(outfp, infd); return 1; /* success */ } bool is_interactive(void) { return isatty(0); } #endif /* removed in xtruss */ /* * X11-forwarding-related things suitable for console. */ char *platform_get_x_display(void) { return dupstr(getenv("DISPLAY")); } work/unix/network.c0000664000000000000000000013764414714222463011574 0ustar /* * Unix networking abstraction. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "putty.h" #include "network.h" #include "tree234.h" /* Solaris needs for SIOCATMARK. */ #ifndef SIOCATMARK #include #endif #ifndef X11_UNIX_PATH # define X11_UNIX_PATH "/tmp/.X11-unix/X" #endif /* * Access to sockaddr types without breaking C strict aliasing rules. */ union sockaddr_union { struct sockaddr_storage storage; struct sockaddr sa; struct sockaddr_in sin; #ifndef NO_IPV6 struct sockaddr_in6 sin6; #endif struct sockaddr_un su; }; /* * Mutable state that goes with a SockAddr: stores information * about where in the list of candidate IP(v*) addresses we've * currently got to. */ typedef struct SockAddrStep_tag SockAddrStep; struct SockAddrStep_tag { #ifndef NO_IPV6 struct addrinfo *ai; /* steps along addr->ais */ #endif int curraddr; }; typedef struct NetSocket NetSocket; struct NetSocket { const char *error; int s; Plug *plug; bufchain output_data; bool connected; /* irrelevant for listening sockets */ bool writable; bool frozen; /* this causes readability notifications to be ignored */ bool localhost_only; /* for listening sockets */ char oobdata[1]; size_t sending_oob; bool oobpending; /* is there OOB data available to read? */ bool oobinline; enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; bool incomingeof; int pending_error; /* in case send() returns error */ bool listener; bool nodelay, keepalive; /* for connect()-type sockets */ bool privport; int port; /* and again */ SockAddr *addr; SockAddrStep step; /* * We sometimes need pairs of Socket structures to be linked: * if we are listening on the same IPv6 and v4 port, for * example. So here we define `parent' and `child' pointers to * track this link. */ NetSocket *parent, *child; Socket sock; }; struct SockAddr { int refcount; const char *error; enum { UNRESOLVED, UNIX, IP } superfamily; #ifndef NO_IPV6 struct addrinfo *ais; /* Addresses IPv6 style. */ #else unsigned long *addresses; /* Addresses IPv4 style. */ int naddresses; #endif char hostname[512]; /* Store an unresolved host name. */ }; /* * Which address family this address belongs to. AF_INET for IPv4; * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has * not been done and a simple host name is held in this SockAddr * structure. */ #ifndef NO_IPV6 #define SOCKADDR_FAMILY(addr, step) \ ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \ (addr)->superfamily == UNIX ? AF_UNIX : \ (step).ai ? (step).ai->ai_family : AF_INET) #else /* Here we gratuitously reference 'step' to avoid gcc warnings about * 'set but not used' when compiling -DNO_IPV6 */ #define SOCKADDR_FAMILY(addr, step) \ ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \ (addr)->superfamily == UNIX ? AF_UNIX : \ (step).curraddr ? AF_INET : AF_INET) #endif /* * Start a SockAddrStep structure to step through multiple * addresses. */ #ifndef NO_IPV6 #define START_STEP(addr, step) \ ((step).ai = (addr)->ais, (step).curraddr = 0) #else #define START_STEP(addr, step) \ ((step).curraddr = 0) #endif static tree234 *sktree; static void uxsel_tell(NetSocket *s); static int cmpfortree(void *av, void *bv) { NetSocket *a = (NetSocket *) av, *b = (NetSocket *) bv; int as = a->s, bs = b->s; if (as < bs) return -1; if (as > bs) return +1; if (a < b) return -1; if (a > b) return +1; return 0; } static int cmpforsearch(void *av, void *bv) { NetSocket *b = (NetSocket *) bv; int as = *(int *)av, bs = b->s; if (as < bs) return -1; if (as > bs) return +1; return 0; } void sk_init(void) { sktree = newtree234(cmpfortree); } void sk_cleanup(void) { NetSocket *s; int i; if (sktree) { for (i = 0; (s = index234(sktree, i)) != NULL; i++) { close(s->s); } } } SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_family) { if (host[0] == '/') { *canonicalname = dupstr(host); return unix_sock_addr(host); } SockAddr *ret = snew(SockAddr); #ifndef NO_IPV6 struct addrinfo hints; int err; #else unsigned long a; struct hostent *h = NULL; int n; #endif strbuf *realhost = strbuf_new(); /* Clear the structure and default to IPv4. */ memset(ret, 0, sizeof(SockAddr)); ret->superfamily = UNRESOLVED; ret->error = NULL; ret->refcount = 1; #ifndef NO_IPV6 hints.ai_flags = AI_CANONNAME; hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : address_family == ADDRTYPE_IPV6 ? AF_INET6 : AF_UNSPEC); hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_addr = NULL; hints.ai_canonname = NULL; hints.ai_next = NULL; { char *trimmed_host = host_strduptrim(host); /* strip [] on literals */ err = getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); sfree(trimmed_host); } if (err != 0) { ret->error = gai_strerror(err); strbuf_free(realhost); return ret; } ret->superfamily = IP; if (ret->ais->ai_canonname != NULL) strbuf_catf(realhost, "%s", ret->ais->ai_canonname); else strbuf_catf(realhost, "%s", host); #else if ((a = inet_addr(host)) == (unsigned long)(in_addr_t)(-1)) { /* * Otherwise use the IPv4-only gethostbyname... (NOTE: * we don't use gethostbyname as a fallback!) */ if (ret->superfamily == UNRESOLVED) { /*debug("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host); */ if ( (h = gethostbyname(host)) ) ret->superfamily = IP; } if (ret->superfamily == UNRESOLVED) { ret->error = (h_errno == HOST_NOT_FOUND || h_errno == NO_DATA || h_errno == NO_ADDRESS ? "Host does not exist" : h_errno == TRY_AGAIN ? "Temporary name service failure" : "gethostbyname: unknown error"); strbuf_free(realhost); return ret; } /* This way we are always sure the h->h_name is valid :) */ strbuf_clear(realhost); strbuf_catf(realhost, "%s", h->h_name); for (n = 0; h->h_addr_list[n]; n++); ret->addresses = snewn(n, unsigned long); ret->naddresses = n; for (n = 0; n < ret->naddresses; n++) { memcpy(&a, h->h_addr_list[n], sizeof(a)); ret->addresses[n] = ntohl(a); } } else { /* * This must be a numeric IPv4 address because it caused a * success return from inet_addr. */ ret->superfamily = IP; strbuf_clear(realhost); strbuf_catf(realhost, "%s", host); ret->addresses = snew(unsigned long); ret->naddresses = 1; ret->addresses[0] = ntohl(a); } #endif *canonicalname = strbuf_to_str(realhost); return ret; } SockAddr *sk_nonamelookup(const char *host) { SockAddr *ret = snew(SockAddr); ret->error = NULL; ret->superfamily = UNRESOLVED; strncpy(ret->hostname, host, lenof(ret->hostname)); ret->hostname[lenof(ret->hostname)-1] = '\0'; #ifndef NO_IPV6 ret->ais = NULL; #else ret->addresses = NULL; #endif ret->refcount = 1; return ret; } static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step) { #ifndef NO_IPV6 if (step->ai && step->ai->ai_next) { step->ai = step->ai->ai_next; return true; } else return false; #else if (step->curraddr+1 < addr->naddresses) { step->curraddr++; return true; } else { return false; } #endif } void sk_getaddr(SockAddr *addr, char *buf, int buflen) { if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) { strncpy(buf, addr->hostname, buflen); buf[buflen-1] = '\0'; } else { #ifndef NO_IPV6 if (getnameinfo(addr->ais->ai_addr, addr->ais->ai_addrlen, buf, buflen, NULL, 0, NI_NUMERICHOST) != 0) { buf[0] = '\0'; strncat(buf, "", buflen - 1); } #else struct in_addr a; SockAddrStep step; START_STEP(addr, step); assert(SOCKADDR_FAMILY(addr, step) == AF_INET); a.s_addr = htonl(addr->addresses[0]); strncpy(buf, inet_ntoa(a), buflen); buf[buflen-1] = '\0'; #endif } } /* * This constructs a SockAddr that points at one specific sub-address * of a parent SockAddr. The returned SockAddr does not own all its * own memory: it points into the old one's data structures, so it * MUST NOT be used after the old one is freed, and it MUST NOT be * passed to sk_addr_free. (The latter is why it's returned by value * rather than dynamically allocated - that should clue in anyone * writing a call to it that something is weird about it.) */ static SockAddr sk_extractaddr_tmp( SockAddr *addr, const SockAddrStep *step) { SockAddr toret; toret = *addr; /* structure copy */ toret.refcount = 1; if (addr->superfamily == IP) { #ifndef NO_IPV6 toret.ais = step->ai; #else assert(SOCKADDR_FAMILY(addr, *step) == AF_INET); toret.addresses += step->curraddr; #endif } return toret; } bool sk_addr_needs_port(SockAddr *addr) { if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) { return false; } else { return true; } } bool sk_hostname_is_local(const char *name) { return !strcmp(name, "localhost") || !strcmp(name, "::1") || !strncmp(name, "127.", 4); } #define ipv4_is_loopback(addr) \ (((addr).s_addr & htonl(0xff000000)) == htonl(0x7f000000)) static bool sockaddr_is_loopback(struct sockaddr *sa) { union sockaddr_union *u = (union sockaddr_union *)sa; switch (u->sa.sa_family) { case AF_INET: return ipv4_is_loopback(u->sin.sin_addr); #ifndef NO_IPV6 case AF_INET6: return IN6_IS_ADDR_LOOPBACK(&u->sin6.sin6_addr); #endif case AF_UNIX: return true; default: return false; } } bool sk_address_is_local(SockAddr *addr) { if (addr->superfamily == UNRESOLVED) return false; /* we don't know; assume not */ else if (addr->superfamily == UNIX) return true; else { #ifndef NO_IPV6 return sockaddr_is_loopback(addr->ais->ai_addr); #else struct in_addr a; SockAddrStep step; START_STEP(addr, step); assert(SOCKADDR_FAMILY(addr, step) == AF_INET); a.s_addr = htonl(addr->addresses[0]); return ipv4_is_loopback(a); #endif } } bool sk_address_is_special_local(SockAddr *addr) { return addr->superfamily == UNIX; } int sk_addrtype(SockAddr *addr) { SockAddrStep step; int family; START_STEP(addr, step); family = SOCKADDR_FAMILY(addr, step); return (family == AF_INET ? ADDRTYPE_IPV4 : #ifndef NO_IPV6 family == AF_INET6 ? ADDRTYPE_IPV6 : #endif ADDRTYPE_NAME); } void sk_addrcopy(SockAddr *addr, char *buf) { SockAddrStep step; int family; START_STEP(addr, step); family = SOCKADDR_FAMILY(addr, step); #ifndef NO_IPV6 if (family == AF_INET) memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr, sizeof(struct in_addr)); else if (family == AF_INET6) memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr, sizeof(struct in6_addr)); else unreachable("bad address family in sk_addrcopy"); #else struct in_addr a; assert(family == AF_INET); a.s_addr = htonl(addr->addresses[step.curraddr]); memcpy(buf, (char*) &a.s_addr, 4); #endif } void sk_addr_free(SockAddr *addr) { if (--addr->refcount > 0) return; #ifndef NO_IPV6 if (addr->ais != NULL) freeaddrinfo(addr->ais); #else sfree(addr->addresses); #endif sfree(addr); } SockAddr *sk_addr_dup(SockAddr *addr) { addr->refcount++; return addr; } static Plug *sk_net_plug(Socket *sock, Plug *p) { NetSocket *s = container_of(sock, NetSocket, sock); Plug *ret = s->plug; if (p) s->plug = p; return ret; } static void sk_net_close(Socket *s); static size_t sk_net_write(Socket *s, const void *data, size_t len); static size_t sk_net_write_oob(Socket *s, const void *data, size_t len); static void sk_net_write_eof(Socket *s); static void sk_net_set_frozen(Socket *s, bool is_frozen); static SocketPeerInfo *sk_net_peer_info(Socket *s); static const char *sk_net_socket_error(Socket *s); static const SocketVtable NetSocket_sockvt = { .plug = sk_net_plug, .close = sk_net_close, .write = sk_net_write, .write_oob = sk_net_write_oob, .write_eof = sk_net_write_eof, .set_frozen = sk_net_set_frozen, .socket_error = sk_net_socket_error, .peer_info = sk_net_peer_info, }; static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) { int sockfd = ctx.i; NetSocket *ret; /* * Create NetSocket structure. */ ret = snew(NetSocket); ret->sock.vt = &NetSocket_sockvt; ret->error = NULL; ret->plug = plug; bufchain_init(&ret->output_data); ret->writable = true; /* to start with */ ret->sending_oob = 0; ret->frozen = true; ret->localhost_only = false; /* unused, but best init anyway */ ret->pending_error = 0; ret->oobpending = false; ret->outgoingeof = EOF_NO; ret->incomingeof = false; ret->listener = false; ret->parent = ret->child = NULL; ret->addr = NULL; ret->connected = true; ret->s = sockfd; if (ret->s < 0) { ret->error = strerror(errno); return &ret->sock; } ret->oobinline = false; uxsel_tell(ret); add234(sktree, ret); return &ret->sock; } static int try_connect(NetSocket *sock) { int s; union sockaddr_union u; const union sockaddr_union *sa; int err = 0; short localport; int salen, family; /* * Remove the socket from the tree before we overwrite its * internal socket id, because that forms part of the tree's * sorting criterion. We'll add it back before exiting this * function, whether we changed anything or not. */ del234(sktree, sock); if (sock->s >= 0) close(sock->s); { SockAddr thisaddr = sk_extractaddr_tmp( sock->addr, &sock->step); plug_log(sock->plug, PLUGLOG_CONNECT_TRYING, &thisaddr, sock->port, NULL, 0); } /* * Open socket. */ family = SOCKADDR_FAMILY(sock->addr, sock->step); assert(family != AF_UNSPEC); s = socket(family, SOCK_STREAM, 0); sock->s = s; if (s < 0) { err = errno; goto ret; } cloexec(s); if (sock->oobinline) { int b = 1; if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b)) < 0) { err = errno; close(s); goto ret; } } if (sock->nodelay && family != AF_UNIX) { int b = 1; if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b)) < 0) { err = errno; close(s); goto ret; } } if (sock->keepalive) { int b = 1; if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b)) < 0) { err = errno; close(s); goto ret; } } /* * Bind to local address. */ if (sock->privport) localport = 1023; /* count from 1023 downwards */ else localport = 0; /* just use port 0 (ie kernel picks) */ /* BSD IP stacks need sockaddr_in zeroed before filling in */ memset(&u,'\0',sizeof(u)); /* We don't try to bind to a local address for UNIX domain sockets. (Why * do we bother doing the bind when localport == 0 anyway?) */ if (family != AF_UNIX) { /* Loop round trying to bind */ while (1) { int retcode; #ifndef NO_IPV6 if (family == AF_INET6) { /* XXX use getaddrinfo to get a local address? */ u.sin6.sin6_family = AF_INET6; u.sin6.sin6_addr = in6addr_any; u.sin6.sin6_port = htons(localport); retcode = bind(s, &u.sa, sizeof(u.sin6)); } else #endif { assert(family == AF_INET); u.sin.sin_family = AF_INET; u.sin.sin_addr.s_addr = htonl(INADDR_ANY); u.sin.sin_port = htons(localport); retcode = bind(s, &u.sa, sizeof(u.sin)); } if (retcode >= 0) { err = 0; break; /* done */ } else { err = errno; if (err != EADDRINUSE) /* failed, for a bad reason */ break; } if (localport == 0) break; /* we're only looping once */ localport--; if (localport == 0) break; /* we might have got to the end */ } if (err) goto ret; } /* * Connect to remote address. */ switch(family) { #ifndef NO_IPV6 case AF_INET: /* XXX would be better to have got getaddrinfo() to fill in the port. */ ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port = htons(sock->port); sa = (const union sockaddr_union *)sock->step.ai->ai_addr; salen = sock->step.ai->ai_addrlen; break; case AF_INET6: ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port = htons(sock->port); sa = (const union sockaddr_union *)sock->step.ai->ai_addr; salen = sock->step.ai->ai_addrlen; break; #else case AF_INET: u.sin.sin_family = AF_INET; u.sin.sin_addr.s_addr = htonl(sock->addr->addresses[sock->step.curraddr]); u.sin.sin_port = htons((short) sock->port); sa = &u; salen = sizeof u.sin; break; #endif case AF_UNIX: assert(strlen(sock->addr->hostname) < sizeof u.su.sun_path); u.su.sun_family = AF_UNIX; strcpy(u.su.sun_path, sock->addr->hostname); sa = &u; salen = sizeof u.su; break; default: unreachable("unknown address family"); exit(1); /* XXX: GCC doesn't understand assert() on some systems. */ } nonblock(s); if ((connect(s, &(sa->sa), salen)) < 0) { if ( errno != EINPROGRESS ) { err = errno; goto ret; } } else { /* * If we _don't_ get EWOULDBLOCK, the connect has completed * and we should set the socket as connected and writable. */ sock->connected = true; sock->writable = true; SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step); plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS, &thisaddr, sock->port, NULL, 0); } uxsel_tell(sock); ret: /* * No matter what happened, put the socket back in the tree. */ add234(sktree, sock); if (err) { SockAddr thisaddr = sk_extractaddr_tmp( sock->addr, &sock->step); plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, sock->port, strerror(err), err); } return err; } Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug) { NetSocket *ret; int err; /* * Create NetSocket structure. */ ret = snew(NetSocket); ret->sock.vt = &NetSocket_sockvt; ret->error = NULL; ret->plug = plug; bufchain_init(&ret->output_data); ret->connected = false; /* to start with */ ret->writable = false; /* to start with */ ret->sending_oob = 0; ret->frozen = false; ret->localhost_only = false; /* unused, but best init anyway */ ret->pending_error = 0; ret->parent = ret->child = NULL; ret->oobpending = false; ret->outgoingeof = EOF_NO; ret->incomingeof = false; ret->listener = false; ret->addr = addr; START_STEP(ret->addr, ret->step); ret->s = -1; ret->oobinline = oobinline; ret->nodelay = nodelay; ret->keepalive = keepalive; ret->privport = privport; ret->port = port; do { err = try_connect(ret); } while (err && sk_nextaddr(ret->addr, &ret->step)); if (err) ret->error = strerror(err); return &ret->sock; } Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, bool local_host_only, int orig_address_family) { int s; #ifndef NO_IPV6 struct addrinfo hints, *ai = NULL; char portstr[6]; #endif union sockaddr_union u; union sockaddr_union *addr; int addrlen; NetSocket *ret; int retcode; int address_family; int on = 1; /* * Create NetSocket structure. */ ret = snew(NetSocket); ret->sock.vt = &NetSocket_sockvt; ret->error = NULL; ret->plug = plug; bufchain_init(&ret->output_data); ret->writable = false; /* to start with */ ret->sending_oob = 0; ret->frozen = false; ret->localhost_only = local_host_only; ret->pending_error = 0; ret->parent = ret->child = NULL; ret->oobpending = false; ret->outgoingeof = EOF_NO; ret->incomingeof = false; ret->listener = true; ret->addr = NULL; ret->s = -1; /* * Translate address_family from platform-independent constants * into local reality. */ address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET : #ifndef NO_IPV6 orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 : #endif AF_UNSPEC); #ifndef NO_IPV6 /* Let's default to IPv6. * If the stack doesn't support IPv6, we will fall back to IPv4. */ if (address_family == AF_UNSPEC) address_family = AF_INET6; #else /* No other choice, default to IPv4 */ if (address_family == AF_UNSPEC) address_family = AF_INET; #endif /* * Open socket. */ s = socket(address_family, SOCK_STREAM, 0); #ifndef NO_IPV6 /* If the host doesn't support IPv6 try fallback to IPv4. */ if (s < 0 && address_family == AF_INET6) { address_family = AF_INET; s = socket(address_family, SOCK_STREAM, 0); } #endif if (s < 0) { ret->error = strerror(errno); return &ret->sock; } cloexec(s); ret->oobinline = false; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)) < 0) { ret->error = strerror(errno); close(s); return &ret->sock; } retcode = -1; addr = NULL; addrlen = -1; /* placate optimiser */ if (srcaddr != NULL) { #ifndef NO_IPV6 hints.ai_flags = AI_NUMERICHOST; hints.ai_family = address_family; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_addr = NULL; hints.ai_canonname = NULL; hints.ai_next = NULL; assert(port >= 0 && port <= 99999); sprintf(portstr, "%d", port); { char *trimmed_addr = host_strduptrim(srcaddr); retcode = getaddrinfo(trimmed_addr, portstr, &hints, &ai); sfree(trimmed_addr); } if (retcode == 0) { addr = (union sockaddr_union *)ai->ai_addr; addrlen = ai->ai_addrlen; } #else memset(&u,'\0',sizeof u); u.sin.sin_family = AF_INET; u.sin.sin_port = htons(port); u.sin.sin_addr.s_addr = inet_addr(srcaddr); if (u.sin.sin_addr.s_addr != (in_addr_t)(-1)) { /* Override localhost_only with specified listen addr. */ ret->localhost_only = ipv4_is_loopback(u.sin.sin_addr); } addr = &u; addrlen = sizeof(u.sin); retcode = 0; #endif } if (retcode != 0) { memset(&u,'\0',sizeof u); #ifndef NO_IPV6 if (address_family == AF_INET6) { u.sin6.sin6_family = AF_INET6; u.sin6.sin6_port = htons(port); if (local_host_only) u.sin6.sin6_addr = in6addr_loopback; else u.sin6.sin6_addr = in6addr_any; addr = &u; addrlen = sizeof(u.sin6); } else #endif { u.sin.sin_family = AF_INET; u.sin.sin_port = htons(port); if (local_host_only) u.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); else u.sin.sin_addr.s_addr = htonl(INADDR_ANY); addr = &u; addrlen = sizeof(u.sin); } } retcode = bind(s, &addr->sa, addrlen); #ifndef NO_IPV6 if (ai) freeaddrinfo(ai); #endif if (retcode < 0) { close(s); ret->error = strerror(errno); return &ret->sock; } if (listen(s, SOMAXCONN) < 0) { close(s); ret->error = strerror(errno); return &ret->sock; } #ifndef NO_IPV6 /* * If we were given ADDRTYPE_UNSPEC, we must also create an * IPv4 listening socket and link it to this one. */ if (address_family == AF_INET6 && orig_address_family == ADDRTYPE_UNSPEC) { NetSocket *other; other = container_of( sk_newlistener(srcaddr, port, plug, local_host_only, ADDRTYPE_IPV4), NetSocket, sock); if (other) { if (!other->error) { other->parent = ret; ret->child = other; } else { /* If we couldn't create a listening socket on IPv4 as well * as IPv6, we must return an error overall. */ close(s); sfree(ret); return &other->sock; } } } #endif ret->s = s; uxsel_tell(ret); add234(sktree, ret); return &ret->sock; } static void sk_net_close(Socket *sock) { NetSocket *s = container_of(sock, NetSocket, sock); if (s->child) sk_net_close(&s->child->sock); bufchain_clear(&s->output_data); del234(sktree, s); if (s->s >= 0) { uxsel_del(s->s); close(s->s); } if (s->addr) sk_addr_free(s->addr); delete_callbacks_for_context(s); sfree(s); } void *sk_getxdmdata(Socket *sock, int *lenp) { NetSocket *s; union sockaddr_union u; socklen_t addrlen; char *buf; static unsigned int unix_addr = 0xFFFFFFFF; /* * We must check that this socket really _is_ a NetSocket before * downcasting it. */ if (sock->vt != &NetSocket_sockvt) return NULL; /* failure */ s = container_of(sock, NetSocket, sock); addrlen = sizeof(u); if (getsockname(s->s, &u.sa, &addrlen) < 0) return NULL; switch(u.sa.sa_family) { case AF_INET: *lenp = 6; buf = snewn(*lenp, char); PUT_32BIT_MSB_FIRST(buf, ntohl(u.sin.sin_addr.s_addr)); PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin.sin_port)); break; #ifndef NO_IPV6 case AF_INET6: *lenp = 6; buf = snewn(*lenp, char); if (IN6_IS_ADDR_V4MAPPED(&u.sin6.sin6_addr)) { memcpy(buf, u.sin6.sin6_addr.s6_addr + 12, 4); PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin6.sin6_port)); } else /* This is stupid, but it's what XLib does. */ memset(buf, 0, 6); break; #endif case AF_UNIX: *lenp = 6; buf = snewn(*lenp, char); PUT_32BIT_MSB_FIRST(buf, unix_addr--); PUT_16BIT_MSB_FIRST(buf+4, getpid()); break; /* XXX IPV6 */ default: return NULL; } return buf; } /* * Deal with socket errors detected in try_send(). */ static void socket_error_callback(void *vs) { NetSocket *s = (NetSocket *)vs; /* * Just in case other socket work has caused this socket to vanish * or become somehow non-erroneous before this callback arrived... */ if (!find234(sktree, s, NULL) || !s->pending_error) return; /* * An error has occurred on this socket. Pass it to the plug. */ plug_closing(s->plug, strerror(s->pending_error), s->pending_error, 0); } /* * The function which tries to send on a socket once it's deemed * writable. */ void try_send(NetSocket *s) { while (s->sending_oob || bufchain_size(&s->output_data) > 0) { int nsent; int err; const void *data; size_t len; int urgentflag; if (s->sending_oob) { urgentflag = MSG_OOB; len = s->sending_oob; data = &s->oobdata; } else { urgentflag = 0; ptrlen bufdata = bufchain_prefix(&s->output_data); data = bufdata.ptr; len = bufdata.len; } nsent = send(s->s, data, len, urgentflag); noise_ultralight(NOISE_SOURCE_IOLEN, nsent); if (nsent <= 0) { err = (nsent < 0 ? errno : 0); if (err == EWOULDBLOCK) { /* * Perfectly normal: we've sent all we can for the moment. */ s->writable = false; return; } else { /* * We unfortunately can't just call plug_closing(), * because it's quite likely that we're currently * _in_ a call from the code we'd be calling back * to, so we'd have to make half the SSH code * reentrant. Instead we flag a pending error on * the socket, to be dealt with (by calling * plug_closing()) at some suitable future moment. */ s->pending_error = err; /* * Immediately cease selecting on this socket, so that * we don't tight-loop repeatedly trying to do * whatever it was that went wrong. */ uxsel_tell(s); /* * Arrange to be called back from the top level to * deal with the error condition on this socket. */ queue_toplevel_callback(socket_error_callback, s); return; } } else { if (s->sending_oob) { if (nsent < len) { memmove(s->oobdata, s->oobdata+nsent, len-nsent); s->sending_oob = len - nsent; } else { s->sending_oob = 0; } } else { bufchain_consume(&s->output_data, nsent); } } } /* * If we reach here, we've finished sending everything we might * have needed to send. Send EOF, if we need to. */ if (s->outgoingeof == EOF_PENDING) { shutdown(s->s, SHUT_WR); s->outgoingeof = EOF_SENT; } /* * Also update the select status, because we don't need to select * for writing any more. */ uxsel_tell(s); } static size_t sk_net_write(Socket *sock, const void *buf, size_t len) { NetSocket *s = container_of(sock, NetSocket, sock); assert(s->outgoingeof == EOF_NO); /* * Add the data to the buffer list on the socket. */ bufchain_add(&s->output_data, buf, len); /* * Now try sending from the start of the buffer list. */ if (s->writable) try_send(s); /* * Update the select() status to correctly reflect whether or * not we should be selecting for write. */ uxsel_tell(s); return bufchain_size(&s->output_data); } static size_t sk_net_write_oob(Socket *sock, const void *buf, size_t len) { NetSocket *s = container_of(sock, NetSocket, sock); assert(s->outgoingeof == EOF_NO); /* * Replace the buffer list on the socket with the data. */ bufchain_clear(&s->output_data); assert(len <= sizeof(s->oobdata)); memcpy(s->oobdata, buf, len); s->sending_oob = len; /* * Now try sending from the start of the buffer list. */ if (s->writable) try_send(s); /* * Update the select() status to correctly reflect whether or * not we should be selecting for write. */ uxsel_tell(s); return s->sending_oob; } static void sk_net_write_eof(Socket *sock) { NetSocket *s = container_of(sock, NetSocket, sock); assert(s->outgoingeof == EOF_NO); /* * Mark the socket as pending outgoing EOF. */ s->outgoingeof = EOF_PENDING; /* * Now try sending from the start of the buffer list. */ if (s->writable) try_send(s); /* * Update the select() status to correctly reflect whether or * not we should be selecting for write. */ uxsel_tell(s); } static void net_select_result(int fd, int event) { int ret; char buf[20480]; /* nice big buffer for plenty of speed */ NetSocket *s; bool atmark = true; /* Find the Socket structure */ s = find234(sktree, &fd, cmpforsearch); if (!s) return; /* boggle */ noise_ultralight(NOISE_SOURCE_IOID, fd); switch (event) { case SELECT_X: /* exceptional */ if (!s->oobinline) { /* * On a non-oobinline socket, this indicates that we * can immediately perform an OOB read and get back OOB * data, which we will send to the back end with * type==2 (urgent data). */ ret = recv(s->s, buf, sizeof(buf), MSG_OOB); noise_ultralight(NOISE_SOURCE_IOLEN, ret); if (ret <= 0) { plug_closing(s->plug, ret == 0 ? "Internal networking trouble" : strerror(errno), errno, 0); } else { /* * Receiving actual data on a socket means we can * stop falling back through the candidate * addresses to connect to. */ if (s->addr) { sk_addr_free(s->addr); s->addr = NULL; } plug_receive(s->plug, 2, buf, ret); } break; } /* * If we reach here, this is an oobinline socket, which * means we should set s->oobpending and then deal with it * when we get called for the readability event (which * should also occur). */ s->oobpending = true; break; case SELECT_R: /* readable; also acceptance */ if (s->listener) { /* * On a listening socket, the readability event means a * connection is ready to be accepted. */ union sockaddr_union su; socklen_t addrlen = sizeof(su); accept_ctx_t actx; int t; /* socket of connection */ memset(&su, 0, addrlen); t = accept(s->s, &su.sa, &addrlen); if (t < 0) { break; } nonblock(t); actx.i = t; if ((!s->addr || s->addr->superfamily != UNIX) && s->localhost_only && !sockaddr_is_loopback(&su.sa)) { close(t); /* someone let nonlocal through?! */ } else if (plug_accepting(s->plug, sk_net_accept, actx)) { close(t); /* denied or error */ } break; } /* * If we reach here, this is not a listening socket, so * readability really means readability. */ /* In the case the socket is still frozen, we don't even bother */ if (s->frozen) break; /* * We have received data on the socket. For an oobinline * socket, this might be data _before_ an urgent pointer, * in which case we send it to the back end with type==1 * (data prior to urgent). */ if (s->oobinline && s->oobpending) { int atmark_from_ioctl; if (ioctl(s->s, SIOCATMARK, &atmark_from_ioctl) == 0) { atmark = atmark_from_ioctl; if (atmark) s->oobpending = false; /* clear this indicator */ } } else atmark = true; ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0); noise_ultralight(NOISE_SOURCE_IOLEN, ret); if (ret < 0) { if (errno == EWOULDBLOCK) { break; } } if (ret < 0) { plug_closing(s->plug, strerror(errno), errno, 0); } else if (0 == ret) { s->incomingeof = true; /* stop trying to read now */ uxsel_tell(s); plug_closing(s->plug, NULL, 0, 0); } else { /* * Receiving actual data on a socket means we can * stop falling back through the candidate * addresses to connect to. */ if (s->addr) { sk_addr_free(s->addr); s->addr = NULL; } plug_receive(s->plug, atmark ? 0 : 1, buf, ret); } break; case SELECT_W: /* writable */ if (!s->connected) { /* * select/poll reports a socket as _writable_ when an * asynchronous connect() attempt either completes or * fails. So first we must find out which. */ { int err; socklen_t errlen = sizeof(err); char *errmsg = NULL; if (getsockopt(s->s, SOL_SOCKET, SO_ERROR, &err, &errlen)<0) { errmsg = dupprintf("getsockopt(SO_ERROR): %s", strerror(errno)); err = errno; /* got to put something in here */ } else if (err != 0) { errmsg = dupstr(strerror(err)); } if (errmsg) { /* * The asynchronous connection attempt failed. * Report the problem via plug_log, and try again * with the next candidate address, if we have * more than one. */ SockAddr thisaddr; assert(s->addr); thisaddr = sk_extractaddr_tmp(s->addr, &s->step); plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port, errmsg, err); while (err && s->addr && sk_nextaddr(s->addr, &s->step)) { err = try_connect(s); } if (err) { plug_closing(s->plug, strerror(err), err, 0); return; /* socket is now presumably defunct */ } if (!s->connected) return; /* another async attempt in progress */ } else { /* * The connection attempt succeeded. */ SockAddr thisaddr = sk_extractaddr_tmp(s->addr, &s->step); plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS, &thisaddr, s->port, NULL, 0); } } /* * If we get here, we've managed to make a connection. */ if (s->addr) { sk_addr_free(s->addr); s->addr = NULL; } s->connected = true; s->writable = true; uxsel_tell(s); } else { size_t bufsize_before, bufsize_after; s->writable = true; bufsize_before = s->sending_oob + bufchain_size(&s->output_data); try_send(s); bufsize_after = s->sending_oob + bufchain_size(&s->output_data); if (bufsize_after < bufsize_before) plug_sent(s->plug, bufsize_after); } break; } } /* * Special error values are returned from sk_namelookup and sk_new * if there's a problem. These functions extract an error message, * or return NULL if there's no problem. */ const char *sk_addr_error(SockAddr *addr) { return addr->error; } static const char *sk_net_socket_error(Socket *sock) { NetSocket *s = container_of(sock, NetSocket, sock); return s->error; } static void sk_net_set_frozen(Socket *sock, bool is_frozen) { NetSocket *s = container_of(sock, NetSocket, sock); if (s->frozen == is_frozen) return; s->frozen = is_frozen; uxsel_tell(s); } static SocketPeerInfo *sk_net_peer_info(Socket *sock) { NetSocket *s = container_of(sock, NetSocket, sock); union sockaddr_union addr; socklen_t addrlen = sizeof(addr); #ifndef NO_IPV6 char buf[INET6_ADDRSTRLEN]; #endif SocketPeerInfo *pi; if (getpeername(s->s, &addr.sa, &addrlen) < 0) return NULL; pi = snew(SocketPeerInfo); pi->addressfamily = ADDRTYPE_UNSPEC; pi->addr_text = NULL; pi->port = -1; pi->log_text = NULL; if (addr.storage.ss_family == AF_INET) { pi->addressfamily = ADDRTYPE_IPV4; memcpy(pi->addr_bin.ipv4, &addr.sin.sin_addr, 4); pi->port = ntohs(addr.sin.sin_port); pi->addr_text = dupstr(inet_ntoa(addr.sin.sin_addr)); pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port); #ifndef NO_IPV6 } else if (addr.storage.ss_family == AF_INET6) { pi->addressfamily = ADDRTYPE_IPV6; memcpy(pi->addr_bin.ipv6, &addr.sin6.sin6_addr, 16); pi->port = ntohs(addr.sin6.sin6_port); pi->addr_text = dupstr( inet_ntop(AF_INET6, &addr.sin6.sin6_addr, buf, sizeof(buf))); pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port); #endif } else if (addr.storage.ss_family == AF_UNIX) { pi->addressfamily = ADDRTYPE_LOCAL; /* * For Unix sockets, the source address is unlikely to be * helpful, so we leave addr_txt NULL (and we certainly can't * fill in port, obviously). Instead, we try SO_PEERCRED and * try to get the source pid, and put that in the log text. */ int pid, uid, gid; if (so_peercred(s->s, &pid, &uid, &gid)) { char uidbuf[64], gidbuf[64]; sprintf(uidbuf, "%d", uid); sprintf(gidbuf, "%d", gid); struct passwd *pw = getpwuid(uid); struct group *gr = getgrgid(gid); pi->log_text = dupprintf("pid %d (%s:%s)", pid, pw ? pw->pw_name : uidbuf, gr ? gr->gr_name : gidbuf); } } else { sfree(pi); return NULL; } return pi; } int sk_net_get_fd(Socket *sock) { /* This function is not fully general: it only works on NetSocket */ if (sock->vt != &NetSocket_sockvt) return -1; /* failure */ NetSocket *s = container_of(sock, NetSocket, sock); return s->s; } static void uxsel_tell(NetSocket *s) { int rwx = 0; if (!s->pending_error) { if (s->listener) { rwx |= SELECT_R; /* read == accept */ } else { if (!s->connected) rwx |= SELECT_W; /* write == connect */ if (s->connected && !s->frozen && !s->incomingeof) rwx |= SELECT_R | SELECT_X; if (bufchain_size(&s->output_data)) rwx |= SELECT_W; } } uxsel_set(s->s, rwx, net_select_result); } int net_service_lookup(char *service) { struct servent *se; se = getservbyname(service, NULL); if (se != NULL) return ntohs(se->s_port); else return 0; } char *get_hostname(void) { size_t size = 0; char *hostname = NULL; do { sgrowarray(hostname, size, size); if ((gethostname(hostname, size) < 0) && (errno != ENAMETOOLONG)) { sfree(hostname); hostname = NULL; break; } } while (strlen(hostname) >= size-1); return hostname; } SockAddr *platform_get_x11_unix_address(const char *sockpath, int displaynum) { SockAddr *ret = snew(SockAddr); int n; memset(ret, 0, sizeof *ret); ret->superfamily = UNIX; /* * In special circumstances (notably Mac OS X Leopard), we'll * have been passed an explicit Unix socket path. */ if (sockpath) { n = snprintf(ret->hostname, sizeof ret->hostname, "%s", sockpath); } else { n = snprintf(ret->hostname, sizeof ret->hostname, "%s%d", X11_UNIX_PATH, displaynum); } if (n < 0) ret->error = "snprintf failed"; else if (n >= sizeof ret->hostname) ret->error = "X11 UNIX name too long"; #ifndef NO_IPV6 ret->ais = NULL; #else ret->addresses = NULL; ret->naddresses = 0; #endif ret->refcount = 1; return ret; } SockAddr *unix_sock_addr(const char *path) { SockAddr *ret = snew(SockAddr); int n; memset(ret, 0, sizeof *ret); ret->superfamily = UNIX; n = snprintf(ret->hostname, sizeof ret->hostname, "%s", path); if (n < 0) ret->error = "snprintf failed"; else if (n >= sizeof ret->hostname || n >= sizeof(((struct sockaddr_un *)0)->sun_path)) ret->error = "socket pathname too long"; #ifndef NO_IPV6 ret->ais = NULL; #else ret->addresses = NULL; ret->naddresses = 0; #endif ret->refcount = 1; return ret; } Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug) { int s; union sockaddr_union u; union sockaddr_union *addr; int addrlen; NetSocket *ret; int retcode; /* * Create NetSocket structure. */ ret = snew(NetSocket); ret->sock.vt = &NetSocket_sockvt; ret->error = NULL; ret->plug = plug; bufchain_init(&ret->output_data); ret->writable = false; /* to start with */ ret->sending_oob = 0; ret->frozen = false; ret->localhost_only = true; ret->pending_error = 0; ret->parent = ret->child = NULL; ret->oobpending = false; ret->outgoingeof = EOF_NO; ret->incomingeof = false; ret->listener = true; ret->addr = listenaddr; ret->s = -1; assert(listenaddr->superfamily == UNIX); /* * Open socket. */ s = socket(AF_UNIX, SOCK_STREAM, 0); if (s < 0) { ret->error = strerror(errno); return &ret->sock; } cloexec(s); ret->oobinline = false; memset(&u, '\0', sizeof(u)); u.su.sun_family = AF_UNIX; #if __GNUC__ >= 8 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wstringop-truncation" #endif // __GNUC__ >= 8 strncpy(u.su.sun_path, listenaddr->hostname, sizeof(u.su.sun_path)-1); #if __GNUC__ >= 8 # pragma GCC diagnostic pop #endif // __GNUC__ >= 8 addr = &u; addrlen = sizeof(u.su); if (unlink(u.su.sun_path) < 0 && errno != ENOENT) { close(s); ret->error = strerror(errno); return &ret->sock; } retcode = bind(s, &addr->sa, addrlen); if (retcode < 0) { close(s); ret->error = strerror(errno); return &ret->sock; } if (listen(s, SOMAXCONN) < 0) { close(s); ret->error = strerror(errno); return &ret->sock; } ret->s = s; uxsel_tell(ret); add234(sktree, ret); return &ret->sock; } work/unix/peerinfo.c0000664000000000000000000000111214714222463011666 0ustar /* * Unix: wrapper for getsockopt(SO_PEERCRED), conditionalised on * appropriate autoconfery. */ #if HAVE_CMAKE_H #include "cmake.h" #endif #if HAVE_SO_PEERCRED #define _GNU_SOURCE #include #endif #include #include "putty.h" bool so_peercred(int fd, int *pid, int *uid, int *gid) { #if HAVE_SO_PEERCRED struct ucred cr; socklen_t crlen = sizeof(cr); if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) == 0) { *pid = cr.pid; *uid = cr.uid; *gid = cr.gid; return true; } #endif return false; } work/unix/platform.h0000664000000000000000000003747114714222463011731 0ustar /* * unix/platform.h: Unix-specific inter-module stuff. */ #ifndef PUTTY_UNIX_PLATFORM_H #define PUTTY_UNIX_PLATFORM_H #include /* for FILENAME_MAX */ #include /* C99 int types */ #ifndef NO_LIBDL #include /* Dynamic library loading */ #endif /* NO_LIBDL */ #if 0 /* removed in xtruss */ #include "charset.h" #endif /* removed in xtruss */ #include /* for mode_t */ #ifdef OSX_GTK /* * Assorted tweaks to various parts of the GTK front end which all * need to be enabled when compiling on OS X. Because I might need the * same tweaks on other systems in future, I don't want to * conditionalise all of them on OSX_GTK directly, so instead, each * one has its own name and we enable them all centrally here if * OSX_GTK is defined at configure time. */ #define NOT_X_WINDOWS /* of course, all the X11 stuff should be disabled */ #define NO_PTY_PRE_INIT /* OS X gets very huffy if we try to set[ug]id */ #define SET_NONBLOCK_VIA_OPENPT /* work around missing fcntl functionality */ #define OSX_META_KEY_CONFIG /* two possible Meta keys to choose from */ /* this potential one of the Meta keys needs manual handling */ #define META_MANUAL_MASK (GDK_MOD1_MASK) #define JUST_USE_GTK_CLIPBOARD_UTF8 /* low-level gdk_selection_* fails */ #define BUILDINFO_PLATFORM_GTK "OS X (GTK)" #define BUILDINFO_GTK #elif defined NOT_X_WINDOWS #define BUILDINFO_PLATFORM_GTK "Unix (pure GTK)" #define BUILDINFO_GTK #else #define BUILDINFO_PLATFORM_GTK "Unix (GTK + X11)" #define BUILDINFO_GTK #endif /* BUILDINFO_PLATFORM varies its expansion between the GTK and * pure-CLI utilities, so that Unix Plink, PSFTP etc don't announce * themselves incongruously as having something to do with GTK. */ #define BUILDINFO_PLATFORM_CLI "Unix" extern const bool buildinfo_gtk_relevant; #define BUILDINFO_PLATFORM (buildinfo_gtk_relevant ? \ BUILDINFO_PLATFORM_GTK : BUILDINFO_PLATFORM_CLI) char *buildinfo_gtk_version(void); struct Filename { char *path; }; FILE *f_open(const struct Filename *, char const *, bool); struct FontSpec { char *name; /* may be "" to indicate no selected font at all */ }; struct FontSpec *fontspec_new(const char *name); extern const struct BackendVtable pty_backend; #define BROKEN_PIPE_ERROR_CODE EPIPE /* used in ssh/sharing.c */ /* * Under GTK, we send MA_CLICK _and_ MA_2CLK, or MA_CLICK _and_ * MA_3CLK, when a button is pressed for the second or third time. */ #define MULTICLICK_ONLY_EVENT 0 /* * Under GTK, there is no context help available. */ #define HELPCTX(x) P(NULL) #define FILTER_KEY_FILES NULL /* FIXME */ #define FILTER_DYNLIB_FILES NULL /* FIXME */ /* * Under X, selection data must not be NUL-terminated. */ #define SELECTION_NUL_TERMINATED 0 /* * Under X, copying to the clipboard terminates lines with just LF. */ #define SEL_NL { 10 } /* Simple wraparound timer function */ unsigned long getticks(void); #define GETTICKCOUNT getticks #define TICKSPERSEC 1000 /* we choose to use milliseconds */ #define CURSORBLINK 450 /* no standard way to set this */ #define WCHAR wchar_t #define BYTE unsigned char #define PLATFORM_CLIPBOARDS(X) \ X(CLIP_PRIMARY, "X11 primary selection") \ X(CLIP_CLIPBOARD, "XDG clipboard") \ X(CLIP_CUSTOM_1, "") \ X(CLIP_CUSTOM_2, "") \ X(CLIP_CUSTOM_3, "") \ /* end of list */ #ifdef OSX_GTK /* OS X has no PRIMARY selection */ #define MOUSE_SELECT_CLIPBOARD CLIP_NULL #define MOUSE_PASTE_CLIPBOARD CLIP_LOCAL #define CLIPNAME_IMPLICIT "Last selected text" #define CLIPNAME_EXPLICIT "System clipboard" #define CLIPNAME_EXPLICIT_OBJECT "system clipboard" /* These defaults are the ones that more or less comply with the OS X * Human Interface Guidelines, i.e. copy/paste to the system clipboard * is _not_ implicit but requires a specific UI action. This is at * odds with all other PuTTY front ends' defaults, but on OS X there * is no multi-decade precedent for PuTTY working the other way. */ #define CLIPUI_DEFAULT_AUTOCOPY false #define CLIPUI_DEFAULT_MOUSE CLIPUI_IMPLICIT #define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT #define MENU_CLIPBOARD CLIP_CLIPBOARD #define COPYALL_CLIPBOARDS CLIP_CLIPBOARD #else #define MOUSE_SELECT_CLIPBOARD CLIP_PRIMARY #define MOUSE_PASTE_CLIPBOARD CLIP_PRIMARY #define CLIPNAME_IMPLICIT "PRIMARY" #define CLIPNAME_EXPLICIT "CLIPBOARD" #define CLIPNAME_EXPLICIT_OBJECT "CLIPBOARD" /* These defaults are the ones Unix PuTTY has historically had since * it was first thought of in 2002 */ #define CLIPUI_DEFAULT_AUTOCOPY false #define CLIPUI_DEFAULT_MOUSE CLIPUI_IMPLICIT #define CLIPUI_DEFAULT_INS CLIPUI_IMPLICIT #define MENU_CLIPBOARD CLIP_CLIPBOARD #define COPYALL_CLIPBOARDS CLIP_PRIMARY, CLIP_CLIPBOARD /* X11 supports arbitrary named clipboards */ #define NAMED_CLIPBOARDS #endif /* The per-session frontend structure managed by gtkwin.c */ typedef struct GtkFrontend GtkFrontend; /* Callback when a dialog box finishes, and a no-op implementation of it */ typedef void (*post_dialog_fn_t)(void *ctx, int result); void trivial_post_dialog_fn(void *vctx, int result); /* Start up a session window, with or without a preliminary config box */ void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx); void new_session_window(Conf *conf, const char *geometry_string); /* Defined in gtkmain.c */ void launch_duplicate_session(Conf *conf); void launch_new_session(void); void launch_saved_session(const char *str); void session_window_closed(void); void window_setup_error(const char *errmsg); #ifdef MAY_REFER_TO_GTK_IN_HEADERS GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend); #endif const struct BackendVtable *select_backend(Conf *conf); /* Defined in gtkcomm.c */ void gtkcomm_setup(void); /* Used to pass application-menu operations from gtkapp.c to gtkwin.c */ enum MenuAction { MA_COPY, MA_PASTE, MA_COPY_ALL, MA_DUPLICATE_SESSION, MA_RESTART_SESSION, MA_CHANGE_SETTINGS, MA_CLEAR_SCROLLBACK, MA_RESET_TERMINAL, MA_EVENT_LOG }; void app_menu_action(GtkFrontend *frontend, enum MenuAction); /* Arrays of pixmap data used for GTK window icons. (main_icon is for * the process's main window; cfg_icon is the modified icon used for * its config box.) */ extern const char *const *const main_icon[]; extern const char *const *const cfg_icon[]; extern const int n_main_icon, n_cfg_icon; /* Things gtkdlg.c needs from gtkwin.c */ #ifdef MAY_REFER_TO_GTK_IN_HEADERS enum DialogSlot { DIALOG_SLOT_RECONFIGURE, DIALOG_SLOT_NETWORK_PROMPT, DIALOG_SLOT_LOGFILE_PROMPT, DIALOG_SLOT_WARN_ON_CLOSE, DIALOG_SLOT_CONNECTION_FATAL, DIALOG_SLOT_LIMIT /* must remain last */ }; GtkWidget *gtk_seat_get_window(Seat *seat); void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog); void unregister_dialog(Seat *seat, enum DialogSlot slot); void set_window_icon(GtkWidget *window, const char *const *const *icon, int n_icon); extern GdkAtom compound_text_atom; #endif /* Things gtkwin.c needs from gtkdlg.c */ #ifdef MAY_REFER_TO_GTK_IN_HEADERS GtkWidget *create_config_box(const char *title, Conf *conf, bool midsession, int protcfginfo, post_dialog_fn_t after, void *afterctx); #endif void nonfatal_message_box(void *window, const char *msg); void about_box(void *window); typedef struct eventlog_stuff eventlog_stuff; eventlog_stuff *eventlogstuff_new(void); void eventlogstuff_free(eventlog_stuff *); void showeventlog(eventlog_stuff *estuff, void *parentwin); void logevent_dlg(eventlog_stuff *estuff, const char *string); int gtkdlg_askappend(Seat *seat, Filename *filename, void (*callback)(void *ctx, int result), void *ctx); int gtk_seat_verify_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **fingerprints, void (*callback)(void *ctx, int result), void *ctx); int gtk_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, void (*callback)(void *ctx, int result), void *ctx); int gtk_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, int result), void *ctx); #ifdef MAY_REFER_TO_GTK_IN_HEADERS struct message_box_button { const char *title; char shortcut; int type; /* more negative means more appropriate to be the Esc action */ int value; /* message box's return value if this is pressed */ }; struct message_box_buttons { const struct message_box_button *buttons; int nbuttons; }; extern const struct message_box_buttons buttons_yn, buttons_ok; GtkWidget *create_message_box( GtkWidget *parentwin, const char *title, const char *msg, int minwid, bool selectable, const struct message_box_buttons *buttons, post_dialog_fn_t after, void *afterctx); #endif /* gtkwin.c needs this special function in xkeysym.c */ int keysym_to_unicode(int keysym); /* Things uxstore.c needs from gtkwin.c */ char *x_get_default(const char *key); /* Things uxstore.c provides to gtkwin.c */ void provide_xrm_string(const char *string, const char *progname); /* Function that {gtkapp,gtkmain}.c needs from ux{pterm,putty}.c. Does * early process setup that varies between applications (e.g. * pty_pre_init or sk_init), and is passed a boolean by the caller * indicating whether this is an OS X style multi-session monolithic * process or an ordinary Unix one-shot. */ void setup(bool single_session_in_this_process); /* * Per-application constants that affect behaviour of shared modules. */ /* Do we need an Event Log menu item? (yes for PuTTY, no for pterm) */ extern const bool use_event_log; /* Do we need a New Session menu item? (yes for PuTTY, no for pterm) */ extern const bool new_session; /* Do we need a Saved Sessions menu item? (yes for PuTTY, no for pterm) */ extern const bool saved_sessions; /* When we Duplicate Session, do we need to double-check that the Conf * is in a launchable state? (no for pterm, because conf_launchable * returns an irrelevant answer, since we'll force use of the pty * backend which ignores all the relevant settings) */ extern const bool dup_check_launchable; /* In the Duplicate Session serialised data, do we send/receive an * argv array after the main Conf? (yes for pterm, no for PuTTY) */ extern const bool use_pty_argv; /* * OS X environment munging: this is the prefix we expect to find on * environment variable names that were changed by osxlaunch. * Extracted from the command line of the OS X pterm main binary, and * used in uxpty.c to restore the original environment before * launching its subprocess. */ extern char *pty_osx_envrestore_prefix; /* Things provided by uxcons.c */ struct termios; void stderr_tty_init(void); /* call at startup if stderr might be a tty */ void premsg(struct termios *); void postmsg(struct termios *); /* The interface used by uxsel.c */ typedef struct uxsel_id uxsel_id; void uxsel_init(void); typedef void (*uxsel_callback_fn)(int fd, int event); void uxsel_set(int fd, int rwx, uxsel_callback_fn callback); void uxsel_del(int fd); enum { SELECT_R = 1, SELECT_W = 2, SELECT_X = 4 }; void select_result(int fd, int event); int first_fd(int *state, int *rwx); int next_fd(int *state, int *rwx); /* The following are expected to be provided _to_ uxsel.c by the frontend */ uxsel_id *uxsel_input_add(int fd, int rwx); /* returns an id */ void uxsel_input_remove(uxsel_id *id); /* uxcfg.c */ struct controlbox; void unix_setup_config_box( struct controlbox *b, bool midsession, int protocol); /* gtkcfg.c */ void gtk_setup_config_box( struct controlbox *b, bool midsession, void *window); /* * In the Unix Unicode layer, DEFAULT_CODEPAGE is a special value * which causes mb_to_wc and wc_to_mb to call _libc_ rather than * libcharset. That way, we can interface the various charsets * supported by libcharset with the one supported by mbstowcs and * wcstombs (which will be the character set in which stuff read * from the command line or config files is assumed to be encoded). */ #define DEFAULT_CODEPAGE 0xFFFF #define CP_UTF8 CS_UTF8 /* from libcharset */ #define strnicmp strncasecmp #define stricmp strcasecmp /* BSD-semantics version of signal(), and another helpful function */ void (*putty_signal(int sig, void (*func)(int)))(int); void block_signal(int sig, bool block_it); /* uxmisc.c */ void cloexec(int); void noncloexec(int); bool nonblock(int); bool no_nonblock(int); char *make_dir_and_check_ours(const char *dirname); char *make_dir_path(const char *path, mode_t mode); /* * Exports from unicode.c. */ struct unicode_data; bool init_ucs(struct unicode_data *ucsdata, char *line_codepage, bool utf8_override, int font_charset, int vtmode); /* * Spare functions exported directly from uxnet.c. */ void *sk_getxdmdata(Socket *sock, int *lenp); int sk_net_get_fd(Socket *sock); SockAddr *unix_sock_addr(const char *path); Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug); /* * General helpful Unix stuff: more helpful version of the FD_SET * macro, which also handles maxfd. */ #define FD_SET_MAX(fd, max, set) do { \ FD_SET(fd, &set); \ if (max < fd + 1) max = fd + 1; \ } while (0) /* * Exports from uxser.c. */ extern const struct BackendVtable serial_backend; /* * uxpeer.c, wrapping getsockopt(SO_PEERCRED). */ bool so_peercred(int fd, int *pid, int *uid, int *gid); /* * uxfdsock.c. */ Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug); /* * Default font setting, which can vary depending on NOT_X_WINDOWS. */ #ifdef NOT_X_WINDOWS #define DEFAULT_GTK_FONT "client:Monospace 12" #else #define DEFAULT_GTK_FONT "server:fixed" #endif /* * uxpty.c. */ void pty_pre_init(void); /* pty+utmp setup before dropping privilege */ /* Pass in the argv[] for an instance of the pty backend created by * the standard vtable constructor. Only called from (non-OSX) pterm, * which will construct exactly one such instance, and initialises * this from the command line. */ extern char **pty_argv; /* * gtkask.c. */ char *gtk_askpass_main(const char *display, const char *wintitle, const char *prompt, bool *success); /* * procnet.c. */ bool socket_peer_is_same_user(int fd); static inline bool sk_peer_trusted(Socket *sock) { int fd = sk_net_get_fd(sock); return fd >= 0 && socket_peer_is_same_user(fd); } /* * uxsftpserver.c. */ extern const SftpServerVtable unix_live_sftpserver_vt; /* * uxpoll.c. */ typedef struct pollwrapper pollwrapper; pollwrapper *pollwrap_new(void); void pollwrap_free(pollwrapper *pw); void pollwrap_clear(pollwrapper *pw); void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events); void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx); int pollwrap_poll_instant(pollwrapper *pw); int pollwrap_poll_endless(pollwrapper *pw); int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds); int pollwrap_get_fd_events(pollwrapper *pw, int fd); int pollwrap_get_fd_rwx(pollwrapper *pw, int fd); static inline bool pollwrap_check_fd_rwx(pollwrapper *pw, int fd, int rwx) { return (pollwrap_get_fd_rwx(pw, fd) & rwx) != 0; } /* * uxcliloop.c. */ typedef bool (*cliloop_pw_setup_t)(void *ctx, pollwrapper *pw); typedef void (*cliloop_pw_check_t)(void *ctx, pollwrapper *pw); typedef bool (*cliloop_continue_t)(void *ctx, bool found_any_fd, bool ran_any_callback); void cli_main_loop(cliloop_pw_setup_t pw_setup, cliloop_pw_check_t pw_check, cliloop_continue_t cont, void *ctx); bool cliloop_no_pw_setup(void *ctx, pollwrapper *pw); void cliloop_no_pw_check(void *ctx, pollwrapper *pw); bool cliloop_always_continue(void *ctx, bool, bool); #endif /* PUTTY_UNIX_PLATFORM_H */ work/unix/utils/0000775000000000000000000000000014714222463011060 5ustar work/unix/utils/block_signal.c0000664000000000000000000000063214714222463013654 0ustar /* * Handy function to block or unblock a signal, which does all the * messing about with sigset_t for you. */ #include #include #include "defs.h" void block_signal(int sig, bool block_it) { sigset_t ss; sigemptyset(&ss); sigaddset(&ss, sig); if(sigprocmask(block_it ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0) < 0) { perror("sigprocmask"); exit(1); } } work/unix/utils/cloexec.c0000664000000000000000000000235414714222463012652 0ustar /* * Set and clear the FD_CLOEXEC fcntl option on a file descriptor. * * We don't realistically expect these operations to fail (the most * plausible error condition is EBADF, but we always believe ourselves * to be passing a valid fd so even that's an assertion-fail sort of * response), so we don't make any effort to return sensible error * codes to the caller - we just log to standard error and die * unceremoniously. */ #include #include #include #include #include #include "putty.h" void cloexec(int fd) { int fdflags; fdflags = fcntl(fd, F_GETFD); if (fdflags < 0) { fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); exit(1); } if (fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) < 0) { fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); exit(1); } } void noncloexec(int fd) { int fdflags; fdflags = fcntl(fd, F_GETFD); if (fdflags < 0) { fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); exit(1); } if (fcntl(fd, F_SETFD, fdflags & ~FD_CLOEXEC) < 0) { fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); exit(1); } } work/unix/utils/dputs.c0000664000000000000000000000070114714222463012361 0ustar /* * Implementation of dputs() for Unix. * * The debug messages are written to standard output, and also into a * file called debug.log. */ #include #include "putty.h" static FILE *debug_fp = NULL; void dputs(const char *buf) { if (!debug_fp) { debug_fp = fopen("debug.log", "w"); } if (write(1, buf, strlen(buf)) < 0) {} /* 'error check' to placate gcc */ fputs(buf, debug_fp); fflush(debug_fp); } work/unix/utils/filename.c0000664000000000000000000000263014714222463013005 0ustar /* * Implementation of Filename for Unix, including f_open(). */ #include #include #include "putty.h" Filename *filename_from_str(const char *str) { Filename *ret = snew(Filename); ret->path = dupstr(str); return ret; } Filename *filename_copy(const Filename *fn) { return filename_from_str(fn->path); } const char *filename_to_str(const Filename *fn) { return fn->path; } bool filename_equal(const Filename *f1, const Filename *f2) { return !strcmp(f1->path, f2->path); } bool filename_is_null(const Filename *fn) { return !fn->path[0]; } void filename_free(Filename *fn) { sfree(fn->path); sfree(fn); } void filename_serialise(BinarySink *bs, const Filename *f) { put_asciz(bs, f->path); } Filename *filename_deserialise(BinarySource *src) { return filename_from_str(get_asciz(src)); } char filename_char_sanitise(char c) { if (c == '/') return '.'; return c; } FILE *f_open(const Filename *filename, char const *mode, bool is_private) { if (!is_private) { return fopen(filename->path, mode); } else { int fd; assert(mode[0] == 'w'); /* is_private is meaningless for read, and tricky for append */ fd = open(filename->path, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd < 0) return NULL; return fdopen(fd, mode); } } work/unix/utils/fontspec.c0000664000000000000000000000115414714222463013046 0ustar /* * Implementation of FontSpec for Unix. This is more or less * degenerate - on this platform a font specification is just a * string. */ #include "putty.h" FontSpec *fontspec_new(const char *name) { FontSpec *f = snew(FontSpec); f->name = dupstr(name); return f; } FontSpec *fontspec_copy(const FontSpec *f) { return fontspec_new(f->name); } void fontspec_free(FontSpec *f) { sfree(f->name); sfree(f); } void fontspec_serialise(BinarySink *bs, FontSpec *f) { put_asciz(bs, f->name); } FontSpec *fontspec_deserialise(BinarySource *src) { return fontspec_new(get_asciz(src)); } work/unix/utils/getticks.c0000664000000000000000000000161014714222463013037 0ustar /* * Implement getticks() for Unix. */ #include #include #include "putty.h" unsigned long getticks(void) { /* * We want to use milliseconds rather than the microseconds or * nanoseconds given by the underlying clock functions, because we * need a decent number of them to fit into a 32-bit word so it * can be used for keepalives. */ #if HAVE_CLOCK_GETTIME && HAVE_CLOCK_MONOTONIC { /* Use CLOCK_MONOTONIC if available, so as to be unconfused if * the system clock changes. */ struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) return ts.tv_sec * TICKSPERSEC + ts.tv_nsec / (1000000000 / TICKSPERSEC); } #endif { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * TICKSPERSEC + tv.tv_usec / (1000000 / TICKSPERSEC); } } work/unix/utils/nonblock.c0000664000000000000000000000253514714222463013036 0ustar /* * Set and clear the O_NONBLOCK fcntl option on an open file. * * We don't realistically expect these operations to fail (the most * plausible error condition is EBADF, but we always believe ourselves * to be passing a valid fd so even that's an assertion-fail sort of * response), so we don't make any effort to return sensible error * codes to the caller - we just log to standard error and die * unceremoniously. * * Returns the previous state of O_NONBLOCK. */ #include #include #include #include #include #include "putty.h" bool nonblock(int fd) { int fdflags; fdflags = fcntl(fd, F_GETFL); if (fdflags < 0) { fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); exit(1); } if (fcntl(fd, F_SETFL, fdflags | O_NONBLOCK) < 0) { fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); exit(1); } return fdflags & O_NONBLOCK; } bool no_nonblock(int fd) { int fdflags; fdflags = fcntl(fd, F_GETFL); if (fdflags < 0) { fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); exit(1); } if (fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) { fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); exit(1); } return fdflags & O_NONBLOCK; } work/unix/utils/pollwrap.c0000664000000000000000000001146714714222463013075 0ustar /* * Wrapper system around poll() that lets me treat it more or less * like select(), but avoiding the inherent limitation of select() * that it can't handle the full range of fds that are capable of * existing. * * The pollwrapper structure contains the 'struct pollfd' array passed * to poll() itself, and also a tree234 that maps each fd to its * location in the list, which makes it convenient to add or remove * individual fds from the system or change what events you're * watching for on them. So the API is _shaped_ basically like select, * even if none of the details are identical: from outside this * module, a pollwrapper can be used wherever you'd otherwise have had * an fd_set. * * Also, this module translate between the simple select r/w/x * classification and the richer poll flags. We have to stick to r/w/x * in this code base, because it ports to other systems where that's * all you get. */ /* On some systems this is needed to get poll.h to define eg.. POLLRDNORM */ #define _XOPEN_SOURCE #include #include "putty.h" #include "tree234.h" struct pollwrapper { struct pollfd *fds; size_t nfd, fdsize; tree234 *fdtopos; }; typedef struct pollwrap_fdtopos pollwrap_fdtopos; struct pollwrap_fdtopos { int fd; size_t pos; }; static int pollwrap_fd_cmp(void *av, void *bv) { pollwrap_fdtopos *a = (pollwrap_fdtopos *)av; pollwrap_fdtopos *b = (pollwrap_fdtopos *)bv; return a->fd < b->fd ? -1 : a->fd > b->fd ? +1 : 0; } pollwrapper *pollwrap_new(void) { pollwrapper *pw = snew(pollwrapper); pw->fdsize = 16; pw->nfd = 0; pw->fds = snewn(pw->fdsize, struct pollfd); pw->fdtopos = newtree234(pollwrap_fd_cmp); return pw; } void pollwrap_free(pollwrapper *pw) { pollwrap_clear(pw); freetree234(pw->fdtopos); sfree(pw->fds); sfree(pw); } void pollwrap_clear(pollwrapper *pw) { pw->nfd = 0; for (pollwrap_fdtopos *f2p; (f2p = delpos234(pw->fdtopos, 0)) != NULL ;) sfree(f2p); } void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events) { pollwrap_fdtopos *f2p, f2p_find; assert(fd >= 0); f2p_find.fd = fd; f2p = find234(pw->fdtopos, &f2p_find, NULL); if (!f2p) { sgrowarray(pw->fds, pw->fdsize, pw->nfd); size_t index = pw->nfd++; pw->fds[index].fd = fd; pw->fds[index].events = pw->fds[index].revents = 0; f2p = snew(pollwrap_fdtopos); f2p->fd = fd; f2p->pos = index; pollwrap_fdtopos *added = add234(pw->fdtopos, f2p); assert(added == f2p); } pw->fds[f2p->pos].events |= events; } /* Omit any of the POLL{RD,WR}{NORM,BAND} flag values that are still * not defined by poll.h, just in case */ #ifndef POLLRDNORM #define POLLRDNORM 0 #endif #ifndef POLLRDBAND #define POLLRDBAND 0 #endif #ifndef POLLWRNORM #define POLLWRNORM 0 #endif #ifndef POLLWRBAND #define POLLWRBAND 0 #endif #define SELECT_R_IN (POLLIN | POLLRDNORM | POLLRDBAND) #define SELECT_W_IN (POLLOUT | POLLWRNORM | POLLWRBAND) #define SELECT_X_IN (POLLPRI) #define SELECT_R_OUT (SELECT_R_IN | POLLERR | POLLHUP) #define SELECT_W_OUT (SELECT_W_IN | POLLERR) #define SELECT_X_OUT (SELECT_X_IN) void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx) { int events = 0; if (rwx & SELECT_R) events |= SELECT_R_IN; if (rwx & SELECT_W) events |= SELECT_W_IN; if (rwx & SELECT_X) events |= SELECT_X_IN; pollwrap_add_fd_events(pw, fd, events); } int pollwrap_poll_instant(pollwrapper *pw) { return poll(pw->fds, pw->nfd, 0); } int pollwrap_poll_endless(pollwrapper *pw) { return poll(pw->fds, pw->nfd, -1); } int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds) { assert(milliseconds >= 0); return poll(pw->fds, pw->nfd, milliseconds); } static void pollwrap_get_fd_events_revents(pollwrapper *pw, int fd, int *events_p, int *revents_p) { pollwrap_fdtopos *f2p, f2p_find; int events = 0, revents = 0; assert(fd >= 0); f2p_find.fd = fd; f2p = find234(pw->fdtopos, &f2p_find, NULL); if (f2p) { events = pw->fds[f2p->pos].events; revents = pw->fds[f2p->pos].revents; } if (events_p) *events_p = events; if (revents_p) *revents_p = revents; } int pollwrap_get_fd_events(pollwrapper *pw, int fd) { int revents; pollwrap_get_fd_events_revents(pw, fd, NULL, &revents); return revents; } int pollwrap_get_fd_rwx(pollwrapper *pw, int fd) { int events, revents; pollwrap_get_fd_events_revents(pw, fd, &events, &revents); int rwx = 0; if ((events & POLLIN) && (revents & SELECT_R_OUT)) rwx |= SELECT_R; if ((events & POLLOUT) && (revents & SELECT_W_OUT)) rwx |= SELECT_W; if ((events & POLLPRI) && (revents & SELECT_X_OUT)) rwx |= SELECT_X; return rwx; } work/unix/utils/signal.c0000664000000000000000000000145714714222463012510 0ustar /* * PuTTY's wrapper on signal(2). * * Calling signal() is non-portable, as it varies in meaning between * platforms and depending on feature macros, and has stupid semantics * at least some of the time. * * This function provides the same interface as the libc function, but * provides consistent semantics. It assumes POSIX semantics for * sigaction() (so you might need to do some more work if you port to * something ancient like SunOS 4). */ #include #include "defs.h" void (*putty_signal(int sig, void (*func)(int)))(int) { struct sigaction sa; struct sigaction old; sa.sa_handler = func; if(sigemptyset(&sa.sa_mask) < 0) return SIG_ERR; sa.sa_flags = SA_RESTART; if(sigaction(sig, &sa, &old) < 0) return SIG_ERR; return old.sa_handler; } work/unix/uxsel.c0000664000000000000000000000561214714222463011230 0ustar /* * 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() or poll()-type things doing to them during an extended * program run; at the other end it talks to pterm.c or uxplink.c or * anything else which might have its own means of actually doing * those select()-type things. */ #include #include "putty.h" #include "tree234.h" struct fd { int fd; int rwx; /* 4=except 2=write 1=read */ uxsel_callback_fn callback; uxsel_id *id; /* for uxsel_input_remove */ }; static tree234 *fds; static int uxsel_fd_cmp(void *av, void *bv) { struct fd *a = (struct fd *)av; struct fd *b = (struct fd *)bv; if (a->fd < b->fd) return -1; if (a->fd > b->fd) return +1; return 0; } static int uxsel_fd_findcmp(void *av, void *bv) { int *a = (int *)av; struct fd *b = (struct fd *)bv; if (*a < b->fd) return -1; if (*a > b->fd) return +1; return 0; } void uxsel_init(void) { fds = newtree234(uxsel_fd_cmp); } /* * Here is the interface to fd-supplying modules. They supply an * fd, a set of read/write/execute states, and a callback function * for when the fd satisfies one of those states. Repeated calls to * uxsel_set on the same fd are perfectly legal and serve to change * the rwx state (typically you only want to select an fd for * writing when you actually have pending data you want to write to * it!). */ void uxsel_set(int fd, int rwx, uxsel_callback_fn callback) { struct fd *newfd; assert(fd >= 0); uxsel_del(fd); if (rwx) { newfd = snew(struct fd); newfd->fd = fd; newfd->rwx = rwx; newfd->callback = callback; newfd->id = uxsel_input_add(fd, rwx); add234(fds, newfd); } } void uxsel_del(int fd) { struct fd *oldfd = find234(fds, &fd, uxsel_fd_findcmp); if (oldfd) { if (oldfd->id) uxsel_input_remove(oldfd->id); del234(fds, oldfd); sfree(oldfd); } } /* * And here is the interface to select-functionality-supplying * modules. */ int next_fd(int *state, int *rwx) { struct fd *fd; fd = index234(fds, (*state)++); if (fd) { *rwx = fd->rwx; return fd->fd; } else return -1; } int first_fd(int *state, int *rwx) { *state = 0; return next_fd(state, rwx); } void select_result(int fd, int event) { struct fd *fdstruct = find234(fds, &fd, uxsel_fd_findcmp); noise_ultralight(NOISE_SOURCE_IOID, fd); /* * Apparently this can sometimes be NULL. Can't see how, but I * assume it means I need to ignore the event since it's on an * fd I've stopped being interested in. Sigh. */ if (fdstruct) fdstruct->callback(fd, event); } work/unix/uxxtruss.c0000664000000000000000000000646514714222463012024 0ustar /* * Main program for Unix xtruss. */ #include #include #include #include #include #include #include #include "putty.h" #include "ssh.h" #include "xtruss.h" struct xtruss_platform { pid_t childpid; }; const bool buildinfo_gtk_relevant = false; static int signalpipe[2] = { -1, -1 }; static void sigchld(int signum) { if (write(signalpipe[1], "x", 1) <= 0) { /* * If this fails with EAGAIN, it's because the pipe buffer is * full, in which case we can ignore the error because we know * the main loop is already going to be called back after this * signal. * * If it fails for any other reason, that's probably because * the pipe doesn't even exist, in which case the main program * isn't interested in receiving these notifications anyway, * so we might as well do nothing. * * (In other words, the only reason I'm checking the return * status of write() after all is that modern compilers * complain if you don't.) */ } } void xtruss_start_subprocess(xtruss_state *xs) { pid_t pid = fork(); if (pid < 0) { perror("fork"); exit(1); } else if (pid == 0) { putenv(dupprintf("DISPLAY=%s", xs->env_disp)); putenv(dupprintf("XAUTHORITY=%s", xs->env_auth)); execvp(xs->subcommand[0], xs->subcommand); perror("exec"); exit(127); } else { xs->platform->childpid = pid; } } static bool xtruss_pw_setup(void *ctx, pollwrapper *pw) { if (signalpipe[0] >= 0) pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); return true; } static void xtruss_pw_check(void *ctx, pollwrapper *pw) { xtruss_state *xs = (xtruss_state *)ctx; if (signalpipe[0] >= 0 && pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { /* Empty the pipe */ char buf[256]; while (read(signalpipe[0], buf, sizeof(buf)) > 0); int retd, status; while ((retd = waitpid(-1, &status, WNOHANG)) > 0) { if (xs->platform->childpid >= 0 && xs->platform->childpid == retd) { if (WIFEXITED(status)) { xs->exit_status = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { xs->exit_status = 128 + WTERMSIG(status); } } } } } static bool xtruss_continue(void *ctx, bool found_any_fd, bool ran_any_callback) { xtruss_state *xs = (xtruss_state *)ctx; return xs->exit_status < 0; } int main(int argc, char **argv) { xtruss_state *xs = xtruss_new(); xs->platform = snew(struct xtruss_platform); memset(xs->platform, 0, sizeof(*xs->platform)); xs->platform->childpid = -1; xtruss_cmdline(xs, argc, argv); /* * Set up the pipe we'll use to tell us about SIGCHLD. */ if (pipe(signalpipe) < 0) { perror("pipe"); exit(1); } nonblock(signalpipe[0]); nonblock(signalpipe[1]); putty_signal(SIGCHLD, sigchld); sk_init(); uxsel_init(); xtruss_start(xs); cli_main_loop(xtruss_pw_setup, xtruss_pw_check, xtruss_continue, xs); assert(xs->exit_status >= 0); exit(xs->exit_status); } work/unix/x11.c0000664000000000000000000001423614714222463010503 0ustar /* * ux_x11.c: fetch local auth data for X forwarding. */ #include #include #include #include #include #include #include #include "putty.h" #include "ssh.h" #include "network.h" void platform_get_x11_auth(struct X11Display *disp, Conf *conf) { char *xauthfile; bool needs_free; /* * Find the .Xauthority file. */ needs_free = false; xauthfile = getenv("XAUTHORITY"); if (!xauthfile) { xauthfile = getenv("HOME"); if (xauthfile) { xauthfile = dupcat(xauthfile, "/.Xauthority"); needs_free = true; } } if (xauthfile) { x11_get_auth_from_authfile(disp, xauthfile); if (needs_free) sfree(xauthfile); } } const bool platform_uses_x11_unix_by_default = true; int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, const char *screen_number_suffix, ptrlen authproto, ptrlen authdata, Socket **sockets, Conf *conf) { char *tmpdir; char *authfilename = NULL; strbuf *authfiledata = NULL; char *unix_path = NULL; SockAddr *a_tcp = NULL, *a_unix = NULL; int authfd; FILE *authfp; int displayno; authfiledata = strbuf_new_nm(); int nsockets = 0; /* * Look for a free TCP port to run our server on. */ for (displayno = mindisp;; displayno++) { const char *err; int tcp_port = displayno + 6000; int addrtype = ADDRTYPE_IPV4; sockets[nsockets] = new_listener( NULL, tcp_port, plug, false, conf, addrtype); err = sk_socket_error(sockets[nsockets]); if (!err) { char *hostname = get_hostname(); if (hostname) { char *canonicalname = NULL; a_tcp = sk_namelookup(hostname, &canonicalname, addrtype); sfree(canonicalname); } sfree(hostname); nsockets++; break; /* success! */ } else { sk_close(sockets[nsockets]); } /* * If we weren't able to bind to this port because it's in use * by another program, go round this loop and try again. But * for any other reason, give up completely and return failure * to our caller. * * sk_socket_error currently has no machine-readable component * (it would need a cross-platform abstraction of the socket * error types we care about, plus translation from each OS * error enumeration into that). So we use the disgusting * approach of a string compare between the error string and * the one EADDRINUSE would have given :-( */ if (strcmp(err, strerror(EADDRINUSE))) goto out; } if (a_tcp) { x11_format_auth_for_authfile( BinarySink_UPCAST(authfiledata), a_tcp, displayno, authproto, authdata); } /* * Try to establish the Unix-domain analogue. That may or may not * work - file permissions in /tmp may prevent it, for example - * but it's worth a try, and we don't consider it a fatal error if * it doesn't work. */ unix_path = dupprintf("/tmp/.X11-unix/X%d", displayno); a_unix = unix_sock_addr(unix_path); sockets[nsockets] = new_unix_listener(a_unix, plug); if (!sk_socket_error(sockets[nsockets])) { x11_format_auth_for_authfile( BinarySink_UPCAST(authfiledata), a_unix, displayno, authproto, authdata); nsockets++; } else { sk_close(sockets[nsockets]); sfree(unix_path); unix_path = NULL; } /* * Decide where the authority data will be written. */ tmpdir = getenv("TMPDIR"); if (!tmpdir || !*tmpdir) tmpdir = "/tmp"; authfilename = dupcat(tmpdir, "/", progname, "-Xauthority-XXXXXX"); { int oldumask = umask(077); authfd = mkstemp(authfilename); umask(oldumask); } if (authfd < 0) { while (nsockets-- > 0) sk_close(sockets[nsockets]); goto out; } /* * Spawn a subprocess which will try to reliably delete our * auth file when we terminate, in case we die unexpectedly. */ { int cleanup_pipe[2]; pid_t pid; /* Don't worry if pipe or fork fails; it's not _that_ critical. */ if (!pipe(cleanup_pipe)) { if ((pid = fork()) == 0) { int buf[1024]; /* * Our parent process holds the writing end of * this pipe, and writes nothing to it. Hence, * we expect read() to return EOF as soon as * that process terminates. */ close(0); close(1); close(2); setpgid(0, 0); close(cleanup_pipe[1]); close(authfd); while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0); unlink(authfilename); if (unix_path) unlink(unix_path); _exit(0); } else if (pid < 0) { close(cleanup_pipe[0]); close(cleanup_pipe[1]); } else { close(cleanup_pipe[0]); cloexec(cleanup_pipe[1]); } } } authfp = fdopen(authfd, "wb"); fwrite(authfiledata->u, 1, authfiledata->len, authfp); fclose(authfp); { char *display = dupprintf(":%d%s", displayno, screen_number_suffix); conf_set_str_str(conf, CONF_environmt, "DISPLAY", display); sfree(display); } conf_set_str_str(conf, CONF_environmt, "XAUTHORITY", authfilename); /* * FIXME: return at least the DISPLAY and XAUTHORITY env settings, * and perhaps also the display number */ out: if (a_tcp) sk_addr_free(a_tcp); /* a_unix doesn't need freeing, because new_unix_listener took it over */ sfree(authfilename); strbuf_free(authfiledata); sfree(unix_path); return nsockets; } work/utils/0000775000000000000000000000000014714222463010075 5ustar work/utils/CMakeLists.txt0000664000000000000000000000074714714222463012645 0ustar add_sources_from_current_dir(utils bufchain.c burnstr.c chomp.c conf.c debug.c dupcat.c dupprintf.c dupstr.c fgetline.c host_strchr.c host_strchr_internal.c host_strcspn.c host_strduptrim.c host_strrchr.c marshal.c memory.c nullstrcmp.c out_of_memory.c ptrlen.c sk_free_peer_info.c smemclr.c smemeq.c strbuf.c string_length_for_printf.c tree234.c x11authfile.c x11authnames.c x11_dehexify.c x11_make_greeting.c x11_parse_ip.c) work/utils/bufchain.c0000664000000000000000000001056314714222463012025 0ustar /* * Generic routines to deal with send buffers: a linked list of * smallish blocks, with the operations * * - add an arbitrary amount of data to the end of the list * - remove the first N bytes from the list * - return a (pointer,length) pair giving some initial data in * the list, suitable for passing to a send or write system * call * - retrieve a larger amount of initial data from the list * - return the current size of the buffer chain in bytes */ #include "defs.h" #include "misc.h" #define BUFFER_MIN_GRANULE 512 struct bufchain_granule { struct bufchain_granule *next; char *bufpos, *bufend, *bufmax; }; static void uninitialised_queue_idempotent_callback(IdempotentCallback *ic) { unreachable("bufchain callback used while uninitialised"); } void bufchain_init(bufchain *ch) { ch->head = ch->tail = NULL; ch->buffersize = 0; ch->ic = NULL; ch->queue_idempotent_callback = uninitialised_queue_idempotent_callback; } void bufchain_clear(bufchain *ch) { struct bufchain_granule *b; while (ch->head) { b = ch->head; ch->head = ch->head->next; smemclr(b, sizeof(*b)); sfree(b); } ch->tail = NULL; ch->buffersize = 0; } size_t bufchain_size(bufchain *ch) { return ch->buffersize; } void bufchain_set_callback_inner( bufchain *ch, IdempotentCallback *ic, void (*queue_idempotent_callback)(IdempotentCallback *ic)) { ch->queue_idempotent_callback = queue_idempotent_callback; ch->ic = ic; } void bufchain_add(bufchain *ch, const void *data, size_t len) { const char *buf = (const char *)data; if (len == 0) return; ch->buffersize += len; while (len > 0) { if (ch->tail && ch->tail->bufend < ch->tail->bufmax) { size_t copylen = min(len, ch->tail->bufmax - ch->tail->bufend); memcpy(ch->tail->bufend, buf, copylen); buf += copylen; len -= copylen; ch->tail->bufend += copylen; } if (len > 0) { size_t grainlen = max(sizeof(struct bufchain_granule) + len, BUFFER_MIN_GRANULE); struct bufchain_granule *newbuf; newbuf = smalloc(grainlen); newbuf->bufpos = newbuf->bufend = (char *)newbuf + sizeof(struct bufchain_granule); newbuf->bufmax = (char *)newbuf + grainlen; newbuf->next = NULL; if (ch->tail) ch->tail->next = newbuf; else ch->head = newbuf; ch->tail = newbuf; } } if (ch->ic) ch->queue_idempotent_callback(ch->ic); } void bufchain_consume(bufchain *ch, size_t len) { struct bufchain_granule *tmp; assert(ch->buffersize >= len); while (len > 0) { int remlen = len; assert(ch->head != NULL); if (remlen >= ch->head->bufend - ch->head->bufpos) { remlen = ch->head->bufend - ch->head->bufpos; tmp = ch->head; ch->head = tmp->next; if (!ch->head) ch->tail = NULL; smemclr(tmp, sizeof(*tmp)); sfree(tmp); } else ch->head->bufpos += remlen; ch->buffersize -= remlen; len -= remlen; } } ptrlen bufchain_prefix(bufchain *ch) { return make_ptrlen(ch->head->bufpos, ch->head->bufend - ch->head->bufpos); } void bufchain_fetch(bufchain *ch, void *data, size_t len) { struct bufchain_granule *tmp; char *data_c = (char *)data; tmp = ch->head; assert(ch->buffersize >= len); while (len > 0) { int remlen = len; assert(tmp != NULL); if (remlen >= tmp->bufend - tmp->bufpos) remlen = tmp->bufend - tmp->bufpos; memcpy(data_c, tmp->bufpos, remlen); tmp = tmp->next; len -= remlen; data_c += remlen; } } void bufchain_fetch_consume(bufchain *ch, void *data, size_t len) { bufchain_fetch(ch, data, len); bufchain_consume(ch, len); } bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len) { if (ch->buffersize >= len) { bufchain_fetch_consume(ch, data, len); return true; } else { return false; } } size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len) { if (len > ch->buffersize) len = ch->buffersize; if (len) bufchain_fetch_consume(ch, data, len); return len; } work/utils/burnstr.c0000664000000000000000000000045314714222463011742 0ustar /* * 'Burn' a dynamically allocated string, in the sense of destroying * it beyond recovery: overwrite it with zeroes and then free it. */ #include "defs.h" #include "misc.h" void burnstr(char *string) { if (string) { smemclr(string, strlen(string)); sfree(string); } } work/utils/chomp.c0000664000000000000000000000111114714222463011341 0ustar /* * Perl-style 'chomp', for a line we just read with fgetline. * * Unlike Perl chomp, however, we're deliberately forgiving of strange * line-ending conventions. * * Also we forgive NULL on input, so you can just write 'line = * chomp(fgetline(fp));' and not bother checking for NULL until * afterwards. */ #include #include "defs.h" #include "misc.h" char *chomp(char *str) { if (str) { int len = strlen(str); while (len > 0 && (str[len-1] == '\r' || str[len-1] == '\n')) len--; str[len] = '\0'; } return str; } work/utils/conf.c0000664000000000000000000003633314714222463011176 0ustar /* * conf.c: implementation of the internal storage format used for * the configuration of a PuTTY session. */ #include #include #include #include "tree234.h" #include "putty.h" /* * Enumeration of types used in keys and values. */ typedef enum { TYPE_NONE, TYPE_BOOL, TYPE_INT, TYPE_STR, TYPE_FILENAME, TYPE_FONT } Type; /* * Arrays which allow us to look up the subkey and value types for a * given primary key id. */ #define CONF_SUBKEYTYPE_DEF(valtype, keytype, keyword) TYPE_ ## keytype, static int subkeytypes[] = { CONFIG_OPTIONS(CONF_SUBKEYTYPE_DEF) }; #define CONF_VALUETYPE_DEF(valtype, keytype, keyword) TYPE_ ## valtype, static int valuetypes[] = { CONFIG_OPTIONS(CONF_VALUETYPE_DEF) }; /* * Configuration keys are primarily integers (big enum of all the * different configurable options); some keys have string-designated * subkeys, such as the list of environment variables (subkeys * defined by the variable names); some have integer-designated * subkeys (wordness, colours, preference lists). */ struct key { int primary; union { int i; char *s; } secondary; }; /* Variant form of struct key which doesn't contain dynamic data, used * for lookups. */ struct constkey { int primary; union { int i; const char *s; } secondary; }; struct value { union { bool boolval; int intval; char *stringval; Filename *fileval; FontSpec *fontval; } u; }; struct conf_entry { struct key key; struct value value; }; struct conf_tag { tree234 *tree; }; /* * Because 'struct key' is the first element in 'struct conf_entry', * it's safe (guaranteed by the C standard) to cast arbitrarily back * and forth between the two types. Therefore, we only need one * comparison function, which can double as a main sort function for * the tree (comparing two conf_entry structures with each other) * and a search function (looking up an externally supplied key). */ static int conf_cmp(void *av, void *bv) { struct key *a = (struct key *)av; struct key *b = (struct key *)bv; if (a->primary < b->primary) return -1; else if (a->primary > b->primary) return +1; switch (subkeytypes[a->primary]) { case TYPE_INT: if (a->secondary.i < b->secondary.i) return -1; else if (a->secondary.i > b->secondary.i) return +1; return 0; case TYPE_STR: return strcmp(a->secondary.s, b->secondary.s); default: return 0; } } static int conf_cmp_constkey(void *av, void *bv) { struct key *a = (struct key *)av; struct constkey *b = (struct constkey *)bv; if (a->primary < b->primary) return -1; else if (a->primary > b->primary) return +1; switch (subkeytypes[a->primary]) { case TYPE_INT: if (a->secondary.i < b->secondary.i) return -1; else if (a->secondary.i > b->secondary.i) return +1; return 0; case TYPE_STR: return strcmp(a->secondary.s, b->secondary.s); default: return 0; } } /* * Free any dynamic data items pointed to by a 'struct key'. We * don't free the structure itself, since it's probably part of a * larger allocated block. */ static void free_key(struct key *key) { if (subkeytypes[key->primary] == TYPE_STR) sfree(key->secondary.s); } /* * Copy a 'struct key' into another one, copying its dynamic data * if necessary. */ static void copy_key(struct key *to, struct key *from) { to->primary = from->primary; switch (subkeytypes[to->primary]) { case TYPE_INT: to->secondary.i = from->secondary.i; break; case TYPE_STR: to->secondary.s = dupstr(from->secondary.s); break; } } /* * Free any dynamic data items pointed to by a 'struct value'. We * don't free the value itself, since it's probably part of a larger * allocated block. */ static void free_value(struct value *val, int type) { if (type == TYPE_STR) sfree(val->u.stringval); else if (type == TYPE_FILENAME) filename_free(val->u.fileval); else if (type == TYPE_FONT) fontspec_free(val->u.fontval); } /* * Copy a 'struct value' into another one, copying its dynamic data * if necessary. */ static void copy_value(struct value *to, struct value *from, int type) { switch (type) { case TYPE_BOOL: to->u.boolval = from->u.boolval; break; case TYPE_INT: to->u.intval = from->u.intval; break; case TYPE_STR: to->u.stringval = dupstr(from->u.stringval); break; case TYPE_FILENAME: to->u.fileval = filename_copy(from->u.fileval); break; case TYPE_FONT: to->u.fontval = fontspec_copy(from->u.fontval); break; } } /* * Free an entire 'struct conf_entry' and its dynamic data. */ static void free_entry(struct conf_entry *entry) { free_key(&entry->key); free_value(&entry->value, valuetypes[entry->key.primary]); sfree(entry); } Conf *conf_new(void) { Conf *conf = snew(struct conf_tag); conf->tree = newtree234(conf_cmp); return conf; } static void conf_clear(Conf *conf) { struct conf_entry *entry; while ((entry = delpos234(conf->tree, 0)) != NULL) free_entry(entry); } void conf_free(Conf *conf) { conf_clear(conf); freetree234(conf->tree); sfree(conf); } static void conf_insert(Conf *conf, struct conf_entry *entry) { struct conf_entry *oldentry = add234(conf->tree, entry); if (oldentry && oldentry != entry) { del234(conf->tree, oldentry); free_entry(oldentry); oldentry = add234(conf->tree, entry); assert(oldentry == entry); } } void conf_copy_into(Conf *newconf, Conf *oldconf) { struct conf_entry *entry, *entry2; int i; conf_clear(newconf); for (i = 0; (entry = index234(oldconf->tree, i)) != NULL; i++) { entry2 = snew(struct conf_entry); copy_key(&entry2->key, &entry->key); copy_value(&entry2->value, &entry->value, valuetypes[entry->key.primary]); add234(newconf->tree, entry2); } } Conf *conf_copy(Conf *oldconf) { Conf *newconf = conf_new(); conf_copy_into(newconf, oldconf); return newconf; } bool conf_get_bool(Conf *conf, int primary) { struct key key; struct conf_entry *entry; assert(subkeytypes[primary] == TYPE_NONE); assert(valuetypes[primary] == TYPE_BOOL); key.primary = primary; entry = find234(conf->tree, &key, NULL); assert(entry); return entry->value.u.boolval; } int conf_get_int(Conf *conf, int primary) { struct key key; struct conf_entry *entry; assert(subkeytypes[primary] == TYPE_NONE); assert(valuetypes[primary] == TYPE_INT); key.primary = primary; entry = find234(conf->tree, &key, NULL); assert(entry); return entry->value.u.intval; } int conf_get_int_int(Conf *conf, int primary, int secondary) { struct key key; struct conf_entry *entry; assert(subkeytypes[primary] == TYPE_INT); assert(valuetypes[primary] == TYPE_INT); key.primary = primary; key.secondary.i = secondary; entry = find234(conf->tree, &key, NULL); assert(entry); return entry->value.u.intval; } char *conf_get_str(Conf *conf, int primary) { struct key key; struct conf_entry *entry; assert(subkeytypes[primary] == TYPE_NONE); assert(valuetypes[primary] == TYPE_STR); key.primary = primary; entry = find234(conf->tree, &key, NULL); assert(entry); return entry->value.u.stringval; } char *conf_get_str_str_opt(Conf *conf, int primary, const char *secondary) { struct key key; struct conf_entry *entry; assert(subkeytypes[primary] == TYPE_STR); assert(valuetypes[primary] == TYPE_STR); key.primary = primary; key.secondary.s = (char *)secondary; entry = find234(conf->tree, &key, NULL); return entry ? entry->value.u.stringval : NULL; } char *conf_get_str_str(Conf *conf, int primary, const char *secondary) { char *ret = conf_get_str_str_opt(conf, primary, secondary); assert(ret); return ret; } char *conf_get_str_strs(Conf *conf, int primary, char *subkeyin, char **subkeyout) { struct constkey key; struct conf_entry *entry; assert(subkeytypes[primary] == TYPE_STR); assert(valuetypes[primary] == TYPE_STR); key.primary = primary; if (subkeyin) { key.secondary.s = subkeyin; entry = findrel234(conf->tree, &key, NULL, REL234_GT); } else { key.secondary.s = ""; entry = findrel234(conf->tree, &key, conf_cmp_constkey, REL234_GE); } if (!entry || entry->key.primary != primary) return NULL; *subkeyout = entry->key.secondary.s; return entry->value.u.stringval; } char *conf_get_str_nthstrkey(Conf *conf, int primary, int n) { struct constkey key; struct conf_entry *entry; int index; assert(subkeytypes[primary] == TYPE_STR); assert(valuetypes[primary] == TYPE_STR); key.primary = primary; key.secondary.s = ""; entry = findrelpos234(conf->tree, &key, conf_cmp_constkey, REL234_GE, &index); if (!entry || entry->key.primary != primary) return NULL; entry = index234(conf->tree, index + n); if (!entry || entry->key.primary != primary) return NULL; return entry->key.secondary.s; } Filename *conf_get_filename(Conf *conf, int primary) { struct key key; struct conf_entry *entry; assert(subkeytypes[primary] == TYPE_NONE); assert(valuetypes[primary] == TYPE_FILENAME); key.primary = primary; entry = find234(conf->tree, &key, NULL); assert(entry); return entry->value.u.fileval; } FontSpec *conf_get_fontspec(Conf *conf, int primary) { struct key key; struct conf_entry *entry; assert(subkeytypes[primary] == TYPE_NONE); assert(valuetypes[primary] == TYPE_FONT); key.primary = primary; entry = find234(conf->tree, &key, NULL); assert(entry); return entry->value.u.fontval; } void conf_set_bool(Conf *conf, int primary, bool value) { struct conf_entry *entry = snew(struct conf_entry); assert(subkeytypes[primary] == TYPE_NONE); assert(valuetypes[primary] == TYPE_BOOL); entry->key.primary = primary; entry->value.u.boolval = value; conf_insert(conf, entry); } void conf_set_int(Conf *conf, int primary, int value) { struct conf_entry *entry = snew(struct conf_entry); assert(subkeytypes[primary] == TYPE_NONE); assert(valuetypes[primary] == TYPE_INT); entry->key.primary = primary; entry->value.u.intval = value; conf_insert(conf, entry); } void conf_set_int_int(Conf *conf, int primary, int secondary, int value) { struct conf_entry *entry = snew(struct conf_entry); assert(subkeytypes[primary] == TYPE_INT); assert(valuetypes[primary] == TYPE_INT); entry->key.primary = primary; entry->key.secondary.i = secondary; entry->value.u.intval = value; conf_insert(conf, entry); } void conf_set_str(Conf *conf, int primary, const char *value) { struct conf_entry *entry = snew(struct conf_entry); assert(subkeytypes[primary] == TYPE_NONE); assert(valuetypes[primary] == TYPE_STR); entry->key.primary = primary; entry->value.u.stringval = dupstr(value); conf_insert(conf, entry); } void conf_set_str_str(Conf *conf, int primary, const char *secondary, const char *value) { struct conf_entry *entry = snew(struct conf_entry); assert(subkeytypes[primary] == TYPE_STR); assert(valuetypes[primary] == TYPE_STR); entry->key.primary = primary; entry->key.secondary.s = dupstr(secondary); entry->value.u.stringval = dupstr(value); conf_insert(conf, entry); } void conf_del_str_str(Conf *conf, int primary, const char *secondary) { struct key key; struct conf_entry *entry; assert(subkeytypes[primary] == TYPE_STR); assert(valuetypes[primary] == TYPE_STR); key.primary = primary; key.secondary.s = (char *)secondary; entry = find234(conf->tree, &key, NULL); if (entry) { del234(conf->tree, entry); free_entry(entry); } } void conf_set_filename(Conf *conf, int primary, const Filename *value) { struct conf_entry *entry = snew(struct conf_entry); assert(subkeytypes[primary] == TYPE_NONE); assert(valuetypes[primary] == TYPE_FILENAME); entry->key.primary = primary; entry->value.u.fileval = filename_copy(value); conf_insert(conf, entry); } void conf_set_fontspec(Conf *conf, int primary, const FontSpec *value) { struct conf_entry *entry = snew(struct conf_entry); assert(subkeytypes[primary] == TYPE_NONE); assert(valuetypes[primary] == TYPE_FONT); entry->key.primary = primary; entry->value.u.fontval = fontspec_copy(value); conf_insert(conf, entry); } void conf_serialise(BinarySink *bs, Conf *conf) { int i; struct conf_entry *entry; for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) { put_uint32(bs, entry->key.primary); switch (subkeytypes[entry->key.primary]) { case TYPE_INT: put_uint32(bs, entry->key.secondary.i); break; case TYPE_STR: put_asciz(bs, entry->key.secondary.s); break; } switch (valuetypes[entry->key.primary]) { case TYPE_BOOL: put_bool(bs, entry->value.u.boolval); break; case TYPE_INT: put_uint32(bs, entry->value.u.intval); break; case TYPE_STR: put_asciz(bs, entry->value.u.stringval); break; case TYPE_FILENAME: filename_serialise(bs, entry->value.u.fileval); break; case TYPE_FONT: fontspec_serialise(bs, entry->value.u.fontval); break; } } put_uint32(bs, 0xFFFFFFFFU); } bool conf_deserialise(Conf *conf, BinarySource *src) { struct conf_entry *entry; unsigned primary; while (1) { primary = get_uint32(src); if (get_err(src)) return false; if (primary == 0xFFFFFFFFU) return true; if (primary >= N_CONFIG_OPTIONS) return false; entry = snew(struct conf_entry); entry->key.primary = primary; switch (subkeytypes[entry->key.primary]) { case TYPE_INT: entry->key.secondary.i = toint(get_uint32(src)); break; case TYPE_STR: entry->key.secondary.s = dupstr(get_asciz(src)); break; } switch (valuetypes[entry->key.primary]) { case TYPE_BOOL: entry->value.u.boolval = get_bool(src); break; case TYPE_INT: entry->value.u.intval = toint(get_uint32(src)); break; case TYPE_STR: entry->value.u.stringval = dupstr(get_asciz(src)); break; case TYPE_FILENAME: entry->value.u.fileval = filename_deserialise(src); break; case TYPE_FONT: entry->value.u.fontval = fontspec_deserialise(src); break; } if (get_err(src)) { free_entry(entry); return false; } conf_insert(conf, entry); } } work/utils/debug.c0000664000000000000000000000270414714222463011332 0ustar /* * Debugging routines used by the debug() macros, at least if you * compiled with -DDEBUG (aka the PUTTY_DEBUG cmake option) so that * those macros don't optimise down to nothing. */ #include "defs.h" #include "misc.h" #include "utils/utils.h" void debug_printf(const char *fmt, ...) { char *buf; va_list ap; va_start(ap, fmt); buf = dupvprintf(fmt, ap); dputs(buf); sfree(buf); va_end(ap); } void debug_memdump(const void *buf, int len, bool L) { int i; const unsigned char *p = buf; char foo[17]; if (L) { int delta; debug_printf("\t%d (0x%x) bytes:\n", len, len); delta = 15 & (uintptr_t)p; p -= delta; len += delta; } for (; 0 < len; p += 16, len -= 16) { dputs(" "); if (L) debug_printf("%p: ", p); strcpy(foo, "................"); /* sixteen dots */ for (i = 0; i < 16 && i < len; ++i) { if (&p[i] < (unsigned char *) buf) { dputs(" "); /* 3 spaces */ foo[i] = ' '; } else { debug_printf("%c%2.2x", &p[i] != (unsigned char *) buf && i % 4 ? '.' : ' ', p[i] ); if (p[i] >= ' ' && p[i] <= '~') foo[i] = (char) p[i]; } } foo[i] = '\0'; debug_printf("%*s%s\n", (16 - i) * 3 + 2, "", foo); } } work/utils/dupcat.c0000664000000000000000000000160214714222463011520 0ustar /* * Implementation function behind dupcat() in misc.h. * * This function is called with an arbitrary number of 'const char *' * parameters, of which the last one is a null pointer. The wrapper * macro puts on the null pointer itself, so normally callers don't * have to. */ #include #include #include "defs.h" #include "misc.h" char *dupcat_fn(const char *s1, ...) { int len; char *p, *q, *sn; va_list ap; len = strlen(s1); va_start(ap, s1); while (1) { sn = va_arg(ap, char *); if (!sn) break; len += strlen(sn); } va_end(ap); p = snewn(len + 1, char); strcpy(p, s1); q = p + strlen(p); va_start(ap, s1); while (1) { sn = va_arg(ap, char *); if (!sn) break; strcpy(q, sn); q += strlen(q); } va_end(ap); return p; } work/utils/dupprintf.c0000664000000000000000000000661614714222463012265 0ustar /* * Do an sprintf(), but into a custom-allocated buffer. * * Currently I'm doing this via vsnprintf. This has worked so far, * but it's not good, because vsnprintf is not available on all * platforms. There's an ifdef to use `_vsnprintf', which seems * to be the local name for it on Windows. Other platforms may * lack it completely, in which case it'll be time to rewrite * this function in a totally different way. * * The only `properly' portable solution I can think of is to * implement my own format string scanner, which figures out an * upper bound for the length of each formatting directive, * allocates the buffer as it goes along, and calls sprintf() to * actually process each directive. If I ever need to actually do * this, some caveats: * * - It's very hard to find a reliable upper bound for * floating-point values. %f, in particular, when supplied with * a number near to the upper or lower limit of representable * numbers, could easily take several hundred characters. It's * probably feasible to predict this statically using the * constants in , or even to predict it dynamically by * looking at the exponent of the specific float provided, but * it won't be fun. * * - Don't forget to _check_, after calling sprintf, that it's * used at most the amount of space we had available. * * - Fault any formatting directive we don't fully understand. The * aim here is to _guarantee_ that we never overflow the buffer, * because this is a security-critical function. If we see a * directive we don't know about, we should panic and die rather * than run any risk. */ #include #include "defs.h" #include "misc.h" #include "utils/utils.h" /* Work around lack of va_copy in old MSC */ #if defined _MSC_VER && !defined va_copy #define va_copy(a, b) TYPECHECK( \ (va_list *)0 == &(a) && (va_list *)0 == &(b), \ memcpy(&a, &b, sizeof(va_list))) #endif /* Also lack of vsnprintf before VS2015 */ #if defined _WINDOWS && \ !defined __MINGW32__ && \ !defined __WINE__ && \ _MSC_VER < 1900 #define vsnprintf _vsnprintf #endif char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr, const char *fmt, va_list ap) { size_t size = *sizeptr; sgrowarrayn_nm(buf, size, oldlen, 512); while (1) { va_list aq; va_copy(aq, ap); int len = vsnprintf(buf + oldlen, size - oldlen, fmt, aq); va_end(aq); if (len >= 0 && len < size) { /* This is the C99-specified criterion for snprintf to have * been completely successful. */ *sizeptr = size; return buf; } else if (len > 0) { /* This is the C99 error condition: the returned length is * the required buffer size not counting the NUL. */ sgrowarrayn_nm(buf, size, oldlen + 1, len); } else { /* This is the pre-C99 glibc error condition: <0 means the * buffer wasn't big enough, so we enlarge it a bit and hope. */ sgrowarray_nm(buf, size, size); } } } char *dupvprintf(const char *fmt, va_list ap) { size_t size = 0; return dupvprintf_inner(NULL, 0, &size, fmt, ap); } char *dupprintf(const char *fmt, ...) { char *ret; va_list ap; va_start(ap, fmt); ret = dupvprintf(fmt, ap); va_end(ap); return ret; } work/utils/dupstr.c0000664000000000000000000000045414714222463011565 0ustar /* * Allocate a duplicate of an ordinary C NUL-terminated string. */ #include #include "defs.h" #include "misc.h" char *dupstr(const char *s) { char *p = NULL; if (s) { int len = strlen(s); p = snewn(len + 1, char); strcpy(p, s); } return p; } work/utils/fgetline.c0000664000000000000000000000122314714222463012034 0ustar /* * Read an entire line of text from a file. Return a buffer * malloced to be as big as necessary (caller must free). */ #include "defs.h" #include "misc.h" char *fgetline(FILE *fp) { char *ret = snewn(512, char); size_t size = 512, len = 0; while (fgets(ret + len, size - len, fp)) { len += strlen(ret + len); if (len > 0 && ret[len-1] == '\n') break; /* got a newline, we're done */ sgrowarrayn_nm(ret, size, len, 512); } if (len == 0) { /* first fgets returned NULL */ sfree(ret); return NULL; } ret[len] = '\0'; return ret; } work/utils/host_strchr.c0000664000000000000000000000047414714222463012610 0ustar /* * strchr-like wrapper around host_strchr_internal. */ #include #include #include "defs.h" #include "misc.h" #include "utils/utils.h" char *host_strchr(const char *s, int c) { char set[2]; set[0] = c; set[1] = '\0'; return (char *) host_strchr_internal(s, set, true); } work/utils/host_strchr_internal.c0000664000000000000000000000525714714222463014510 0ustar /* * Find a character in a string, unless it's a colon contained within * square brackets. Used for untangling strings of the form * 'host:port', where host can be an IPv6 literal. * * This internal function provides an API that's a bit like strchr (in * that it returns a pointer to the character it found, or NULL), and * a bit like strcspn (in that you can give it a set of characters to * look for, not just one). Also it has an option to return the first * or last character it finds. Other functions in the utils directory * provide wrappers on it with APIs more like familiar * functions. */ #include #include #include "defs.h" #include "misc.h" #include "utils/utils.h" const char *host_strchr_internal(const char *s, const char *set, bool first) { int brackets = 0; const char *ret = NULL; while (1) { if (!*s) return ret; if (*s == '[') brackets++; else if (*s == ']' && brackets > 0) brackets--; else if (brackets && *s == ':') /* never match */ ; else if (strchr(set, *s)) { ret = s; if (first) return ret; } s++; } } #ifdef TEST int main(void) { int passes = 0, fails = 0; #define TEST1(func, string, arg2, suffix, result) do \ { \ const char *str = string; \ unsigned ret = func(str, arg2) suffix; \ if (ret == result) { \ passes++; \ } else { \ printf("fail: %s(%s,%s)%s = %u, expected %u\n", \ #func, #string, #arg2, #suffix, ret, \ (unsigned)result); \ fails++; \ } \ } while (0) TEST1(host_strchr, "[1:2:3]:4:5", ':', -str, 7); TEST1(host_strrchr, "[1:2:3]:4:5", ':', -str, 9); TEST1(host_strcspn, "[1:2:3]:4:5", "/:",, 7); TEST1(host_strchr, "[1:2:3]", ':', == NULL, 1); TEST1(host_strrchr, "[1:2:3]", ':', == NULL, 1); TEST1(host_strcspn, "[1:2:3]", "/:",, 7); TEST1(host_strcspn, "[1:2/3]", "/:",, 4); TEST1(host_strcspn, "[1:2:3]/", "/:",, 7); printf("passed %d failed %d total %d\n", passes, fails, passes+fails); return fails != 0 ? 1 : 0; } #endif /* TEST */ work/utils/host_strcspn.c0000664000000000000000000000055014714222463012772 0ustar /* * strcspn-like wrapper around host_strchr_internal. */ #include #include #include "defs.h" #include "misc.h" #include "utils/utils.h" size_t host_strcspn(const char *s, const char *set) { const char *answer = host_strchr_internal(s, set, true); if (answer) return answer - s; else return strlen(s); } work/utils/host_strduptrim.c0000664000000000000000000000263714714222463013523 0ustar /* * Trim square brackets off the outside of an IPv6 address literal. * Leave all other strings unchanged. Returns a fresh dynamically * allocated string. */ #include #include #include "defs.h" #include "misc.h" char *host_strduptrim(const char *s) { if (s[0] == '[') { const char *p = s+1; int colons = 0; while (*p && *p != ']') { if (isxdigit((unsigned char)*p)) /* OK */; else if (*p == ':') colons++; else break; p++; } if (*p == '%') { /* * This delimiter character introduces an RFC 4007 scope * id suffix (e.g. suffixing the address literal with * %eth1 or %2 or some such). There's no syntax * specification for the scope id, so just accept anything * except the closing ]. */ p += strcspn(p, "]"); } if (*p == ']' && !p[1] && colons > 1) { /* * This looks like an IPv6 address literal (hex digits and * at least two colons, plus optional scope id, contained * in square brackets). Trim off the brackets. */ return dupprintf("%.*s", (int)(p - (s+1)), s+1); } } /* * Any other shape of string is simply duplicated. */ return dupstr(s); } work/utils/host_strrchr.c0000664000000000000000000000047614714222463012774 0ustar /* * strchr-like wrapper around host_strchr_internal. */ #include #include #include "defs.h" #include "misc.h" #include "utils/utils.h" char *host_strrchr(const char *s, int c) { char set[2]; set[0] = c; set[1] = '\0'; return (char *) host_strchr_internal(s, set, false); } work/utils/ltime.c0000664000000000000000000000055614714222463011361 0ustar /* * Portable implementation of ltime() for any ISO-C platform where * time_t behaves. (In practice, we've found that platforms such as * Windows and Mac have needed their own specialised implementations.) */ #include #include struct tm ltime(void) { time_t t; time(&t); assert (t != ((time_t)-1)); return *localtime(&t); } work/utils/marshal.c0000664000000000000000000001570714714222463011702 0ustar #include #include #include #include "marshal.h" #include "misc.h" void BinarySink_put_data(BinarySink *bs, const void *data, size_t len) { bs->write(bs, data, len); } void BinarySink_put_datapl(BinarySink *bs, ptrlen pl) { BinarySink_put_data(bs, pl.ptr, pl.len); } void BinarySink_put_padding(BinarySink *bs, size_t len, unsigned char padbyte) { char buf[16]; memset(buf, padbyte, sizeof(buf)); while (len > 0) { size_t thislen = len < sizeof(buf) ? len : sizeof(buf); bs->write(bs, buf, thislen); len -= thislen; } } void BinarySink_put_byte(BinarySink *bs, unsigned char val) { bs->write(bs, &val, 1); } void BinarySink_put_bool(BinarySink *bs, bool val) { unsigned char cval = val ? 1 : 0; bs->write(bs, &cval, 1); } void BinarySink_put_uint16(BinarySink *bs, unsigned long val) { unsigned char data[2]; PUT_16BIT_MSB_FIRST(data, val); bs->write(bs, data, sizeof(data)); } void BinarySink_put_uint32(BinarySink *bs, unsigned long val) { unsigned char data[4]; PUT_32BIT_MSB_FIRST(data, val); bs->write(bs, data, sizeof(data)); } void BinarySink_put_uint64(BinarySink *bs, uint64_t val) { unsigned char data[8]; PUT_64BIT_MSB_FIRST(data, val); bs->write(bs, data, sizeof(data)); } void BinarySink_put_string(BinarySink *bs, const void *data, size_t len) { /* Check that the string length fits in a uint32, without doing a * potentially implementation-defined shift of more than 31 bits */ assert((len >> 31) < 2); BinarySink_put_uint32(bs, len); bs->write(bs, data, len); } void BinarySink_put_stringpl(BinarySink *bs, ptrlen pl) { BinarySink_put_string(bs, pl.ptr, pl.len); } void BinarySink_put_stringz(BinarySink *bs, const char *str) { BinarySink_put_string(bs, str, strlen(str)); } void BinarySink_put_stringsb(BinarySink *bs, struct strbuf *buf) { BinarySink_put_string(bs, buf->s, buf->len); strbuf_free(buf); } void BinarySink_put_asciz(BinarySink *bs, const char *str) { bs->write(bs, str, strlen(str) + 1); } bool BinarySink_put_pstring(BinarySink *bs, const char *str) { size_t len = strlen(str); if (len > 255) return false; /* can't write a Pascal-style string this long */ BinarySink_put_byte(bs, len); bs->write(bs, str, len); return true; } /* ---------------------------------------------------------------------- */ static bool BinarySource_data_avail(BinarySource *src, size_t wanted) { if (src->err) return false; if (wanted <= src->len - src->pos) return true; src->err = BSE_OUT_OF_DATA; return false; } #define avail(wanted) BinarySource_data_avail(src, wanted) #define advance(dist) (src->pos += dist) #define here ((const void *)((const unsigned char *)src->data + src->pos)) #define consume(dist) \ ((const void *)((const unsigned char *)src->data + \ ((src->pos += dist) - dist))) ptrlen BinarySource_get_data(BinarySource *src, size_t wanted) { if (!avail(wanted)) return make_ptrlen("", 0); return make_ptrlen(consume(wanted), wanted); } unsigned char BinarySource_get_byte(BinarySource *src) { const unsigned char *ucp; if (!avail(1)) return 0; ucp = consume(1); return *ucp; } bool BinarySource_get_bool(BinarySource *src) { const unsigned char *ucp; if (!avail(1)) return false; ucp = consume(1); return *ucp != 0; } unsigned BinarySource_get_uint16(BinarySource *src) { const unsigned char *ucp; if (!avail(2)) return 0; ucp = consume(2); return GET_16BIT_MSB_FIRST(ucp); } unsigned long BinarySource_get_uint32(BinarySource *src) { const unsigned char *ucp; if (!avail(4)) return 0; ucp = consume(4); return GET_32BIT_MSB_FIRST(ucp); } uint64_t BinarySource_get_uint64(BinarySource *src) { const unsigned char *ucp; if (!avail(8)) return 0; ucp = consume(8); return GET_64BIT_MSB_FIRST(ucp); } ptrlen BinarySource_get_string(BinarySource *src) { const unsigned char *ucp; size_t len; if (!avail(4)) return make_ptrlen("", 0); ucp = consume(4); len = GET_32BIT_MSB_FIRST(ucp); if (!avail(len)) return make_ptrlen("", 0); return make_ptrlen(consume(len), len); } const char *BinarySource_get_asciz(BinarySource *src) { const char *start, *end; if (src->err) return ""; start = here; end = memchr(start, '\0', src->len - src->pos); if (!end) { src->err = BSE_OUT_OF_DATA; return ""; } advance(end + 1 - start); return start; } static ptrlen BinarySource_get_chars_internal( BinarySource *src, const char *set, bool include) { const char *start = here; while (avail(1)) { bool present = NULL != strchr(set, *(const char *)consume(0)); if (present != include) break; (void) consume(1); } const char *end = here; return make_ptrlen(start, end - start); } ptrlen BinarySource_get_chars(BinarySource *src, const char *include_set) { return BinarySource_get_chars_internal(src, include_set, true); } ptrlen BinarySource_get_nonchars(BinarySource *src, const char *exclude_set) { return BinarySource_get_chars_internal(src, exclude_set, false); } ptrlen BinarySource_get_chomped_line(BinarySource *src) { const char *start, *end; if (src->err) return make_ptrlen(here, 0); start = here; end = memchr(start, '\n', src->len - src->pos); if (end) advance(end + 1 - start); else advance(src->len - src->pos); end = here; if (end > start && end[-1] == '\n') end--; if (end > start && end[-1] == '\r') end--; return make_ptrlen(start, end - start); } ptrlen BinarySource_get_pstring(BinarySource *src) { const unsigned char *ucp; size_t len; if (!avail(1)) return make_ptrlen("", 0); ucp = consume(1); len = *ucp; if (!avail(len)) return make_ptrlen("", 0); return make_ptrlen(consume(len), len); } void BinarySource_REWIND_TO__(BinarySource *src, size_t pos) { if (pos <= src->len) { src->pos = pos; src->err = BSE_NO_ERROR; /* clear any existing error */ } else { src->pos = src->len; src->err = BSE_OUT_OF_DATA; /* new error if we rewind out of range */ } } static void stdio_sink_write(BinarySink *bs, const void *data, size_t len) { stdio_sink *sink = BinarySink_DOWNCAST(bs, stdio_sink); fwrite(data, 1, len, sink->fp); } void stdio_sink_init(stdio_sink *sink, FILE *fp) { sink->fp = fp; BinarySink_INIT(sink, stdio_sink_write); } static void bufchain_sink_write(BinarySink *bs, const void *data, size_t len) { bufchain_sink *sink = BinarySink_DOWNCAST(bs, bufchain_sink); bufchain_add(sink->ch, data, len); } void bufchain_sink_init(bufchain_sink *sink, bufchain *ch) { sink->ch = ch; BinarySink_INIT(sink, bufchain_sink_write); } work/utils/memory.c0000664000000000000000000000564114714222463011557 0ustar /* * PuTTY's memory allocation wrappers. */ #include #include #include #include "defs.h" #include "puttymem.h" #include "misc.h" void *safemalloc(size_t factor1, size_t factor2, size_t addend) { if (factor1 > SIZE_MAX / factor2) goto fail; size_t product = factor1 * factor2; if (addend > SIZE_MAX) goto fail; if (product > SIZE_MAX - addend) goto fail; size_t size = product + addend; if (size == 0) size = 1; void *p; #ifdef MINEFIELD p = minefield_c_malloc(size); #else p = malloc(size); #endif if (!p) goto fail; return p; fail: out_of_memory(); } void *saferealloc(void *ptr, size_t n, size_t size) { void *p; if (n > INT_MAX / size) { p = NULL; } else { size *= n; if (!ptr) { #ifdef MINEFIELD p = minefield_c_malloc(size); #else p = malloc(size); #endif } else { #ifdef MINEFIELD p = minefield_c_realloc(ptr, size); #else p = realloc(ptr, size); #endif } } if (!p) out_of_memory(); return p; } void safefree(void *ptr) { if (ptr) { #ifdef MINEFIELD minefield_c_free(ptr); #else free(ptr); #endif } } void *safegrowarray(void *ptr, size_t *allocated, size_t eltsize, size_t oldlen, size_t extralen, bool secret) { /* The largest value we can safely multiply by eltsize */ assert(eltsize > 0); size_t maxsize = (~(size_t)0) / eltsize; size_t oldsize = *allocated; /* Range-check the input values */ assert(oldsize <= maxsize); assert(oldlen <= maxsize); assert(extralen <= maxsize - oldlen); /* If the size is already enough, don't bother doing anything! */ if (oldsize > oldlen + extralen) return ptr; /* Find out how much we need to grow the array by. */ size_t increment = (oldlen + extralen) - oldsize; /* Invent a new size. We want to grow the array by at least * 'increment' elements; by at least a fixed number of bytes (to * get things started when sizes are small); and by some constant * factor of its old size (to avoid repeated calls to this * function taking quadratic time overall). */ if (increment < 256 / eltsize) increment = 256 / eltsize; if (increment < oldsize / 16) increment = oldsize / 16; /* But we also can't grow beyond maxsize. */ size_t maxincr = maxsize - oldsize; if (increment > maxincr) increment = maxincr; size_t newsize = oldsize + increment; void *toret; if (secret) { toret = safemalloc(newsize, eltsize, 0); if (oldsize) { memcpy(toret, ptr, oldsize * eltsize); smemclr(ptr, oldsize * eltsize); sfree(ptr); } } else { toret = saferealloc(ptr, newsize, eltsize); } *allocated = newsize; return toret; } work/utils/nullstrcmp.c0000664000000000000000000000070114714222463012442 0ustar /* * Compare two strings, just like strcmp, except that we tolerate null * pointers as a legal input, and define them to compare before any * non-null string (even the empty string). */ #include #include "defs.h" #include "misc.h" int nullstrcmp(const char *a, const char *b) { if (a == NULL && b == NULL) return 0; if (a == NULL) return -1; if (b == NULL) return +1; return strcmp(a, b); } work/utils/out_of_memory.c0000664000000000000000000000027014714222463013123 0ustar /* * Standard implementation of the out_of_memory function called by our * malloc wrappers. */ #include "putty.h" void out_of_memory(void) { modalfatalbox("Out of memory"); } work/utils/ptrlen.c0000664000000000000000000000441414714222463011550 0ustar /* * Functions to deal with ptrlens. */ #include "defs.h" #include "misc.h" #include "ssh.h" bool ptrlen_eq_string(ptrlen pl, const char *str) { size_t len = strlen(str); return (pl.len == len && !memcmp(pl.ptr, str, len)); } bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2) { return (pl1.len == pl2.len && !memcmp(pl1.ptr, pl2.ptr, pl1.len)); } int ptrlen_strcmp(ptrlen pl1, ptrlen pl2) { size_t minlen = pl1.len < pl2.len ? pl1.len : pl2.len; if (minlen) { /* tolerate plX.ptr==NULL as long as plX.len==0 */ int cmp = memcmp(pl1.ptr, pl2.ptr, minlen); if (cmp) return cmp; } return pl1.len < pl2.len ? -1 : pl1.len > pl2.len ? +1 : 0; } bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail) { if (whole.len >= prefix.len && !memcmp(whole.ptr, prefix.ptr, prefix.len)) { if (tail) { tail->ptr = (const char *)whole.ptr + prefix.len; tail->len = whole.len - prefix.len; } return true; } return false; } bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail) { if (whole.len >= suffix.len && !memcmp((char *)whole.ptr + (whole.len - suffix.len), suffix.ptr, suffix.len)) { if (tail) { tail->ptr = whole.ptr; tail->len = whole.len - suffix.len; } return true; } return false; } ptrlen ptrlen_get_word(ptrlen *input, const char *separators) { const char *p = input->ptr, *end = p + input->len; ptrlen toret; while (p < end && strchr(separators, *p)) p++; toret.ptr = p; while (p < end && !strchr(separators, *p)) p++; toret.len = p - (const char *)toret.ptr; size_t to_consume = p - (const char *)input->ptr; assert(to_consume <= input->len); input->ptr = (const char *)input->ptr + to_consume; input->len -= to_consume; return toret; } char *mkstr(ptrlen pl) { char *p = snewn(pl.len + 1, char); memcpy(p, pl.ptr, pl.len); p[pl.len] = '\0'; return p; } bool strstartswith(const char *s, const char *t) { return !strncmp(s, t, strlen(t)); } bool strendswith(const char *s, const char *t) { size_t slen = strlen(s), tlen = strlen(t); return slen >= tlen && !strcmp(s + (slen - tlen), t); } work/utils/sk_free_peer_info.c0000664000000000000000000000037314714222463013710 0ustar /* * Free a SocketPeerInfo, and everything that dangles off it. */ #include "putty.h" void sk_free_peer_info(SocketPeerInfo *pi) { if (pi) { sfree((char *)pi->addr_text); sfree((char *)pi->log_text); sfree(pi); } } work/utils/smemclr.c0000664000000000000000000000323014714222463011701 0ustar /* * Securely wipe memory. * * The actual wiping is no different from what memset would do: the * point of 'securely' is to try to be sure over-clever compilers * won't optimise away memsets on variables that are about to be freed * or go out of scope. See * https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/771-BSI.html */ #include "defs.h" #include "misc.h" /* * Trivial function that is given a pointer to some memory and ignores * it. */ static void no_op(void *ptr, size_t size) {} /* * Function pointer that is given a pointer to some memory, and from * the compiler's point of view, _might_ read it, or otherwise depend * on its contents. * * In fact, this function pointer always points to no_op() above. But * because the pointer itself is volatile-qualified, the compiler * isn't allowed to optimise based on the assumption that that will * always be the case. So it has to call through the function pointer * anyway, on the basis that it _might_ have magically changed at run * time into a pointer to some completely arbitrary function. And * therefore it must also avoid optimising away any observable effect * beforehand that a completely arbitrary function might depend on - * such as the zeroing of our memory region. */ static void (*const volatile maybe_read)(void *ptr, size_t size) = no_op; void smemclr(void *b, size_t n) { if (b && n > 0) { /* * Zero out the memory. */ memset(b, 0, n); /* * Call the above function pointer, which (for all the * compiler knows) might check that we've really zeroed the * memory. */ maybe_read(b, n); } } work/utils/smemeq.c0000664000000000000000000000145614714222463011536 0ustar /* * Compare two fixed-size regions of memory, in a crypto-safe way, * i.e. without timing or cache side channels indicating anything * about what the answer was or where the first difference (if any) * might have been. */ #include "defs.h" #include "misc.h" bool smemeq(const void *av, const void *bv, size_t len) { const unsigned char *a = (const unsigned char *)av; const unsigned char *b = (const unsigned char *)bv; unsigned val = 0; while (len-- > 0) { val |= *a++ ^ *b++; } /* Now val is 0 iff we want to return 1, and in the range * 0x01..0xFF iff we want to return 0. So subtracting from 0x100 * will clear bit 8 iff we want to return 0, and leave it set iff * we want to return 1, so then we can just shift down. */ return (0x100 - val) >> 8; } work/utils/strbuf.c0000664000000000000000000000641714714222463011556 0ustar /* * Functions to work with strbufs. */ #include "defs.h" #include "misc.h" #include "utils/utils.h" struct strbuf_impl { size_t size; struct strbuf visible; bool nm; /* true if we insist on non-moving buffer resizes */ }; #define STRBUF_SET_UPTR(buf) \ ((buf)->visible.u = (unsigned char *)(buf)->visible.s) #define STRBUF_SET_PTR(buf, ptr) \ ((buf)->visible.s = (ptr), STRBUF_SET_UPTR(buf)) void *strbuf_append(strbuf *buf_o, size_t len) { struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); char *toret; sgrowarray_general( buf->visible.s, buf->size, buf->visible.len + 1, len, buf->nm); STRBUF_SET_UPTR(buf); toret = buf->visible.s + buf->visible.len; buf->visible.len += len; buf->visible.s[buf->visible.len] = '\0'; return toret; } void strbuf_shrink_to(strbuf *buf, size_t new_len) { assert(new_len <= buf->len); buf->len = new_len; buf->s[buf->len] = '\0'; } void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove) { assert(amount_to_remove <= buf->len); buf->len -= amount_to_remove; buf->s[buf->len] = '\0'; } bool strbuf_chomp(strbuf *buf, char char_to_remove) { if (buf->len > 0 && buf->s[buf->len-1] == char_to_remove) { strbuf_shrink_by(buf, 1); return true; } return false; } static void strbuf_BinarySink_write( BinarySink *bs, const void *data, size_t len) { strbuf *buf_o = BinarySink_DOWNCAST(bs, strbuf); memcpy(strbuf_append(buf_o, len), data, len); } static strbuf *strbuf_new_general(bool nm) { struct strbuf_impl *buf = snew(struct strbuf_impl); BinarySink_INIT(&buf->visible, strbuf_BinarySink_write); buf->visible.len = 0; buf->size = 512; buf->nm = nm; STRBUF_SET_PTR(buf, snewn(buf->size, char)); *buf->visible.s = '\0'; return &buf->visible; } strbuf *strbuf_new(void) { return strbuf_new_general(false); } strbuf *strbuf_new_nm(void) { return strbuf_new_general(true); } void strbuf_free(strbuf *buf_o) { struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); if (buf->visible.s) { smemclr(buf->visible.s, buf->size); sfree(buf->visible.s); } sfree(buf); } char *strbuf_to_str(strbuf *buf_o) { struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); char *ret = buf->visible.s; sfree(buf); return ret; } void strbuf_catfv(strbuf *buf_o, const char *fmt, va_list ap) { struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); STRBUF_SET_PTR(buf, dupvprintf_inner(buf->visible.s, buf->visible.len, &buf->size, fmt, ap)); buf->visible.len += strlen(buf->visible.s + buf->visible.len); } void strbuf_catf(strbuf *buf_o, const char *fmt, ...) { va_list ap; va_start(ap, fmt); strbuf_catfv(buf_o, fmt, ap); va_end(ap); } strbuf *strbuf_new_for_agent_query(void) { strbuf *buf = strbuf_new(); strbuf_append(buf, 4); return buf; } void strbuf_finalise_agent_query(strbuf *buf_o) { struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); assert(buf->visible.len >= 5); PUT_32BIT_MSB_FIRST(buf->visible.u, buf->visible.len - 4); } work/utils/string_length_for_printf.c0000664000000000000000000000113514714222463015340 0ustar /* * Convert a size_t value to int, by saturating it at INT_MAX. Useful * if you want to use the printf idiom "%.*s", where the '*' precision * specifier expects an int in the variadic argument list, but what * you have is not an int but a size_t. This method of converting to * int will at least do something _safe_ with overlong values, even if * (due to the limitation of printf itself) the whole string still * won't be printed. */ #include #include "defs.h" #include "misc.h" int string_length_for_printf(size_t s) { if (s > INT_MAX) return INT_MAX; return s; } work/utils/tree234.c0000664000000000000000000015156514714222463011446 0ustar /* * tree234.c: reasonably generic counted 2-3-4 tree routines. * * This file is copyright 1999-2001 Simon Tatham. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include "defs.h" #include "tree234.h" #include "puttymem.h" #ifdef TEST static int verbose = 0; #define LOG(x) do \ { \ if (verbose > 2) \ printf x; \ } while (0) #else #define LOG(x) #endif typedef struct node234_Tag node234; struct tree234_Tag { node234 *root; cmpfn234 cmp; }; struct node234_Tag { node234 *parent; node234 *kids[4]; int counts[4]; void *elems[3]; }; /* * Create a 2-3-4 tree. */ tree234 *newtree234(cmpfn234 cmp) { tree234 *ret = snew(tree234); LOG(("created tree %p\n", ret)); ret->root = NULL; ret->cmp = cmp; return ret; } /* * Free a 2-3-4 tree (not including freeing the elements). */ static void freenode234(node234 * n) { if (!n) return; freenode234(n->kids[0]); freenode234(n->kids[1]); freenode234(n->kids[2]); freenode234(n->kids[3]); sfree(n); } void freetree234(tree234 * t) { freenode234(t->root); sfree(t); } /* * Internal function to count a node. */ static int countnode234(node234 * n) { int count = 0; int i; if (!n) return 0; for (i = 0; i < 4; i++) count += n->counts[i]; for (i = 0; i < 3; i++) if (n->elems[i]) count++; return count; } /* * Internal function to return the number of elements in a node. */ static int elements234(node234 *n) { int i; for (i = 0; i < 3; i++) if (!n->elems[i]) break; return i; } /* * Count the elements in a tree. */ int count234(tree234 * t) { if (t->root) return countnode234(t->root); else return 0; } /* * Add an element e to a 2-3-4 tree t. Returns e on success, or if * an existing element compares equal, returns that. */ static void *add234_internal(tree234 * t, void *e, int index) { node234 *n, **np, *left, *right; void *orig_e = e; int c, lcount, rcount; LOG(("adding node %p to tree %p\n", e, t)); if (t->root == NULL) { t->root = snew(node234); t->root->elems[1] = t->root->elems[2] = NULL; t->root->kids[0] = t->root->kids[1] = NULL; t->root->kids[2] = t->root->kids[3] = NULL; t->root->counts[0] = t->root->counts[1] = 0; t->root->counts[2] = t->root->counts[3] = 0; t->root->parent = NULL; t->root->elems[0] = e; LOG((" created root %p\n", t->root)); return orig_e; } n = NULL; /* placate gcc; will always be set below since t->root != NULL */ np = &t->root; while (*np) { int childnum; n = *np; LOG((" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d\n", n, n->kids[0], n->counts[0], n->elems[0], n->kids[1], n->counts[1], n->elems[1], n->kids[2], n->counts[2], n->elems[2], n->kids[3], n->counts[3])); if (index >= 0) { if (!n->kids[0]) { /* * Leaf node. We want to insert at kid position * equal to the index: * * 0 A 1 B 2 C 3 */ childnum = index; } else { /* * Internal node. We always descend through it (add * always starts at the bottom, never in the * middle). */ do { /* this is a do ... while (0) to allow `break' */ if (index <= n->counts[0]) { childnum = 0; break; } index -= n->counts[0] + 1; if (index <= n->counts[1]) { childnum = 1; break; } index -= n->counts[1] + 1; if (index <= n->counts[2]) { childnum = 2; break; } index -= n->counts[2] + 1; if (index <= n->counts[3]) { childnum = 3; break; } return NULL; /* error: index out of range */ } while (0); } } else { if ((c = t->cmp(e, n->elems[0])) < 0) childnum = 0; else if (c == 0) return n->elems[0]; /* already exists */ else if (n->elems[1] == NULL || (c = t->cmp(e, n->elems[1])) < 0) childnum = 1; else if (c == 0) return n->elems[1]; /* already exists */ else if (n->elems[2] == NULL || (c = t->cmp(e, n->elems[2])) < 0) childnum = 2; else if (c == 0) return n->elems[2]; /* already exists */ else childnum = 3; } np = &n->kids[childnum]; LOG((" moving to child %d (%p)\n", childnum, *np)); } /* * We need to insert the new element in n at position np. */ left = NULL; lcount = 0; right = NULL; rcount = 0; while (n) { LOG((" at %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d\n", n, n->kids[0], n->counts[0], n->elems[0], n->kids[1], n->counts[1], n->elems[1], n->kids[2], n->counts[2], n->elems[2], n->kids[3], n->counts[3])); LOG((" need to insert %p/%d [%p] %p/%d at position %d\n", left, lcount, e, right, rcount, (int)(np - n->kids))); if (n->elems[1] == NULL) { /* * Insert in a 2-node; simple. */ if (np == &n->kids[0]) { LOG((" inserting on left of 2-node\n")); n->kids[2] = n->kids[1]; n->counts[2] = n->counts[1]; n->elems[1] = n->elems[0]; n->kids[1] = right; n->counts[1] = rcount; n->elems[0] = e; n->kids[0] = left; n->counts[0] = lcount; } else { /* np == &n->kids[1] */ LOG((" inserting on right of 2-node\n")); n->kids[2] = right; n->counts[2] = rcount; n->elems[1] = e; n->kids[1] = left; n->counts[1] = lcount; } if (n->kids[0]) n->kids[0]->parent = n; if (n->kids[1]) n->kids[1]->parent = n; if (n->kids[2]) n->kids[2]->parent = n; LOG((" done\n")); break; } else if (n->elems[2] == NULL) { /* * Insert in a 3-node; simple. */ if (np == &n->kids[0]) { LOG((" inserting on left of 3-node\n")); n->kids[3] = n->kids[2]; n->counts[3] = n->counts[2]; n->elems[2] = n->elems[1]; n->kids[2] = n->kids[1]; n->counts[2] = n->counts[1]; n->elems[1] = n->elems[0]; n->kids[1] = right; n->counts[1] = rcount; n->elems[0] = e; n->kids[0] = left; n->counts[0] = lcount; } else if (np == &n->kids[1]) { LOG((" inserting in middle of 3-node\n")); n->kids[3] = n->kids[2]; n->counts[3] = n->counts[2]; n->elems[2] = n->elems[1]; n->kids[2] = right; n->counts[2] = rcount; n->elems[1] = e; n->kids[1] = left; n->counts[1] = lcount; } else { /* np == &n->kids[2] */ LOG((" inserting on right of 3-node\n")); n->kids[3] = right; n->counts[3] = rcount; n->elems[2] = e; n->kids[2] = left; n->counts[2] = lcount; } if (n->kids[0]) n->kids[0]->parent = n; if (n->kids[1]) n->kids[1]->parent = n; if (n->kids[2]) n->kids[2]->parent = n; if (n->kids[3]) n->kids[3]->parent = n; LOG((" done\n")); break; } else { node234 *m = snew(node234); m->parent = n->parent; LOG((" splitting a 4-node; created new node %p\n", m)); /* * Insert in a 4-node; split into a 2-node and a * 3-node, and move focus up a level. * * I don't think it matters which way round we put the * 2 and the 3. For simplicity, we'll put the 3 first * always. */ if (np == &n->kids[0]) { m->kids[0] = left; m->counts[0] = lcount; m->elems[0] = e; m->kids[1] = right; m->counts[1] = rcount; m->elems[1] = n->elems[0]; m->kids[2] = n->kids[1]; m->counts[2] = n->counts[1]; e = n->elems[1]; n->kids[0] = n->kids[2]; n->counts[0] = n->counts[2]; n->elems[0] = n->elems[2]; n->kids[1] = n->kids[3]; n->counts[1] = n->counts[3]; } else if (np == &n->kids[1]) { m->kids[0] = n->kids[0]; m->counts[0] = n->counts[0]; m->elems[0] = n->elems[0]; m->kids[1] = left; m->counts[1] = lcount; m->elems[1] = e; m->kids[2] = right; m->counts[2] = rcount; e = n->elems[1]; n->kids[0] = n->kids[2]; n->counts[0] = n->counts[2]; n->elems[0] = n->elems[2]; n->kids[1] = n->kids[3]; n->counts[1] = n->counts[3]; } else if (np == &n->kids[2]) { m->kids[0] = n->kids[0]; m->counts[0] = n->counts[0]; m->elems[0] = n->elems[0]; m->kids[1] = n->kids[1]; m->counts[1] = n->counts[1]; m->elems[1] = n->elems[1]; m->kids[2] = left; m->counts[2] = lcount; /* e = e; */ n->kids[0] = right; n->counts[0] = rcount; n->elems[0] = n->elems[2]; n->kids[1] = n->kids[3]; n->counts[1] = n->counts[3]; } else { /* np == &n->kids[3] */ m->kids[0] = n->kids[0]; m->counts[0] = n->counts[0]; m->elems[0] = n->elems[0]; m->kids[1] = n->kids[1]; m->counts[1] = n->counts[1]; m->elems[1] = n->elems[1]; m->kids[2] = n->kids[2]; m->counts[2] = n->counts[2]; n->kids[0] = left; n->counts[0] = lcount; n->elems[0] = e; n->kids[1] = right; n->counts[1] = rcount; e = n->elems[2]; } m->kids[3] = n->kids[3] = n->kids[2] = NULL; m->counts[3] = n->counts[3] = n->counts[2] = 0; m->elems[2] = n->elems[2] = n->elems[1] = NULL; if (m->kids[0]) m->kids[0]->parent = m; if (m->kids[1]) m->kids[1]->parent = m; if (m->kids[2]) m->kids[2]->parent = m; if (n->kids[0]) n->kids[0]->parent = n; if (n->kids[1]) n->kids[1]->parent = n; LOG((" left (%p): %p/%d [%p] %p/%d [%p] %p/%d\n", m, m->kids[0], m->counts[0], m->elems[0], m->kids[1], m->counts[1], m->elems[1], m->kids[2], m->counts[2])); LOG((" right (%p): %p/%d [%p] %p/%d\n", n, n->kids[0], n->counts[0], n->elems[0], n->kids[1], n->counts[1])); left = m; lcount = countnode234(left); right = n; rcount = countnode234(right); } if (n->parent) np = (n->parent->kids[0] == n ? &n->parent->kids[0] : n->parent->kids[1] == n ? &n->parent->kids[1] : n->parent->kids[2] == n ? &n->parent->kids[2] : &n->parent->kids[3]); n = n->parent; } /* * If we've come out of here by `break', n will still be * non-NULL and all we need to do is go back up the tree * updating counts. If we've come here because n is NULL, we * need to create a new root for the tree because the old one * has just split into two. */ if (n) { while (n->parent) { int count = countnode234(n); int childnum; childnum = (n->parent->kids[0] == n ? 0 : n->parent->kids[1] == n ? 1 : n->parent->kids[2] == n ? 2 : 3); n->parent->counts[childnum] = count; n = n->parent; } } else { LOG((" root is overloaded, split into two\n")); t->root = snew(node234); t->root->kids[0] = left; t->root->counts[0] = lcount; t->root->elems[0] = e; t->root->kids[1] = right; t->root->counts[1] = rcount; t->root->elems[1] = NULL; t->root->kids[2] = NULL; t->root->counts[2] = 0; t->root->elems[2] = NULL; t->root->kids[3] = NULL; t->root->counts[3] = 0; t->root->parent = NULL; if (t->root->kids[0]) t->root->kids[0]->parent = t->root; if (t->root->kids[1]) t->root->kids[1]->parent = t->root; LOG((" new root is %p/%d [%p] %p/%d\n", t->root->kids[0], t->root->counts[0], t->root->elems[0], t->root->kids[1], t->root->counts[1])); } return orig_e; } void *add234(tree234 * t, void *e) { if (!t->cmp) /* tree is unsorted */ return NULL; return add234_internal(t, e, -1); } void *addpos234(tree234 * t, void *e, int index) { if (index < 0 || /* index out of range */ t->cmp) /* tree is sorted */ return NULL; /* return failure */ return add234_internal(t, e, index); /* this checks the upper bound */ } /* * Look up the element at a given numeric index in a 2-3-4 tree. * Returns NULL if the index is out of range. */ void *index234(tree234 * t, int index) { node234 *n; if (!t->root) return NULL; /* tree is empty */ if (index < 0 || index >= countnode234(t->root)) return NULL; /* out of range */ n = t->root; while (n) { if (index < n->counts[0]) n = n->kids[0]; else if (index -= n->counts[0] + 1, index < 0) return n->elems[0]; else if (index < n->counts[1]) n = n->kids[1]; else if (index -= n->counts[1] + 1, index < 0) return n->elems[1]; else if (index < n->counts[2]) n = n->kids[2]; else if (index -= n->counts[2] + 1, index < 0) return n->elems[2]; else n = n->kids[3]; } /* We shouldn't ever get here. I wonder how we did. */ return NULL; } /* * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not * found. e is always passed as the first argument to cmp, so cmp * can be an asymmetric function if desired. cmp can also be passed * as NULL, in which case the compare function from the tree proper * will be used. */ void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, int relation, int *index) { search234_state ss; int reldir = (relation == REL234_LT || relation == REL234_LE ? -1 : relation == REL234_GT || relation == REL234_GE ? +1 : 0); bool equal_permitted = (relation != REL234_LT && relation != REL234_GT); void *toret; /* Only LT / GT relations are permitted with a null query element. */ assert(!(equal_permitted && !e)); if (cmp == NULL) cmp = t->cmp; search234_start(&ss, t); while (ss.element) { int cmpret; if (e) { cmpret = cmp(e, ss.element); } else { cmpret = -reldir; /* invent a fixed compare result */ } if (cmpret == 0) { /* * We've found an element that compares exactly equal to * the query element. */ if (equal_permitted) { /* If our search relation permits equality, we've * finished already. */ if (index) *index = ss.index; return ss.element; } else { /* Otherwise, pretend this element was slightly too * big/small, according to the direction of search. */ cmpret = reldir; } } search234_step(&ss, cmpret); } /* * No element compares equal to the one we were after, but * ss.index indicates the index that element would have if it were * inserted. * * So if our search relation is EQ, we must simply return failure. */ if (relation == REL234_EQ) return NULL; /* * Otherwise, we must do an index lookup for the previous index * (if we're going left - LE or LT) or this index (if we're going * right - GE or GT). */ if (relation == REL234_LT || relation == REL234_LE) { ss.index--; } /* * We know the index of the element we want; just call index234 * to do the rest. This will return NULL if the index is out of * bounds, which is exactly what we want. */ toret = index234(t, ss.index); if (toret && index) *index = ss.index; return toret; } void *find234(tree234 * t, void *e, cmpfn234 cmp) { return findrelpos234(t, e, cmp, REL234_EQ, NULL); } void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation) { return findrelpos234(t, e, cmp, relation, NULL); } void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index) { return findrelpos234(t, e, cmp, REL234_EQ, index); } void search234_start(search234_state *state, tree234 *t) { state->_node = t->root; state->_base = 0; /* index of first element in this node's subtree */ state->_last = -1; /* indicate that this node is not previously visted */ search234_step(state, 0); } void search234_step(search234_state *state, int direction) { node234 *node = state->_node; int i; if (!node) { state->element = NULL; state->index = 0; return; } if (state->_last != -1) { /* * We're already pointing at some element of a node, so we * should restrict to the elements left or right of it, * depending on the requested search direction. */ assert(direction); assert(node); if (direction > 0) state->_lo = state->_last + 1; else state->_hi = state->_last - 1; if (state->_lo > state->_hi) { /* * We've run out of elements in this node, i.e. we've * narrowed to nothing but a child pointer. Descend to * that child, and update _base to the leftmost index of * its subtree. */ for (i = 0; i < state->_lo; i++) state->_base += 1 + node->counts[i]; state->_node = node = node->kids[state->_lo]; state->_last = -1; } } if (state->_last == -1) { /* * We've just entered a new node - either because of the above * code, or because we were called from search234_start - and * anything in that node is a viable answer. */ state->_lo = 0; state->_hi = node ? elements234(node)-1 : 0; } /* * Now we've got something we can return. */ if (!node) { state->element = NULL; state->index = state->_base; } else { state->_last = (state->_lo + state->_hi) / 2; state->element = node->elems[state->_last]; state->index = state->_base + state->_last; for (i = 0; i <= state->_last; i++) state->index += node->counts[i]; } } /* * Delete an element e in a 2-3-4 tree. Does not free the element, * merely removes all links to it from the tree nodes. */ static void *delpos234_internal(tree234 * t, int index) { node234 *n; void *retval; int ei = -1; retval = 0; n = t->root; LOG(("deleting item %d from tree %p\n", index, t)); while (1) { while (n) { int ki; node234 *sub; LOG( (" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d index=%d\n", n, n->kids[0], n->counts[0], n->elems[0], n->kids[1], n->counts[1], n->elems[1], n->kids[2], n->counts[2], n->elems[2], n->kids[3], n->counts[3], index)); if (index < n->counts[0]) { ki = 0; } else if (index -= n->counts[0] + 1, index < 0) { ei = 0; break; } else if (index < n->counts[1]) { ki = 1; } else if (index -= n->counts[1] + 1, index < 0) { ei = 1; break; } else if (index < n->counts[2]) { ki = 2; } else if (index -= n->counts[2] + 1, index < 0) { ei = 2; break; } else { ki = 3; } /* * Recurse down to subtree ki. If it has only one element, * we have to do some transformation to start with. */ LOG((" moving to subtree %d\n", ki)); sub = n->kids[ki]; if (!sub->elems[1]) { LOG((" subtree has only one element!\n")); if (ki > 0 && n->kids[ki - 1]->elems[1]) { /* * Case 3a, left-handed variant. Child ki has * only one element, but child ki-1 has two or * more. So we need to move a subtree from ki-1 * to ki. * * . C . . B . * / \ -> / \ * [more] a A b B c d D e [more] a A b c C d D e */ node234 *sib = n->kids[ki - 1]; int lastelem = (sib->elems[2] ? 2 : sib->elems[1] ? 1 : 0); sub->kids[2] = sub->kids[1]; sub->counts[2] = sub->counts[1]; sub->elems[1] = sub->elems[0]; sub->kids[1] = sub->kids[0]; sub->counts[1] = sub->counts[0]; sub->elems[0] = n->elems[ki - 1]; sub->kids[0] = sib->kids[lastelem + 1]; sub->counts[0] = sib->counts[lastelem + 1]; if (sub->kids[0]) sub->kids[0]->parent = sub; n->elems[ki - 1] = sib->elems[lastelem]; sib->kids[lastelem + 1] = NULL; sib->counts[lastelem + 1] = 0; sib->elems[lastelem] = NULL; n->counts[ki] = countnode234(sub); LOG((" case 3a left\n")); LOG( (" index and left subtree count before adjustment: %d, %d\n", index, n->counts[ki - 1])); index += n->counts[ki - 1]; n->counts[ki - 1] = countnode234(sib); index -= n->counts[ki - 1]; LOG( (" index and left subtree count after adjustment: %d, %d\n", index, n->counts[ki - 1])); } else if (ki < 3 && n->kids[ki + 1] && n->kids[ki + 1]->elems[1]) { /* * Case 3a, right-handed variant. ki has only * one element but ki+1 has two or more. Move a * subtree from ki+1 to ki. * * . B . . C . * / \ -> / \ * a A b c C d D e [more] a A b B c d D e [more] */ node234 *sib = n->kids[ki + 1]; int j; sub->elems[1] = n->elems[ki]; sub->kids[2] = sib->kids[0]; sub->counts[2] = sib->counts[0]; if (sub->kids[2]) sub->kids[2]->parent = sub; n->elems[ki] = sib->elems[0]; sib->kids[0] = sib->kids[1]; sib->counts[0] = sib->counts[1]; for (j = 0; j < 2 && sib->elems[j + 1]; j++) { sib->kids[j + 1] = sib->kids[j + 2]; sib->counts[j + 1] = sib->counts[j + 2]; sib->elems[j] = sib->elems[j + 1]; } sib->kids[j + 1] = NULL; sib->counts[j + 1] = 0; sib->elems[j] = NULL; n->counts[ki] = countnode234(sub); n->counts[ki + 1] = countnode234(sib); LOG((" case 3a right\n")); } else { /* * Case 3b. ki has only one element, and has no * neighbour with more than one. So pick a * neighbour and merge it with ki, taking an * element down from n to go in the middle. * * . B . . * / \ -> | * a A b c C d a A b B c C d * * (Since at all points we have avoided * descending to a node with only one element, * we can be sure that n is not reduced to * nothingness by this move, _unless_ it was * the very first node, ie the root of the * tree. In that case we remove the now-empty * root and replace it with its single large * child as shown.) */ node234 *sib; int j; if (ki > 0) { ki--; index += n->counts[ki] + 1; } sib = n->kids[ki]; sub = n->kids[ki + 1]; sub->kids[3] = sub->kids[1]; sub->counts[3] = sub->counts[1]; sub->elems[2] = sub->elems[0]; sub->kids[2] = sub->kids[0]; sub->counts[2] = sub->counts[0]; sub->elems[1] = n->elems[ki]; sub->kids[1] = sib->kids[1]; sub->counts[1] = sib->counts[1]; if (sub->kids[1]) sub->kids[1]->parent = sub; sub->elems[0] = sib->elems[0]; sub->kids[0] = sib->kids[0]; sub->counts[0] = sib->counts[0]; if (sub->kids[0]) sub->kids[0]->parent = sub; n->counts[ki + 1] = countnode234(sub); sfree(sib); /* * That's built the big node in sub. Now we * need to remove the reference to sib in n. */ for (j = ki; j < 3 && n->kids[j + 1]; j++) { n->kids[j] = n->kids[j + 1]; n->counts[j] = n->counts[j + 1]; n->elems[j] = j < 2 ? n->elems[j + 1] : NULL; } n->kids[j] = NULL; n->counts[j] = 0; if (j < 3) n->elems[j] = NULL; LOG((" case 3b ki=%d\n", ki)); if (!n->elems[0]) { /* * The root is empty and needs to be * removed. */ LOG((" shifting root!\n")); t->root = sub; sub->parent = NULL; sfree(n); } } } n = sub; } if (!retval) retval = n->elems[ei]; if (ei == -1) return NULL; /* although this shouldn't happen */ /* * Treat special case: this is the one remaining item in * the tree. n is the tree root (no parent), has one * element (no elems[1]), and has no kids (no kids[0]). */ if (!n->parent && !n->elems[1] && !n->kids[0]) { LOG((" removed last element in tree\n")); sfree(n); t->root = NULL; return retval; } /* * Now we have the element we want, as n->elems[ei], and we * have also arranged for that element not to be the only * one in its node. So... */ if (!n->kids[0] && n->elems[1]) { /* * Case 1. n is a leaf node with more than one element, * so it's _really easy_. Just delete the thing and * we're done. */ int i; LOG((" case 1\n")); for (i = ei; i < 2 && n->elems[i + 1]; i++) n->elems[i] = n->elems[i + 1]; n->elems[i] = NULL; /* * Having done that to the leaf node, we now go back up * the tree fixing the counts. */ while (n->parent) { int childnum; childnum = (n->parent->kids[0] == n ? 0 : n->parent->kids[1] == n ? 1 : n->parent->kids[2] == n ? 2 : 3); n->parent->counts[childnum]--; n = n->parent; } return retval; /* finished! */ } else if (n->kids[ei]->elems[1]) { /* * Case 2a. n is an internal node, and the root of the * subtree to the left of e has more than one element. * So find the predecessor p to e (ie the largest node * in that subtree), place it where e currently is, and * then start the deletion process over again on the * subtree with p as target. */ node234 *m = n->kids[ei]; void *target; LOG((" case 2a\n")); while (m->kids[0]) { m = (m->kids[3] ? m->kids[3] : m->kids[2] ? m->kids[2] : m->kids[1] ? m->kids[1] : m->kids[0]); } target = (m->elems[2] ? m->elems[2] : m->elems[1] ? m->elems[1] : m->elems[0]); n->elems[ei] = target; index = n->counts[ei] - 1; n = n->kids[ei]; } else if (n->kids[ei + 1]->elems[1]) { /* * Case 2b, symmetric to 2a but s/left/right/ and * s/predecessor/successor/. (And s/largest/smallest/). */ node234 *m = n->kids[ei + 1]; void *target; LOG((" case 2b\n")); while (m->kids[0]) { m = m->kids[0]; } target = m->elems[0]; n->elems[ei] = target; n = n->kids[ei + 1]; index = 0; } else { /* * Case 2c. n is an internal node, and the subtrees to * the left and right of e both have only one element. * So combine the two subnodes into a single big node * with their own elements on the left and right and e * in the middle, then restart the deletion process on * that subtree, with e still as target. */ node234 *a = n->kids[ei], *b = n->kids[ei + 1]; int j; LOG((" case 2c\n")); a->elems[1] = n->elems[ei]; a->kids[2] = b->kids[0]; a->counts[2] = b->counts[0]; if (a->kids[2]) a->kids[2]->parent = a; a->elems[2] = b->elems[0]; a->kids[3] = b->kids[1]; a->counts[3] = b->counts[1]; if (a->kids[3]) a->kids[3]->parent = a; sfree(b); n->counts[ei] = countnode234(a); /* * That's built the big node in a, and destroyed b. Now * remove the reference to b (and e) in n. */ for (j = ei; j < 2 && n->elems[j + 1]; j++) { n->elems[j] = n->elems[j + 1]; n->kids[j + 1] = n->kids[j + 2]; n->counts[j + 1] = n->counts[j + 2]; } n->elems[j] = NULL; n->kids[j + 1] = NULL; n->counts[j + 1] = 0; /* * It's possible, in this case, that we've just removed * the only element in the root of the tree. If so, * shift the root. */ if (n->elems[0] == NULL) { LOG((" shifting root!\n")); t->root = a; a->parent = NULL; sfree(n); } /* * Now go round the deletion process again, with n * pointing at the new big node and e still the same. */ n = a; index = a->counts[0] + a->counts[1] + 1; } } } void *delpos234(tree234 * t, int index) { if (index < 0 || index >= countnode234(t->root)) return NULL; return delpos234_internal(t, index); } void *del234(tree234 * t, void *e) { int index; if (!findrelpos234(t, e, NULL, REL234_EQ, &index)) return NULL; /* it wasn't in there anyway */ return delpos234_internal(t, index); /* it's there; delete it. */ } #ifdef TEST /* * Test code for the 2-3-4 tree. This code maintains an alternative * representation of the data in the tree, in an array (using the * obvious and slow insert and delete functions). After each tree * operation, the verify() function is called, which ensures all * the tree properties are preserved: * - node->child->parent always equals node * - tree->root->parent always equals NULL * - number of kids == 0 or number of elements + 1; * - tree has the same depth everywhere * - every node has at least one element * - subtree element counts are accurate * - any NULL kid pointer is accompanied by a zero count * - in a sorted tree: ordering property between elements of a * node and elements of its children is preserved * and also ensures the list represented by the tree is the same * list it should be. (This last check also doubly verifies the * ordering properties, because the `same list it should be' is by * definition correctly ordered. It also ensures all nodes are * distinct, because the enum functions would get caught in a loop * if not.) */ #include #include int n_errors = 0; /* * Error reporting function. */ PRINTF_LIKE(1, 2) void error(char *fmt, ...) { va_list ap; printf("ERROR: "); va_start(ap, fmt); vfprintf(stdout, fmt, ap); va_end(ap); printf("\n"); n_errors++; } /* The array representation of the data. */ void **array; int arraylen, arraysize; cmpfn234 cmp; /* The tree representation of the same data. */ tree234 *tree; typedef struct { int treedepth; int elemcount; } chkctx; int chknode(chkctx * ctx, int level, node234 * node, void *lowbound, void *highbound) { int nkids, nelems; int i; int count; /* Count the non-NULL kids. */ for (nkids = 0; nkids < 4 && node->kids[nkids]; nkids++); /* Ensure no kids beyond the first NULL are non-NULL. */ for (i = nkids; i < 4; i++) if (node->kids[i]) { error("node %p: nkids=%d but kids[%d] non-NULL", node, nkids, i); } else if (node->counts[i]) { error("node %p: kids[%d] NULL but count[%d]=%d nonzero", node, i, i, node->counts[i]); } /* Count the non-NULL elements. */ for (nelems = 0; nelems < 3 && node->elems[nelems]; nelems++); /* Ensure no elements beyond the first NULL are non-NULL. */ for (i = nelems; i < 3; i++) if (node->elems[i]) { error("node %p: nelems=%d but elems[%d] non-NULL", node, nelems, i); } if (nkids == 0) { /* * If nkids==0, this is a leaf node; verify that the tree * depth is the same everywhere. */ if (ctx->treedepth < 0) ctx->treedepth = level; /* we didn't know the depth yet */ else if (ctx->treedepth != level) error("node %p: leaf at depth %d, previously seen depth %d", node, level, ctx->treedepth); } else { /* * If nkids != 0, then it should be nelems+1, unless nelems * is 0 in which case nkids should also be 0 (and so we * shouldn't be in this condition at all). */ int shouldkids = (nelems ? nelems + 1 : 0); if (nkids != shouldkids) { error("node %p: %d elems should mean %d kids but has %d", node, nelems, shouldkids, nkids); } } /* * nelems should be at least 1. */ if (nelems == 0) { error("node %p: no elems", node); } /* * Add nelems to the running element count of the whole tree. */ ctx->elemcount += nelems; /* * Check ordering property: all elements should be strictly > * lowbound, strictly < highbound, and strictly < each other in * sequence. (lowbound and highbound are NULL at edges of tree * - both NULL at root node - and NULL is considered to be < * everything and > everything. IYSWIM.) */ if (cmp) { for (i = -1; i < nelems; i++) { void *lower = (i == -1 ? lowbound : node->elems[i]); void *higher = (i + 1 == nelems ? highbound : node->elems[i + 1]); if (lower && higher && cmp(lower, higher) >= 0) { error("node %p: kid comparison [%d=%s,%d=%s] failed", node, i, (char *)lower, i + 1, (char *)higher); } } } /* * Check parent pointers: all non-NULL kids should have a * parent pointer coming back to this node. */ for (i = 0; i < nkids; i++) if (node->kids[i]->parent != node) { error("node %p kid %d: parent ptr is %p not %p", node, i, node->kids[i]->parent, node); } /* * Now (finally!) recurse into subtrees. */ count = nelems; for (i = 0; i < nkids; i++) { void *lower = (i == 0 ? lowbound : node->elems[i - 1]); void *higher = (i >= nelems ? highbound : node->elems[i]); int subcount = chknode(ctx, level + 1, node->kids[i], lower, higher); if (node->counts[i] != subcount) { error("node %p kid %d: count says %d, subtree really has %d", node, i, node->counts[i], subcount); } count += subcount; } return count; } void verify(void) { chkctx ctx[1]; int i; void *p; ctx->treedepth = -1; /* depth unknown yet */ ctx->elemcount = 0; /* no elements seen yet */ /* * Verify validity of tree properties. */ if (tree->root) { if (tree->root->parent != NULL) error("root->parent is %p should be null", tree->root->parent); chknode(ctx, 0, tree->root, NULL, NULL); } if (verbose) printf("tree depth: %d\n", ctx->treedepth); /* * Enumerate the tree and ensure it matches up to the array. */ for (i = 0; NULL != (p = index234(tree, i)); i++) { if (i >= arraylen) error("tree contains more than %d elements", arraylen); if (array[i] != p) error("enum at position %d: array says %s, tree says %s", i, (char *)array[i], (char *)p); } if (ctx->elemcount != i) { error("tree really contains %d elements, enum gave %d", ctx->elemcount, i); } if (i < arraylen) { error("enum gave only %d elements, array has %d", i, arraylen); } i = count234(tree); if (ctx->elemcount != i) { error("tree really contains %d elements, count234 gave %d", ctx->elemcount, i); } } void internal_addtest(void *elem, int index, void *realret) { int i, j; void *retval; if (arraysize < arraylen + 1) { arraysize = arraylen + 1 + 256; array = sresize(array, arraysize, void *); } i = index; /* now i points to the first element >= elem */ retval = elem; /* expect elem returned (success) */ for (j = arraylen; j > i; j--) array[j] = array[j - 1]; array[i] = elem; /* add elem to array */ arraylen++; if (realret != retval) { error("add: retval was %p expected %p", realret, retval); } verify(); } void addtest(void *elem) { int i; void *realret; realret = add234(tree, elem); i = 0; while (i < arraylen && cmp(elem, array[i]) > 0) i++; if (i < arraylen && !cmp(elem, array[i])) { void *retval = array[i]; /* expect that returned not elem */ if (realret != retval) { error("add: retval was %p expected %p", realret, retval); } } else internal_addtest(elem, i, realret); } void addpostest(void *elem, int i) { void *realret; realret = addpos234(tree, elem, i); internal_addtest(elem, i, realret); } void delpostest(int i) { int index = i; void *elem = array[i], *ret; /* i points to the right element */ while (i < arraylen - 1) { array[i] = array[i + 1]; i++; } arraylen--; /* delete elem from array */ if (tree->cmp) ret = del234(tree, elem); else ret = delpos234(tree, index); if (ret != elem) { error("del returned %p, expected %p", ret, elem); } verify(); } void deltest(void *elem) { int i; i = 0; while (i < arraylen && cmp(elem, array[i]) > 0) i++; if (i >= arraylen || cmp(elem, array[i]) != 0) return; /* don't do it! */ delpostest(i); } /* A sample data set and test utility. Designed for pseudo-randomness, * and yet repeatability. */ /* * This random number generator uses the `portable implementation' * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits; * change it if not. */ int randomnumber(unsigned *seed) { *seed *= 1103515245; *seed += 12345; return ((*seed) / 65536) % 32768; } int mycmp(void *av, void *bv) { char const *a = (char const *) av; char const *b = (char const *) bv; return strcmp(a, b); } #define lenof(x) ( sizeof((x)) / sizeof(*(x)) ) char *strings[] = { "a", "ab", "absque", "coram", "de", "palam", "clam", "cum", "ex", "e", "sine", "tenus", "pro", "prae", "banana", "carrot", "cabbage", "broccoli", "onion", "zebra", "penguin", "blancmange", "pangolin", "whale", "hedgehog", "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux", "murfl", "spoo", "breen", "flarn", "octothorpe", "snail", "tiger", "elephant", "octopus", "warthog", "armadillo", "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin", "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper", "wand", "ring", "amulet" }; #define NSTR lenof(strings) void findtest(void) { const static int rels[] = { REL234_EQ, REL234_GE, REL234_LE, REL234_LT, REL234_GT }; const static char *const relnames[] = { "EQ", "GE", "LE", "LT", "GT" }; int i, j, rel, index; char *p, *ret, *realret, *realret2; int lo, hi, mid, c; for (i = 0; i < NSTR; i++) { p = strings[i]; for (j = 0; j < sizeof(rels) / sizeof(*rels); j++) { rel = rels[j]; lo = 0; hi = arraylen - 1; while (lo <= hi) { mid = (lo + hi) / 2; c = strcmp(p, array[mid]); if (c < 0) hi = mid - 1; else if (c > 0) lo = mid + 1; else break; } if (c == 0) { if (rel == REL234_LT) ret = (mid > 0 ? array[--mid] : NULL); else if (rel == REL234_GT) ret = (mid < arraylen - 1 ? array[++mid] : NULL); else ret = array[mid]; } else { assert(lo == hi + 1); if (rel == REL234_LT || rel == REL234_LE) { mid = hi; ret = (hi >= 0 ? array[hi] : NULL); } else if (rel == REL234_GT || rel == REL234_GE) { mid = lo; ret = (lo < arraylen ? array[lo] : NULL); } else ret = NULL; } realret = findrelpos234(tree, p, NULL, rel, &index); if (realret != ret) { error("find(\"%s\",%s) gave %s should be %s", p, relnames[j], realret, ret); } if (realret && index != mid) { error("find(\"%s\",%s) gave %d should be %d", p, relnames[j], index, mid); } if (realret && rel == REL234_EQ) { realret2 = index234(tree, index); if (realret2 != realret) { error("find(\"%s\",%s) gave %s(%d) but %d -> %s", p, relnames[j], realret, index, index, realret2); } } if (verbose) printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j], realret, index); } } realret = findrelpos234(tree, NULL, NULL, REL234_GT, &index); if (arraylen && (realret != array[0] || index != 0)) { error("find(NULL,GT) gave %s(%d) should be %s(0)", realret, index, (char *)array[0]); } else if (!arraylen && (realret != NULL)) { error("find(NULL,GT) gave %s(%d) should be NULL", realret, index); } realret = findrelpos234(tree, NULL, NULL, REL234_LT, &index); if (arraylen && (realret != array[arraylen - 1] || index != arraylen - 1)) { error("find(NULL,LT) gave %s(%d) should be %s(0)", realret, index, (char *)array[arraylen - 1]); } else if (!arraylen && (realret != NULL)) { error("find(NULL,LT) gave %s(%d) should be NULL", realret, index); } } void searchtest_recurse(search234_state ss, int lo, int hi, char **expected, char *directionbuf, char *directionptr) { *directionptr = '\0'; if (!ss.element) { if (lo != hi) { error("search234(%s) gave NULL for non-empty interval [%d,%d)", directionbuf, lo, hi); } else if (ss.index != lo) { error("search234(%s) gave index %d should be %d", directionbuf, ss.index, lo); } else { if (verbose) printf("%*ssearch234(%s) gave NULL,%d\n", (int)(directionptr-directionbuf) * 2, "", directionbuf, ss.index); } } else if (lo == hi) { error("search234(%s) gave %s for empty interval [%d,%d)", directionbuf, (char *)ss.element, lo, hi); } else if (ss.element != expected[ss.index]) { error("search234(%s) gave element %s should be %s", directionbuf, (char *)ss.element, expected[ss.index]); } else if (ss.index < lo || ss.index >= hi) { error("search234(%s) gave index %d should be in [%d,%d)", directionbuf, ss.index, lo, hi); return; } else { search234_state next; if (verbose) printf("%*ssearch234(%s) gave %s,%d\n", (int)(directionptr-directionbuf) * 2, "", directionbuf, (char *)ss.element, ss.index); next = ss; search234_step(&next, -1); *directionptr = '-'; searchtest_recurse(next, lo, ss.index, expected, directionbuf, directionptr+1); next = ss; search234_step(&next, +1); *directionptr = '+'; searchtest_recurse(next, ss.index+1, hi, expected, directionbuf, directionptr+1); } } void searchtest(void) { char *expected[NSTR], *p; char directionbuf[NSTR * 10]; int n; search234_state ss; if (verbose) printf("beginning searchtest:"); for (n = 0; (p = index234(tree, n)) != NULL; n++) { expected[n] = p; if (verbose) printf(" %d=%s", n, p); } if (verbose) printf(" count=%d\n", n); search234_start(&ss, tree); searchtest_recurse(ss, 0, n, expected, directionbuf, directionbuf); } void out_of_memory(void) { fprintf(stderr, "out of memory!\n"); exit(2); } int main(int argc, char **argv) { int in[NSTR]; int i, j, k; unsigned seed = 0; for (i = 1; i < argc; i++) { char *arg = argv[i]; if (!strcmp(arg, "-v")) { verbose++; } else { fprintf(stderr, "unrecognised option '%s'\n", arg); return 1; } } for (i = 0; i < NSTR; i++) in[i] = 0; array = NULL; arraylen = arraysize = 0; tree = newtree234(mycmp); cmp = mycmp; verify(); searchtest(); for (i = 0; i < 10000; i++) { j = randomnumber(&seed); j %= NSTR; if (verbose) printf("trial: %d\n", i); if (in[j]) { if (verbose) printf("deleting %s (%d)\n", strings[j], j); deltest(strings[j]); in[j] = 0; } else { if (verbose) printf("adding %s (%d)\n", strings[j], j); addtest(strings[j]); in[j] = 1; } findtest(); searchtest(); } while (arraylen > 0) { j = randomnumber(&seed); j %= arraylen; deltest(array[j]); } freetree234(tree); /* * Now try an unsorted tree. We don't really need to test * delpos234 because we know del234 is based on it, so it's * already been tested in the above sorted-tree code; but for * completeness we'll use it to tear down our unsorted tree * once we've built it. */ tree = newtree234(NULL); cmp = NULL; verify(); for (i = 0; i < 1000; i++) { if (verbose) printf("trial: %d\n", i); j = randomnumber(&seed); j %= NSTR; k = randomnumber(&seed); k %= count234(tree) + 1; if (verbose) printf("adding string %s at index %d\n", strings[j], k); addpostest(strings[j], k); } while (count234(tree) > 0) { if (verbose) printf("cleanup: tree size %d\n", count234(tree)); j = randomnumber(&seed); j %= count234(tree); if (verbose) printf("deleting string %s from index %d\n", (const char *)array[j], j); delpostest(j); } printf("%d errors found\n", n_errors); return (n_errors != 0); } #endif /* TEST */ work/utils/utils.h0000664000000000000000000000061214714222463011405 0ustar /* * Internal header to the utils subdirectory, for definitions shared * between the library implementations but not intended to be exposed * further than that. */ void dputs(const char *); char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr, const char *fmt, va_list ap); const char *host_strchr_internal(const char *s, const char *set, bool first); work/utils/x11_dehexify.c0000664000000000000000000000114114714222463012534 0ustar /* * Utility function to convert a textual representation of an X11 * auth hex cookie into binary auth data. */ #include "putty.h" void *x11_dehexify(ptrlen hexpl, int *outlen) { int len, i; unsigned char *ret; len = hexpl.len / 2; ret = snewn(len, unsigned char); for (i = 0; i < len; i++) { char bytestr[3]; unsigned val = 0; bytestr[0] = ((const char *)hexpl.ptr)[2*i]; bytestr[1] = ((const char *)hexpl.ptr)[2*i+1]; bytestr[2] = '\0'; sscanf(bytestr, "%x", &val); ret[i] = val; } *outlen = len; return ret; } work/utils/x11_make_greeting.c0000664000000000000000000000410314714222463013531 0ustar /* * Construct an X11 greeting packet, including making up the right * authorisation data. */ #include "putty.h" #include "ssh.h" void *x11_make_greeting(int endian, int protomajor, int protominor, int auth_proto, const void *auth_data, int auth_len, const char *peer_addr, int peer_port, int *outlen) { unsigned char *greeting; unsigned char realauthdata[64]; const char *authname; const unsigned char *authdata; int authnamelen, authnamelen_pad; int authdatalen, authdatalen_pad; int greeting_len; authname = x11_authnames[auth_proto]; authnamelen = strlen(authname); authnamelen_pad = (authnamelen + 3) & ~3; if (auth_proto == X11_MIT) { authdata = auth_data; authdatalen = auth_len; } else if (auth_proto == X11_XDM && auth_len == 16) { time_t t; unsigned long peer_ip = 0; x11_parse_ip(peer_addr, &peer_ip); authdata = realauthdata; authdatalen = 24; memset(realauthdata, 0, authdatalen); memcpy(realauthdata, auth_data, 8); PUT_32BIT_MSB_FIRST(realauthdata+8, peer_ip); PUT_16BIT_MSB_FIRST(realauthdata+12, peer_port); t = time(NULL); PUT_32BIT_MSB_FIRST(realauthdata+14, t); des_encrypt_xdmauth((char *)auth_data + 9, realauthdata, authdatalen); } else { authdata = realauthdata; authdatalen = 0; } authdatalen_pad = (authdatalen + 3) & ~3; greeting_len = 12 + authnamelen_pad + authdatalen_pad; greeting = snewn(greeting_len, unsigned char); memset(greeting, 0, greeting_len); greeting[0] = endian; PUT_16BIT_X11(endian, greeting+2, protomajor); PUT_16BIT_X11(endian, greeting+4, protominor); PUT_16BIT_X11(endian, greeting+6, authnamelen); PUT_16BIT_X11(endian, greeting+8, authdatalen); memcpy(greeting+12, authname, authnamelen); memcpy(greeting+12+authnamelen_pad, authdata, authdatalen); smemclr(realauthdata, sizeof(realauthdata)); *outlen = greeting_len; return greeting; } work/utils/x11_parse_ip.c0000664000000000000000000000066614714222463012544 0ustar /* * Try to make sense of a string as an IPv4 address, for * XDM-AUTHORIZATION-1 purposes. */ #include #include "putty.h" bool x11_parse_ip(const char *addr_string, unsigned long *ip) { int i[4]; if (addr_string && 4 == sscanf(addr_string, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { *ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; return true; } else { return false; } } work/utils/x11authfile.c0000664000000000000000000002167614714222463012410 0ustar /* * Functions to handle .Xauthority files. */ #include #include #include #include "putty.h" #include "ssh.h" ptrlen BinarySource_get_string_xauth(BinarySource *src) { size_t len = get_uint16(src); return get_data(src, len); } #define get_string_xauth(src) \ BinarySource_get_string_xauth(BinarySource_UPCAST(src)) void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl) { assert((pl.len >> 16) == 0); put_uint16(bs, pl.len); put_datapl(bs, pl); } #define put_stringpl_xauth(bs, ptrlen) \ BinarySink_put_stringpl_xauth(BinarySink_UPCAST(bs),ptrlen) void x11_get_auth_from_authfile(struct X11Display *disp, const char *authfilename) { FILE *authfp; char *buf; int size; BinarySource src[1]; int family, protocol; ptrlen addr, protoname, data; char *displaynum_string; int displaynum; bool ideal_match = false; char *ourhostname; /* A maximally sized (wildly implausible) .Xauthority record * consists of a 16-bit integer to start with, then four strings, * each of which has a 16-bit length field followed by that many * bytes of data (i.e. up to 0xFFFF bytes). */ const size_t MAX_RECORD_SIZE = 2 + 4 * (2+0xFFFF); /* We'll want a buffer of twice that size (see below). */ const size_t BUF_SIZE = 2 * MAX_RECORD_SIZE; /* * Normally we should look for precisely the details specified in * `disp'. However, there's an oddity when the display is local: * displays like "localhost:0" usually have their details stored * in a Unix-domain-socket record (even if there isn't actually a * real Unix-domain socket available, as with OpenSSH's proxy X11 * server). * * This is apparently a fudge to get round the meaninglessness of * "localhost" in a shared-home-directory context -- xauth entries * for Unix-domain sockets already disambiguate this by storing * the *local* hostname in the conveniently-blank hostname field, * but IP "localhost" records couldn't do this. So, typically, an * IP "localhost" entry in the auth database isn't present and if * it were it would be ignored. * * However, we don't entirely trust that (say) Windows X servers * won't rely on a straight "localhost" entry, bad idea though * that is; so if we can't find a Unix-domain-socket entry we'll * fall back to an IP-based entry if we can find one. */ bool localhost = !disp->unixdomain && sk_address_is_local(disp->addr); authfp = fopen(authfilename, "rb"); if (!authfp) return; ourhostname = get_hostname(); /* * Allocate enough space to hold two maximally sized records, so * that a full record can start anywhere in the first half. That * way we avoid the accidentally-quadratic algorithm that would * arise if we moved everything to the front of the buffer after * consuming each record; instead, we only move everything to the * front after our current position gets past the half-way mark. * Before then, there's no need to move anyway; so this guarantees * linear time, in that every byte written into this buffer moves * at most once (because every move is from the second half of the * buffer to the first half). */ buf = snewn(BUF_SIZE, char); size = fread(buf, 1, BUF_SIZE, authfp); BinarySource_BARE_INIT(src, buf, size); while (!ideal_match) { bool match = false; if (src->pos >= MAX_RECORD_SIZE) { size -= src->pos; memcpy(buf, buf + src->pos, size); size += fread(buf + size, 1, BUF_SIZE - size, authfp); BinarySource_BARE_INIT(src, buf, size); } family = get_uint16(src); addr = get_string_xauth(src); displaynum_string = mkstr(get_string_xauth(src)); displaynum = displaynum_string[0] ? atoi(displaynum_string) : -1; sfree(displaynum_string); protoname = get_string_xauth(src); data = get_string_xauth(src); if (get_err(src)) break; /* * Now we have a full X authority record in memory. See * whether it matches the display we're trying to * authenticate to. * * The details we've just read should be interpreted as * follows: * * - 'family' is the network address family used to * connect to the display. 0 means IPv4; 6 means IPv6; * 256 means Unix-domain sockets. * * - 'addr' is the network address itself. For IPv4 and * IPv6, this is a string of binary data of the * appropriate length (respectively 4 and 16 bytes) * representing the address in big-endian format, e.g. * 7F 00 00 01 means IPv4 localhost. For Unix-domain * sockets, this is the host name of the machine on * which the Unix-domain display resides (so that an * .Xauthority file on a shared file system can contain * authority entries for Unix-domain displays on * several machines without them clashing). * * - 'displaynum' is the display number. An empty display * number is a wildcard for any display number. * * - 'protoname' is the authorisation protocol, encoded as * its canonical string name (i.e. "MIT-MAGIC-COOKIE-1", * "XDM-AUTHORIZATION-1" or something we don't recognise). * * - 'data' is the actual authorisation data, stored in * binary form. */ if (disp->displaynum < 0 || (displaynum >= 0 && disp->displaynum != displaynum)) continue; /* not the one */ for (protocol = 1; protocol < lenof(x11_authnames); protocol++) if (ptrlen_eq_string(protoname, x11_authnames[protocol])) break; if (protocol == lenof(x11_authnames)) continue; /* don't recognise this protocol, look for another */ switch (family) { case 0: /* IPv4 */ if (!disp->unixdomain && sk_addrtype(disp->addr) == ADDRTYPE_IPV4) { char buf[4]; sk_addrcopy(disp->addr, buf); if (addr.len == 4 && !memcmp(addr.ptr, buf, 4)) { match = true; /* If this is a "localhost" entry, note it down * but carry on looking for a Unix-domain entry. */ ideal_match = !localhost; } } break; case 6: /* IPv6 */ if (!disp->unixdomain && sk_addrtype(disp->addr) == ADDRTYPE_IPV6) { char buf[16]; sk_addrcopy(disp->addr, buf); if (addr.len == 16 && !memcmp(addr.ptr, buf, 16)) { match = true; ideal_match = !localhost; } } break; case 256: /* Unix-domain / localhost */ if ((disp->unixdomain || localhost) && ourhostname && ptrlen_eq_string(addr, ourhostname)) { /* A matching Unix-domain socket is always the best * match. */ match = true; ideal_match = true; } break; } if (match) { /* Current best guess -- may be overridden if !ideal_match */ disp->localauthproto = protocol; sfree(disp->localauthdata); /* free previous guess, if any */ disp->localauthdata = snewn(data.len, unsigned char); memcpy(disp->localauthdata, data.ptr, data.len); disp->localauthdatalen = data.len; } } fclose(authfp); smemclr(buf, 2 * MAX_RECORD_SIZE); sfree(buf); sfree(ourhostname); } void x11_format_auth_for_authfile( BinarySink *bs, SockAddr *addr, int display_no, ptrlen authproto, ptrlen authdata) { if (sk_address_is_special_local(addr)) { char *ourhostname = get_hostname(); put_uint16(bs, 256); /* indicates Unix-domain socket */ put_stringpl_xauth(bs, ptrlen_from_asciz(ourhostname)); sfree(ourhostname); } else if (sk_addrtype(addr) == ADDRTYPE_IPV4) { char ipv4buf[4]; sk_addrcopy(addr, ipv4buf); put_uint16(bs, 0); /* indicates IPv4 */ put_stringpl_xauth(bs, make_ptrlen(ipv4buf, 4)); } else if (sk_addrtype(addr) == ADDRTYPE_IPV6) { char ipv6buf[16]; sk_addrcopy(addr, ipv6buf); put_uint16(bs, 6); /* indicates IPv6 */ put_stringpl_xauth(bs, make_ptrlen(ipv6buf, 16)); } else { unreachable("Bad address type in x11_format_auth_for_authfile"); } { char *numberbuf = dupprintf("%d", display_no); put_stringpl_xauth(bs, ptrlen_from_asciz(numberbuf)); sfree(numberbuf); } put_stringpl_xauth(bs, authproto); put_stringpl_xauth(bs, authdata); } work/utils/x11authnames.c0000664000000000000000000000030114714222463012552 0ustar /* * Definition of the array of X11 authorisation method names. */ #include "putty.h" const char *const x11_authnames[X11_NAUTHS] = { "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1" }; work/version.h0000664000000000000000000000025314714222463010573 0ustar /* * When my automated build system does a full build, Buildscr * overwrites this file with one that defines a version number. This * version of the file does not. */ work/x11disp.c0000664000000000000000000001306114714222463010373 0ustar /* * Functions to manage an X11Display structure, by creating one from * an ordinary display name string, and freeing one. */ #include #include #include #include #include "putty.h" #include "ssh.h" #include "ssh/channel.h" #include "tree234.h" struct X11Display *x11_setup_display(const char *display, Conf *conf, char **error_msg) { struct X11Display *disp = snew(struct X11Display); char *localcopy; *error_msg = NULL; if (!display || !*display) { localcopy = platform_get_x_display(); if (!localcopy || !*localcopy) { sfree(localcopy); localcopy = dupstr(":0"); /* plausible default for any platform */ } } else localcopy = dupstr(display); /* * Parse the display name. * * We expect this to have one of the following forms: * * - the standard X format which looks like * [ [ protocol '/' ] host ] ':' displaynumber [ '.' screennumber ] * (X11 also permits a double colon to indicate DECnet, but * that's not our problem, thankfully!) * * - only seen in the wild on MacOS (so far): a pathname to a * Unix-domain socket, which will typically and confusingly * end in ":0", and which I'm currently distinguishing from * the standard scheme by noting that it starts with '/'. */ if (localcopy[0] == '/') { disp->unixsocketpath = localcopy; disp->unixdomain = true; disp->hostname = NULL; disp->displaynum = -1; disp->screennum = 0; disp->addr = NULL; } else { char *colon, *dot, *slash; char *protocol, *hostname; colon = host_strrchr(localcopy, ':'); if (!colon) { *error_msg = dupprintf("display name '%s' has no ':number'" " suffix", localcopy); sfree(disp); sfree(localcopy); return NULL; } *colon++ = '\0'; dot = strchr(colon, '.'); if (dot) *dot++ = '\0'; disp->displaynum = atoi(colon); if (dot) disp->screennum = atoi(dot); else disp->screennum = 0; protocol = NULL; hostname = localcopy; if (colon > localcopy) { slash = strchr(localcopy, '/'); if (slash) { *slash++ = '\0'; protocol = localcopy; hostname = slash; } } disp->hostname = *hostname ? dupstr(hostname) : NULL; if (protocol) disp->unixdomain = (!strcmp(protocol, "local") || !strcmp(protocol, "unix")); else if (!*hostname || !strcmp(hostname, "unix")) disp->unixdomain = platform_uses_x11_unix_by_default; else disp->unixdomain = false; if (!disp->hostname && !disp->unixdomain) disp->hostname = dupstr("localhost"); disp->unixsocketpath = NULL; disp->addr = NULL; sfree(localcopy); } /* * Look up the display hostname, if we need to. */ if (!disp->unixdomain) { const char *err; disp->port = 6000 + disp->displaynum; disp->addr = name_lookup(disp->hostname, disp->port, &disp->realhost, conf, ADDRTYPE_UNSPEC, NULL, NULL); if ((err = sk_addr_error(disp->addr)) != NULL) { *error_msg = dupprintf("unable to resolve host name '%s' in " "display name", disp->hostname); sk_addr_free(disp->addr); sfree(disp->hostname); sfree(disp->unixsocketpath); sfree(disp); return NULL; } } /* * Try upgrading an IP-style localhost display to a Unix-socket * display (as the standard X connection libraries do). */ if (!disp->unixdomain && sk_address_is_local(disp->addr)) { SockAddr *ux = platform_get_x11_unix_address(NULL, disp->displaynum); const char *err = sk_addr_error(ux); if (!err) { /* Create trial connection to see if there is a useful Unix-domain * socket */ Socket *s = sk_new(sk_addr_dup(ux), 0, false, false, false, false, nullplug); err = sk_socket_error(s); sk_close(s); } if (err) { sk_addr_free(ux); } else { sk_addr_free(disp->addr); disp->unixdomain = true; disp->addr = ux; /* Fill in the rest in a moment */ } } if (disp->unixdomain) { if (!disp->addr) disp->addr = platform_get_x11_unix_address(disp->unixsocketpath, disp->displaynum); if (disp->unixsocketpath) disp->realhost = dupstr(disp->unixsocketpath); else disp->realhost = dupprintf("unix:%d", disp->displaynum); disp->port = 0; } /* * Fetch the local authorisation details. */ disp->localauthproto = X11_NO_AUTH; disp->localauthdata = NULL; disp->localauthdatalen = 0; platform_get_x11_auth(disp, conf); return disp; } void x11_free_display(struct X11Display *disp) { sfree(disp->hostname); sfree(disp->unixsocketpath); if (disp->localauthdata) smemclr(disp->localauthdata, disp->localauthdatalen); sfree(disp->localauthdata); sk_addr_free(disp->addr); sfree(disp); } work/xtruss-proxy.c0000664000000000000000000001254614714222463011640 0ustar #include #include #include "putty.h" #include "network.h" #include "ssh.h" #include "ssh/server.h" /* for MAX_X11_SOCKETS */ #include "xtruss.h" struct xtruss_proxy { struct xtruss_state *xs; int nsockets; Socket *sockets[MAX_X11_SOCKETS]; Plug plug; }; struct xtruss_proxy_conn { struct xlog *xlog; Socket *dssock, *ussock; Plug dsplug, usplug; }; static void xproxy_log_error( Plug *plug, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code); static int xproxy_accept( Plug *p, accept_fn_t constructor, accept_ctx_t ctx); static void xproxy_ds_receive( Plug *plug, int urgent, const char *data, size_t len); static void xproxy_us_receive( Plug *plug, int urgent, const char *data, size_t len); static void xproxy_ds_sent(Plug *plug, size_t backlog); static void xproxy_us_sent(Plug *plug, size_t backlog); static void xproxy_ds_closing( Plug *plug, const char *error_msg, int error_code, bool calling_back); static void xproxy_us_closing( Plug *plug, const char *error_msg, int error_code, bool calling_back); static const PlugVtable xproxy_listener_plugvt = { .log = xproxy_log_error, .accepting = xproxy_accept, }; static const PlugVtable xproxy_downstream_plugvt = { .log = xproxy_log_error, .closing = xproxy_ds_closing, .receive = xproxy_ds_receive, .sent = xproxy_ds_sent, }; static const PlugVtable xproxy_upstream_plugvt = { .log = xproxy_log_error, .closing = xproxy_us_closing, .receive = xproxy_us_receive, .sent = xproxy_us_sent, }; static void xproxy_log_error( Plug *plug, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code) { /* This function does double duty between both vtables, because it * doesn't need to know which kind of thing it's part of */ if (type == PLUGLOG_CONNECT_FAILED) fprintf(stderr, "Socket error: %s\n", error_msg); } static void xproxy_conn_free(struct xtruss_proxy_conn *conn) { if (conn->ussock) sk_close(conn->ussock); if (conn->dssock) sk_close(conn->dssock); sfree(conn); } static int xproxy_accept( Plug *p, accept_fn_t constructor, accept_ctx_t ctx) { struct xtruss_proxy *xp = container_of(p, struct xtruss_proxy, plug); struct xtruss_proxy_conn *conn = snew(struct xtruss_proxy_conn); memset(conn, 0, sizeof(*conn)); conn->dsplug.vt = &xproxy_downstream_plugvt; conn->usplug.vt = &xproxy_upstream_plugvt; conn->dssock = constructor(ctx, &conn->dsplug); const char *err; if ((err = sk_socket_error(conn->dssock)) != NULL) { xproxy_conn_free(conn); return 1; } conn->ussock = sk_new( sk_addr_dup(xp->xs->x11disp->addr), xp->xs->x11disp->port, false, true, false, false, &conn->usplug); if ((err = sk_socket_error(conn->ussock)) != NULL) { xproxy_conn_free(conn); return 1; } sk_set_frozen(conn->dssock, false); conn->xlog = xlog_new(xp->xs, XLOG_FULL); return 0; } const int MAX_BACKLOG = 32768; static void xproxy_ds_receive( Plug *plug, int urgent, const char *data, size_t len) { struct xtruss_proxy_conn *conn = container_of( plug, struct xtruss_proxy_conn, dsplug); xlog_c2s(conn->xlog, data, len); size_t backlog = sk_write(conn->ussock, data, len); sk_set_frozen(conn->dssock, backlog > MAX_BACKLOG); } static void xproxy_us_receive( Plug *plug, int urgent, const char *data, size_t len) { struct xtruss_proxy_conn *conn = container_of( plug, struct xtruss_proxy_conn, usplug); xlog_s2c(conn->xlog, data, len); size_t backlog = sk_write(conn->dssock, data, len); sk_set_frozen(conn->ussock, backlog > MAX_BACKLOG); } static void xproxy_ds_sent(Plug *plug, size_t backlog) { struct xtruss_proxy_conn *conn = container_of( plug, struct xtruss_proxy_conn, dsplug); sk_set_frozen(conn->ussock, backlog > MAX_BACKLOG); } static void xproxy_us_sent(Plug *plug, size_t backlog) { struct xtruss_proxy_conn *conn = container_of( plug, struct xtruss_proxy_conn, usplug); sk_set_frozen(conn->dssock, backlog > MAX_BACKLOG); } static void xproxy_ds_closing( Plug *plug, const char *error_msg, int error_code, bool calling_back) { struct xtruss_proxy_conn *conn = container_of( plug, struct xtruss_proxy_conn, dsplug); xproxy_conn_free(conn); } static void xproxy_us_closing( Plug *plug, const char *error_msg, int error_code, bool calling_back) { struct xtruss_proxy_conn *conn = container_of( plug, struct xtruss_proxy_conn, dsplug); xproxy_conn_free(conn); } void xtruss_proxy_start(xtruss_state *xs) { struct xtruss_proxy *xp = snew(struct xtruss_proxy); memset(xp, 0, sizeof(*xp)); xp->xs = xs; xp->plug.vt = &xproxy_listener_plugvt; xp->nsockets = platform_make_x11_server( &xp->plug, appname, 10, "", ptrlen_from_asciz(x11_authnames[xs->x11disp->localauthproto]), make_ptrlen(xs->x11disp->localauthdata, xs->x11disp->localauthdatalen), xp->sockets, xs->conf); if (xp->nsockets == 0) { fprintf(stderr, "xtruss: unable to create proxy X display\n"); exit(1); } xs->env_disp = conf_get_str_str(xs->conf, CONF_environmt, "DISPLAY"); xs->env_auth = conf_get_str_str(xs->conf, CONF_environmt, "XAUTHORITY"); } work/xtruss-record.c0000664000000000000000000006177414714222463011744 0ustar #include #include #include "putty.h" #include "network.h" #include "ssh.h" #include "sshcr.h" #include "xtruss.h" struct winq { unsigned winid; struct winq *next; }; struct atom_list { unsigned long atomval; char *atomname; struct atom_list *next; }; struct xrecord_state { struct xtruss_state *xs; char *welcome_message; size_t welcome_message_len; struct atom_list *atoms; int crState; tree234 *xlogs_by_id; strbuf *buf; unsigned rbase, rmask, rootwin, clientid, xrecordopcode, wmsatom; struct winq *whead, *wtail; Socket *sock; Plug plug; }; static int xlog_cmp_id(void *av, void *bv) { unsigned aid = xlog_get_clientid((struct xlog *)av); unsigned bid = xlog_get_clientid((struct xlog *)bv); return aid < bid ? -1 : aid > bid ? +1 : 0; } static int xlog_find_id(void *av, void *bv) { unsigned aid = *(unsigned *)av; unsigned bid = xlog_get_clientid((struct xlog *)bv); return aid < bid ? -1 : aid > bid ? +1 : 0; } static void xrecord_log_error( Plug *plug, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code); static void xrecord_receive( Plug *plug, int urgent, const char *data, size_t len); static void xrecord_sent(Plug *plug, size_t backlog); static void xrecord_closing( Plug *plug, const char *error_msg, int error_code, bool calling_back); static void xrecord_coroutine(struct xrecord_state *xr, const void *vdata, int len); static const PlugVtable xrecord_plugvt = { .log = xrecord_log_error, .closing = xrecord_closing, .receive = xrecord_receive, .sent = xrecord_sent, }; static void xrecord_log_error( Plug *plug, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code) { if (type == PLUGLOG_CONNECT_FAILED) fprintf(stderr, "X11 socket error: %s\n", error_msg); } static void xrecord_receive( Plug *plug, int urgent, const char *data, size_t len) { struct xrecord_state *xr = container_of(plug, struct xrecord_state, plug); xrecord_coroutine(xr, data, len); } static void xrecord_sent(Plug *plug, size_t backlog) { } static void xrecord_closing( Plug *plug, const char *error_msg, int error_code, bool calling_back) { if (error_msg) fprintf(stderr, "X11 socket error: %s\n", error_msg); else fprintf(stderr, "X11 socket unexpectedly closed\n"); exit(1); } static void xrecord_coroutine(struct xrecord_state *xr, const void *vdata, int len) { const unsigned char *data = (const unsigned char *)vdata; unsigned char buf[512]; crBegin(xr->crState); /* * Start by sending the X init packet. */ { char peer_addr[32]; int peer_port; int socketdatalen = 0; /* placate compiler warning */ unsigned char *socketdata = sk_getxdmdata(xr->sock, &socketdatalen); if (socketdata && socketdatalen==6) { sprintf(peer_addr, "%d.%d.%d.%d", socketdata[0], socketdata[1], socketdata[2], socketdata[3]); peer_port = GET_16BIT_MSB_FIRST(socketdata + 4); sfree(socketdata); } else { strcpy(peer_addr, "0.0.0.0"); peer_port = 0; } int greeting_len = 0; char *greeting = x11_make_greeting( 'B', 11, 0, xr->xs->x11disp->localauthproto, xr->xs->x11disp->localauthdata, xr->xs->x11disp->localauthdatalen, peer_addr, peer_port, &greeting_len); sk_write(xr->sock, greeting, greeting_len); smemclr(greeting, greeting_len); sfree(greeting); } /* * We expect to see a successful authorisation and a welcome * message. Extract our resource base and mask, plus the root * window id. * * [FIXME: what are we supposed to do in a multi-screen * situation?] */ strbuf_clear(xr->buf); crReadUpTo(xr->buf, 8); crReadUpTo(xr->buf, 8 + 4*GET_16BIT_MSB_FIRST(xr->buf->u + 6)); if (xr->buf->u[0] != 1) { int n = xr->buf->u[1]; if (n > xr->buf->len - 8) n = xr->buf->len - 8; fprintf(stderr, "xtruss: X server denied authorisation (\"%.*s\")\n", n, xr->buf->u + 8); exit(1); } xr->rbase = GET_32BIT_MSB_FIRST(xr->buf->u + 12); xr->rmask = GET_32BIT_MSB_FIRST(xr->buf->u + 16); { int rootoffset = GET_16BIT_MSB_FIRST(xr->buf->u + 24); rootoffset = 40 + ((rootoffset + 3) & ~3); rootoffset += 8 * xr->buf->u[29]; xr->rootwin = GET_32BIT_MSB_FIRST(xr->buf->u + rootoffset); } /* * Save our own welcome message, which we'll use to initialise * each xlog instance we create while tracing. */ xr->welcome_message = snewn(xr->buf->len, char); memcpy(xr->welcome_message, xr->buf->u, xr->buf->len); xr->welcome_message_len = xr->buf->len; /* * Simple means of allocating a small number of resource ids in * such a way that they're easy to compute in a static manner * and will not clash with one another no matter what (valid) * value is taken by xr->rmask. */ #define FONTID (xr->rbase | (xr->rmask & 0x11111111)) #define CURID (xr->rbase | (xr->rmask & 0x22222222)) #define RCID (xr->rbase | (xr->rmask & 0x33333333)) /* * First check that the RECORD extension is present and correct. * If it isn't, we should find out before we faff about getting * the user to pick a window. */ buf[0] = 98; buf[1] = 0; /* QueryExtension opcode and padding */ PUT_16BIT_MSB_FIRST(buf+2, 4); /* request length */ PUT_16BIT_MSB_FIRST(buf+4, 6); /* name length */ memset(buf+6, 0, 10); memcpy(buf+8, "RECORD", 6); sk_write(xr->sock, buf, 16); /* * Read the reply, which hopefully will say Success and tell us * the major opcode for the extension. */ strbuf_clear(xr->buf); crReadUpTo(xr->buf, 32); if (xr->buf->u[0] == 1) crReadUpTo(xr->buf, 32 + 4*GET_32BIT_MSB_FIRST(xr->buf->u + 4)); #define EXPECT_REPLY(name) do { \ if (xr->buf->u[0] == 0) { \ const char *err = xlog_translate_error(xr->buf->u[1]); \ if (err) \ fprintf(stderr, "xtruss: X server returned %s error to" \ " %s\n", err, name); \ else \ fprintf(stderr, "xtruss: X server returned unknown error %d to" \ " %s\n", xr->buf->u[1], name); \ exit(1); \ } else if (xr->buf->u[0] != 1) { \ const char *ev = xlog_translate_event(xr->buf->u[0]); \ if (ev) \ fprintf(stderr, "xtruss: unexpected event received (%s)\n", ev); \ else \ fprintf(stderr, "xtruss: unexpected event received (%d)\n", \ xr->buf->u[0]); \ exit(1); \ } \ } while (0) EXPECT_REPLY("QueryExtension"); if (xr->buf->u[8] != 1) { fprintf(stderr, "xtruss: cannot use -p: X server does not support" " the X RECORD extension\n"); exit(1); } xr->xrecordopcode = xr->buf->u[9]; /* * Now initialise the RECORD extension. */ buf[0] = xr->xrecordopcode; buf[1] = 0;/* RecordQueryVersion */ PUT_16BIT_MSB_FIRST(buf+2, 2); /* request length */ PUT_16BIT_MSB_FIRST(buf+4, 1); /* major version */ PUT_16BIT_MSB_FIRST(buf+6, 13); /* minor version */ sk_write(xr->sock, buf, 8); /* * Read the reply, which hopefully will say Success. */ strbuf_clear(xr->buf); crReadUpTo(xr->buf, 32); if (xr->buf->u[0] == 1) crReadUpTo(xr->buf, 32 + 4*GET_32BIT_MSB_FIRST(xr->buf->u+4)); EXPECT_REPLY("RecordQueryVersion"); /* * Query currently defined atoms */ #define MAX_PREDEFINED_ATOM_ID 68 #define MAX_ATOM_ID 0x1FFFFFFF #define NEXT_ATOM_ID (xr->atoms ? xr->atoms->atomval + 1 : MAX_PREDEFINED_ATOM_ID + 1) if (!xr->xs->xrskipatoms) { while (NEXT_ATOM_ID <= MAX_ATOM_ID) { buf[0] = 17; buf[1] = 0; /* GetAtomName opcode and padding */ PUT_16BIT_MSB_FIRST(buf + 2, 2); /* request length */ PUT_32BIT_MSB_FIRST(buf + 4, NEXT_ATOM_ID); /* atom id */ sk_write(xr->sock, buf, 8); strbuf_clear(xr->buf); crReadUpTo(xr->buf, 32); if (xr->buf->u[0] != 1) { break; } crReadUpTo(xr->buf, 32 + 4 * GET_32BIT_MSB_FIRST(xr->buf->u + 4)); { struct atom_list *al = snew(struct atom_list); unsigned atom_length = GET_16BIT_MSB_FIRST(xr->buf->u + 8); al->atomname = snewn(atom_length + 1, char); memcpy(al->atomname, xr->buf->u + 32, atom_length); al->atomname[atom_length] = 0; al->atomval = NEXT_ATOM_ID; al->next = xr->atoms; xr->atoms = al; } } } if (xr->xs->xrselectclient) { fprintf(stderr, "xtruss: click mouse in a window belonging to the " "client you want to trace\n"); /* * Open the 'cursor' font. */ buf[0] = 45; buf[1] = 0; /* OpenFont opcode and padding */ PUT_16BIT_MSB_FIRST(buf+2, 5); /* request length */ PUT_32BIT_MSB_FIRST(buf+4, FONTID); /* font id */ PUT_16BIT_MSB_FIRST(buf+8, 6); /* name length */ memset(buf+10, 0, 10); memcpy(buf+12, "cursor", 6); sk_write(xr->sock, buf, 20); /* * Create a cursor based on a crosshair glyph from that * font. */ buf[0] = 94; buf[1] = 0; /* CreateGlyphCursor opcode + padding */ PUT_16BIT_MSB_FIRST(buf+2, 8); /* request length */ PUT_32BIT_MSB_FIRST(buf+4, CURID); /* cursor id */ PUT_32BIT_MSB_FIRST(buf+8, FONTID); /* font id for cursor itself */ PUT_32BIT_MSB_FIRST(buf+12, FONTID); /* font id for cursor mask */ PUT_16BIT_MSB_FIRST(buf+16, 34); /* character code for cursor */ PUT_16BIT_MSB_FIRST(buf+18, 35); /* character code for cursor mask */ PUT_16BIT_MSB_FIRST(buf+20, 0xFFFF); /* foreground red */ PUT_16BIT_MSB_FIRST(buf+22, 0xFFFF); /* foreground green */ PUT_16BIT_MSB_FIRST(buf+24, 0xFFFF); /* foreground blue */ PUT_16BIT_MSB_FIRST(buf+26, 0x0000); /* background red */ PUT_16BIT_MSB_FIRST(buf+28, 0x0000); /* background green */ PUT_16BIT_MSB_FIRST(buf+30, 0x0000); /* background blue */ sk_write(xr->sock, buf, 32); /* * Grab the mouse pointer, selecting the cursor we just * created. */ buf[0] = 26; /* GrabPointer opcode */ buf[1] = 0; /* owner-events */ PUT_16BIT_MSB_FIRST(buf+2, 6); /* request length */ PUT_32BIT_MSB_FIRST(buf+4, xr->rootwin); /* grab window id */ PUT_16BIT_MSB_FIRST(buf+8, 4); /* event mask (ButtonPress only) */ buf[10] = 1; /* pointer-mode = Asynchronous */ buf[11] = 1; /* keyboard-mode = Asynchronous */ PUT_32BIT_MSB_FIRST(buf+12, xr->rootwin); /* confine window id */ PUT_32BIT_MSB_FIRST(buf+16, CURID); /* cursor id */ PUT_32BIT_MSB_FIRST(buf+20, 0); /* timestamp = CurrentTime */ sk_write(xr->sock, buf, 24); /* * Now we expect to see a reply to the GrabPointer * operation. If that says Success, we can then sit and wait * for a ButtonPress event which will give us a resource id * to trace. */ strbuf_clear(xr->buf); crReadUpTo(xr->buf, 32); if (xr->buf->u[0] == 1) crReadUpTo(xr->buf, 32 + 4*GET_32BIT_MSB_FIRST(xr->buf->u + 4)); EXPECT_REPLY("GrabPointer"); if (xr->buf->u[1] != 0) { char reason[32]; switch (xr->buf->u[1]) { case 1: sprintf(reason, "AlreadyGrabbed"); break; case 2: sprintf(reason, "InvalidTime"); break; case 3: sprintf(reason, "NotViewable"); break; case 4: sprintf(reason, "Frozen"); break; default: sprintf(reason, "unknown error code %d", xr->buf->u[1]); break; } fprintf(stderr, "xtruss: could not grab mouse pointer for window" " selection: %s\n", reason); exit(1); } /* * Wait for our ButtonPress. */ while (1) { strbuf_clear(xr->buf); crReadUpTo(xr->buf, 32); if (xr->buf->u[0] == 0) { const char *err = xlog_translate_error(xr->buf->u[1]); if (err) fprintf(stderr, "xtruss: X server returned unexpected %s " "error\n", err); else fprintf(stderr, "xtruss: X server returned unexpected and " "unknown error %d\n", xr->buf->u[1]); exit(1); } else if (xr->buf->u[0] == 1) { /* read the rest of the reply packet */ crReadUpTo(xr->buf, 32 + 4*GET_32BIT_MSB_FIRST(xr->buf->u + 4)); fprintf(stderr, "xtruss: unexpected reply received\n"); } else if ((xr->buf->u[0] & 0x7F) == 4) { /* That's our ButtonPress */ break; } else if ((xr->buf->u[0] & 0x7F) == 34) { /* MappingNotify can be sent unsolicited */ continue; } else { const char *ev = xlog_translate_event(xr->buf->u[0]); if (ev) fprintf(stderr, "xtruss: unexpected event received (%s)\n", ev); else fprintf(stderr, "xtruss: unexpected event received (%d)\n", xr->buf->u[0]); exit(1); } } xr->clientid = GET_32BIT_MSB_FIRST(xr->buf->u + 16); /* * We've got our base window id. Now we can ungrab the * pointer, free our cursor, and close our font. */ buf[0] = 27; buf[1] = 0; /* UngrabPointer opcode and padding */ PUT_16BIT_MSB_FIRST(buf+2, 2); /* request length */ PUT_32BIT_MSB_FIRST(buf+4, 0); /* timestamp = CurrentTime */ sk_write(xr->sock, buf, 8); buf[0] = 95; /* FreeCursor opcode */ buf[1] = 0; /* unused */ PUT_16BIT_MSB_FIRST(buf+2, 2); /* request length */ PUT_32BIT_MSB_FIRST(buf+4, CURID); /* cursor id to free */ sk_write(xr->sock, buf, 8); buf[0] = 46; /* CloseFont opcode */ buf[1] = 0; /* unused */ PUT_16BIT_MSB_FIRST(buf+2, 2); /* request length */ PUT_32BIT_MSB_FIRST(buf+4, FONTID); /* font id to free */ sk_write(xr->sock, buf, 8); /* * The window id we've retrieved was almost certainly owned * by the WM rather than by some actual client. So now we * must search down the tree of its child windows until we * find one which has a WM_STATE property (meaning that the * window manager has marked it as a top-level client * window). * * We do this in breadth-first order, partly because * managing a queue is marginally easier in a coroutine of * this type than managing a recursion, but mostly because * it seems like a more sensible order to avoid getting too * bogged down in any complicated window furniture we might * encounter before the real client window. */ /* * Start by finding the WM_STATE atom. */ buf[0] = 16; /* InternAtom opcode */ buf[1] = 1; /* don't create the WM_STATE atom */ PUT_16BIT_MSB_FIRST(buf+2, 4); /* request length */ PUT_16BIT_MSB_FIRST(buf+4, 8); /* name length */ PUT_16BIT_MSB_FIRST(buf+6, 0); /* padding */ memcpy(buf+8, "WM_STATE", 8); /* name */ sk_write(xr->sock, buf, 16); do { strbuf_clear(xr->buf); crReadUpTo(xr->buf, 32); if (xr->buf->u[0] == 1) crReadUpTo(xr->buf, 32 + 4*GET_32BIT_MSB_FIRST(xr->buf->u+4)); } while (xr->buf->u[0] > 1);/* ignore events */ EXPECT_REPLY("InternAtom"); xr->wmsatom = GET_32BIT_MSB_FIRST(xr->buf->u + 8); if (!xr->wmsatom) { /* * The WM_STATE atom is not understood by the server at * all, which certainly means no window will have a * property by that name. In this situation (similarly * to if we do not find a WM_STATE-marked window at all) * we return the window we started with. Presumably, in * this situation, no window manager is running at all, * or if it is it's an odd one. */ break; } xr->whead = xr->wtail = snew(struct winq); xr->whead->winid = xr->clientid; xr->whead->next = NULL; while (xr->whead) { /* * Query the WM_STATE property on the window. */ buf[0] = 20; /* GetProperty opcode */ buf[1] = 0; /* do not delete the property! */ PUT_16BIT_MSB_FIRST(buf+2, 6); /* request length */ PUT_32BIT_MSB_FIRST(buf+4, xr->whead->winid); /* window */ PUT_32BIT_MSB_FIRST(buf+8, xr->wmsatom); /* property ("WM_STATE") */ PUT_32BIT_MSB_FIRST(buf+12, 0); /* type (AnyPropertyType) */ PUT_32BIT_MSB_FIRST(buf+16, 0); /* long-offset */ PUT_32BIT_MSB_FIRST(buf+20, 0); /* long-length */ sk_write(xr->sock, buf, 24); do { strbuf_clear(xr->buf); crReadUpTo(xr->buf, 32); if (xr->buf->u[0] == 1) crReadUpTo(xr->buf, 32 + 4*GET_32BIT_MSB_FIRST(xr->buf->u+4)); } while (xr->buf->u[0] > 1);/* ignore events */ EXPECT_REPLY("GetProperty"); if (GET_32BIT_MSB_FIRST(xr->buf->u+8) != 0) { /* * Found it! */ xr->clientid = xr->whead->winid; while (xr->whead) { struct winq *next = xr->whead->next; sfree(xr->whead); xr->whead = next; } xr->whead = xr->wtail = NULL; break; } /* * This wasn't the droid^Wwindow we're looking for. Get * a list of its child windows, and add them to the * queue. */ buf[0] = 15; buf[1] = 0; /* QueryTree opcode and padding */ PUT_16BIT_MSB_FIRST(buf+2, 2); /* request length */ PUT_32BIT_MSB_FIRST(buf+4, xr->whead->winid); /* window */ sk_write(xr->sock, buf, 8); do { strbuf_clear(xr->buf); crReadUpTo(xr->buf, 32); if (xr->buf->u[0] == 1) crReadUpTo(xr->buf, 32 + 4*GET_32BIT_MSB_FIRST(xr->buf->u+4)); } while (xr->buf->u[0] > 1);/* ignore events */ EXPECT_REPLY("QueryTree"); { int i, n = GET_16BIT_MSB_FIRST(xr->buf->u + 16); if (n > (xr->buf->len - 32) / 4) n = (xr->buf->len - 32) / 4; /* buffer overrun check */ for (i = 0; i < n; i++) { xr->wtail->next = snew(struct winq); xr->wtail = xr->wtail->next; xr->wtail->next = NULL; xr->wtail->winid = GET_32BIT_MSB_FIRST(xr->buf->u + 32 + 4*i); } } /* * And now dequeue the window we've just processed. */ { struct winq *old = xr->whead; xr->whead = xr->whead->next; sfree(old); } } } /* * Initialise and start a recording context for the given client * id. */ buf[0] = xr->xrecordopcode; buf[1] = 1;/* RecordCreateContext */ PUT_16BIT_MSB_FIRST(buf+2, 12); /* request length */ PUT_32BIT_MSB_FIRST(buf+4, RCID); /* context id */ buf[8] = 0; /* element header (none) */ buf[9] = buf[10] = buf[11] = 0; /* padding */ PUT_32BIT_MSB_FIRST(buf+12, 1); /* number of client ids */ PUT_32BIT_MSB_FIRST(buf+16, 1); /* number of record ranges */ PUT_32BIT_MSB_FIRST(buf+20, xr->clientid); /* client id itself */ buf[24] = 0; buf[25] = 127; /* want all core requests */ buf[26] = 0; buf[27] = 127; /* and all core replies */ buf[28] = 128; buf[29] = 255; /* want all extension major opcodes */ PUT_16BIT_MSB_FIRST(buf+30, 0); PUT_16BIT_MSB_FIRST(buf+32, 65535);/* and all extension minor opcodes */ buf[34] = 128; buf[35] = 255; /* and the same in replies */ PUT_16BIT_MSB_FIRST(buf+36, 0); PUT_16BIT_MSB_FIRST(buf+38, 65535); buf[40] = 2; buf[41] = 255; /* want all delivered events */ buf[42] = 0; buf[43] = 0; /* but no device events */ buf[44] = 0; buf[45] = 255; /* want all errors */ buf[46] = 0; /* don't want client-started */ buf[47] = 1; /* but do want client-died */ sk_write(xr->sock, buf, 48); buf[0] = xr->xrecordopcode; buf[1] = 5;/* RecordEnableContext */ PUT_16BIT_MSB_FIRST(buf+2, 2); /* request length */ PUT_32BIT_MSB_FIRST(buf+4, RCID); /* context id */ sk_write(xr->sock, buf, 8); /* * Now we expect to receive an indefinite stream of replies to * that last request. */ while (1) { unsigned our_id; struct xlog *our_xl; do { strbuf_clear(xr->buf); crReadUpTo(xr->buf, 32); if (xr->buf->u[0] == 1) crReadUpTo(xr->buf, 32 + 4*GET_32BIT_MSB_FIRST(xr->buf->u+4)); } while (xr->buf->u[0] > 1);/* ignore events */ EXPECT_REPLY("RecordEnableContext"); our_id = GET_32BIT_MSB_FIRST(xr->buf->u+12); our_xl = find234(xr->xlogs_by_id, &our_id, xlog_find_id); if (!our_xl) { struct atom_list *al = xr->atoms; our_xl = xlog_new(xr->xs, XLOG_BARE); xlog_set_clientid(our_xl, our_id); struct xlog *added = add234(xr->xlogs_by_id, our_xl); assert(added == our_xl); xlog_use_welcome_message(our_xl, xr->welcome_message, xr->welcome_message_len); while (al) { xlog_intern_atom(our_xl, al->atomname, al->atomval); al = al->next; } } switch (xr->buf->u[1]) { case 4: /* * StartOfData record, sent immediately after we enabled * the recording context. Ignore it. */ break; case 1: /* * Data from the client, i.e. requests. Expect it to * come with a header telling us its sequence number. */ xlog_set_endianness(our_xl, xr->buf->u[9] ? 'l' : 'B'); xlog_set_next_seq(our_xl, GET_32BIT_MSB_FIRST(xr->buf->u+20)); xlog_c2s(our_xl, xr->buf->u + 32, xr->buf->len - 32); break; case 0: /* * Data from the server, i.e. replies, errors and * events. Expect it to come with a header telling us * its sequence number. */ xlog_set_endianness(our_xl, xr->buf->u[9] ? 'l' : 'B'); xlog_s2c(our_xl, xr->buf->u + 32, xr->buf->len - 32); break; case 3: /* * An X client has disconnected. */ if (xr->xs->xrexit) xr->xs->exit_status = 0; /* terminate cleanly */ del234(xr->xlogs_by_id, our_xl); xlog_free(our_xl); break; case 2: /* * An X client has connected. (Only expected if we're in * a "record all clients" type of mode.) */ default: fprintf(stderr, "xtruss: unexpected data record type received " "(%d)\n", xr->buf->u[1]); break; } } crFinishV; } void xtruss_xrecord_start(xtruss_state *xs) { struct xrecord_state *xr = snew(struct xrecord_state); memset(xr, 0, sizeof(*xr)); xr->xs = xs; xr->xlogs_by_id = newtree234(xlog_cmp_id); xr->clientid = xs->xrclientid; xr->buf = strbuf_new(); xr->plug.vt = &xrecord_plugvt; xr->sock = sk_new(sk_addr_dup(xs->x11disp->addr), xs->x11disp->port, false, true, false, false, &xr->plug); const char *err; if ((err = sk_socket_error(xr->sock)) != NULL) { fprintf(stderr, "X11 socket connection failed: %s\n", err); exit(1); } xrecord_coroutine(xr, NULL, 0); } work/xtruss-trace.c0000664000000000000000000067733214714222463011567 0ustar #include #include #include #include "putty.h" #include "ssh.h" #include "sshcr.h" #include "xtruss.h" /* ---------------------------------------------------------------------- * Code to parse and log the data flowing (in both directions) * within an X connection. */ const int sizelimit = 256; /* for long strings in trace output; could * make this configurable */ /* * Unusual 24-bit data marshalling functions, used for 24-bit bitmaps. */ static inline uint32_t GET_24BIT_LSB_FIRST(const void *vp) { const uint8_t *p = (const uint8_t *)vp; return (((uint32_t)p[0] ) | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16)); } static inline uint32_t GET_24BIT_MSB_FIRST(const void *vp) { const uint8_t *p = (const uint8_t *)vp; return (((uint32_t)p[2] ) | ((uint32_t)p[1] << 8) | ((uint32_t)p[0] << 16)); } /* * Macro wrappers to take X endianness into account (plus READ8 for * visual consistency). */ #define READ8(p) ((unsigned char)*(p)) #define READ16(p) (xl->endianness == 'l' ? \ GET_16BIT_LSB_FIRST(p) : GET_16BIT_MSB_FIRST(p)) #define READ32(p) (xl->endianness == 'l' ? \ GET_32BIT_LSB_FIRST(p) : GET_32BIT_MSB_FIRST(p)) /* * Translate a bits-per-image-element count into an appropriate * HEXSTRING* display data type. */ #define STRING_TYPE(byteorder, bits) ( \ (bits) == 32 ? (byteorder ? HEXSTRING4B : HEXSTRING4L) : \ (bits) == 24 ? (byteorder ? HEXSTRING3B : HEXSTRING3L) : \ (bits) == 16 ? (byteorder ? HEXSTRING2B : HEXSTRING2L) : \ HEXSTRING1) /* * Parametric macro defining each known extension as an internal * identifier and its protocol-level string id. */ #define KNOWNEXTENSIONS(X) \ X(EXT_BIGREQUESTS, "BIG-REQUESTS") \ X(EXT_GENERICEVENT, "Generic Event Extension") \ X(EXT_MITSHM, "MIT-SHM") \ X(EXT_RENDER, "RENDER") /* * The number of bits left that the extension number is shifted in * request/event/error numbers. */ #define EXTSHIFT 17 /* * Define the EXT_* ids as a series of values with the low EXTSHIFT bits * clear. */ #define EXTENUM(e,s) dummy1##e, dummy2##e = dummy1##e+(1<>EXTSHIFT] gives the name of each * known extension. */ #define EXTNAME(e,s) s, const char *const extname[] = { NULL, KNOWNEXTENSIONS(EXTNAME) }; /* Flag to indicate that an event is a GenericEvent */ #define GENERICEVENT 0x10000 struct request { struct request *next, *prev; int opcode; int seqnum; char *text; /* * Values for the 'replies' field: * - 0 means no reply is expected to this request (though an * error may occur) * - 1 means exactly one reply is expected * - 2 means multiple replies are expected and none has yet * been seen (so that if the incoming sequence numbers skip * the request we should print a notification that something * odd happened) * - 3 means multiple replies are expected and at least one has * appeared (so that when sequence numbers move on we * silently discard this request). */ int replies; /* 0=no reply expected, 1=single reply expected, 2=multiple */ /* * Machine-readable representations of parts of the request, * preserved from xlog_do_request() so they can be referred to * when the reply comes in. */ int first_keycode, keycode_count; /* for GetKeyboardMapping */ char *atomname; /* for InternAtom */ unsigned long atomnum; /* for GetAtomName */ /* * Machine-readable representation of the extension seen in a * QueryExtension, so that when we get its details back we can * start logging requests as belonging to that extension. */ char *extname; int extid; /* * Machine-readable representation of the pixmap parameters in a * GetImage, so we can log the image data correctly when it * comes back. */ int pixmapformat, pixmapwidth, pixmapheight; bool printed; /* do we print this request at all? */ }; struct pixmapformat { int depth, bits_per_pixel, scanline_pad; }; struct resdepth { unsigned long resource; int depth; }; static int resdepthcmp(void *av, void *bv) { const struct resdepth *a = (const struct resdepth *)av; const struct resdepth *b = (const struct resdepth *)bv; if (a->resource < b->resource) return -1; else if (a->resource > b->resource) return +1; else return 0; } static int resdepthfind(void *av, void *bv) { const unsigned long *a = (const unsigned long *)av; const struct resdepth *b = (const struct resdepth *)bv; if (*a < b->resource) return -1; else if (*a > b->resource) return +1; else return 0; } struct atom { unsigned long atomval; char *atomname; }; static int atomcmp(void *av, void *bv) { const struct atom *a = (const struct atom *)av; const struct atom *b = (const struct atom *)bv; if (a->atomval < b->atomval) return -1; else if (a->atomval > b->atomval) return +1; else return 0; } static int atomfind(void *av, void *bv) { const unsigned long *a = (const unsigned long *)av; const struct atom *b = (const struct atom *)bv; if (*a < b->atomval) return -1; else if (*a > b->atomval) return +1; else return 0; } static void internatom(tree234 *atoms, char *name, unsigned long val) { struct atom *a = snew(struct atom); a->atomval = val; a->atomname = name; if (add234(atoms, a) != a) { sfree(a->atomname); sfree(a); } } static char const * stdatoms[] = { "PRIMARY", "SECONDARY", "ARC", "ATOM", "BITMAP", "CARDINAL", "COLORMAP", "CURSOR", "CUT_BUFFER0", "CUT_BUFFER1", "CUT_BUFFER2", "CUT_BUFFER3", "CUT_BUFFER4", "CUT_BUFFER5", "CUT_BUFFER6", "CUT_BUFFER7", "DRAWABLE", "FONT", "INTEGER", "PIXMAP", "POINT", "RECTANGLE", "RESOURCE_MANAGER", "RGB_COLOR_MAP", "RGB_BEST_MAP", "RGB_BLUE_MAP", "RGB_DEFAULT_MAP", "RGB_GRAY_MAP", "RGB_GREEN_MAP", "RGB_RED_MAP", "STRING", "VISUALID", "WINDOW", "WM_COMMAND", "WM_HINTS", "WM_CLIENT_MACHINE", "WM_ICON_NAME", "WM_ICON_SIZE", "WM_NAME", "WM_NORMAL_HINTS", "WM_SIZE_HINTS", "WM_ZOOM_HINTS", "MIN_SPACE", "NORM_SPACE", "MAX_SPACE", "END_SPACE", "SUPERSCRIPT_X", "SUPERSCRIPT_Y", "SUBSCRIPT_X", "SUBSCRIPT_Y", "UNDERLINE_POSITION", "UNDERLINE_THICKNESS", "STRIKEOUT_ASCENT", "STRIKEOUT_DESCENT", "ITALIC_ANGLE", "X_HEIGHT", "QUAD_WIDTH", "WEIGHT", "POINT_SIZE", "RESOLUTION", "COPYRIGHT", "NOTICE", "FONT_NAME", "FAMILY_NAME", "FULL_NAME", "CAP_HEIGHT", "WM_CLASS", "WM_TRANSIENT_FOR" }; static void internstdatoms(tree234 *atoms) { int i; for (i = 0; i < lenof(stdatoms); i++) /* Standard atoms are numbered contiguously from 1. */ internatom(atoms, dupstr(stdatoms[i]), i+1); } static bool atomeq(tree234 *atoms, unsigned long atomnum, char const *atomstr) { const struct atom *a = find234(atoms, &atomnum, atomfind); if (a != NULL) return !strcmp(a->atomname, atomstr); return false; } struct xlog { struct xtruss_state *xs; int c2sstate, s2cstate; strbuf *textbuf, *c2sbuf, *s2cbuf; unsigned c2stmp, s2ctmp; unsigned c2soff, s2coff; char *extreqs[128]; /* extension name for each >=128 request opcode */ char *extevents[128]; /* name of extension based at a given event */ char *exterrors[256]; /* name of extension based at a given error */ int extidreqs[128]; /* our extension ids */ int extidevents[128]; int extiderrors[256]; int endianness; bool error; int reqlogstate; bool overflow; int nextseq; XLogType type; unsigned clientid; struct request *rhead, *rtail; int bitmap_scanline_unit, bitmap_scanline_pad, image_byte_order; struct pixmapformat *pixmapformats; int npixmapformats; /* * Tree storing a mapping from X resource ids to image depths. * This information has to be retained in order to correctly * decode the RENDER extension request RenderAddGlyphs: we must * remember the depth of every PICTFORMAT from the reply to * RenderQueryPictFormats, and then remember the depth assigned * to every GLYPHSET created. */ tree234 *resdepths; tree234 *atoms; }; struct xlog *xlog_new(xtruss_state *xs, XLogType type) { int i; struct xlog *xl = snew(struct xlog); memset(xl, 0, sizeof(*xl)); xl->xs = xs; xl->endianness = -1; /* as-yet-unknown */ xl->c2sbuf = strbuf_new(); xl->s2cbuf = strbuf_new(); xl->c2soff = xl->s2coff = 0; xl->error = false; xl->textbuf = strbuf_new(); xl->rhead = xl->rtail = NULL; xl->nextseq = 1; xl->type = type; /* * Fake a known-invalid and fairly unique client ID. It will wrap around * after 65536 clients, but that should be enough for most purposes. In * any case, its only use is for disambiguating interleaved server welcome * messages in hex-dump output. */ xl->clientid = 0xFFFF0000U | xs->newclientid++; xs->newclientid &= 0xFFFF; for (i = 0; i < 128; i++) xl->extreqs[i] = NULL, xl->extidreqs[i] = 0; for (i = 0; i < 128; i++) xl->extevents[i] = NULL, xl->extidevents[i] = 0; for (i = 0; i < 256; i++) xl->exterrors[i] = NULL; xl->pixmapformats = NULL; xl->resdepths = newtree234(resdepthcmp); xl->atoms = newtree234(atomcmp); internstdatoms(xl->atoms); return xl; } static void free_request(struct request *req) { sfree(req->text); sfree(req->extname); sfree(req->atomname); sfree(req); } void xlog_free(struct xlog *xl) { int i; struct resdepth *gsd; struct atom *a; while ((gsd = delpos234(xl->resdepths, 0)) != NULL) sfree(gsd); freetree234(xl->resdepths); while ((a = delpos234(xl->atoms, 0)) != NULL) { sfree((void *)a->atomname); sfree(a); } freetree234(xl->atoms); while (xl->rhead) { struct request *nexthead = xl->rhead->next; free_request(xl->rhead); xl->rhead = nexthead; } for (i = 0; i < 128; i++) sfree(xl->extreqs[i]); for (i = 0; i < 128; i++) sfree(xl->extevents[i]); for (i = 0; i < 256; i++) sfree(xl->exterrors[i]); sfree(xl->pixmapformats); strbuf_free(xl->c2sbuf); strbuf_free(xl->s2cbuf); strbuf_free(xl->textbuf); sfree(xl); } static void xlog_new_line(struct xlog *xl) { if (xl->xs->currreq) { /* FIXME: in some modes we might wish to print the sequence number * here, which would be easy of course */ assert(xl->xs->currreq->printed); fprintf(xl->xs->outfp, " = \n"); fflush(xl->xs->outfp); xl->xs->currreq = NULL; } if (xl->xs->print_client_ids) { if ((xl->clientid & 0xFFFF0000U) == 0xFFFF0000U) fprintf(xl->xs->outfp, "new-%04x: ", xl->clientid & 0xFFFFU); else fprintf(xl->xs->outfp, "%08x: ", xl->clientid); } } static void xlog_error(struct xlog *xl, const char *fmt, ...) { va_list ap; xlog_new_line(xl); fprintf(xl->xs->outfp, "protocol error: "); va_start(ap, fmt); vfprintf(xl->xs->outfp, fmt, ap); va_end(ap); fprintf(xl->xs->outfp, "\n"); fflush(xl->xs->outfp); xl->error = true; } #define err(args) do { xlog_error args; crReturnV; } while (0) #define warn(args) do { xlog_error args; crReturnV; } while (0) /* Convenience macro for appending a fixed string to a strbuf, minus its \0 */ #define put_datastr(bs, str) put_datapl(bs, ptrlen_from_asciz(str)) static void print_c_string(struct xlog *xl, const char *data, int len) { while (len--) { char c = *data++; if (c == '\n') put_datastr(xl->textbuf, "\\n"); else if (c == '\r') put_datastr(xl->textbuf, "\\r"); else if (c == '\t') put_datastr(xl->textbuf, "\\t"); else if (c == '\b') put_datastr(xl->textbuf, "\\b"); else if (c == '\\') put_datastr(xl->textbuf, "\\\\"); else if (c == '"') put_datastr(xl->textbuf, "\\\""); else if (c >= 32 && c <= 126) put_byte(xl->textbuf, c); else strbuf_catf(xl->textbuf, "\\%03o", (unsigned char)c); } } static void writemaskv(struct xlog *xl, int ival, va_list ap) { const char *sep = ""; const char *svname; int svi; while (1) { svname = va_arg(ap, const char *); if (!svname) break; svi = va_arg(ap, int); if (svi & ival) { put_datastr(xl->textbuf, sep); put_datastr(xl->textbuf, svname); sep = "|"; } } if (!*sep) put_byte(xl->textbuf, '0'); /* special case: no flags set */ } static void writemask(struct xlog *xl, int ival, ...) { va_list ap; va_start(ap, ival); writemaskv(xl, ival, ap); va_end(ap); } static void xlog_request_name(struct xlog *xl, struct request *req, const char *buf, bool known) { if (!in_set(&xl->xs->requests_to_log, known ? buf : "UnknownRequest")) req->printed = false; put_datastr(xl->textbuf, buf); xl->reqlogstate = 0; } static void set_overflow(struct xlog *xl) { xl->overflow = true; } #define FETCH8(p, n) ( (n)+1>len ? (set_overflow(xl),0) : READ8((p)+(n)) ) #define FETCH16(p, n) ( (n)+2>len ? (set_overflow(xl),0) : READ16((p)+(n)) ) #define FETCH32(p, n) ( (n)+4>len ? (set_overflow(xl),0) : READ32((p)+(n)) ) #define STRING(p, n, l) ( (n)+(l)>len ? (set_overflow(xl),NULL) : (char *)(p)+(n) ) /* * Enumeration of data type codes. These don't exactly match the X * ones: they're really requests to xlog_param to _render_ the type * in a certain way. */ enum { DECU, /* unsigned decimal integer */ DEC8, /* 8-bit signed decimal */ DEC16, /* 16-bit signed decimal */ DEC32, /* 32-bit signed decimal */ HEX8, HEX16, HEX32, RATIONAL16, BOOLEAN, WINDOW, PIXMAP, FONT, GCONTEXT, CURSOR, COLORMAP, DRAWABLE, FONTABLE, VISUALID, ATOM, EVENTMASK, KEYMASK, GENMASK, ENUM, STRING, HEXSTRING1, HEXSTRING2, HEXSTRING2L, HEXSTRING2B, HEXSTRING3, HEXSTRING3B, HEXSTRING3L, HEXSTRING4, HEXSTRING4B, HEXSTRING4L, SETBEGIN, NOTHING, NOTEVENEQUALSIGN, PICTURE, /* RENDER extension */ PICTFORMAT, /* RENDER extension */ GLYPHSET, /* RENDER extension */ GLYPHABLE, /* RENDER extension */ FIXED, /* RENDER extension */ SPECVAL = 0x8000 }; static void xlog_param(struct xlog *xl, const char *paramname, int type, ...) { va_list ap; const char *sval, *sep, *trail; int ival, ival2; if (xl->reqlogstate == 0) { put_byte(xl->textbuf, '('); xl->reqlogstate = 1; } else if (xl->reqlogstate == 3) { xl->reqlogstate = 1; } else { put_datastr(xl->textbuf, ", "); } if (xl->overflow && xl->reqlogstate != 2) { put_datastr(xl->textbuf, ""); xl->reqlogstate = 2; } else { /* FIXME: perhaps optionally omit parameter names? */ put_datastr(xl->textbuf, paramname); if ((type &~ SPECVAL) != NOTEVENEQUALSIGN) put_byte(xl->textbuf, '='); va_start(ap, type); switch (type &~ SPECVAL) { case STRING: ival = va_arg(ap, int); sval = va_arg(ap, const char *); trail = ""; if (sizelimit > 0 && xl->textbuf->len + ival > sizelimit) { int limitlen = sizelimit - xl->textbuf->len; if (limitlen < 20) limitlen = 20; if (ival > limitlen) { ival = limitlen; trail = "..."; } } put_byte(xl->textbuf, '\"'); print_c_string(xl, sval, ival); put_byte(xl->textbuf, '\"'); put_datastr(xl->textbuf, trail); break; case HEXSTRING1: ival = va_arg(ap, int); sval = va_arg(ap, const char *); trail = ""; if (sizelimit > 0 && xl->textbuf->len + 3*ival-1 > sizelimit) { int limitlen = (sizelimit - xl->textbuf->len + 1) / 3; if (limitlen < 8) limitlen = 8; if (ival > limitlen) { ival = limitlen; trail = "..."; } } sep = ""; while (ival-- > 0) { unsigned val = 0xFF & *sval; strbuf_catf(xl->textbuf, "%s%02X", sep, val); sval++; sep = ":"; } if (*trail) strbuf_catf(xl->textbuf, "%s%s", sep, trail); break; case HEXSTRING2: case HEXSTRING2B: case HEXSTRING2L: if (type == HEXSTRING2) type = (xl->endianness == 'l' ? HEXSTRING2L : HEXSTRING2B); ival = va_arg(ap, int); sval = va_arg(ap, const char *); trail = ""; if (sizelimit > 0 && xl->textbuf->len + 5*ival-1 > sizelimit) { int limitlen = (sizelimit - xl->textbuf->len + 1) / 5; if (limitlen < 4) limitlen = 4; if (ival > limitlen) { ival = limitlen; trail = "..."; } } sep = ""; while (ival-- > 0) { unsigned val; if (type == HEXSTRING2L) val = GET_16BIT_LSB_FIRST(sval); else val = GET_16BIT_MSB_FIRST(sval); strbuf_catf(xl->textbuf, "%s%04X", sep, val); sval += 2; sep = ":"; } if (*trail) { put_datastr(xl->textbuf, sep); put_datastr(xl->textbuf, trail); } break; case HEXSTRING3: case HEXSTRING3B: case HEXSTRING3L: if (type == HEXSTRING3) type = (xl->endianness == 'l' ? HEXSTRING3L : HEXSTRING3B); ival = va_arg(ap, int); sval = va_arg(ap, const char *); trail = ""; if (sizelimit > 0 && xl->textbuf->len + 7*ival-1 > sizelimit) { int limitlen = (sizelimit - xl->textbuf->len + 1) / 7; if (limitlen < 2) limitlen = 2; if (ival > limitlen) { ival = limitlen; trail = "..."; } } sep = ""; while (ival-- > 0) { unsigned val; if (type == HEXSTRING3L) val = GET_24BIT_LSB_FIRST(sval); else val = GET_24BIT_MSB_FIRST(sval); strbuf_catf(xl->textbuf, "%s%06X", sep, val); sval += 3; sep = ":"; } if (*trail) { put_datastr(xl->textbuf, sep); put_datastr(xl->textbuf, trail); } break; case HEXSTRING4: case HEXSTRING4B: case HEXSTRING4L: if (type == HEXSTRING4) type = (xl->endianness == 'l' ? HEXSTRING4L : HEXSTRING4B); ival = va_arg(ap, int); sval = va_arg(ap, const char *); trail = ""; if (sizelimit > 0 && xl->textbuf->len + 9*ival-1 > sizelimit) { int limitlen = (sizelimit - xl->textbuf->len + 1) / 9; if (limitlen < 2) limitlen = 2; if (ival > limitlen) { ival = limitlen; trail = "..."; } } sep = ""; while (ival-- > 0) { unsigned val; if (type == HEXSTRING4L) val = GET_32BIT_LSB_FIRST(sval); else val = GET_32BIT_MSB_FIRST(sval); strbuf_catf(xl->textbuf, "%s%08X", sep, val); sval += 4; sep = ":"; } if (*trail) { put_datastr(xl->textbuf, sep); put_datastr(xl->textbuf, trail); } break; case SETBEGIN: /* * This type code contains no data at all. We just print * an open brace, and then data fields will be filled in * later and terminated by a call to xlog_set_end(). */ put_byte(xl->textbuf, '{'); xl->reqlogstate = 3; /* suppress comma after open brace */ break; case NOTHING: /* * This type code is even simpler than SETBEGIN: we * print nothing, and expect the caller to write their * own formatting of the data. */ break; case RATIONAL16: ival = va_arg(ap, int); ival &= 0xFFFF; if (ival & 0x8000) ival -= 0x10000; ival2 = va_arg(ap, int); ival2 &= 0xFFFF; if (ival2 & 0x8000) ival2 -= 0x10000; strbuf_catf(xl->textbuf, "%d/%d", ival, ival2); break; default: ival = va_arg(ap, int); if (type & SPECVAL) { const char *svname; int svi; bool done = false; while (1) { svname = va_arg(ap, const char *); if (!svname) break; svi = va_arg(ap, int); if (svi == ival) { put_datastr(xl->textbuf, svname); done = true; break; } } if (done) break; type &= ~SPECVAL; } switch (type) { case DECU: strbuf_catf(xl->textbuf, "%u", (unsigned)ival); break; case DEC8: ival &= 0xFF; if (ival & 0x80) ival -= 0x100; strbuf_catf(xl->textbuf, "%d", ival); break; case DEC16: ival &= 0xFFFF; if (ival & 0x8000) ival -= 0x10000; strbuf_catf(xl->textbuf, "%d", ival); break; case DEC32: #if UINT_MAX > 0xFFFFFFFF ival &= 0xFFFFFFFF; if (ival & 0x80000000) ival -= 0x100000000; #endif strbuf_catf(xl->textbuf, "%d", ival); break; case HEX8: strbuf_catf(xl->textbuf, "0x%02X", ival); break; case HEX16: strbuf_catf(xl->textbuf, "0x%04X", ival); break; case HEX32: strbuf_catf(xl->textbuf, "0x%08X", ival); break; case FIXED: #if UINT_MAX > 0xFFFFFFFF ival &= 0xFFFFFFFF; if (ival & 0x80000000) ival -= 0x100000000; #endif strbuf_catf(xl->textbuf, "%.5f", ival / 65536.0); break; case ENUM: /* This type is used for values which are expected to * _always_ take one of their special values, so we * want a rendition of any non-special value which * makes it clear that something isn't right. */ strbuf_catf(xl->textbuf, "Unknown%d", ival); break; case BOOLEAN: if (ival == 0) put_datastr(xl->textbuf, "False"); else if (ival == 1) put_datastr(xl->textbuf, "True"); else strbuf_catf(xl->textbuf, "BadBool%d", ival); break; case WINDOW: strbuf_catf(xl->textbuf, "w#%08X", ival); break; case PIXMAP: strbuf_catf(xl->textbuf, "p#%08X", ival); break; case FONT: strbuf_catf(xl->textbuf, "f#%08X", ival); break; case GCONTEXT: strbuf_catf(xl->textbuf, "g#%08X", ival); break; case VISUALID: strbuf_catf(xl->textbuf, "v#%08X", ival); break; case PICTURE: strbuf_catf(xl->textbuf, "pc#%08X", ival); break; case PICTFORMAT: strbuf_catf(xl->textbuf, "pf#%08X", ival); break; case GLYPHSET: strbuf_catf(xl->textbuf, "gs#%08X", ival); break; case CURSOR: /* Extra characters in the prefix distinguish from COLORMAP */ strbuf_catf(xl->textbuf, "cur#%08X", ival); break; case COLORMAP: /* Extra characters in the prefix distinguish from CURSOR */ strbuf_catf(xl->textbuf, "col#%08X", ival); break; case DRAWABLE: /* * FIXME: DRAWABLE can be either WINDOW or PIXMAP. * It would be good, I think, to keep track of the * currently live IDs of both so that we can * determine which is which and print the * _appropriate_ type prefix. */ strbuf_catf(xl->textbuf, "wp#%08X", ival); break; case FONTABLE: /* * FIXME: FONTABLE can be either FONT or GCONTEXT. * It would be good, I think, to keep track of the * currently live IDs of both so that we can * determine which is which and print the * _appropriate_ type prefix. */ strbuf_catf(xl->textbuf, "fg#%08X", ival); break; case GLYPHABLE: /* * GLYPHABLE can be FONTABLE or GLYPHSET. Sigh. */ strbuf_catf(xl->textbuf, "gsfg#%08X", ival); break; case EVENTMASK: writemask(xl, ival, "KeyPress", 0x00000001, "KeyRelease", 0x00000002, "ButtonPress", 0x00000004, "ButtonRelease", 0x00000008, "EnterWindow", 0x00000010, "LeaveWindow", 0x00000020, "PointerMotion", 0x00000040, "PointerMotionHint", 0x00000080, "Button1Motion", 0x00000100, "Button2Motion", 0x00000200, "Button3Motion", 0x00000400, "Button4Motion", 0x00000800, "Button5Motion", 0x00001000, "ButtonMotion", 0x00002000, "KeymapState", 0x00004000, "Exposure", 0x00008000, "VisibilityChange", 0x00010000, "StructureNotify", 0x00020000, "ResizeRedirect", 0x00040000, "SubstructureNotify", 0x00080000, "SubstructureRedirect", 0x00100000, "FocusChange", 0x00200000, "PropertyChange", 0x00400000, "ColormapChange", 0x00800000, "OwnerGrabButton", 0x01000000, (char *)NULL); break; case KEYMASK: writemask(xl, ival, "Shift", 0x0001, "Lock", 0x0002, "Control", 0x0004, "Mod1", 0x0008, "Mod2", 0x0010, "Mod3", 0x0020, "Mod4", 0x0040, "Mod5", 0x0080, "Button1", 0x0100, "Button2", 0x0200, "Button3", 0x0400, "Button4", 0x0800, "Button5", 0x1000, (char *)NULL); break; case GENMASK: writemaskv(xl, ival, ap); break; case ATOM: { unsigned long lval = ival; const struct atom *a = find234(xl->atoms, &lval, atomfind); if (a != NULL) { put_datastr(xl->textbuf, "a\""); print_c_string(xl, a->atomname, strlen(a->atomname)); put_datastr(xl->textbuf, "\""); } else strbuf_catf(xl->textbuf, "a#%d", ival); } break; } break; } va_end(ap); } } static bool xlog_check_list_length(struct xlog *xl) { if (sizelimit > 0 && xl->textbuf->len > sizelimit) { xlog_param(xl, "...", NOTEVENEQUALSIGN); return true; } return false; } static void xlog_set_end(struct xlog *xl) { put_byte(xl->textbuf, '}'); if (xl->reqlogstate != 2) xl->reqlogstate = 1; } static void xlog_reply_begin(struct xlog *xl) { put_byte(xl->textbuf, '{'); xl->reqlogstate = 3; } static void xlog_reply_end(struct xlog *xl) { put_byte(xl->textbuf, '}'); } static void xlog_request_done(struct xlog *xl, struct request *req) { if (xl->reqlogstate) put_byte(xl->textbuf, ')'); req->next = NULL; req->prev = xl->rtail; if (xl->rtail) xl->rtail->next = req; else xl->rhead = req; xl->rtail = req; req->seqnum = xl->nextseq; xl->nextseq = (xl->nextseq+1) & 0xFFFF; req->text = dupstr(xl->textbuf->s); if (req->printed) { xlog_new_line(xl); if (req->replies) { fprintf(xl->xs->outfp, "%s", req->text); xl->xs->currreq = req; } else { fprintf(xl->xs->outfp, "%s\n", req->text); } fflush(xl->xs->outfp); } } /* Indicate that we're about to print a response to a particular request */ static void xlog_respond_to(struct xlog *xl, struct request *req) { if (req && !req->printed) return; if (req != NULL && xl->xs->currreq == req) { fprintf(xl->xs->outfp, " = "); } else { xlog_new_line(xl); if (req) fprintf(xl->xs->outfp, " ... %s = ", req->text); else fprintf(xl->xs->outfp, "--- error received for unknown request: "); } xl->xs->currreq = req; } static void xlog_response_done(struct xtruss_state *xs, struct request *req, const char *text) { if (!req || req->printed) { fprintf(xs->outfp, "%s\n", text); fflush(xs->outfp); } xs->currreq = NULL; } static void xlog_rectangle(struct xlog *xl, const unsigned char *data, int len, int pos) { xlog_param(xl, "x", DEC16, FETCH16(data, pos)); xlog_param(xl, "y", DEC16, FETCH16(data, pos+2)); xlog_param(xl, "width", DECU, FETCH16(data, pos+4)); xlog_param(xl, "height", DECU, FETCH16(data, pos+6)); } static void xlog_point(struct xlog *xl, const unsigned char *data, int len, int pos) { xlog_param(xl, "x", DEC16, FETCH16(data, pos)); xlog_param(xl, "y", DEC16, FETCH16(data, pos+2)); } static void xlog_arc(struct xlog *xl, const unsigned char *data, int len, int pos) { xlog_param(xl, "x", DEC16, FETCH16(data, pos)); xlog_param(xl, "y", DEC16, FETCH16(data, pos+2)); xlog_param(xl, "width", DECU, FETCH16(data, pos+4)); xlog_param(xl, "height", DECU, FETCH16(data, pos+6)); xlog_param(xl, "angle1", DEC16, FETCH16(data, pos+8)); xlog_param(xl, "angle2", DEC16, FETCH16(data, pos+10)); } static void xlog_segment(struct xlog *xl, const unsigned char *data, int len, int pos) { xlog_param(xl, "x1", DEC16, FETCH16(data, pos)); xlog_param(xl, "y1", DEC16, FETCH16(data, pos+2)); xlog_param(xl, "x2", DEC16, FETCH16(data, pos+4)); xlog_param(xl, "y2", DEC16, FETCH16(data, pos+6)); } static void xlog_coloritem(struct xlog *xl, const unsigned char *data, int len, int pos) { int mask = FETCH8(data, pos+10); xlog_param(xl, "pixel", HEX32, FETCH32(data, pos)); if (mask & 1) xlog_param(xl, "red", HEX16, FETCH16(data, pos+4)); if (mask & 2) xlog_param(xl, "green", HEX16, FETCH16(data, pos+6)); if (mask & 4) xlog_param(xl, "blue", HEX16, FETCH16(data, pos+8)); } static void xlog_timecoord(struct xlog *xl, const unsigned char *data, int len, int pos) { xlog_param(xl, "x", DEC16, FETCH16(data, pos+4)); xlog_param(xl, "y", DEC16, FETCH16(data, pos+6)); xlog_param(xl, "time", HEX32, FETCH32(data, pos)); } static void xlog_fontprop(struct xlog *xl, const unsigned char *data, int len, int pos) { xlog_param(xl, "name", ATOM, FETCH32(data, pos)); xlog_param(xl, "value", HEX32, FETCH32(data, pos+4)); } static void xlog_charinfo(struct xlog *xl, const unsigned char *data, int len, int pos) { xlog_param(xl, "left-side-bearing", DEC16, FETCH16(data, pos)); xlog_param(xl, "right-side-bearing", DEC16, FETCH16(data, pos+2)); xlog_param(xl, "character-width", DEC16, FETCH16(data, pos+4)); xlog_param(xl, "ascent", DEC16, FETCH16(data, pos+6)); xlog_param(xl, "descent", DEC16, FETCH16(data, pos+8)); xlog_param(xl, "attributes", DEC16, FETCH16(data, pos+10)); } const char *xlog_translate_event(int eventtype) { switch (eventtype & ~0x80) { case 2: return "KeyPress"; case 3: return "KeyRelease"; case 4: return "ButtonPress"; case 5: return "ButtonRelease"; case 6: return "MotionNotify"; case 7: return "EnterNotify"; case 8: return "LeaveNotify"; case 9: return "FocusIn"; case 10: return "FocusOut"; case 11: return "KeymapNotify"; case 12: return "Expose"; case 13: return "GraphicsExposure"; case 14: return "NoExposure"; case 15: return "VisibilityNotify"; case 16: return "CreateNotify"; case 17: return "DestroyNotify"; case 18: return "UnmapNotify"; case 19: return "MapNotify"; case 20: return "MapRequest"; case 21: return "ReparentNotify"; case 22: return "ConfigureNotify"; case 23: return "ConfigureRequest"; case 24: return "GravityNotify"; case 25: return "ResizeRequest"; case 26: return "CirculateNotify"; case 27: return "CirculateRequest"; case 28: return "PropertyNotify"; case 29: return "SelectionClear"; case 30: return "SelectionRequest"; case 31: return "SelectionNotify"; case 32: return "ColormapNotify"; case 33: return "ClientMessage"; case 34: return "MappingNotify"; case EXT_MITSHM | 0: return "ShmCompletion"; default: return NULL; } } static void xlog_event(struct xlog *xl, const unsigned char *data, int len, int pos, int *filter) { int event, i; const char *name; xl->reqlogstate = 3; event = FETCH8(data, pos); if (event & 0x80) { put_datastr(xl->textbuf, "SendEvent-generated "); event &= ~0x80; } name = NULL; if (event == 35) { /* GenericEvent */ int opcode = FETCH8(data, 1); int gevent = FETCH16(data, 8); char const *extname = NULL; if (opcode >= 128) { extname = xl->extreqs[opcode-128]; if (xl->extidreqs[opcode-128]) { event = xl->extidreqs[opcode-128] | GENERICEVENT | gevent; name = xlog_translate_event(event); } } if (name == NULL) { if (extname != NULL) strbuf_catf(xl->textbuf, "%s:UnknownGenericEvent%d", extname, gevent); else strbuf_catf(xl->textbuf, "%d:UnknownGenericEvent%d", opcode, gevent); } } else if (event < 64) { /* Core event */ name = xlog_translate_event(event); if (name == NULL) strbuf_catf(xl->textbuf, "UnknownEvent%d", event); } else { /* Extension event */ for (i = 0; event-i >= 64; i++) if (xl->extevents[event-i]) { char const *extname = xl->extevents[event-i]; if (xl->extidevents[event-i]) { event = xl->extidevents[event-i] + i; name = xlog_translate_event(event); } if (name == NULL) strbuf_catf(xl->textbuf, "%s:UnknownEvent%d", extname, i); break; } if (event-i < 64) strbuf_catf(xl->textbuf, "UnknownEvent%d", event); } if (name) { if (filter) *filter = in_set(&xl->xs->events_to_log, name); put_datastr(xl->textbuf, name); } else { if (filter) *filter = in_set(&xl->xs->events_to_log, "UnknownEvent"); } switch (event) { case 2: case 3: case 4: case 5: case 6: case 7: case 8: /* KeyPress, KeyRelease, ButtonPress, ButtonRelease, MotionNotify, * EnterNotify, LeaveNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "root", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "event", WINDOW, FETCH32(data, pos+12)); xlog_param(xl, "child", WINDOW | SPECVAL, FETCH32(data, pos+16), "None", 0); if (event < 7) { xlog_param(xl, "same-screen", BOOLEAN, FETCH8(data, pos+30)); } else if (event == 7 || event == 8) { xlog_param(xl, "mode", ENUM | SPECVAL, FETCH8(data, pos+30), "Normal", 0, "Grab", 1, "Ungrab", 2, (char *)NULL); xlog_param(xl, "same-screen", BOOLEAN, (FETCH8(data, pos+31) >> 1) & 1); xlog_param(xl, "focus", BOOLEAN, FETCH8(data, pos+31) & 1); } xlog_param(xl, "root-x", DEC16, FETCH16(data, pos+20)); xlog_param(xl, "root-y", DEC16, FETCH16(data, pos+22)); xlog_param(xl, "event-x", DEC16, FETCH16(data, pos+24)); xlog_param(xl, "event-y", DEC16, FETCH16(data, pos+26)); if (event < 6) xlog_param(xl, "detail", DECU, FETCH8(data, pos+1)); else if (event == 6) xlog_param(xl, "detail", ENUM | SPECVAL, FETCH8(data, pos+1), "Normal", 0, "Hint", 1, (char *)NULL); else if (event == 7 || event == 8) xlog_param(xl, "detail", ENUM | SPECVAL, FETCH8(data, pos+1), "Ancestor", 0, "Virtual", 1, "Inferior", 2, "Nonlinear", 3, "NonlinearVirtual", 4, (char *)NULL); xlog_param(xl, "state", HEX16, FETCH16(data, pos+28)); xlog_param(xl, "time", HEX32, FETCH32(data, pos+4)); put_byte(xl->textbuf, ')'); break; case 9: case 10: /* FocusIn, FocusOut */ put_byte(xl->textbuf, '('); xlog_param(xl, "event", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "mode", ENUM | SPECVAL, FETCH8(data, pos+8), "Normal", 0, "Grab", 1, "Ungrab", 2, "WhileGrabbed", 3, (char *)NULL); xlog_param(xl, "detail", ENUM | SPECVAL, FETCH8(data, pos+1), "Ancestor", 0, "Virtual", 1, "Inferior", 2, "Nonlinear", 3, "NonlinearVirtual", 4, "Pointer", 5, "PointerRoot", 6, "None", 7, (char *)NULL); put_byte(xl->textbuf, ')'); break; case 11: /* KeymapNotify */ put_byte(xl->textbuf, '('); { int i; int ppos = pos + 1; for (i = 1; i < 32; i++) { char buf[64]; sprintf(buf, "keys[%d]", i); xlog_param(xl, buf, HEX8, FETCH8(data, ppos)); ppos++; if (i+1 < 32 && xlog_check_list_length(xl)) break; } } put_byte(xl->textbuf, ')'); break; case 12: /* Expose */ put_byte(xl->textbuf, '('); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "x", DECU, FETCH16(data, pos+8)); xlog_param(xl, "y", DECU, FETCH16(data, pos+10)); xlog_param(xl, "width", DECU, FETCH16(data, pos+12)); xlog_param(xl, "height", DECU, FETCH16(data, pos+14)); xlog_param(xl, "count", DECU, FETCH16(data, pos+16)); put_byte(xl->textbuf, ')'); break; case 13: /* GraphicsExposure */ put_byte(xl->textbuf, '('); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, pos+4)); xlog_param(xl, "x", DECU, FETCH16(data, pos+8)); xlog_param(xl, "y", DECU, FETCH16(data, pos+10)); xlog_param(xl, "width", DECU, FETCH16(data, pos+12)); xlog_param(xl, "height", DECU, FETCH16(data, pos+14)); xlog_param(xl, "count", DECU, FETCH16(data, pos+18)); xlog_param(xl, "major-opcode", DECU | SPECVAL, FETCH8(data, pos+20), "CopyArea", 62, "CopyPlane", 63, (char *)NULL); xlog_param(xl, "minor-opcode", DECU, FETCH16(data, pos+16)); put_byte(xl->textbuf, ')'); break; case 14: /* NoExposure */ put_byte(xl->textbuf, '('); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, pos+4)); xlog_param(xl, "major-opcode", DECU | SPECVAL, FETCH8(data, pos+10), "CopyArea", 62, "CopyPlane", 63, (char *)NULL); xlog_param(xl, "minor-opcode", DECU, FETCH16(data, pos+8)); put_byte(xl->textbuf, ')'); break; case 15: /* VisibilityNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "state", ENUM | SPECVAL, FETCH8(data, pos+8), "Unobscured", 0, "PartiallyObscured", 1, "FullyObscured", 2, (char *)NULL); put_byte(xl->textbuf, ')'); break; case 16: /* CreateNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "parent", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "x", DEC16, FETCH16(data, pos+12)); xlog_param(xl, "y", DEC16, FETCH16(data, pos+14)); xlog_param(xl, "width", DECU, FETCH16(data, pos+16)); xlog_param(xl, "height", DECU, FETCH16(data, pos+18)); xlog_param(xl, "border-width", DECU, FETCH16(data, pos+20)); xlog_param(xl, "override-redirect", BOOLEAN, FETCH8(data, pos+22)); put_byte(xl->textbuf, ')'); break; case 17: /* DestroyNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "event", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+8)); put_byte(xl->textbuf, ')'); break; case 18: /* UnmapNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "event", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "from-configure", BOOLEAN, FETCH8(data, pos+12)); put_byte(xl->textbuf, ')'); break; case 19: /* MapNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "event", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "override-redirect", BOOLEAN, FETCH8(data, pos+12)); put_byte(xl->textbuf, ')'); break; case 20: /* MapRequest */ put_byte(xl->textbuf, '('); xlog_param(xl, "parent", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+8)); put_byte(xl->textbuf, ')'); break; case 21: /* ReparentNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "event", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "parent", WINDOW, FETCH32(data, pos+12)); xlog_param(xl, "x", DEC16, FETCH16(data, pos+16)); xlog_param(xl, "y", DEC16, FETCH16(data, pos+18)); xlog_param(xl, "override-redirect", BOOLEAN, FETCH8(data, pos+20)); put_byte(xl->textbuf, ')'); break; case 22: /* ConfigureNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "event", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "x", DEC16, FETCH16(data, pos+16)); xlog_param(xl, "y", DEC16, FETCH16(data, pos+18)); xlog_param(xl, "width", DECU, FETCH16(data, pos+20)); xlog_param(xl, "height", DECU, FETCH16(data, pos+22)); xlog_param(xl, "border-width", DECU, FETCH16(data, pos+24)); xlog_param(xl, "above-sibling", WINDOW | SPECVAL, FETCH32(data, pos+12), "None", 0, (char *)NULL); xlog_param(xl, "override-redirect", BOOLEAN, FETCH8(data, pos+26)); put_byte(xl->textbuf, ')'); break; case 23: /* ConfigureRequest */ put_byte(xl->textbuf, '('); xlog_param(xl, "parent", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "x", DEC16, FETCH16(data, pos+16)); xlog_param(xl, "y", DEC16, FETCH16(data, pos+18)); xlog_param(xl, "width", DECU, FETCH16(data, pos+20)); xlog_param(xl, "height", DECU, FETCH16(data, pos+22)); xlog_param(xl, "border-width", DECU, FETCH16(data, pos+24)); xlog_param(xl, "sibling", WINDOW | SPECVAL, FETCH32(data, pos+12), "None", 0, (char *)NULL); xlog_param(xl, "stack-mode", ENUM | SPECVAL, FETCH8(data, pos+1), "Above", 0, "Below", 1, "TopIf", 2, "BottomIf", 3, "Opposite", 4, (char *)NULL); /* * Mostly, these bit masks appearing in the X protocol with * bit names corresponding to fields in the same packet are * there to indicate that some fields are unused. This one * is unusual in that all fields are filled in regardless of * this bit mask; the bit mask tells the receiving client * which of the fields have just been changed, and which are * unchanged and merely being re-reported as a courtesy. */ xlog_param(xl, "value-mask", GENMASK, FETCH16(data, pos+26), "x", 0x0001, "y", 0x0002, "width", 0x0004, "height", 0x0008, "border-width", 0x0010, "sibling", 0x0020, "stack-mode", 0x0040, (char *)NULL); put_byte(xl->textbuf, ')'); break; case 24: /* GravityNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "event", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "x", DEC16, FETCH16(data, pos+12)); xlog_param(xl, "y", DEC16, FETCH16(data, pos+14)); put_byte(xl->textbuf, ')'); break; case 25: /* ResizeRequest */ put_byte(xl->textbuf, '('); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "width", DEC16, FETCH16(data, pos+8)); xlog_param(xl, "height", DEC16, FETCH16(data, pos+10)); put_byte(xl->textbuf, ')'); break; case 26: /* CirculateNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "event", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "place", ENUM | SPECVAL, FETCH8(data, pos+16), "Top", 0, "Bottom", 1, (char *)NULL); put_byte(xl->textbuf, ')'); break; case 27: /* CirculateRequest */ put_byte(xl->textbuf, '('); xlog_param(xl, "parent", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "place", ENUM | SPECVAL, FETCH8(data, pos+16), "Top", 0, "Bottom", 1, (char *)NULL); put_byte(xl->textbuf, ')'); break; case 28: /* PropertyNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "atom", ATOM, FETCH32(data, pos+8)); xlog_param(xl, "state", ENUM | SPECVAL, FETCH8(data, pos+16), "NewValue", 0, "Deleted", 1, (char *)NULL); xlog_param(xl, "time", HEX32, FETCH32(data, pos+12)); put_byte(xl->textbuf, ')'); break; case 29: /* SelectionClear */ put_byte(xl->textbuf, '('); xlog_param(xl, "owner", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "selection", ATOM, FETCH32(data, pos+12)); xlog_param(xl, "time", HEX32, FETCH32(data, pos+4)); put_byte(xl->textbuf, ')'); break; case 30: /* SelectionRequest */ put_byte(xl->textbuf, '('); xlog_param(xl, "owner", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "selection", ATOM, FETCH32(data, pos+16)); xlog_param(xl, "target", ATOM, FETCH32(data, pos+20)); xlog_param(xl, "property", ATOM | SPECVAL, FETCH32(data, pos+24), "None", 0, (char *)NULL); xlog_param(xl, "requestor", WINDOW, FETCH32(data, pos+12)); xlog_param(xl, "time", HEX32 | SPECVAL, FETCH32(data, pos+4), "CurrentTime", 0, (char *)NULL); put_byte(xl->textbuf, ')'); break; case 31: /* SelectionNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "requestor", WINDOW, FETCH32(data, pos+8)); xlog_param(xl, "selection", ATOM, FETCH32(data, pos+12)); xlog_param(xl, "target", ATOM, FETCH32(data, pos+16)); xlog_param(xl, "property", ATOM | SPECVAL, FETCH32(data, pos+20), "None", 0, (char *)NULL); xlog_param(xl, "time", HEX32 | SPECVAL, FETCH32(data, pos+4), "CurrentTime", 0, (char *)NULL); put_byte(xl->textbuf, ')'); break; case 32: /* ColormapNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "colormap", COLORMAP | SPECVAL, FETCH32(data, pos+8), "None", 0, (char *)NULL); xlog_param(xl, "new", BOOLEAN, FETCH8(data, pos+12)); xlog_param(xl, "state", ENUM | SPECVAL, FETCH8(data, pos+13), "Uninstalled", 0, "Installed", 1, (char *)NULL); put_byte(xl->textbuf, ')'); break; case 33: /* ClientMessage */ put_byte(xl->textbuf, '('); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+4)); xlog_param(xl, "type", ATOM, FETCH32(data, pos+8)); xlog_param(xl, "format", DECU, FETCH8(data, pos+1)); switch (FETCH8(data, pos+1)) { case 8: xlog_param(xl, "data", STRING, 20, STRING(data, pos+12, 20)); break; case 16: xlog_param(xl, "data", HEXSTRING2, 10, STRING(data, pos+12, 20)); break; case 32: if (atomeq(xl->atoms, FETCH32(data, pos+8), "WM_PROTOCOLS")) { xlog_param(xl, "data", SETBEGIN); xlog_param(xl, "protocol", ATOM, FETCH32(data, pos+12)); xlog_param(xl, "time", HEX32, FETCH32(data, pos+16)); if (atomeq(xl->atoms, FETCH32(data, pos+12), "WM_DELETE_WINDOW") || atomeq(xl->atoms, FETCH32(data, pos+12), "WM_TAKE_FOCUS") || atomeq(xl->atoms, FETCH32(data, pos+12), "WM_SAVE_YOURSELF")) /* No further fields */; else if (atomeq(xl->atoms, FETCH32(data, pos+12), "_NET_WM_PING")) xlog_param(xl, "client-window", WINDOW, FETCH32(data, pos+20)); else if (atomeq(xl->atoms, FETCH32(data, pos+12), "_NET_WM_SYNC_REQUEST")) { xlog_param(xl, "update-request-number", SETBEGIN); xlog_param(xl, "hi", HEX32, FETCH32(data, pos+20)); xlog_param(xl, "lo", HEX32, FETCH32(data, pos+20)); xlog_set_end(xl); } else xlog_param(xl, "protocol-data", HEXSTRING4, 3, STRING(data, pos+20, 12)); xlog_set_end(xl); } else if (atomeq(xl->atoms, FETCH32(data, pos+8), "_XIM_XCONNECT")) { unsigned maj = FETCH32(data, pos+16); unsigned min = FETCH32(data, pos+20); xlog_param(xl, "data", SETBEGIN); xlog_param(xl, "window", WINDOW, FETCH32(data, pos+12)); xlog_param(xl, "major-version", DECU, maj); xlog_param(xl, "minor-version", DECU, min); /* Last field is only meaningful for two formats. */ if ((maj == 0 && min == 2) || (maj == 2 && min == 1)) xlog_param(xl, "dividing-size", DECU, FETCH32(data, pos+24)); xlog_set_end(xl); } else if (atomeq(xl->atoms, FETCH32(data, pos+8), "_XIM_PROTOCOL")) { xlog_param(xl, "data", SETBEGIN); xlog_param(xl, "prop-length", DEC32, FETCH32(data, pos+12)); xlog_param(xl, "prop-atom", ATOM, FETCH32(data, pos+16)); xlog_set_end(xl); } else { xlog_param(xl, "data", HEXSTRING4, 5, STRING(data, pos+12, 20)); } break; default: put_datastr(xl->textbuf, ""); break; } put_byte(xl->textbuf, ')'); break; case 34: /* MappingNotify */ put_byte(xl->textbuf, '('); xlog_param(xl, "request", ENUM | SPECVAL, FETCH8(data, pos+4), "Modifier", 0, "Keyboard", 1, "Pointer", 2, (char *)NULL); xlog_param(xl, "first-keycode", DECU, FETCH8(data, pos+5)); xlog_param(xl, "count", DECU, FETCH8(data, pos+6)); put_byte(xl->textbuf, ')'); break; case EXT_MITSHM | 0: /* ShmCompletion */ put_byte(xl->textbuf, '('); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, pos+4)); xlog_param(xl, "shmseg", HEX32, FETCH32(data, pos+8)); xlog_param(xl, "minor-event", DECU, FETCH16(data, pos+12)); xlog_param(xl, "major-event", DECU, FETCH8(data, pos+14)); xlog_param(xl, "offset", HEX32, FETCH32(data, pos+16)); put_byte(xl->textbuf, ')'); break; default: /* unknown event */ break; } xl->reqlogstate = 1; } static int xlog_image_data(struct xlog *xl, const char *paramname, const unsigned char *data, int len, int startoffset, int format, int width, int height, int depth) { int bpp = -1, pad = -1, nbitmaps = 1; /* * Figure out the size and format of the image data, and * log it as a hex string. */ if (format == 2) { /* * Z pixmap. */ int i; for (i = 0; i < xl->npixmapformats; i++) { if (xl->pixmapformats[i].depth == depth) { bpp = xl->pixmapformats[i].bits_per_pixel; pad = xl->pixmapformats[i].scanline_pad; break; } } } else { bpp = xl->bitmap_scanline_unit; pad = xl->bitmap_scanline_pad; nbitmaps = depth; } if (bpp < 0) { xlog_param(xl, "", NOTEVENEQUALSIGN); return -1; } else { int scanlinewidth, unitsize, stringtype, nunits; scanlinewidth = width; scanlinewidth *= bpp; scanlinewidth += pad - 1; scanlinewidth &= ~(pad - 1); scanlinewidth /= 8; unitsize = (bpp + 7) / 8; stringtype = STRING_TYPE(xl->image_byte_order, bpp); nunits = (scanlinewidth / unitsize) * /* units/scanline */ height * nbitmaps; /* number of scanlines */ xlog_param(xl, paramname, stringtype, nunits, STRING(data, startoffset, nunits * unitsize)); return nunits * unitsize; } } void xlog_use_welcome_message(struct xlog *xl, const void *vdata, int len) { const unsigned char *data = (const unsigned char *)vdata; xl->bitmap_scanline_unit = FETCH8(data, 32); xl->bitmap_scanline_pad = FETCH8(data, 33); xl->image_byte_order = FETCH8(data, 30); xl->npixmapformats = FETCH8(data, 29); xl->pixmapformats = snewn(xl->npixmapformats, struct pixmapformat); { int i, pos = 40 + FETCH16(data, 24); pos = (pos + 3) &~ 3; for (i = 0; i < xl->npixmapformats; i++) { xl->pixmapformats[i].depth = FETCH8(data, pos); xl->pixmapformats[i].bits_per_pixel = FETCH8(data, pos+1); xl->pixmapformats[i].scanline_pad = FETCH8(data, pos+2); pos += 8; } } } static void xlog_do_request(struct xlog *xl, const void *vdata, int len) { const unsigned char *data = (const unsigned char *)vdata; struct request *req; strbuf_clear(xl->textbuf); xl->overflow = false; req = snew(struct request); req->opcode = data[0]; req->replies = 0; req->extname = NULL; req->extid = 0; req->printed = true; req->atomname = NULL; req->atomnum = 0; /* * Translate requests belonging to known extensions so we can * switch on them. */ if (req->opcode >= 128 && xl->extidreqs[req->opcode-128]) req->opcode = xl->extidreqs[req->opcode-128] | data[1]; switch (req->opcode) { case 1: case 2: { unsigned i, bitmask; switch (data[0]) { case 1: xlog_request_name(xl, req, "CreateWindow", true); xlog_param(xl, "wid", WINDOW, FETCH32(data, 4)); xlog_param(xl, "parent", WINDOW, FETCH32(data, 8)); xlog_param(xl, "class", ENUM | SPECVAL, FETCH16(data, 22), "CopyFromParent", 0, "InputOutput", 1, "InputOnly", 2, (char *)NULL); xlog_param(xl, "depth", DECU, FETCH8(data, 1)); xlog_param(xl, "visual", VISUALID | SPECVAL, FETCH32(data, 24), "CopyFromParent", 0, (char *)NULL); xlog_param(xl, "x", DEC16, FETCH16(data, 12)); xlog_param(xl, "y", DEC16, FETCH16(data, 14)); xlog_param(xl, "width", DECU, FETCH16(data, 16)); xlog_param(xl, "height", DECU, FETCH16(data, 18)); xlog_param(xl, "border-width", DECU, FETCH16(data, 20)); i = 32; break; default /* case 2 */: xlog_request_name(xl, req, "ChangeWindowAttributes", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); i = 12; } bitmask = FETCH32(data, i-4); if (bitmask & 0x00000001) { xlog_param(xl, "background-pixmap", PIXMAP | SPECVAL, FETCH32(data, i), "None", 0, "ParentRelative", 1, (char *)NULL); i += 4; } if (bitmask & 0x00000002) { xlog_param(xl, "background-pixel", HEX32, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000004) { xlog_param(xl, "border-pixmap", PIXMAP | SPECVAL, FETCH32(data, i), "None", 0, "CopyFromParent", 1, (char *)NULL); i += 4; } if (bitmask & 0x00000008) { xlog_param(xl, "border-pixel", HEX32, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000010) { xlog_param(xl, "bit-gravity", ENUM | SPECVAL, FETCH8(data, i), "Forget", 0, "NorthWest", 1, "North", 2, "NorthEast", 3, "West", 4, "Center", 5, "East", 6, "SouthWest", 7, "South", 8, "SouthEast", 9, "Static", 10, (char *)NULL); i += 4; } if (bitmask & 0x00000020) { xlog_param(xl, "win-gravity", ENUM | SPECVAL, FETCH8(data, i), "Unmap", 0, "NorthWest", 1, "North", 2, "NorthEast", 3, "West", 4, "Center", 5, "East", 6, "SouthWest", 7, "South", 8, "SouthEast", 9, "Static", 10, (char *)NULL); i += 4; } if (bitmask & 0x00000040) { xlog_param(xl, "backing-store", ENUM | SPECVAL, FETCH8(data, i), "NotUseful", 0, "WhenMapped", 1, "Always", 2, (char *)NULL); i += 4; } if (bitmask & 0x00000080) { xlog_param(xl, "backing-planes", DECU, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000100) { xlog_param(xl, "backing-pixel", HEX32, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000200) { xlog_param(xl, "override-redirect", BOOLEAN, FETCH8(data, i)); i += 4; } if (bitmask & 0x00000400) { xlog_param(xl, "save-under", BOOLEAN, FETCH8(data, i)); i += 4; } if (bitmask & 0x00000800) { xlog_param(xl, "event-mask", EVENTMASK, FETCH32(data, i)); i += 4; } if (bitmask & 0x00001000) { xlog_param(xl, "do-not-propagate-mask", EVENTMASK, FETCH32(data, i)); i += 4; } if (bitmask & 0x00002000) { xlog_param(xl, "colormap", COLORMAP | SPECVAL, FETCH32(data, i), "CopyFromParent", 0, (char *)NULL); i += 4; } if (bitmask & 0x00004000) { xlog_param(xl, "cursor", CURSOR | SPECVAL, FETCH32(data, i), "None", 0, (char *)NULL); i += 4; } } break; case 3: xlog_request_name(xl, req, "GetWindowAttributes", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); req->replies = 1; break; case 4: xlog_request_name(xl, req, "DestroyWindow", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); break; case 5: xlog_request_name(xl, req, "DestroySubwindows", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); break; case 6: xlog_request_name(xl, req, "ChangeSaveSet", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); xlog_param(xl, "mode", ENUM | SPECVAL, FETCH8(data, 1), "Insert", 0, "Delete", 1, (char *)NULL); break; case 7: xlog_request_name(xl, req, "ReparentWindow", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); xlog_param(xl, "parent", WINDOW, FETCH32(data, 8)); xlog_param(xl, "x", DEC16, FETCH16(data, 12)); xlog_param(xl, "y", DEC16, FETCH16(data, 14)); break; case 8: xlog_request_name(xl, req, "MapWindow", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); break; case 9: xlog_request_name(xl, req, "MapSubwindows", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); break; case 10: xlog_request_name(xl, req, "UnmapWindow", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); break; case 11: xlog_request_name(xl, req, "UnmapSubwindows", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); break; case 12: xlog_request_name(xl, req, "ConfigureWindow", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); { unsigned i = 12; unsigned bitmask = FETCH16(data, i-4); if (bitmask & 0x0001) { xlog_param(xl, "x", DEC16, FETCH16(data, i)); i += 4; } if (bitmask & 0x0002) { xlog_param(xl, "y", DEC16, FETCH16(data, i)); i += 4; } if (bitmask & 0x0004) { xlog_param(xl, "width", DECU, FETCH16(data, i)); i += 4; } if (bitmask & 0x0008) { xlog_param(xl, "height", DECU, FETCH16(data, i)); i += 4; } if (bitmask & 0x0010) { xlog_param(xl, "border-width", DECU, FETCH16(data, i)); i += 4; } if (bitmask & 0x0020) { xlog_param(xl, "sibling", WINDOW, FETCH32(data, i)); i += 4; } if (bitmask & 0x0040) { xlog_param(xl, "stack-mode", ENUM | SPECVAL, FETCH8(data, i), "Above", 0, "Below", 1, "TopIf", 2, "BottomIf", 3, "Opposite", 4, (char *)NULL); i += 4; } } break; case 13: xlog_request_name(xl, req, "CirculateWindow", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); xlog_param(xl, "direction", ENUM | SPECVAL, FETCH8(data, 1), "RaiseLowest", 0, "LowerHighest", 1, (char *)NULL); break; case 14: xlog_request_name(xl, req, "GetGeometry", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); req->replies = 1; break; case 15: xlog_request_name(xl, req, "QueryTree", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); req->replies = 1; break; case 16: { unsigned long atomlen; char *atomstr; xlog_request_name(xl, req, "InternAtom", true); atomlen = FETCH16(data, 4); atomstr = STRING(data, 8, atomlen); if (!xl->overflow) { req->atomname = snewn(atomlen + 1, char); memcpy(req->atomname, atomstr, atomlen); req->atomname[atomlen] = '\0'; } xlog_param(xl, "name", STRING, atomlen, atomstr); xlog_param(xl, "only-if-exists", BOOLEAN, FETCH8(data, 1)); req->replies = 1; } break; case 17: xlog_request_name(xl, req, "GetAtomName", true); xlog_param(xl, "atom", ATOM, FETCH32(data, 4)); req->atomnum = FETCH32(data, 4); req->replies = 1; break; case 18: xlog_request_name(xl, req, "ChangeProperty", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); xlog_param(xl, "property", ATOM, FETCH32(data, 8)); xlog_param(xl, "type", ATOM, FETCH32(data, 12)); xlog_param(xl, "format", DECU, FETCH8(data, 16)); xlog_param(xl, "mode", ENUM | SPECVAL, FETCH8(data, 1), "Replace", 0, "Prepend", 1, "Append", 2, (char *)NULL); switch (FETCH8(data, 16)) { case 8: xlog_param(xl, "data", STRING, FETCH32(data, 20), STRING(data, 24, FETCH32(data, 20))); break; case 16: xlog_param(xl, "data", HEXSTRING2, FETCH32(data, 20), STRING(data, 24, 2*FETCH32(data, 20))); break; case 32: xlog_param(xl, "data", HEXSTRING4, FETCH32(data, 20), STRING(data, 24, 4*FETCH32(data, 20))); break; default: put_datastr(xl->textbuf, ""); break; } break; case 19: xlog_request_name(xl, req, "DeleteProperty", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); xlog_param(xl, "property", ATOM, FETCH32(data, 8)); break; case 20: xlog_request_name(xl, req, "GetProperty", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); xlog_param(xl, "property", ATOM, FETCH32(data, 8)); xlog_param(xl, "type", ATOM | SPECVAL, FETCH32(data, 12), "AnyPropertyType", 0, (char *)NULL); xlog_param(xl, "long-offset", DECU, FETCH32(data, 16)); xlog_param(xl, "long-length", DECU, FETCH32(data, 20)); xlog_param(xl, "delete", BOOLEAN, FETCH8(data, 1)); req->replies = 1; break; case 21: xlog_request_name(xl, req, "ListProperties", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); req->replies = 1; break; case 22: xlog_request_name(xl, req, "SetSelectionOwner", true); xlog_param(xl, "selection", ATOM, FETCH32(data, 8)); xlog_param(xl, "owner", WINDOW, FETCH32(data, 4)); xlog_param(xl, "time", HEX32 | SPECVAL, FETCH32(data, 12), "CurrentTime", 0, (char *)NULL); break; case 23: xlog_request_name(xl, req, "GetSelectionOwner", true); xlog_param(xl, "selection", ATOM, FETCH32(data, 4)); req->replies = 1; break; case 24: xlog_request_name(xl, req, "ConvertSelection", true); xlog_param(xl, "selection", ATOM, FETCH32(data, 8)); xlog_param(xl, "target", ATOM, FETCH32(data, 12)); xlog_param(xl, "property", ATOM, FETCH32(data, 16)); xlog_param(xl, "requestor", WINDOW, FETCH32(data, 4)); xlog_param(xl, "time", HEX32 | SPECVAL, FETCH32(data, 20), "CurrentTime", 0, (char *)NULL); break; case 25: xlog_request_name(xl, req, "SendEvent", true); xlog_param(xl, "destination", WINDOW | SPECVAL, FETCH32(data, 4), "PointerWindow", 0, "InputFocus", 1, (char *)NULL); xlog_param(xl, "propagate", BOOLEAN, FETCH8(data, 1)); xlog_param(xl, "event-mask", EVENTMASK, FETCH32(data, 8)); xlog_param(xl, "event", NOTHING); xlog_event(xl, data, len, 12, NULL); break; case 26: xlog_request_name(xl, req, "GrabPointer", true); xlog_param(xl, "grab-window", WINDOW | SPECVAL, FETCH32(data, 4), "PointerWindow", 0, "InputFocus", 1, (char *)NULL); xlog_param(xl, "owner-events", BOOLEAN, FETCH8(data, 1)); xlog_param(xl, "event-mask", EVENTMASK, FETCH16(data, 8)); xlog_param(xl, "pointer-mode", ENUM | SPECVAL, FETCH8(data, 10), "Synchronous", 0, "Asynchronous", 1, (char *)NULL); xlog_param(xl, "keyboard-mode", ENUM | SPECVAL, FETCH8(data, 11), "Synchronous", 0, "Asynchronous", 1, (char *)NULL); xlog_param(xl, "confine-to", WINDOW | SPECVAL, FETCH32(data, 12), "None", 0, (char *)NULL); xlog_param(xl, "cursor", CURSOR | SPECVAL, FETCH32(data, 16), "None", 0, (char *)NULL); xlog_param(xl, "time", HEX32 | SPECVAL, FETCH32(data, 20), "CurrentTime", 0, (char *)NULL); req->replies = 1; break; case 27: xlog_request_name(xl, req, "UngrabPointer", true); xlog_param(xl, "time", HEX32 | SPECVAL, FETCH32(data, 4), "CurrentTime", 0, (char *)NULL); break; case 28: xlog_request_name(xl, req, "GrabButton", true); xlog_param(xl, "modifiers", KEYMASK | SPECVAL, FETCH16(data, 22), "AnyModifier", 0x8000, (char *)NULL); xlog_param(xl, "button", DECU | SPECVAL, FETCH8(data, 20), "AnyButton", 0, (char *)NULL); xlog_param(xl, "grab-window", WINDOW | SPECVAL, FETCH32(data, 4), "PointerWindow", 0, "InputFocus", 1, (char *)NULL); xlog_param(xl, "owner-events", BOOLEAN, FETCH8(data, 1)); xlog_param(xl, "event-mask", EVENTMASK, FETCH16(data, 8)); xlog_param(xl, "pointer-mode", ENUM | SPECVAL, FETCH8(data, 10), "Synchronous", 0, "Asynchronous", 1, (char *)NULL); xlog_param(xl, "keyboard-mode", ENUM | SPECVAL, FETCH8(data, 11), "Synchronous", 0, "Asynchronous", 1, (char *)NULL); xlog_param(xl, "confine-to", WINDOW | SPECVAL, FETCH32(data, 12), "None", 0, (char *)NULL); xlog_param(xl, "cursor", CURSOR | SPECVAL, FETCH32(data, 16), "None", 0, (char *)NULL); break; case 29: xlog_request_name(xl, req, "UngrabButton", true); xlog_param(xl, "modifiers", KEYMASK | SPECVAL, FETCH16(data, 8), "AnyModifier", 0x8000, (char *)NULL); xlog_param(xl, "button", DECU | SPECVAL, FETCH8(data, 1), "AnyButton", 0, (char *)NULL); xlog_param(xl, "grab-window", WINDOW | SPECVAL, FETCH32(data, 4), "PointerWindow", 0, "InputFocus", 1, (char *)NULL); break; case 30: xlog_request_name(xl, req, "ChangeActivePointerGrab", true); xlog_param(xl, "event-mask", EVENTMASK, FETCH16(data, 12)); xlog_param(xl, "cursor", CURSOR | SPECVAL, FETCH32(data, 4), "None", 0, (char *)NULL); xlog_param(xl, "time", HEX32 | SPECVAL, FETCH32(data, 8), "CurrentTime", 0, (char *)NULL); break; case 31: xlog_request_name(xl, req, "GrabKeyboard", true); xlog_param(xl, "grab-window", WINDOW | SPECVAL, FETCH32(data, 4), "PointerWindow", 0, "InputFocus", 1, (char *)NULL); xlog_param(xl, "owner-events", BOOLEAN, FETCH8(data, 1)); xlog_param(xl, "pointer-mode", ENUM | SPECVAL, FETCH8(data, 12), "Synchronous", 0, "Asynchronous", 1, (char *)NULL); xlog_param(xl, "keyboard-mode", ENUM | SPECVAL, FETCH8(data, 13), "Synchronous", 0, "Asynchronous", 1, (char *)NULL); xlog_param(xl, "time", HEX32 | SPECVAL, FETCH32(data, 8), "CurrentTime", 0, (char *)NULL); req->replies = 1; break; case 32: xlog_request_name(xl, req, "UngrabKeyboard", true); xlog_param(xl, "time", HEX32 | SPECVAL, FETCH32(data, 4), "CurrentTime", 0, (char *)NULL); break; case 33: xlog_request_name(xl, req, "GrabKey", true); xlog_param(xl, "key", DECU | SPECVAL, FETCH8(data, 10), "AnyKey", 0, (char *)NULL); xlog_param(xl, "modifiers", KEYMASK | SPECVAL, FETCH16(data, 8), "AnyModifier", 0x8000, (char *)NULL); xlog_param(xl, "grab-window", WINDOW | SPECVAL, FETCH32(data, 4), "PointerWindow", 0, "InputFocus", 1, (char *)NULL); xlog_param(xl, "owner-events", BOOLEAN, FETCH8(data, 1)); xlog_param(xl, "pointer-mode", ENUM | SPECVAL, FETCH8(data, 11), "Synchronous", 0, "Asynchronous", 1, (char *)NULL); xlog_param(xl, "keyboard-mode", ENUM | SPECVAL, FETCH8(data, 12), "Synchronous", 0, "Asynchronous", 1, (char *)NULL); break; case 34: xlog_request_name(xl, req, "UngrabKey", true); xlog_param(xl, "key", DECU | SPECVAL, FETCH8(data, 1), "AnyKey", 0, (char *)NULL); xlog_param(xl, "modifiers", KEYMASK | SPECVAL, FETCH16(data, 8), "AnyModifier", 0x8000, (char *)NULL); xlog_param(xl, "grab-window", WINDOW | SPECVAL, FETCH32(data, 4), "PointerWindow", 0, "InputFocus", 1, (char *)NULL); break; case 35: xlog_request_name(xl, req, "AllowEvents", true); xlog_param(xl, "mode", ENUM | SPECVAL, FETCH8(data, 1), "AsyncPointer", 0, "SyncPointer", 1, "ReplayPointe", 2, "AsyncKeyboard", 3, "SyncKeyboard", 4, "ReplayKeyboard", 5, "AsyncBoth", 6, "SyncBoth", 7, (char *)NULL); xlog_param(xl, "time", HEX32 | SPECVAL, FETCH32(data, 4), "CurrentTime", 0, (char *)NULL); break; case 36: xlog_request_name(xl, req, "GrabServer", true); /* no arguments */ break; case 37: xlog_request_name(xl, req, "UngrabServer", true); /* no arguments */ break; case 38: xlog_request_name(xl, req, "QueryPointer", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); req->replies = 1; break; case 39: xlog_request_name(xl, req, "GetMotionEvents", true); xlog_param(xl, "start", HEX32 | SPECVAL, FETCH32(data, 8), "CurrentTime", 0, (char *)NULL); xlog_param(xl, "stop", HEX32 | SPECVAL, FETCH32(data, 12), "CurrentTime", 0, (char *)NULL); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); req->replies = 1; break; case 40: xlog_request_name(xl, req, "TranslateCoordinates", true); xlog_param(xl, "src-window", WINDOW, FETCH32(data, 4)); xlog_param(xl, "dst-window", WINDOW, FETCH32(data, 8)); xlog_param(xl, "src-x", DEC16, FETCH16(data, 12)); xlog_param(xl, "src-y", DEC16, FETCH16(data, 14)); req->replies = 1; break; case 41: xlog_request_name(xl, req, "WarpPointer", true); xlog_param(xl, "src-window", WINDOW | SPECVAL, FETCH32(data, 4), "None", 0, (char *)NULL); xlog_param(xl, "dst-window", WINDOW | SPECVAL, FETCH32(data, 8), "None", 0, (char *)NULL); xlog_param(xl, "src-x", DEC16, FETCH16(data, 12)); xlog_param(xl, "src-y", DEC16, FETCH16(data, 14)); xlog_param(xl, "src-width", DECU, FETCH16(data, 16)); xlog_param(xl, "src-height", DECU, FETCH16(data, 18)); xlog_param(xl, "dst-x", DEC16, FETCH16(data, 20)); xlog_param(xl, "dst-y", DEC16, FETCH16(data, 22)); break; case 42: xlog_request_name(xl, req, "SetInputFocus", true); xlog_param(xl, "focus", WINDOW, FETCH32(data, 4)); xlog_param(xl, "revert-to", ENUM | SPECVAL, FETCH8(data, 1), "None", 0, "PointerRoot", 1, "Parent", 2, (char *)NULL); xlog_param(xl, "time", HEX32 | SPECVAL, FETCH32(data, 8), "CurrentTime", 0, (char *)NULL); break; case 43: xlog_request_name(xl, req, "GetInputFocus", true); req->replies = 1; break; case 44: xlog_request_name(xl, req, "QueryKeymap", true); req->replies = 1; break; case 45: xlog_request_name(xl, req, "OpenFont", true); xlog_param(xl, "fid", FONT, FETCH32(data, 4)); xlog_param(xl, "name", STRING, FETCH16(data, 8), STRING(data, 12, FETCH16(data, 8))); break; case 46: xlog_request_name(xl, req, "CloseFont", true); xlog_param(xl, "font", FONT, FETCH32(data, 4)); break; case 47: xlog_request_name(xl, req, "QueryFont", true); xlog_param(xl, "font", FONTABLE, FETCH32(data, 4)); req->replies = 1; break; case 48: xlog_request_name(xl, req, "QueryTextExtents", true); xlog_param(xl, "font", FONTABLE, FETCH32(data, 4)); { int stringlen = len - 8; stringlen /= 2; if (FETCH8(data, 1) != 0) stringlen--; if (stringlen < 0) stringlen = 0; xlog_param(xl, "string", HEXSTRING2B, stringlen, STRING(data, 8, 2*stringlen)); } req->replies = 1; break; case 49: xlog_request_name(xl, req, "ListFonts", true); xlog_param(xl, "pattern", STRING, FETCH16(data, 6), STRING(data, 8, FETCH16(data, 6))); xlog_param(xl, "max-names", DECU, FETCH16(data, 4)); req->replies = 1; break; case 50: xlog_request_name(xl, req, "ListFontsWithInfo", true); xlog_param(xl, "pattern", STRING, FETCH16(data, 6), STRING(data, 8, FETCH16(data, 6))); xlog_param(xl, "max-names", DECU, FETCH16(data, 4)); req->replies = 2; /* this request expects multiple replies */ break; case 51: xlog_request_name(xl, req, "SetFontPath", true); { int i, n; int pos = 8; n = FETCH16(data, 4); for (i = 0; i < n; i++) { char buf[64]; int slen; sprintf(buf, "path[%d]", i); slen = FETCH8(data, pos); xlog_param(xl, buf, STRING, slen, STRING(data, pos+1, slen)); pos += slen + 1; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 52: xlog_request_name(xl, req, "GetFontPath", true); req->replies = 1; break; case 53: xlog_request_name(xl, req, "CreatePixmap", true); xlog_param(xl, "pid", PIXMAP, FETCH32(data, 4)); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 8)); xlog_param(xl, "depth", DECU, FETCH8(data, 1)); xlog_param(xl, "width", DECU, FETCH16(data, 10)); xlog_param(xl, "height", DECU, FETCH16(data, 12)); break; case 54: xlog_request_name(xl, req, "FreePixmap", true); xlog_param(xl, "pixmap", PIXMAP, FETCH32(data, 4)); break; case 55: case 56: { unsigned i, bitmask; switch (data[0]) { case 55: xlog_request_name(xl, req, "CreateGC", true); xlog_param(xl, "cid", GCONTEXT, FETCH32(data, 4)); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 8)); i = 16; break; default /* case 56 */: xlog_request_name(xl, req, "ChangeGC", true); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 4)); i = 12; break; } bitmask = FETCH32(data, i-4); if (bitmask & 0x00000001) { xlog_param(xl, "function", ENUM | SPECVAL, FETCH8(data, i), "Clear", 0, "And", 1, "AndReverse", 2, "Copy", 3, "AndInverted", 4, "NoOp", 5, "Xor", 6, "Or", 7, "Nor", 8, "Equiv", 9, "Invert", 10, "OrReverse", 11, "CopyInverted", 12, "OrInverted", 13, "Nand", 14, "Set", 15, (char *)NULL); i += 4; } if (bitmask & 0x00000002) { xlog_param(xl, "plane-mask", HEX32, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000004) { xlog_param(xl, "foreground", HEX32, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000008) { xlog_param(xl, "background", HEX32, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000010) { xlog_param(xl, "line-width", DECU, FETCH16(data, i)); i += 4; } if (bitmask & 0x00000020) { xlog_param(xl, "line-style", ENUM | SPECVAL, FETCH8(data, i), "Solid", 0, "OnOffDash", 1, "DoubleDash", 2, (char *)NULL); i += 4; } if (bitmask & 0x00000040) { xlog_param(xl, "cap-style", ENUM | SPECVAL, FETCH8(data, i), "NotLast", 0, "Butt", 1, "Round", 2, "Projecting", 3, (char *)NULL); i += 4; } if (bitmask & 0x00000080) { xlog_param(xl, "join-style", ENUM | SPECVAL, FETCH8(data, i), "Miter", 0, "Round", 1, "Bevel", 2, (char *)NULL); i += 4; } if (bitmask & 0x00000100) { xlog_param(xl, "fill-style", ENUM | SPECVAL, FETCH8(data, i), "Solid", 0, "Tiled", 1, "Stippled", 2, "OpaqueStippled", 3, (char *)NULL); i += 4; } if (bitmask & 0x00000200) { xlog_param(xl, "fill-rule", ENUM | SPECVAL, FETCH8(data, i), "EvenOdd", 0, "Winding", 1, (char *)NULL); i += 4; } if (bitmask & 0x00000400) { xlog_param(xl, "tile", PIXMAP, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000800) { xlog_param(xl, "stipple", PIXMAP, FETCH32(data, i)); i += 4; } if (bitmask & 0x00001000) { xlog_param(xl, "tile-stipple-x-origin", DEC16, FETCH16(data, i)); i += 4; } if (bitmask & 0x00002000) { xlog_param(xl, "tile-stipple-y-origin", DEC16, FETCH16(data, i)); i += 4; } if (bitmask & 0x00004000) { xlog_param(xl, "font", FONT, FETCH32(data, i)); i += 4; } if (bitmask & 0x00008000) { xlog_param(xl, "subwindow-mode", ENUM | SPECVAL, FETCH8(data, i), "ClipByChildren", 0, "IncludeInferiors", 1, (char *)NULL); i += 4; } if (bitmask & 0x00010000) { xlog_param(xl, "graphics-exposures", BOOLEAN, FETCH8(data, i)); i += 4; } if (bitmask & 0x00020000) { xlog_param(xl, "clip-x-origin", DEC16, FETCH16(data, i)); i += 4; } if (bitmask & 0x00040000) { xlog_param(xl, "clip-y-origin", DEC16, FETCH16(data, i)); i += 4; } if (bitmask & 0x00080000) { xlog_param(xl, "clip-mask", PIXMAP | SPECVAL, FETCH32(data, i), "None", 0, (char *)NULL); i += 4; } if (bitmask & 0x00100000) { xlog_param(xl, "dash-offset", DECU, FETCH16(data, i)); i += 4; } if (bitmask & 0x00200000) { xlog_param(xl, "dashes", DECU, FETCH8(data, i)); i += 4; } if (bitmask & 0x00400000) { xlog_param(xl, "arc-mode", ENUM | SPECVAL, FETCH8(data, i), "Chord", 0, "PieSlice", 1, (char *)NULL); i += 4; } } break; case 57: xlog_request_name(xl, req, "CopyGC", true); xlog_param(xl, "src-gc", GCONTEXT, FETCH32(data, 4)); xlog_param(xl, "dst-gc", GCONTEXT, FETCH32(data, 8)); xlog_param(xl, "value-mask", GENMASK, FETCH32(data, 12), "function", 0x00000001, "plane-mask", 0x00000002, "foreground", 0x00000004, "background", 0x00000008, "line-width", 0x00000010, "line-style", 0x00000020, "cap-style", 0x00000040, "join-style", 0x00000080, "fill-style", 0x00000100, "fill-rule", 0x00000200, "tile", 0x00000400, "stipple", 0x00000800, "tile-stipple-x-origin", 0x00001000, "tile-stipple-y-origin", 0x00002000, "font", 0x00004000, "subwindow-mode", 0x00008000, "graphics-exposures", 0x00010000, "clip-x-origin", 0x00020000, "clip-y-origin", 0x00040000, "clip-mask", 0x00080000, "dash-offset", 0x00100000, "dashes", 0x00200000, "arc-mode", 0x00400000, (char *)NULL); break; case 58: xlog_request_name(xl, req, "SetDashes", true); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 4)); xlog_param(xl, "dash-offset", DECU, FETCH16(data, 8)); { int i, n; n = FETCH16(data, 10); for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "dashes[%d]", i); xlog_param(xl, buf, DECU, FETCH8(data, 12+i)); if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 59: xlog_request_name(xl, req, "SetClipRectangles", true); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 4)); xlog_param(xl, "clip-x-origin", DEC16, FETCH16(data, 8)); xlog_param(xl, "clip-y-origin", DEC16, FETCH16(data, 10)); { int pos = 12; int i = 0; char buf[64]; while (pos + 8 <= len) { sprintf(buf, "rectangles[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_rectangle(xl, data, len, pos); xlog_set_end(xl); pos += 8; if (pos < len && xlog_check_list_length(xl)) break; } } xlog_param(xl, "ordering", ENUM | SPECVAL, FETCH8(data, 1), "UnSorted", 0, "YSorted", 1, "YXSorted", 2, "YXBanded", 3, (char *)NULL); break; case 60: xlog_request_name(xl, req, "FreeGC", true); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 4)); break; case 61: xlog_request_name(xl, req, "ClearArea", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); xlog_param(xl, "x", DEC16, FETCH16(data, 8)); xlog_param(xl, "y", DEC16, FETCH16(data, 10)); xlog_param(xl, "width", DECU, FETCH16(data, 12)); xlog_param(xl, "height", DECU, FETCH16(data, 14)); xlog_param(xl, "exposures", BOOLEAN, FETCH8(data, 1)); break; case 62: xlog_request_name(xl, req, "CopyArea", true); xlog_param(xl, "src-drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "dst-drawable", DRAWABLE, FETCH32(data, 8)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 12)); xlog_param(xl, "src-x", DEC16, FETCH16(data, 16)); xlog_param(xl, "src-y", DEC16, FETCH16(data, 18)); xlog_param(xl, "width", DECU, FETCH16(data, 24)); xlog_param(xl, "height", DECU, FETCH16(data, 26)); xlog_param(xl, "dst-x", DEC16, FETCH16(data, 20)); xlog_param(xl, "dst-y", DEC16, FETCH16(data, 22)); break; case 63: xlog_request_name(xl, req, "CopyPlane", true); xlog_param(xl, "src-drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "dst-drawable", DRAWABLE, FETCH32(data, 8)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 12)); xlog_param(xl, "src-x", DEC16, FETCH16(data, 16)); xlog_param(xl, "src-y", DEC16, FETCH16(data, 18)); xlog_param(xl, "width", DECU, FETCH16(data, 24)); xlog_param(xl, "height", DECU, FETCH16(data, 26)); xlog_param(xl, "dst-x", DEC16, FETCH16(data, 20)); xlog_param(xl, "dst-y", DEC16, FETCH16(data, 22)); xlog_param(xl, "bit-plane", DECU, FETCH32(data, 28)); break; case 64: xlog_request_name(xl, req, "PolyPoint", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); xlog_param(xl, "coordinate-mode", ENUM | SPECVAL, FETCH8(data, 1), "Origin", 0, "Previous", 1, (char *)NULL); { int pos = 12; int i = 0; char buf[64]; while (pos + 4 <= len) { sprintf(buf, "points[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_point(xl, data, len, pos); xlog_set_end(xl); pos += 4; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case 65: xlog_request_name(xl, req, "PolyLine", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); xlog_param(xl, "coordinate-mode", ENUM | SPECVAL, FETCH8(data, 1), "Origin", 0, "Previous", 1, (char *)NULL); { int pos = 12; int i = 0; char buf[64]; while (pos + 4 <= len) { sprintf(buf, "points[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_point(xl, data, len, pos); xlog_set_end(xl); pos += 4; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case 66: xlog_request_name(xl, req, "PolySegment", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); { int pos = 12; int i = 0; char buf[64]; while (pos + 8 <= len) { sprintf(buf, "segments[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_segment(xl, data, len, pos); xlog_set_end(xl); pos += 8; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case 67: xlog_request_name(xl, req, "PolyRectangle", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); { int pos = 12; int i = 0; char buf[64]; while (pos + 8 <= len) { sprintf(buf, "rectangles[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_rectangle(xl, data, len, pos); xlog_set_end(xl); pos += 8; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case 68: xlog_request_name(xl, req, "PolyArc", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); { int pos = 12; int i = 0; char buf[64]; while (pos + 12 <= len) { sprintf(buf, "arcs[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_arc(xl, data, len, pos); xlog_set_end(xl); pos += 12; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case 69: xlog_request_name(xl, req, "FillPoly", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); xlog_param(xl, "shape", ENUM | SPECVAL, FETCH8(data, 12), "Complex", 0, "Nonconvex", 1, "Convex", 2, (char *)NULL); xlog_param(xl, "coordinate-mode", ENUM | SPECVAL, FETCH8(data, 13), "Origin", 0, "Previous", 1, (char *)NULL); { int pos = 16; int i = 0; char buf[64]; while (pos + 4 <= len) { sprintf(buf, "points[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_point(xl, data, len, pos); xlog_set_end(xl); pos += 4; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case 70: xlog_request_name(xl, req, "PolyFillRectangle", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); { int pos = 12; int i = 0; char buf[64]; while (pos + 8 <= len) { sprintf(buf, "rectangles[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_rectangle(xl, data, len, pos); xlog_set_end(xl); pos += 8; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case 71: xlog_request_name(xl, req, "PolyFillArc", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); { int pos = 12; int i = 0; char buf[64]; while (pos + 12 <= len) { sprintf(buf, "arcs[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_arc(xl, data, len, pos); xlog_set_end(xl); pos += 12; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case 72: xlog_request_name(xl, req, "PutImage", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); xlog_param(xl, "depth", DECU, FETCH8(data, 21)); xlog_param(xl, "width", DECU, FETCH16(data, 12)); xlog_param(xl, "height", DECU, FETCH16(data, 14)); xlog_param(xl, "dst-x", DEC16, FETCH16(data, 16)); xlog_param(xl, "dst-y", DEC16, FETCH16(data, 18)); xlog_param(xl, "left-pad", DECU, FETCH8(data, 20)); xlog_param(xl, "format", ENUM | SPECVAL, FETCH8(data, 1), "Bitmap", 0, "XYPixmap", 1, "ZPixmap", 2, (char *)NULL); xlog_image_data(xl, "image-data", data, len, 24, FETCH8(data, 1), FETCH16(data, 12) + FETCH8(data, 20), FETCH16(data, 14), FETCH8(data, 21)); break; case 73: xlog_request_name(xl, req, "GetImage", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "x", DEC16, FETCH16(data, 8)); xlog_param(xl, "y", DEC16, FETCH16(data, 10)); xlog_param(xl, "width", DECU, FETCH16(data, 12)); xlog_param(xl, "height", DECU, FETCH16(data, 14)); xlog_param(xl, "plane-mask", HEX32, FETCH32(data, 16)); xlog_param(xl, "format", ENUM | SPECVAL, FETCH8(data, 1), "XYPixmap", 1, "ZPixmap", 2, (char *)NULL); req->replies = 1; req->pixmapformat = FETCH8(data, 1); req->pixmapwidth = FETCH16(data, 12); req->pixmapheight = FETCH16(data, 14); break; case 74: xlog_request_name(xl, req, "PolyText8", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); xlog_param(xl, "x", DEC16, FETCH16(data, 12)); xlog_param(xl, "y", DEC16, FETCH16(data, 14)); { int pos = 16; int i = 0; /* * We now expect a series of TEXTITEM8s packed tightly * together. These take one of two forms: * - a length byte L from 0 to 254, a delta byte * (denoting a horizontal movement), and a string of * L bytes of text * - the special length byte 255 followed by a * four-byte FONT identifier which is always * big-endian regardless of the connection's normal * endianness */ while (pos + 3 <= len) { char buf[64]; int tilen = FETCH8(data, pos); if (tilen == 0 && pos + 3 == len) { /* * Special case. It's valid to have L==0 in the * middle of a PolyText8 request: that encodes a * delta but no text, and Xlib generates * contiguous streams of these to construct a * larger delta than a single delta field can * hold. But the x-coordinate manipulated by * those deltas only has meaning until the end * of the call; thus, a delta-only record * _right_ at the end can have no purpose. In * fact this construction is used as padding * when there are 3 bytes left to align to the * protocol's 4-byte boundary. So in this case, * we finish. */ break; } sprintf(buf, "items[%d]", i); xlog_param(xl, buf, SETBEGIN); if (tilen == 255) { int font = FETCH32(data, pos+1); if (xl->endianness == 'l') { font = (((font >> 24) & 0x000000FF) | ((font >> 8) & 0x0000FF00) | ((font << 8) & 0x00FF0000) | ((font << 24) & 0xFF000000)); } xlog_param(xl, "font", FONT, font); pos += 5; } else { xlog_param(xl, "delta", DEC8, FETCH8(data, pos+1)); xlog_param(xl, "string", STRING, tilen, STRING(data, pos+2, tilen)); pos += tilen + 2; } xlog_set_end(xl); i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case 75: xlog_request_name(xl, req, "PolyText16", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); xlog_param(xl, "x", DEC16, FETCH16(data, 12)); xlog_param(xl, "y", DEC16, FETCH16(data, 14)); { int pos = 16; int i = 0; /* * TEXTITEM16s look just like TEXTITEM8s, except that * the strings of actual text are twice the length (2L * bytes each time). */ while (pos + 3 <= len) { char buf[64]; int tilen = FETCH8(data, pos); if (tilen == 0 && pos + 3 == len) { /* * Special case. It's valid to have L==0 in the * middle of a PolyText8 request: that encodes a * delta but no text, and Xlib generates * contiguous streams of these to construct a * larger delta than a single delta field can * hold. But the x-coordinate manipulated by * those deltas only has meaning until the end * of the call; thus, a delta-only record * _right_ at the end can have no purpose. In * fact this construction is used as padding * when there are 3 bytes left to align to the * protocol's 4-byte boundary. So in this case, * we finish. */ break; } sprintf(buf, "items[%d]", i); xlog_param(xl, buf, SETBEGIN); if (tilen == 255) { xlog_param(xl, "font", FONT, FETCH32(data, pos+1)); pos += 5; } else { xlog_param(xl, "delta", DEC8, FETCH8(data, pos+1)); xlog_param(xl, "string", HEXSTRING2B, tilen, STRING(data, pos+2, 2*tilen)); pos += 2*tilen + 2; } xlog_set_end(xl); i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case 76: xlog_request_name(xl, req, "ImageText8", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); xlog_param(xl, "x", DEC16, FETCH16(data, 12)); xlog_param(xl, "y", DEC16, FETCH16(data, 14)); xlog_param(xl, "string", STRING, FETCH8(data, 1), STRING(data, 16, FETCH8(data, 1))); break; case 77: xlog_request_name(xl, req, "ImageText16", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); xlog_param(xl, "x", DEC16, FETCH16(data, 12)); xlog_param(xl, "y", DEC16, FETCH16(data, 14)); xlog_param(xl, "string", HEXSTRING2B, FETCH8(data, 1), STRING(data, 16, 2*FETCH8(data, 1))); break; case 78: xlog_request_name(xl, req, "CreateColormap", true); xlog_param(xl, "mid", COLORMAP, FETCH32(data, 4)); xlog_param(xl, "visual", VISUALID, FETCH32(data, 12)); xlog_param(xl, "window", WINDOW, FETCH32(data, 8)); xlog_param(xl, "alloc", ENUM | SPECVAL, FETCH8(data, 1), "None", 0, "All", 1, (char *)NULL); break; case 79: xlog_request_name(xl, req, "FreeColormap", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); break; case 80: xlog_request_name(xl, req, "CopyColormapAndFree", true); xlog_param(xl, "mid", COLORMAP, FETCH32(data, 4)); xlog_param(xl, "src-cmap", COLORMAP, FETCH32(data, 8)); break; case 81: xlog_request_name(xl, req, "InstallColormap", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); break; case 82: xlog_request_name(xl, req, "UninstallColormap", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); break; case 83: xlog_request_name(xl, req, "ListInstalledColormaps", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); req->replies = 1; break; case 84: xlog_request_name(xl, req, "AllocColor", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); xlog_param(xl, "red", HEX16, FETCH16(data, 8)); xlog_param(xl, "green", HEX16, FETCH16(data, 10)); xlog_param(xl, "blue", HEX16, FETCH16(data, 12)); req->replies = 1; break; case 85: xlog_request_name(xl, req, "AllocNamedColor", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); xlog_param(xl, "name", STRING, FETCH16(data, 8), STRING(data, 12, FETCH16(data, 8))); req->replies = 1; break; case 86: xlog_request_name(xl, req, "AllocColorCells", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); xlog_param(xl, "colors", DECU, FETCH16(data, 8)); xlog_param(xl, "planes", DECU, FETCH16(data, 10)); xlog_param(xl, "contiguous", BOOLEAN, FETCH8(data, 1)); req->replies = 1; break; case 87: xlog_request_name(xl, req, "AllocColorPlanes", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); xlog_param(xl, "colors", DECU, FETCH16(data, 8)); xlog_param(xl, "reds", DECU, FETCH16(data, 10)); xlog_param(xl, "greens", DECU, FETCH16(data, 12)); xlog_param(xl, "blues", DECU, FETCH16(data, 14)); xlog_param(xl, "contiguous", BOOLEAN, FETCH8(data, 1)); req->replies = 1; break; case 88: xlog_request_name(xl, req, "FreeColors", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); { int pos = 12; int i = 0; char buf[64]; while (pos + 4 <= len) { sprintf(buf, "pixels[%d]", i); xlog_param(xl, buf, HEX32, FETCH32(data, pos)); pos += 4; i++; if (pos < len && xlog_check_list_length(xl)) break; } } xlog_param(xl, "plane-mask", HEX32, FETCH32(data, 8)); break; case 89: xlog_request_name(xl, req, "StoreColors", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); { int pos = 8; int i = 0; char buf[64]; while (pos + 12 <= len) { sprintf(buf, "items[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_coloritem(xl, data, len, pos); xlog_set_end(xl); pos += 12; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case 90: xlog_request_name(xl, req, "StoreNamedColor", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); xlog_param(xl, "pixel", COLORMAP, FETCH32(data, 8)); xlog_param(xl, "name", STRING, FETCH16(data, 12), STRING(data, 16, FETCH16(data, 12))); xlog_param(xl, "do-red", BOOLEAN, FETCH8(data, 1) & 1); xlog_param(xl, "do-green", BOOLEAN, (FETCH8(data, 1) >> 1) & 1); xlog_param(xl, "do-blue", BOOLEAN, (FETCH8(data, 1) >> 2) & 1); break; case 91: xlog_request_name(xl, req, "QueryColors", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); { int pos = 8; int i = 0; char buf[64]; while (pos + 4 <= len) { sprintf(buf, "pixels[%d]", i); xlog_param(xl, buf, HEX32, FETCH32(data, pos)); pos += 4; i++; if (pos < len && xlog_check_list_length(xl)) break; } } req->replies = 1; break; case 92: xlog_request_name(xl, req, "LookupColor", true); xlog_param(xl, "cmap", COLORMAP, FETCH32(data, 4)); xlog_param(xl, "name", STRING, FETCH16(data, 8), STRING(data, 12, FETCH16(data, 8))); req->replies = 1; break; case 93: xlog_request_name(xl, req, "CreateCursor", true); xlog_param(xl, "cid", CURSOR, FETCH32(data, 4)); xlog_param(xl, "source", PIXMAP, FETCH32(data, 8)); xlog_param(xl, "mask", PIXMAP | SPECVAL, FETCH32(data, 12), "None", 0, (char *)NULL); xlog_param(xl, "fore-red", HEX16, FETCH16(data, 16)); xlog_param(xl, "fore-green", HEX16, FETCH16(data, 18)); xlog_param(xl, "fore-blue", HEX16, FETCH16(data, 20)); xlog_param(xl, "back-red", HEX16, FETCH16(data, 22)); xlog_param(xl, "back-green", HEX16, FETCH16(data, 24)); xlog_param(xl, "back-blue", HEX16, FETCH16(data, 26)); xlog_param(xl, "x", DECU, FETCH16(data, 28)); xlog_param(xl, "y", DECU, FETCH16(data, 30)); break; case 94: xlog_request_name(xl, req, "CreateGlyphCursor", true); xlog_param(xl, "cid", CURSOR, FETCH32(data, 4)); xlog_param(xl, "source-font", FONT, FETCH32(data, 8)); xlog_param(xl, "mask-font", FONT | SPECVAL, FETCH32(data, 12), "None", 0, (char *)NULL); xlog_param(xl, "source-char", DECU, FETCH16(data, 16)); xlog_param(xl, "mask-char", DECU, FETCH16(data, 18)); xlog_param(xl, "fore-red", HEX16, FETCH16(data, 20)); xlog_param(xl, "fore-green", HEX16, FETCH16(data, 22)); xlog_param(xl, "fore-blue", HEX16, FETCH16(data, 24)); xlog_param(xl, "back-red", HEX16, FETCH16(data, 26)); xlog_param(xl, "back-green", HEX16, FETCH16(data, 28)); xlog_param(xl, "back-blue", HEX16, FETCH16(data, 30)); break; case 95: xlog_request_name(xl, req, "FreeCursor", true); xlog_param(xl, "cursor", CURSOR, FETCH32(data, 4)); break; case 96: xlog_request_name(xl, req, "RecolorCursor", true); xlog_param(xl, "cursor", CURSOR, FETCH32(data, 4)); xlog_param(xl, "fore-red", HEX16, FETCH16(data, 8)); xlog_param(xl, "fore-green", HEX16, FETCH16(data, 10)); xlog_param(xl, "fore-blue", HEX16, FETCH16(data, 12)); xlog_param(xl, "back-red", HEX16, FETCH16(data, 14)); xlog_param(xl, "back-green", HEX16, FETCH16(data, 16)); xlog_param(xl, "back-blue", HEX16, FETCH16(data, 18)); break; case 97: xlog_request_name(xl, req, "QueryBestSize", true); xlog_param(xl, "class", ENUM | SPECVAL, FETCH8(data, 1), "Cursor", 0, "Tile", 1, "Stipple", 2, (char *)NULL); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "width", DECU, FETCH16(data, 8)); xlog_param(xl, "height", DECU, FETCH16(data, 10)); req->replies = 1; break; case 98: xlog_request_name(xl, req, "QueryExtension", true); xlog_param(xl, "name", STRING, FETCH16(data, 4), STRING(data, 8, FETCH16(data, 4))); if (!xl->overflow) req->extname = dupprintf("%.*s", READ16(data+4), data+8); { int i; for (i = 1; i < lenof(extname); i++) { if (!strcmp(req->extname, extname[i])) { req->extid = i << EXTSHIFT; break; } } } req->replies = 1; break; case 99: xlog_request_name(xl, req, "ListExtensions", true); req->replies = 1; break; case 100: xlog_request_name(xl, req, "ChangeKeyboardMapping", true); { int keycode = FETCH8(data, 4); int keycode_count = FETCH8(data, 1); int keysyms_per_keycode = FETCH8(data, 5); int pos = 8; int i; char buf[64]; while (keycode_count > 0) { sprintf(buf, "keycode[%d]", keycode); xlog_param(xl, buf, SETBEGIN); for (i = 0; i < keysyms_per_keycode; i++) { sprintf(buf, "keysyms[%d]", i); xlog_param(xl, buf, HEX32, FETCH32(data, pos)); pos += 4; } xlog_set_end(xl); i++; keycode++; keycode_count--; if (keycode_count > 0 && xlog_check_list_length(xl)) break; } } break; case 101: xlog_request_name(xl, req, "GetKeyboardMapping", true); req->first_keycode = FETCH8(data, 4); req->keycode_count = FETCH8(data, 5); xlog_param(xl, "first-keycode", DECU, req->first_keycode); xlog_param(xl, "count", DECU, req->keycode_count); req->replies = 1; break; case 102: xlog_request_name(xl, req, "ChangeKeyboardControl", true); { unsigned i = 8; unsigned bitmask = FETCH32(data, i-4); if (bitmask & 0x00000001) { xlog_param(xl, "key-click-percent", DEC8, FETCH8(data, i)); i += 4; } if (bitmask & 0x00000002) { xlog_param(xl, "bell-percent", DEC8, FETCH8(data, i)); i += 4; } if (bitmask & 0x00000004) { xlog_param(xl, "bell-pitch", DEC16, FETCH16(data, i)); i += 4; } if (bitmask & 0x00000008) { xlog_param(xl, "bell-duration", DEC16, FETCH16(data, i)); i += 4; } if (bitmask & 0x00000010) { xlog_param(xl, "led", DECU, FETCH8(data, i)); i += 4; } if (bitmask & 0x00000020) { xlog_param(xl, "led-mode", ENUM | SPECVAL, FETCH8(data, i), "Off", 0, "On", 1, (char *)NULL); i += 4; } if (bitmask & 0x00000040) { xlog_param(xl, "key", DECU, FETCH8(data, i)); i += 4; } if (bitmask & 0x00000080) { xlog_param(xl, "auto-repeat-mode", ENUM | SPECVAL, FETCH8(data, i), "Off", 0, "On", 1, "Default", 2, (char *)NULL); i += 4; } } break; case 103: xlog_request_name(xl, req, "GetKeyboardControl", true); req->replies = 1; break; case 104: xlog_request_name(xl, req, "Bell", true); xlog_param(xl, "percent", DEC8, FETCH8(data, 1)); break; case 105: xlog_request_name(xl, req, "ChangePointerControl", true); if (FETCH8(data, 10)) xlog_param(xl, "acceleration", RATIONAL16, FETCH16(data, 4), FETCH16(data, 6)); if (FETCH8(data, 11)) xlog_param(xl, "threshold", DEC16, FETCH16(data, 8)); break; case 106: xlog_request_name(xl, req, "GetPointerControl", true); req->replies = 1; break; case 107: xlog_request_name(xl, req, "SetScreenSaver", true); xlog_param(xl, "timeout", DEC16, FETCH16(data, 4)); xlog_param(xl, "interval", DEC16, FETCH16(data, 6)); xlog_param(xl, "prefer-blanking", ENUM | SPECVAL, FETCH8(data, 8), "No", 0, "Yes", 1, "Default", 2, (char *)NULL); xlog_param(xl, "allow-exposures", ENUM | SPECVAL, FETCH8(data, 9), "No", 0, "Yes", 1, "Default", 2, (char *)NULL); break; case 108: xlog_request_name(xl, req, "GetScreenSaver", true); req->replies = 1; break; case 109: xlog_request_name(xl, req, "ChangeHosts", true); xlog_param(xl, "mode", ENUM | SPECVAL, FETCH8(data, 1), "Insert", 0, "Delete", 1, (char *)NULL); xlog_param(xl, "family", ENUM | SPECVAL, FETCH8(data, 4), "Internet", 0, "DECnet", 1, "Chaos", 2, (char *)NULL); xlog_param(xl, "address", HEXSTRING1, FETCH16(data, 6), STRING(data, 8, FETCH16(data, 6))); break; case 110: xlog_request_name(xl, req, "ListHosts", true); req->replies = 1; break; case 111: xlog_request_name(xl, req, "SetAccessControl", true); xlog_param(xl, "mode", ENUM | SPECVAL, FETCH8(data, 1), "Disable", 0, "Enable", 1, (char *)NULL); break; case 112: xlog_request_name(xl, req, "SetCloseDownMode", true); xlog_param(xl, "mode", ENUM | SPECVAL, FETCH8(data, 1), "Destroy", 0, "RetainPermanent", 1, "RetainTemporary", 2, (char *)NULL); break; case 113: xlog_request_name(xl, req, "KillClient", true); xlog_param(xl, "resource", HEX32, FETCH32(data, 4)); break; case 114: xlog_request_name(xl, req, "RotateProperties", true); xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); xlog_param(xl, "delta", DEC16, FETCH16(data, 10)); { int pos = 8; int i = 0; int n = FETCH16(data, 8); char buf[64]; for (i = 0; i < n; i++) { sprintf(buf, "properties[%d]", i); xlog_param(xl, buf, ATOM, FETCH32(data, pos)); pos += 4; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 115: xlog_request_name(xl, req, "ForceScreenSaver", true); xlog_param(xl, "mode", ENUM | SPECVAL, FETCH8(data, 1), "Reset", 0, "Activate", 1, (char *)NULL); break; case 116: xlog_request_name(xl, req, "SetPointerMapping", true); { int pos = 4; int i = 0; int n = FETCH8(data, 1); char buf[64]; for (i = 0; i < n; i++) { sprintf(buf, "map[%d]", i); xlog_param(xl, buf, DECU, FETCH8(data, pos)); pos++; if (i+1 < n && xlog_check_list_length(xl)) break; } } req->replies = 1; break; case 117: xlog_request_name(xl, req, "GetPointerMapping", true); req->replies = 1; break; case 118: xlog_request_name(xl, req, "SetModifierMapping", true); { int keycodes_per_modifier = FETCH8(data, 1); int pos = 4; int mod, i; char buf[64]; for (mod = 0; mod < 8; mod++) { sprintf(buf, "modifier[%d]", mod); xlog_param(xl, buf, SETBEGIN); for (i = 0; i < keycodes_per_modifier; i++) { sprintf(buf, "keycodes[%d]", i); xlog_param(xl, buf, DECU, FETCH8(data, pos)); pos++; } xlog_set_end(xl); if (mod+1 < 8 && xlog_check_list_length(xl)) break; } } req->replies = 1; break; case 119: xlog_request_name(xl, req, "GetModifierMapping", true); req->replies = 1; break; case 127: xlog_request_name(xl, req, "NoOperation", true); break; case EXT_BIGREQUESTS | 0: xlog_request_name(xl, req, "BigReqEnable", true); req->replies = 1; break; case EXT_GENERICEVENT | 0: xlog_request_name(xl, req, "GEQueryVersion", true); xlog_param(xl, "client-major-version", DECU, FETCH16(data, 4)); xlog_param(xl, "client-minor-version", DECU, FETCH16(data, 6)); req->replies = 1; break; case EXT_MITSHM | 0: xlog_request_name(xl, req, "ShmQueryVersion", true); req->replies = 1; break; case EXT_MITSHM | 1: xlog_request_name(xl, req, "ShmAttach", true); xlog_param(xl, "shmseg", HEX32, FETCH32(data, 4)); xlog_param(xl, "shmid", HEX32, FETCH32(data, 8)); xlog_param(xl, "read-only", BOOLEAN, FETCH8(data, 12)); break; case EXT_MITSHM | 2: xlog_request_name(xl, req, "ShmDetach", true); xlog_param(xl, "shmseg", HEX32, FETCH32(data, 4)); break; case EXT_MITSHM | 3: xlog_request_name(xl, req, "ShmPutImage", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 8)); xlog_param(xl, "total-width", DECU, FETCH16(data, 12)); xlog_param(xl, "total-height", DECU, FETCH16(data, 14)); xlog_param(xl, "src-x", DECU, FETCH16(data, 16)); xlog_param(xl, "src-y", DECU, FETCH16(data, 18)); xlog_param(xl, "src-width", DECU, FETCH16(data, 20)); xlog_param(xl, "src-height", DECU, FETCH16(data, 22)); xlog_param(xl, "dst-x", DEC16, FETCH16(data, 24)); xlog_param(xl, "dst-y", DEC16, FETCH16(data, 26)); xlog_param(xl, "depth", DECU, FETCH8(data, 28)); xlog_param(xl, "format", ENUM | SPECVAL, FETCH8(data, 29), "Bitmap", 0, "XYPixmap", 1, "ZPixmap", 2, (char *)NULL); xlog_param(xl, "send-event", BOOLEAN, FETCH8(data, 30)); xlog_param(xl, "shmseg", HEX32, FETCH32(data, 32)); xlog_param(xl, "offset", HEX32, FETCH32(data, 36)); break; case EXT_MITSHM | 4: xlog_request_name(xl, req, "ShmGetImage", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); xlog_param(xl, "x", DEC16, FETCH16(data, 8)); xlog_param(xl, "y", DEC16, FETCH16(data, 10)); xlog_param(xl, "width", DECU, FETCH16(data, 12)); xlog_param(xl, "height", DECU, FETCH16(data, 14)); xlog_param(xl, "plane-mask", HEX32, FETCH32(data, 16)); xlog_param(xl, "format", ENUM | SPECVAL, FETCH8(data, 20), "Bitmap", 0, "XYPixmap", 1, "ZPixmap", 2, (char *)NULL); xlog_param(xl, "shmseg", HEX32, FETCH32(data, 24)); xlog_param(xl, "offset", HEX32, FETCH32(data, 28)); req->replies = 1; break; case EXT_MITSHM | 5: xlog_request_name(xl, req, "ShmCreatePixmap", true); xlog_param(xl, "pid", PIXMAP, FETCH32(data, 4)); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 8)); xlog_param(xl, "width", DECU, FETCH16(data, 12)); xlog_param(xl, "height", DECU, FETCH16(data, 14)); xlog_param(xl, "depth", DECU, FETCH8(data, 16)); xlog_param(xl, "shmseg", HEX32, FETCH32(data, 20)); xlog_param(xl, "offset", HEX32, FETCH32(data, 24)); break; case EXT_RENDER | 0: xlog_request_name(xl, req, "RenderQueryVersion", true); xlog_param(xl, "client-major-version", DECU, FETCH32(data, 4)); xlog_param(xl, "client-minor-version", DECU, FETCH32(data, 8)); req->replies = 1; break; case EXT_RENDER | 1: xlog_request_name(xl, req, "RenderQueryPictFormats", true); req->replies = 1; break; case EXT_RENDER | 2: xlog_request_name(xl, req, "RenderQueryPictIndexValues", true); xlog_param(xl, "format", PICTFORMAT, FETCH32(data, 4)); req->replies = 1; break; case EXT_RENDER | 3: xlog_request_name(xl, req, "RenderQueryDithers", true); /* * This request is not supported by X.Org or Xlib at the * time of writing, so I can't be certain of its contents * format. */ xlog_param(xl, "", NOTEVENEQUALSIGN); req->replies = 1; break; case EXT_RENDER | 4: case EXT_RENDER | 5: { unsigned i, bitmask; switch (req->opcode) { case EXT_RENDER | 4: xlog_request_name(xl, req, "RenderCreatePicture", true); xlog_param(xl, "pid", PICTURE, FETCH32(data, 4)); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 8)); xlog_param(xl, "format", PICTFORMAT, FETCH32(data, 12)); i = 20; break; default /* case EXT_RENDER | 5 */ : xlog_request_name(xl, req, "RenderChangePicture", true); xlog_param(xl, "picture", PICTURE, FETCH32(data, 4)); i = 12; break; } bitmask = FETCH32(data, i-4); if (bitmask & 0x00000001) { xlog_param(xl, "repeat", ENUM | SPECVAL, FETCH32(data, i), "None", 0, "Normal", 1, "Pad", 2, "Reflect", 3, (char *)NULL); i += 4; } if (bitmask & 0x00000002) { xlog_param(xl, "alpha-map", PICTURE | SPECVAL, FETCH32(data, i), "None", 0, (char *)NULL); i += 4; } if (bitmask & 0x00000004) { xlog_param(xl, "alpha-x-origin", DEC16, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000008) { xlog_param(xl, "alpha-y-origin", DEC16, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000010) { xlog_param(xl, "clip-x-origin", DEC16, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000020) { xlog_param(xl, "clip-y-origin", DEC16, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000040) { xlog_param(xl, "clip-mask", PIXMAP | SPECVAL, FETCH32(data, i), "None", 0, (char *)NULL); i += 4; } if (bitmask & 0x00000080) { xlog_param(xl, "graphics-exposures", BOOLEAN, FETCH32(data, i)); i += 4; } if (bitmask & 0x00000100) { xlog_param(xl, "subwindow-mode", ENUM | SPECVAL, FETCH32(data, i), "ClipByChildren", 0, "IncludeInferiors", 1, (char *)NULL); i += 4; } if (bitmask & 0x00000200) { xlog_param(xl, "poly-edge", ENUM | SPECVAL, FETCH32(data, i), "Sharp", 0, "Smooth", 1, (char *)NULL); i += 4; } if (bitmask & 0x00000400) { xlog_param(xl, "poly-mode", ENUM | SPECVAL, FETCH32(data, i), "Precise", 0, "Imprecise", 1, (char *)NULL); i += 4; } if (bitmask & 0x00000800) { xlog_param(xl, "dither", ATOM | SPECVAL, FETCH32(data, i), "None", 0, (char *)NULL); i += 4; } if (bitmask & 0x00001000) { xlog_param(xl, "component-alpha", BOOLEAN, FETCH32(data, i)); i += 4; } } break; case EXT_RENDER | 6: xlog_request_name(xl, req, "RenderSetPictureClipRectangles", true); xlog_param(xl, "picture", PICTURE, FETCH32(data, 4)); xlog_param(xl, "clip-x-origin", DEC16, FETCH16(data, 8)); xlog_param(xl, "clip-y-origin", DEC16, FETCH16(data, 10)); { int pos = 12; int i = 0; char buf[64]; while (pos + 8 <= len) { sprintf(buf, "rectangles[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_rectangle(xl, data, len, pos); xlog_set_end(xl); pos += 8; if (pos < len && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 7: xlog_request_name(xl, req, "RenderFreePicture", true); xlog_param(xl, "picture", PICTURE, FETCH32(data, 4)); break; case EXT_RENDER | 8: xlog_request_name(xl, req, "RenderComposite", true); xlog_param(xl, "op", ENUM | SPECVAL, FETCH8(data, 4), "Clear", 0, "Src", 1, "Dst", 2, "Over", 3, "OverReverse", 4, "In", 5, "InReverse", 6, "Out", 7, "OutReverse", 8, "Atop", 9, "AtopReverse", 10, "Xor", 11, "Add", 12, "Saturate", 13, "DisjointClear", 0x10, "DisjointSrc", 0x11, "DisjointDst", 0x12, "DisjointOver", 0x13, "DisjointOverReverse", 0x14, "DisjointIn", 0x15, "DisjointInReverse", 0x16, "DisjointOut", 0x17, "DisjointOutReverse", 0x18, "DisjointAtop", 0x19, "DisjointAtopReverse", 0x1a, "DisjointXor", 0x1b, "ConjointClear", 0x20, "ConjointSrc", 0x21, "ConjointDst", 0x22, "ConjointOver", 0x23, "ConjointOverReverse", 0x24, "ConjointIn", 0x25, "ConjointInReverse", 0x26, "ConjointOut", 0x27, "ConjointOutReverse", 0x28, "ConjointAtop", 0x29, "ConjointAtopReverse", 0x2a, "ConjointXor", 0x2b); xlog_param(xl, "src", PICTURE, FETCH32(data, 8)); xlog_param(xl, "mask", PICTURE | SPECVAL, FETCH32(data, 12), "None", 0, (char *)NULL); xlog_param(xl, "dst", PICTURE, FETCH32(data, 16)); xlog_param(xl, "src-x", DEC16, FETCH16(data, 20)); xlog_param(xl, "src-y", DEC16, FETCH16(data, 22)); xlog_param(xl, "mask-x", DEC16, FETCH16(data, 24)); xlog_param(xl, "mask-y", DEC16, FETCH16(data, 26)); xlog_param(xl, "dst-x", DEC16, FETCH16(data, 28)); xlog_param(xl, "dst-y", DEC16, FETCH16(data, 30)); xlog_param(xl, "width", DECU, FETCH16(data, 32)); xlog_param(xl, "height", DECU, FETCH16(data, 34)); break; case EXT_RENDER | 9: xlog_request_name(xl, req, "RenderScale", true); xlog_param(xl, "src", PICTURE, FETCH32(data, 4)); xlog_param(xl, "dst", PICTURE, FETCH32(data, 8)); xlog_param(xl, "color-scale", HEX32, FETCH32(data, 12)); xlog_param(xl, "alpha-scale", HEX32, FETCH32(data, 16)); xlog_param(xl, "src-x", DEC16, FETCH16(data, 20)); xlog_param(xl, "src-y", DEC16, FETCH16(data, 22)); xlog_param(xl, "dst-x", DEC16, FETCH16(data, 24)); xlog_param(xl, "dst-y", DEC16, FETCH16(data, 26)); xlog_param(xl, "width", DECU, FETCH16(data, 28)); xlog_param(xl, "height", DECU, FETCH16(data, 30)); break; case EXT_RENDER | 10: xlog_request_name(xl, req, "RenderTrapezoids", true); xlog_param(xl, "op", ENUM | SPECVAL, FETCH8(data, 4), "Clear", 0, "Src", 1, "Dst", 2, "Over", 3, "OverReverse", 4, "In", 5, "InReverse", 6, "Out", 7, "OutReverse", 8, "Atop", 9, "AtopReverse", 10, "Xor", 11, "Add", 12, "Saturate", 13, "DisjointClear", 0x10, "DisjointSrc", 0x11, "DisjointDst", 0x12, "DisjointOver", 0x13, "DisjointOverReverse", 0x14, "DisjointIn", 0x15, "DisjointInReverse", 0x16, "DisjointOut", 0x17, "DisjointOutReverse", 0x18, "DisjointAtop", 0x19, "DisjointAtopReverse", 0x1a, "DisjointXor", 0x1b, "ConjointClear", 0x20, "ConjointSrc", 0x21, "ConjointDst", 0x22, "ConjointOver", 0x23, "ConjointOverReverse", 0x24, "ConjointIn", 0x25, "ConjointInReverse", 0x26, "ConjointOut", 0x27, "ConjointOutReverse", 0x28, "ConjointAtop", 0x29, "ConjointAtopReverse", 0x2a, "ConjointXor", 0x2b); xlog_param(xl, "src", PICTURE, FETCH32(data, 8)); xlog_param(xl, "src-x", DEC16, FETCH16(data, 20)); xlog_param(xl, "src-y", DEC16, FETCH16(data, 22)); xlog_param(xl, "dst", PICTURE, FETCH32(data, 12)); xlog_param(xl, "mask-format", PICTFORMAT | SPECVAL, FETCH32(data, 16), "None", 0, (char *)NULL); { int pos = 24; int i = 0; char buf[64]; while (pos + 40 <= len) { sprintf(buf, "trapezoids[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "top", FIXED, FETCH32(data, pos)); xlog_param(xl, "bottom", FIXED, FETCH32(data, pos+4)); xlog_param(xl, "left.p1.x", FIXED, FETCH32(data, pos+8)); xlog_param(xl, "left.p1.y", FIXED, FETCH32(data, pos+12)); xlog_param(xl, "left.p2.x", FIXED, FETCH32(data, pos+16)); xlog_param(xl, "left.p2.y", FIXED, FETCH32(data, pos+20)); xlog_param(xl, "right.p1.x", FIXED, FETCH32(data, pos+24)); xlog_param(xl, "right.p1.y", FIXED, FETCH32(data, pos+28)); xlog_param(xl, "right.p2.x", FIXED, FETCH32(data, pos+32)); xlog_param(xl, "right.p2.y", FIXED, FETCH32(data, pos+36)); xlog_set_end(xl); pos += 40; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 11: xlog_request_name(xl, req, "RenderTriangles", true); xlog_param(xl, "op", ENUM | SPECVAL, FETCH8(data, 4), "Clear", 0, "Src", 1, "Dst", 2, "Over", 3, "OverReverse", 4, "In", 5, "InReverse", 6, "Out", 7, "OutReverse", 8, "Atop", 9, "AtopReverse", 10, "Xor", 11, "Add", 12, "Saturate", 13, "DisjointClear", 0x10, "DisjointSrc", 0x11, "DisjointDst", 0x12, "DisjointOver", 0x13, "DisjointOverReverse", 0x14, "DisjointIn", 0x15, "DisjointInReverse", 0x16, "DisjointOut", 0x17, "DisjointOutReverse", 0x18, "DisjointAtop", 0x19, "DisjointAtopReverse", 0x1a, "DisjointXor", 0x1b, "ConjointClear", 0x20, "ConjointSrc", 0x21, "ConjointDst", 0x22, "ConjointOver", 0x23, "ConjointOverReverse", 0x24, "ConjointIn", 0x25, "ConjointInReverse", 0x26, "ConjointOut", 0x27, "ConjointOutReverse", 0x28, "ConjointAtop", 0x29, "ConjointAtopReverse", 0x2a, "ConjointXor", 0x2b); xlog_param(xl, "src", PICTURE, FETCH32(data, 8)); xlog_param(xl, "src-x", DEC16, FETCH16(data, 20)); xlog_param(xl, "src-y", DEC16, FETCH16(data, 22)); xlog_param(xl, "dst", PICTURE, FETCH32(data, 12)); xlog_param(xl, "mask-format", PICTFORMAT | SPECVAL, FETCH32(data, 16), "None", 0, (char *)NULL); { int pos = 24; int i = 0; char buf[64]; while (pos + 24 <= len) { sprintf(buf, "triangles[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "p1.x", FIXED, FETCH32(data, pos)); xlog_param(xl, "p1.y", FIXED, FETCH32(data, pos+4)); xlog_param(xl, "p2.x", FIXED, FETCH32(data, pos+8)); xlog_param(xl, "p2.y", FIXED, FETCH32(data, pos+12)); xlog_param(xl, "p3.x", FIXED, FETCH32(data, pos+16)); xlog_param(xl, "p3.y", FIXED, FETCH32(data, pos+20)); xlog_set_end(xl); pos += 24; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 12: case EXT_RENDER | 13: switch (req->opcode) { case EXT_RENDER | 12: xlog_request_name(xl, req, "RenderTriStrip", true); break; case EXT_RENDER | 13: xlog_request_name(xl, req, "RenderTriFan", true); break; } xlog_param(xl, "op", ENUM | SPECVAL, FETCH8(data, 4), "Clear", 0, "Src", 1, "Dst", 2, "Over", 3, "OverReverse", 4, "In", 5, "InReverse", 6, "Out", 7, "OutReverse", 8, "Atop", 9, "AtopReverse", 10, "Xor", 11, "Add", 12, "Saturate", 13, "DisjointClear", 0x10, "DisjointSrc", 0x11, "DisjointDst", 0x12, "DisjointOver", 0x13, "DisjointOverReverse", 0x14, "DisjointIn", 0x15, "DisjointInReverse", 0x16, "DisjointOut", 0x17, "DisjointOutReverse", 0x18, "DisjointAtop", 0x19, "DisjointAtopReverse", 0x1a, "DisjointXor", 0x1b, "ConjointClear", 0x20, "ConjointSrc", 0x21, "ConjointDst", 0x22, "ConjointOver", 0x23, "ConjointOverReverse", 0x24, "ConjointIn", 0x25, "ConjointInReverse", 0x26, "ConjointOut", 0x27, "ConjointOutReverse", 0x28, "ConjointAtop", 0x29, "ConjointAtopReverse", 0x2a, "ConjointXor", 0x2b); xlog_param(xl, "src", PICTURE, FETCH32(data, 8)); xlog_param(xl, "src-x", DEC16, FETCH16(data, 20)); xlog_param(xl, "src-y", DEC16, FETCH16(data, 22)); xlog_param(xl, "dst", PICTURE, FETCH32(data, 12)); xlog_param(xl, "mask-format", PICTFORMAT | SPECVAL, FETCH32(data, 16), "None", 0, (char *)NULL); { int pos = 24; int i = 0; char buf[64]; while (pos + 8 <= len) { sprintf(buf, "points[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "x", FIXED, FETCH32(data, pos)); xlog_param(xl, "y", FIXED, FETCH32(data, pos+4)); xlog_set_end(xl); pos += 8; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 14: xlog_request_name(xl, req, "RenderColorTrapezoids", true); /* * This request is not supported by X.Org or Xlib at the * time of writing, so I can't be certain of its contents * format. */ xlog_param(xl, "", NOTEVENEQUALSIGN); break; case EXT_RENDER | 15: xlog_request_name(xl, req, "RenderColorTriangles", true); /* * This request is not supported by X.Org or Xlib at the * time of writing, so I can't be certain of its contents * format. */ xlog_param(xl, "", NOTEVENEQUALSIGN); break; case EXT_RENDER | 16: xlog_request_name(xl, req, "RenderTransform", true); /* * This request is not supported by X.Org or Xlib at the * time of writing, so I can't be certain of its contents * format. */ xlog_param(xl, "", NOTEVENEQUALSIGN); break; case EXT_RENDER | 17: case EXT_RENDER | 18: switch (req->opcode) { case EXT_RENDER | 17: xlog_request_name(xl, req, "RenderCreateGlyphSet", true); xlog_param(xl, "gsid", GLYPHSET, FETCH32(data, 4)); xlog_param(xl, "format", PICTFORMAT, FETCH32(data, 8)); break; case EXT_RENDER | 18: xlog_request_name(xl, req, "RenderReferenceGlyphSet", true); xlog_param(xl, "gsid", GLYPHSET, FETCH32(data, 4)); xlog_param(xl, "existing", GLYPHSET, FETCH32(data, 8)); } /* * Now remember the depth for this glyphset, by reading it * out of either the PICTFORMAT or the GLYPHSET. */ { struct resdepth *existing; struct resdepth *gsd; struct resdepth *old; unsigned long oldid = FETCH32(data, 8); existing = find234(xl->resdepths, &oldid, resdepthfind); if (existing) { gsd = snew(struct resdepth); gsd->resource = FETCH32(data, 4); gsd->depth = existing->depth; /* * Find any previous entry for this glyphset id, and * override it. */ old = del234(xl->resdepths, gsd); sfree(old); /* * Now add the new one. */ add234(xl->resdepths, gsd); } } break; case EXT_RENDER | 19: xlog_request_name(xl, req, "RenderFreeGlyphSet", true); xlog_param(xl, "glyphset", GLYPHSET, FETCH32(data, 4)); break; case EXT_RENDER | 20: xlog_request_name(xl, req, "RenderAddGlyphs", true); xlog_param(xl, "glyphset", GLYPHSET, FETCH32(data, 4)); { int pos = 12, whpos; int i = 0; char buf[64]; int n = FETCH32(data, pos-4); int depth; for (i = 0; i < n; i++) { sprintf(buf, "glyphids[%d]", i); xlog_param(xl, buf, HEX32, FETCH32(data, pos)); pos += 4; if (i+1 < n && xlog_check_list_length(xl)) break; } whpos = pos; for (i = 0; i < n; i++) { sprintf(buf, "glyphs[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "width", DECU, FETCH16(data, pos)); xlog_param(xl, "height", DECU, FETCH16(data, pos+2)); xlog_param(xl, "x", DEC16, FETCH16(data, pos+4)); xlog_param(xl, "y", DEC16, FETCH16(data, pos+6)); xlog_param(xl, "off-x", DEC16, FETCH16(data, pos+8)); xlog_param(xl, "off-y", DEC16, FETCH16(data, pos+10)); xlog_set_end(xl); pos += 12; if (i+1 < n && xlog_check_list_length(xl)) break; } { unsigned long oldid = FETCH32(data, 4); struct resdepth *rd; rd = find234(xl->resdepths, &oldid, resdepthfind); if (rd) depth = rd->depth; else depth = 0; } for (i = 0; i < n; i++) { int ret; sprintf(buf, "glyphimages[%d]", i); ret = xlog_image_data(xl, buf, data, len, pos, 2, FETCH16(data, whpos+12*i), FETCH16(data, whpos+12*i+2), depth); if (ret < 0) break; /* don't know how to advance to next image */ pos += (ret + 3) &~ 3; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 21: xlog_request_name(xl, req, "RenderAddGlyphsFromPicture", true); /* * This request is not supported by X.Org or Xlib at the * time of writing, so I can't be certain of its contents * format. */ xlog_param(xl, "", NOTEVENEQUALSIGN); break; case EXT_RENDER | 22: xlog_request_name(xl, req, "RenderFreeGlyphs", true); xlog_param(xl, "glyphset", GLYPHSET, FETCH32(data, 4)); { int pos = 12; int i = 0; char buf[64]; int n = FETCH32(data, pos-4); for (i = 0; i < n; i++) { sprintf(buf, "glyphs[%d]", i); xlog_param(xl, buf, HEX32, FETCH32(data, pos)); pos += 4; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 23: case EXT_RENDER | 24: case EXT_RENDER | 25: switch (req->opcode) { case EXT_RENDER | 23: xlog_request_name(xl, req, "RenderCompositeGlyphs8", true); break; case EXT_RENDER | 24: xlog_request_name(xl, req, "RenderCompositeGlyphs16", true); break; case EXT_RENDER | 25: xlog_request_name(xl, req, "RenderCompositeGlyphs32", true); break; } xlog_param(xl, "op", ENUM | SPECVAL, FETCH8(data, 4), "Clear", 0, "Src", 1, "Dst", 2, "Over", 3, "OverReverse", 4, "In", 5, "InReverse", 6, "Out", 7, "OutReverse", 8, "Atop", 9, "AtopReverse", 10, "Xor", 11, "Add", 12, "Saturate", 13, "DisjointClear", 0x10, "DisjointSrc", 0x11, "DisjointDst", 0x12, "DisjointOver", 0x13, "DisjointOverReverse", 0x14, "DisjointIn", 0x15, "DisjointInReverse", 0x16, "DisjointOut", 0x17, "DisjointOutReverse", 0x18, "DisjointAtop", 0x19, "DisjointAtopReverse", 0x1a, "DisjointXor", 0x1b, "ConjointClear", 0x20, "ConjointSrc", 0x21, "ConjointDst", 0x22, "ConjointOver", 0x23, "ConjointOverReverse", 0x24, "ConjointIn", 0x25, "ConjointInReverse", 0x26, "ConjointOut", 0x27, "ConjointOutReverse", 0x28, "ConjointAtop", 0x29, "ConjointAtopReverse", 0x2a, "ConjointXor", 0x2b); xlog_param(xl, "src", PICTURE, FETCH32(data, 8)); xlog_param(xl, "dst", PICTURE, FETCH32(data, 12)); xlog_param(xl, "mask-format", PICTFORMAT | SPECVAL, FETCH32(data, 16), "None", 0, (char *)NULL); xlog_param(xl, "glyphset", GLYPHABLE, FETCH32(data, 20)); xlog_param(xl, "src-x", DEC16, FETCH16(data, 24)); xlog_param(xl, "src-y", DEC16, FETCH32(data, 26)); { int pos = 28; int i = 0; /* * We now expect a series of GLYPHITEMs of the * appropriate size packed tightly together. Each of * these starts with an 8-byte header consisting of a * length byte, three padding bytes, and 16-bit delta x * and y values. If L==255, this is followed by a * four-byte GLYPHSET identifier; otherwise it's * followed by L glyph ids of the appropriate size. */ while (pos < len) { char buf[64]; int tilen = FETCH8(data, pos); sprintf(buf, "items[%d]", i); xlog_param(xl, buf, SETBEGIN); if (tilen == 255) { xlog_param(xl, "glyphset", GLYPHSET, FETCH8(data, pos+8)); pos += 12; } else { xlog_param(xl, "delta-x", DEC16, FETCH16(data, pos+4)); xlog_param(xl, "delta-y", DEC16, FETCH16(data, pos+6)); pos += 8; switch (req->opcode) { case EXT_RENDER | 23: xlog_param(xl, "string", HEXSTRING1, tilen, STRING(data, pos, tilen)); pos += tilen; break; case EXT_RENDER | 24: xlog_param(xl, "string", HEXSTRING2, tilen, STRING(data, pos, tilen*2)); pos += tilen*2; break; case EXT_RENDER | 25: xlog_param(xl, "string", HEXSTRING4, tilen, STRING(data, pos, tilen*4)); pos += tilen*4; break; } pos = (pos + 3) & ~3; } xlog_set_end(xl); i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 26: xlog_request_name(xl, req, "RenderFillRectangles", true); xlog_param(xl, "op", ENUM | SPECVAL, FETCH8(data, 4), "Clear", 0, "Src", 1, "Dst", 2, "Over", 3, "OverReverse", 4, "In", 5, "InReverse", 6, "Out", 7, "OutReverse", 8, "Atop", 9, "AtopReverse", 10, "Xor", 11, "Add", 12, "Saturate", 13, "DisjointClear", 0x10, "DisjointSrc", 0x11, "DisjointDst", 0x12, "DisjointOver", 0x13, "DisjointOverReverse", 0x14, "DisjointIn", 0x15, "DisjointInReverse", 0x16, "DisjointOut", 0x17, "DisjointOutReverse", 0x18, "DisjointAtop", 0x19, "DisjointAtopReverse", 0x1a, "DisjointXor", 0x1b, "ConjointClear", 0x20, "ConjointSrc", 0x21, "ConjointDst", 0x22, "ConjointOver", 0x23, "ConjointOverReverse", 0x24, "ConjointIn", 0x25, "ConjointInReverse", 0x26, "ConjointOut", 0x27, "ConjointOutReverse", 0x28, "ConjointAtop", 0x29, "ConjointAtopReverse", 0x2a, "ConjointXor", 0x2b); xlog_param(xl, "dst", PICTURE, FETCH32(data, 8)); xlog_param(xl, "color", SETBEGIN); xlog_param(xl, "red", HEX16, FETCH16(data, 12)); xlog_param(xl, "green", HEX16, FETCH16(data, 14)); xlog_param(xl, "blue", HEX16, FETCH16(data, 16)); xlog_param(xl, "alpha", HEX16, FETCH16(data, 18)); xlog_set_end(xl); { int pos = 20; int i = 0; char buf[64]; while (pos + 8 <= len) { sprintf(buf, "rectangles[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_rectangle(xl, data, len, pos); xlog_set_end(xl); pos += 8; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 27: xlog_request_name(xl, req, "RenderCreateCursor", true); xlog_param(xl, "cid", CURSOR, FETCH32(data, 4)); xlog_param(xl, "src", PICTURE, FETCH32(data, 8)); xlog_param(xl, "x", DECU, FETCH16(data, 12)); xlog_param(xl, "y", DECU, FETCH16(data, 14)); break; case EXT_RENDER | 28: xlog_request_name(xl, req, "RenderSetPictureTransform", true); xlog_param(xl, "picture", PICTURE, FETCH32(data, 4)); xlog_param(xl, "transform", SETBEGIN); xlog_param(xl, "p11", FIXED, FETCH32(data, 8)); xlog_param(xl, "p12", FIXED, FETCH32(data, 12)); xlog_param(xl, "p13", FIXED, FETCH32(data, 16)); xlog_param(xl, "p21", FIXED, FETCH32(data, 20)); xlog_param(xl, "p22", FIXED, FETCH32(data, 24)); xlog_param(xl, "p23", FIXED, FETCH32(data, 28)); xlog_param(xl, "p31", FIXED, FETCH32(data, 32)); xlog_param(xl, "p32", FIXED, FETCH32(data, 36)); xlog_param(xl, "p33", FIXED, FETCH32(data, 40)); xlog_set_end(xl); break; case EXT_RENDER | 29: xlog_request_name(xl, req, "RenderQueryFilters", true); xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); break; case EXT_RENDER | 30: xlog_request_name(xl, req, "RenderSetPictureFilter", true); xlog_param(xl, "picture", PICTURE, FETCH32(data, 4)); xlog_param(xl, "name", STRING, FETCH16(data, 8), STRING(data, 12, FETCH16(data, 8))); { int pos = (12 + FETCH16(data, 8) + 3) & ~3; int i = 0; char buf[64]; while (pos + 4 <= len) { sprintf(buf, "values[%d]", i); xlog_param(xl, buf, FIXED, FETCH32(data, pos)); pos += 4; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 31: xlog_request_name(xl, req, "RenderCreateAnimCursor", true); xlog_param(xl, "cid", CURSOR, FETCH32(data, 4)); { int pos = 8; int i = 0; char buf[64]; while (pos + 8 <= len) { sprintf(buf, "cursors[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "cursor", CURSOR, FETCH32(data, pos)); xlog_param(xl, "delay", DECU, FETCH32(data, pos+4)); xlog_set_end(xl); pos += 8; i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 32: xlog_request_name(xl, req, "RenderAddTraps", true); xlog_param(xl, "picture", PICTURE, FETCH32(data, 4)); xlog_param(xl, "off-x", DEC16, FETCH16(data, 8)); xlog_param(xl, "off-y", DEC16, FETCH16(data, 10)); { int pos = 12; int i = 0; char buf[64]; while (pos + 24 <= len) { sprintf(buf, "trapezoids[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "top", SETBEGIN); xlog_param(xl, "l", FIXED, FETCH32(data, pos)); xlog_param(xl, "r", FIXED, FETCH32(data, pos+4)); xlog_param(xl, "y", FIXED, FETCH32(data, pos+8)); xlog_set_end(xl); pos += 12; xlog_param(xl, "bot", SETBEGIN); xlog_param(xl, "l", FIXED, FETCH32(data, pos)); xlog_param(xl, "r", FIXED, FETCH32(data, pos+4)); xlog_param(xl, "y", FIXED, FETCH32(data, pos+8)); xlog_set_end(xl); pos += 12; xlog_set_end(xl); i++; if (pos < len && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 33: xlog_request_name(xl, req, "RenderCreateSolidFill", true); xlog_param(xl, "pid", PICTURE, FETCH32(data, 4)); xlog_param(xl, "color", SETBEGIN); xlog_param(xl, "red", HEX16, FETCH16(data, 8)); xlog_param(xl, "green", HEX16, FETCH16(data, 10)); xlog_param(xl, "blue", HEX16, FETCH16(data, 12)); xlog_param(xl, "alpha", HEX16, FETCH16(data, 14)); xlog_set_end(xl); break; case EXT_RENDER | 34: case EXT_RENDER | 35: case EXT_RENDER | 36: { int pos, n, i; char buf[64]; switch (req->opcode) { case EXT_RENDER | 34: xlog_request_name(xl, req, "RenderCreateLinearGradient", true); xlog_param(xl, "pid", PICTURE, FETCH32(data, 4)); xlog_param(xl, "p1", SETBEGIN); xlog_param(xl, "x", FIXED, FETCH32(data, 8)); xlog_param(xl, "y", FIXED, FETCH32(data, 12)); xlog_set_end(xl); xlog_param(xl, "p2", SETBEGIN); xlog_param(xl, "x", FIXED, FETCH32(data, 16)); xlog_param(xl, "y", FIXED, FETCH32(data, 20)); xlog_set_end(xl); pos = 28; break; case EXT_RENDER | 35: xlog_request_name(xl, req, "RenderCreateRadialGradient", true); xlog_param(xl, "pid", PICTURE, FETCH32(data, 4)); xlog_param(xl, "inner_center", SETBEGIN); xlog_param(xl, "x", FIXED, FETCH32(data, 8)); xlog_param(xl, "y", FIXED, FETCH32(data, 12)); xlog_set_end(xl); xlog_param(xl, "outer_center", SETBEGIN); xlog_param(xl, "x", FIXED, FETCH32(data, 16)); xlog_param(xl, "y", FIXED, FETCH32(data, 20)); xlog_set_end(xl); xlog_param(xl, "inner_radius", FIXED, FETCH32(data, 24)); xlog_param(xl, "outer_radius", FIXED, FETCH32(data, 28)); pos = 36; break; default /* case EXT_RENDER | 36 */: xlog_request_name(xl, req, "RenderCreateConicalGradient", true); xlog_param(xl, "pid", PICTURE, FETCH32(data, 4)); xlog_param(xl, "center", SETBEGIN); xlog_param(xl, "x", FIXED, FETCH32(data, 8)); xlog_param(xl, "y", FIXED, FETCH32(data, 12)); xlog_set_end(xl); xlog_param(xl, "angle", FIXED, FETCH32(data, 16)); pos = 24; break; } n = FETCH32(data, pos-4); for (i = 0; i < n; i++) { sprintf(buf, "stops[%d]", i); xlog_param(xl, buf, FIXED, FETCH32(data, pos)); pos += 4; if (i+1 < n && xlog_check_list_length(xl)) break; } for (i = 0; i < n; i++) { sprintf(buf, "stop_colors[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "red", HEX16, FETCH16(data, pos)); xlog_param(xl, "green", HEX16, FETCH16(data, pos+2)); xlog_param(xl, "blue", HEX16, FETCH16(data, pos+4)); xlog_param(xl, "alpha", HEX16, FETCH16(data, pos+6)); xlog_set_end(xl); pos += 8; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; default: if (data[0] >= 128) { /* * Extension opcode. */ int opcode = data[0] - 128; char buf[64]; if (xl->extreqs[opcode]) { sprintf(buf, "%s:UnknownExtensionRequest%d", xl->extreqs[opcode], data[1]); } else { sprintf(buf, "%d:UnknownExtensionRequest%d", data[0], data[1]); } xlog_request_name(xl, req, buf, false); xlog_param(xl, "bytes", DECU, len); } else { char buf[64]; sprintf(buf, "UnknownRequest%d", data[0]); xlog_request_name(xl, req, buf, false); xlog_param(xl, "bytes", DECU, len); } break; } xlog_request_done(xl, req); } static void xlog_do_reply(struct xlog *xl, struct request *req, const void *vdata, int len) { const unsigned char *data = (const unsigned char *)vdata; if (data && !req) { xlog_new_line(xl); fprintf(xl->xs->outfp, "--- reply received for unknown request" " sequence number %lu\n", (unsigned long)FETCH16(data, 2)); fflush(xl->xs->outfp); return; } strbuf_clear(xl->textbuf); xl->overflow = false; xlog_respond_to(xl, req); if (req->replies == 2) req->replies = 3; /* we've now seen a reply */ xlog_reply_begin(xl); if (!data) { /* * This call is notifying us that the sequence numbering in * the server-to-client stream has now gone past the number * of this request. If it was a multi-reply request to which * we've seen at least one reply already, this is normal and * expected, so we discard the request from the queue and * continue. Otherwise, we print a notification that * something odd happened. */ if (req->replies != 3) put_datastr(xl->textbuf, ""); req->replies = 1; /* force discard */ } else switch (req->opcode) { case 3: /* GetWindowAttributes */ xlog_param(xl, "visual", VISUALID, FETCH32(data, 8)); xlog_param(xl, "class", ENUM | SPECVAL, FETCH16(data, 12), "InputOutput", 1, "InputOnly", 2, (char *)NULL); xlog_param(xl, "bit-gravity", ENUM | SPECVAL, FETCH8(data, 14), "Forget", 0, "NorthWest", 1, "North", 2, "NorthEast", 3, "West", 4, "Center", 5, "East", 6, "SouthWest", 7, "South", 8, "SouthEast", 9, "Static", 10, (char *)NULL); xlog_param(xl, "win-gravity", ENUM | SPECVAL, FETCH8(data, 15), "Unmap", 0, "NorthWest", 1, "North", 2, "NorthEast", 3, "West", 4, "Center", 5, "East", 6, "SouthWest", 7, "South", 8, "SouthEast", 9, "Static", 10, (char *)NULL); xlog_param(xl, "backing-store", ENUM | SPECVAL, FETCH8(data, 1), "NotUseful", 0, "WhenMapped", 1, "Always", 2, (char *)NULL); xlog_param(xl, "backing-planes", HEX32, FETCH32(data, 16)); xlog_param(xl, "backing-pixel", HEX32, FETCH32(data, 20)); xlog_param(xl, "save-under", BOOLEAN, FETCH8(data, 24)); xlog_param(xl, "colormap", COLORMAP, FETCH32(data, 28)); xlog_param(xl, "map-is-installed", BOOLEAN, FETCH8(data, 25)); xlog_param(xl, "map-state", ENUM | SPECVAL, FETCH8(data, 26), "Unmapped", 0, "Unviewable", 1, "Viewable", 2, (char *)NULL); xlog_param(xl, "all-event-masks", EVENTMASK, FETCH32(data, 32)); xlog_param(xl, "your-event-mask", EVENTMASK, FETCH32(data, 36)); xlog_param(xl, "do-not-propagate-mask", EVENTMASK, FETCH16(data, 40)); xlog_param(xl, "override-redirect", BOOLEAN, FETCH8(data, 27)); break; case 14: /* GetGeometry */ xlog_param(xl, "root", WINDOW, FETCH32(data, 8)); xlog_param(xl, "depth", DECU, FETCH8(data, 1)); xlog_param(xl, "x", DEC16, FETCH16(data, 12)); xlog_param(xl, "y", DEC16, FETCH16(data, 14)); xlog_param(xl, "width", DECU, FETCH16(data, 16)); xlog_param(xl, "height", DECU, FETCH16(data, 18)); xlog_param(xl, "border-width", DECU, FETCH16(data, 20)); break; case 15: /* QueryTree */ xlog_param(xl, "root", WINDOW, FETCH32(data, 8)); xlog_param(xl, "parent", WINDOW | SPECVAL, FETCH32(data, 12), "None", 0, (char *)NULL); { int pos = 32; int i = 0; int n = FETCH16(data, 16); char buf[64]; for (i = 0; i < n; i++) { sprintf(buf, "children[%d]", i); xlog_param(xl, buf, WINDOW, FETCH32(data, pos)); pos += 4; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 16: /* InternAtom */ xlog_param(xl, "atom", ATOM | SPECVAL, FETCH32(data, 8), "None", 0, (char *)NULL); if (req->atomname) { internatom(xl->atoms, req->atomname, READ32(data + 8)); req->atomname = NULL; } break; case 17: /* GetAtomName */ { unsigned long atomlen; char *atomstr; atomlen = FETCH16(data, 8); atomstr = STRING(data, 32, atomlen); if (!xl->overflow) { char *atomname = snewn(atomlen + 1, char); memcpy(atomname, atomstr, atomlen); atomname[atomlen] = '\0'; internatom(xl->atoms, atomname, req->atomnum); } xlog_param(xl, "name", STRING, atomlen, atomstr); } break; case 20: /* GetProperty */ xlog_param(xl, "type", ATOM | SPECVAL, FETCH32(data, 8), "None", 0, (char *)NULL); if (FETCH32(data, 8) != 0) { xlog_param(xl, "format", DECU, FETCH8(data, 1)); xlog_param(xl, "bytes-after", DECU, FETCH32(data, 12)); switch (FETCH8(data, 1)) { case 8: xlog_param(xl, "data", STRING, FETCH32(data, 16), STRING(data, 32, FETCH32(data, 16))); break; case 16: xlog_param(xl, "data", HEXSTRING2, FETCH32(data, 16), STRING(data, 32, 2*FETCH32(data, 16))); break; case 32: xlog_param(xl, "data", HEXSTRING4, FETCH32(data, 16), STRING(data, 32, 4*FETCH32(data, 16))); break; default: put_datastr(xl->textbuf, ""); break; } } break; case 21: /* ListProperties */ { int pos = 32; int i = 0; int n = FETCH16(data, 8); char buf[64]; for (i = 0; i < n; i++) { sprintf(buf, "atoms[%d]", i); xlog_param(xl, buf, ATOM, FETCH32(data, pos)); pos += 4; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 23: /* GetSelectionOwner */ xlog_param(xl, "owner", WINDOW | SPECVAL, FETCH32(data, 8), "None", 0, (char *)NULL); break; case 26: /* GrabPointer */ xlog_param(xl, "status", ENUM | SPECVAL, FETCH8(data, 1), "Success", 0, "AlreadyGrabbed", 1, "InvalidTime", 2, "NotViewable", 3, "Frozen", 4, (char *)NULL); break; case 31: /* GrabKeyboard */ xlog_param(xl, "status", ENUM | SPECVAL, FETCH8(data, 1), "Success", 0, "AlreadyGrabbed", 1, "InvalidTime", 2, "NotViewable", 3, "Frozen", 4, (char *)NULL); break; case 38: /* QueryPointer */ xlog_param(xl, "root", WINDOW, FETCH32(data, 8)); xlog_param(xl, "child", WINDOW | SPECVAL, FETCH32(data, 12), "None", 0, (char *)NULL); xlog_param(xl, "same-screen", BOOLEAN, FETCH8(data, 1)); xlog_param(xl, "root-x", DEC16, FETCH16(data, 16)); xlog_param(xl, "root-y", DEC16, FETCH16(data, 18)); xlog_param(xl, "win-x", DEC16, FETCH16(data, 20)); xlog_param(xl, "win-y", DEC16, FETCH16(data, 22)); xlog_param(xl, "mask", HEX16, FETCH16(data, 24)); break; case 39: /* GetMotionEvents */ { int pos = 32; int i = 0; int n = FETCH32(data, 8); char buf[64]; for (i = 0; i < n; i++) { sprintf(buf, "events[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_timecoord(xl, data, len, pos); xlog_set_end(xl); pos += 8; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 40: /* TranslateCoordinates */ xlog_param(xl, "same-screen", BOOLEAN, FETCH8(data, 1)); xlog_param(xl, "child", WINDOW | SPECVAL, FETCH32(data, 8), "None", 0, (char *)NULL); xlog_param(xl, "dst-x", DEC16, FETCH16(data, 12)); xlog_param(xl, "dst-y", DEC16, FETCH16(data, 14)); break; case 43: /* GetInputFocus */ xlog_param(xl, "focus", WINDOW | SPECVAL, FETCH32(data, 8), "None", 0, "PointerRoot", 1, (char *)NULL); xlog_param(xl, "revert-to", ENUM | SPECVAL, FETCH8(data, 1), "None", 0, "PointerRoot", 1, "Parent", 2, (char *)NULL); break; case 44: /* QueryKeymap */ { int pos = 8; int i = 0; int n = 32; char buf[64]; for (i = 0; i < n; i++) { sprintf(buf, "keys[%d]", i); xlog_param(xl, buf, DECU, FETCH8(data, pos)); pos++; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 47: /* QueryFont */ xlog_param(xl, "draw-direction", ENUM | SPECVAL, FETCH8(data, 48), "LeftToRight", 0, "RightToLeft", 1, (char *)NULL); xlog_param(xl, "min-char-or-byte2", DECU, FETCH16(data, 40)); xlog_param(xl, "max-char-or-byte2", DECU, FETCH16(data, 42)); xlog_param(xl, "min-byte1", DECU, FETCH8(data, 49)); xlog_param(xl, "max-byte1", DECU, FETCH8(data, 50)); xlog_param(xl, "all-chars-exist", BOOLEAN, FETCH8(data, 51)); xlog_param(xl, "default-char", DECU, FETCH16(data, 44)); xlog_param(xl, "min-bounds", SETBEGIN); xlog_charinfo(xl, data, len, 8); xlog_set_end(xl); xlog_param(xl, "max-bounds", SETBEGIN); xlog_charinfo(xl, data, len, 24); xlog_set_end(xl); xlog_param(xl, "font-ascent", DEC16, FETCH16(data, 52)); xlog_param(xl, "font-descent", DEC16, FETCH16(data, 54)); { int pos = 32; int i = 0; int n; bool printing; char buf[64]; n = FETCH16(data, 46); printing = true; for (i = 0; i < n; i++) { if (printing) { sprintf(buf, "properties[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_fontprop(xl, data, len, pos); xlog_set_end(xl); } pos += 8; if (printing && i+1 < n && xlog_check_list_length(xl)) printing = false; } n = FETCH32(data, 56); for (i = 0; i < n; i++) { sprintf(buf, "char-infos[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_charinfo(xl, data, len, pos); xlog_set_end(xl); pos += 12; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 48: /* QueryTextExtents */ xlog_param(xl, "draw-direction", ENUM | SPECVAL, FETCH8(data, 1), "LeftToRight", 0, "RightToLeft", 1, (char *)NULL); xlog_param(xl, "font-ascent", DEC16, FETCH16(data, 8)); xlog_param(xl, "font-descent", DEC16, FETCH16(data, 10)); xlog_param(xl, "overall-ascent", DEC16, FETCH16(data, 12)); xlog_param(xl, "overall-descent", DEC16, FETCH16(data, 14)); xlog_param(xl, "overall-width", DEC32, FETCH32(data, 16)); xlog_param(xl, "overall-left", DEC32, FETCH32(data, 20)); xlog_param(xl, "overall-right", DEC32, FETCH32(data, 24)); break; case 49: /* ListFonts */ { int i, n; int pos = 32; n = FETCH16(data, 8); for (i = 0; i < n; i++) { char buf[64]; int slen; sprintf(buf, "names[%d]", i); slen = FETCH8(data, pos); xlog_param(xl, buf, STRING, slen, STRING(data, pos+1, slen)); pos += slen + 1; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 50: /* ListFontsWithInfo */ if (FETCH8(data, 1) == 0) { xlog_param(xl, "last-reply", BOOLEAN, 1); break; } xlog_param(xl, "name", STRING, FETCH8(data, 1), STRING(data, 64+8*FETCH16(data, 46), FETCH8(data, 1))); xlog_param(xl, "draw-direction", ENUM | SPECVAL, FETCH8(data, 48), "LeftToRight", 0, "RightToLeft", 1, (char *)NULL); xlog_param(xl, "min-char-or-byte2", DECU, FETCH16(data, 40)); xlog_param(xl, "max-char-or-byte2", DECU, FETCH16(data, 42)); xlog_param(xl, "min-byte1", DECU, FETCH8(data, 49)); xlog_param(xl, "max-byte1", DECU, FETCH8(data, 50)); xlog_param(xl, "all-chars-exist", BOOLEAN, FETCH8(data, 51)); xlog_param(xl, "default-char", DECU, FETCH16(data, 44)); xlog_param(xl, "min-bounds", SETBEGIN); xlog_charinfo(xl, data, len, 8); xlog_set_end(xl); xlog_param(xl, "max-bounds", SETBEGIN); xlog_charinfo(xl, data, len, 24); xlog_set_end(xl); xlog_param(xl, "font-ascent", DEC16, FETCH16(data, 52)); xlog_param(xl, "font-descent", DEC16, FETCH16(data, 54)); { int pos = 64; int i = 0; int n; char buf[64]; n = FETCH16(data, 46); for (i = 0; i < n; i++) { sprintf(buf, "properties[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_fontprop(xl, data, len, pos); xlog_set_end(xl); pos += 8; if (i+1 < n && xlog_check_list_length(xl)) break; } } xlog_param(xl, "replies-hint", DEC16, FETCH32(data, 56)); break; case 52: /* GetFontPath */ { int i, n; int pos = 32; n = FETCH16(data, 8); for (i = 0; i < n; i++) { char buf[64]; int slen; sprintf(buf, "path[%d]", i); slen = FETCH8(data, pos); xlog_param(xl, buf, STRING, slen, STRING(data, pos+1, slen)); pos += slen + 1; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 73: /* GetImage */ xlog_param(xl, "depth", DECU, FETCH8(data, 1)); xlog_param(xl, "visual", VISUALID | SPECVAL, FETCH32(data, 8), "None", 0, (char *)NULL); xlog_image_data(xl, "image-data", data, len, 32, req->pixmapformat, req->pixmapwidth, req->pixmapheight, FETCH8(data, 1)); break; case 83: /* ListInstalledColormaps */ { int i, n; int pos = 32; n = FETCH16(data, 8); for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "cmaps[%d]", i); xlog_param(xl, buf, COLORMAP, FETCH32(data, pos)); pos += 4; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 84: /* AllocColor */ xlog_param(xl, "pixel", HEX32, FETCH32(data, 16)); xlog_param(xl, "red", HEX16, FETCH16(data, 8)); xlog_param(xl, "green", HEX16, FETCH16(data, 10)); xlog_param(xl, "blue", HEX16, FETCH16(data, 12)); break; case 85: /* AllocNamedColor */ xlog_param(xl, "pixel", HEX32, FETCH32(data, 8)); xlog_param(xl, "exact-red", HEX16, FETCH16(data, 12)); xlog_param(xl, "exact-green", HEX16, FETCH16(data, 14)); xlog_param(xl, "exact-blue", HEX16, FETCH16(data, 16)); xlog_param(xl, "visual-red", HEX16, FETCH16(data, 18)); xlog_param(xl, "visual-green", HEX16, FETCH16(data, 20)); xlog_param(xl, "visual-blue", HEX16, FETCH16(data, 22)); break; case 86: /* AllocColorCells */ { int i, n; int pos = 32; bool printing; n = FETCH16(data, 8); printing = true; for (i = 0; i < n; i++) { if (printing) { char buf[64]; sprintf(buf, "pixels[%d]", i); xlog_param(xl, buf, HEX32, FETCH32(data, pos)); } pos += 4; if (printing && i+1 < n && xlog_check_list_length(xl)) printing = false; } n = FETCH16(data, 10); for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "masks[%d]", i); xlog_param(xl, buf, HEX32, FETCH32(data, pos)); pos += 4; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 87: /* AllocColorPlanes */ { int i, n; int pos = 32; n = FETCH16(data, 8); for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "pixels[%d]", i); xlog_param(xl, buf, HEX32, FETCH32(data, pos)); pos += 4; if (i+1 < n && xlog_check_list_length(xl)) break; } } xlog_param(xl, "red-mask", HEX32, FETCH32(data, 12)); xlog_param(xl, "green-mask", HEX32, FETCH32(data, 16)); xlog_param(xl, "blue-mask", HEX32, FETCH32(data, 20)); break; case 91: /* QueryColors */ { int i, n; int pos = 32; n = FETCH16(data, 8); for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "colors[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "red", HEX16, FETCH16(data, pos)); xlog_param(xl, "green", HEX16, FETCH16(data, pos+2)); xlog_param(xl, "blue", HEX16, FETCH16(data, pos+4)); xlog_set_end(xl); pos += 4; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 92: /* LookupColor */ xlog_param(xl, "exact-red", HEX16, FETCH16(data, 8)); xlog_param(xl, "exact-green", HEX16, FETCH16(data, 10)); xlog_param(xl, "exact-blue", HEX16, FETCH16(data, 12)); xlog_param(xl, "visual-red", HEX16, FETCH16(data, 14)); xlog_param(xl, "visual-green", HEX16, FETCH16(data, 16)); xlog_param(xl, "visual-blue", HEX16, FETCH16(data, 18)); break; case 97: /* QueryBestSize */ xlog_param(xl, "width", DECU, FETCH16(data, 8)); xlog_param(xl, "height", DECU, FETCH16(data, 10)); break; case 98: /* QueryExtension */ xlog_param(xl, "present", BOOLEAN, FETCH8(data, 8)); xlog_param(xl, "major-opcode", DECU, FETCH8(data, 9)); xlog_param(xl, "first-event", DECU, FETCH8(data, 10)); xlog_param(xl, "first-error", DECU, FETCH8(data, 11)); assert(req->extname); if (!xl->overflow && FETCH8(data, 8)) { int opcode = FETCH8(data, 9) - 128; if (!xl->extreqs[opcode]) { xl->extreqs[opcode] = dupstr(req->extname); xl->extidreqs[opcode] = req->extid; } opcode = FETCH8(data, 10); if (opcode != 0 && opcode < 128 && !xl->extevents[opcode]) { xl->extevents[opcode] = dupstr(req->extname); if (req->extid) xl->extidevents[opcode] = req->extid; } opcode = FETCH8(data, 11); if (opcode != 0 && !xl->exterrors[opcode]) { xl->exterrors[opcode] = dupstr(req->extname); if (req->extid) xl->extiderrors[opcode] = req->extid; } } break; case 99: /* ListExtensions */ { int i, n; int pos = 32; n = FETCH8(data, 1); for (i = 0; i < n; i++) { char buf[64]; int slen; sprintf(buf, "names[%d]", i); slen = FETCH8(data, pos); xlog_param(xl, buf, STRING, slen, STRING(data, pos+1, slen)); pos += slen + 1; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 101: /* GetKeyboardMapping */ { int keycode = req->first_keycode; int keycode_count = req->keycode_count; int keysyms_per_keycode = FETCH8(data, 1); int pos = 32; int i; char buf[64]; while (keycode_count > 0) { sprintf(buf, "keycode[%d]", keycode); xlog_param(xl, buf, SETBEGIN); for (i = 0; i < keysyms_per_keycode; i++) { sprintf(buf, "keysyms[%d]", i); xlog_param(xl, buf, HEX32, FETCH32(data, pos)); pos += 4; } xlog_set_end(xl); i++; keycode++; keycode_count--; if (keycode_count > 0 && xlog_check_list_length(xl)) break; } } break; case 103: /* GetKeyboardControl */ xlog_param(xl, "key-click-percent", DECU, FETCH8(data, 12)); xlog_param(xl, "bell-percent", DECU, FETCH8(data, 13)); xlog_param(xl, "bell-pitch", DECU, FETCH16(data, 14)); xlog_param(xl, "bell-duration", DECU, FETCH16(data, 16)); xlog_param(xl, "led-mask", HEX32, FETCH32(data, 8)); xlog_param(xl, "global-auto-repeat", ENUM | SPECVAL, FETCH8(data, 1), "Off", 0, "On", 1, (char *)NULL); { int i, n; int pos = 32; n = 32; for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "auto-repeats[%d]", i); xlog_param(xl, buf, HEX8, FETCH8(data, pos)); pos++; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 106: /* GetPointerControl */ xlog_param(xl, "acceleration", RATIONAL16, FETCH16(data, 8), FETCH16(data, 10)); xlog_param(xl, "threshold", DEC16, FETCH16(data, 12)); break; case 108: /* GetScreenSaver */ xlog_param(xl, "timeout", DEC16, FETCH16(data, 8)); xlog_param(xl, "interval", DEC16, FETCH16(data, 10)); xlog_param(xl, "prefer-blanking", ENUM | SPECVAL, FETCH8(data, 12), "No", 0, "Yes", 1, (char *)NULL); xlog_param(xl, "allow-exposures", ENUM | SPECVAL, FETCH8(data, 13), "No", 0, "Yes", 1, (char *)NULL); break; case 110: /* ListHosts */ xlog_param(xl, "mode", ENUM | SPECVAL, FETCH8(data, 1), "Disabled", 0, "Enabled", 1, (char *)NULL); { int i, n; int pos = 32; n = FETCH16(data, 8); for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "hosts[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "family", ENUM | SPECVAL, FETCH8(data, pos), "Internet", 0, "DECnet", 1, "Chaos", 2, (char *)NULL); xlog_param(xl, "address", HEXSTRING1, FETCH16(data, pos+2), STRING(data, pos+4, FETCH16(data, pos+2))); xlog_set_end(xl); pos += 4 + ((FETCH16(data, pos+2) + 3) &~ 3); if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 116: /* SetPointerMapping */ xlog_param(xl, "status", ENUM | SPECVAL, FETCH8(data, 1), "Success", 0, "Busy", 1, (char *)NULL); break; case 117: /* GetPointerMapping */ { int pos = 32; int i = 0; int n = FETCH8(data, 1); char buf[64]; for (i = 0; i < n; i++) { sprintf(buf, "map[%d]", i); xlog_param(xl, buf, DECU, FETCH8(data, pos)); pos++; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case 118: /* SetModifierMapping */ xlog_param(xl, "status", ENUM | SPECVAL, FETCH8(data, 1), "Success", 0, "Busy", 1, "Failed", 2, (char *)NULL); break; case 119: /* GetModifierMapping */ { int keycodes_per_modifier = FETCH8(data, 1); int pos = 32; int mod, i; char buf[64]; for (mod = 0; mod < 8; mod++) { sprintf(buf, "modifier[%d]", mod); xlog_param(xl, buf, SETBEGIN); for (i = 0; i < keycodes_per_modifier; i++) { sprintf(buf, "keycodes[%d]", i); xlog_param(xl, buf, DECU, FETCH8(data, pos)); pos++; } xlog_set_end(xl); if (mod+1 < 8 && xlog_check_list_length(xl)) break; } } break; case EXT_BIGREQUESTS | 0: /* BigReqEnable */ xlog_param(xl, "maximum-request-length", DECU, FETCH32(data, 8)); break; case EXT_GENERICEVENT | 0: /* GEQueryVersion */ xlog_param(xl, "major-version", DECU, FETCH16(data, 8)); xlog_param(xl, "minor-version", DECU, FETCH16(data, 10)); break; case EXT_MITSHM | 0: /* ShmQueryVersion */ xlog_param(xl, "shared-pixmaps", BOOLEAN, FETCH8(data, 1)); xlog_param(xl, "major-version", DECU, FETCH16(data, 8)); xlog_param(xl, "minor-version", DECU, FETCH16(data, 10)); xlog_param(xl, "uid", DECU, FETCH16(data, 12)); xlog_param(xl, "gid", DECU, FETCH16(data, 14)); xlog_param(xl, "pixmap-format", ENUM | SPECVAL, FETCH8(data, 16), "Bitmap", 0, "XYPixmap", 1, "ZPixmap", 2, (char *)NULL); break; case EXT_MITSHM | 4: /* ShmGetImage */ xlog_param(xl, "depth", DECU, FETCH8(data, 1)); xlog_param(xl, "visual", VISUALID, FETCH32(data, 8)); xlog_param(xl, "size", DECU, FETCH32(data, 12)); break; case EXT_RENDER | 0: /* RenderQueryVersion */ xlog_param(xl, "major-version", DECU, FETCH32(data, 8)); xlog_param(xl, "minor-version", DECU, FETCH32(data, 12)); break; case EXT_RENDER | 1: /* RenderQueryPictFormats */ { int i, n; int pos; /* * Go through the list of picture formats and save the * depth of each one. */ n = FETCH32(data, 8); pos = 32; for (i = 0; i < n; i++) { struct resdepth *gsd; struct resdepth *old; gsd = snew(struct resdepth); gsd->resource = FETCH32(data, pos); gsd->depth = FETCH8(data, pos+5); /* * Find any previous entry for this resource id, and * override it. */ old = del234(xl->resdepths, gsd); sfree(old); /* * Now add the new one. */ add234(xl->resdepths, gsd); pos += 28; } /* * Now reset pos, and log stuff as usual. */ n = FETCH32(data, 8); pos = 32; for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "formats[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "id", PICTFORMAT, FETCH32(data, pos)); xlog_param(xl, "type", ENUM | SPECVAL, FETCH8(data, pos+4), "Indexed", 0, "Direct", 1, (char *)NULL); xlog_param(xl, "depth", DECU, FETCH8(data, pos+5)); xlog_param(xl, "direct", SETBEGIN); xlog_param(xl, "red-shift", DECU, FETCH16(data, pos+8)); xlog_param(xl, "red-mask", HEX16, FETCH16(data, pos+10)); xlog_param(xl, "green-shift", DECU, FETCH16(data, pos+12)); xlog_param(xl, "green-mask", HEX16, FETCH16(data, pos+14)); xlog_param(xl, "blue-shift", DECU, FETCH16(data, pos+16)); xlog_param(xl, "blue-mask", HEX16, FETCH16(data, pos+18)); xlog_param(xl, "alpha-shift", DECU, FETCH16(data, pos+20)); xlog_param(xl, "alpha-mask", HEX16, FETCH16(data, pos+22)); xlog_set_end(xl); xlog_param(xl, "colormap", COLORMAP | SPECVAL, FETCH32(data, pos+24), "None", 0, (char *)NULL); xlog_set_end(xl); pos += 28; if (i+1 < n && xlog_check_list_length(xl)) break; } n = FETCH32(data, 12); for (i = 0; i < n; i++) { char buf[64]; int m, j, opos = pos; sprintf(buf, "screens[%d]", i); xlog_param(xl, buf, SETBEGIN); m = FETCH32(data, pos); pos += 8; for (j = 0; j < m; j++) { int l, k; sprintf(buf, "depths[%d]", j); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "depth", DECU, FETCH8(data, pos)); l = FETCH16(data, pos+2); pos += 8; for (k = 0; k < l; k++) { sprintf(buf, "visuals[%d]", k); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "visual", VISUALID | SPECVAL, FETCH32(data, pos), "None",0, (char *)NULL); xlog_param(xl, "format", PICTFORMAT, FETCH32(data, pos+4)); xlog_set_end(xl); pos += 8; if (k+1 < l && xlog_check_list_length(xl)) break; } xlog_set_end(xl); if (j+1 < m && xlog_check_list_length(xl)) break; } xlog_param(xl, "fallback", PICTFORMAT, FETCH32(data, opos+4)); xlog_set_end(xl); if (i+1 < n && xlog_check_list_length(xl)) break; } /* * FIXME: we ought to check the version from * RenderQueryVersion and use it to make this piece * conditional. */ n = FETCH32(data, 24); for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "subpixels[%d]", i); xlog_param(xl, buf, ENUM | SPECVAL, FETCH8(data, pos), "Unknown",0, "HorizontalRGB",1, "HorizontalBGR",2, "VerticalRGB",3, "VerticalBGR",4, "None",5, (char *)NULL); pos++; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 2: /* RenderQueryPictIndexValues */ { int i, n; int pos = 32; n = FETCH32(data, 8); for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "values[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "pixel", HEX32, FETCH32(data, pos)); xlog_param(xl, "red", HEX16, FETCH16(data, pos+4)); xlog_param(xl, "green", HEX16, FETCH16(data, pos+6)); xlog_param(xl, "blue", HEX16, FETCH16(data, pos+8)); xlog_param(xl, "alpha", HEX16, FETCH16(data, pos+10)); xlog_set_end(xl); pos += 12; if (i+1 < n && xlog_check_list_length(xl)) break; } } break; case EXT_RENDER | 3: /* RenderQueryDithers */ /* * This request is listed in renderproto/render.h but does * not include any description, so we'll just have to log it * as 'unable to decode reply data'. */ break; case EXT_RENDER | 29: /* RenderQueryFilters */ { int i, n; int pos = 32; n = FETCH32(data, 8); for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "aliases[%d]", i); xlog_param(xl, buf, DECU, FETCH16(data, pos)); pos += 2; if (i+1 < n && xlog_check_list_length(xl)) break; } n = FETCH32(data, 12); for (i = 0; i < n; i++) { char buf[64]; sprintf(buf, "filters[%d]", i); xlog_param(xl, buf, STRING, FETCH8(data, pos), STRING(data, pos+1, FETCH8(data, pos))); pos += 1 + FETCH8(data, pos); if (i+1 < n && xlog_check_list_length(xl)) break; } } break; default: put_datastr(xl->textbuf, ""); break; } if (xl->textbuf->len > 0) { xlog_reply_end(xl); xlog_response_done(xl->xs, req, xl->textbuf->s); } if (req->replies == 1) req->replies = 0; /* Not expecting more replies */ } const char *xlog_translate_error(int errcode) { switch (errcode) { case 1: return "BadRequest"; case 2: return "BadValue"; case 3: return "BadWindow"; case 4: return "BadPixmap"; case 5: return "BadAtom"; case 6: return "BadCursor"; case 7: return "BadFont"; case 8: return "BadMatch"; case 9: return "BadDrawable"; case 10: return "BadAccess"; case 11: return "BadAlloc"; case 12: return "BadColormap"; case 13: return "BadGContext"; case 14: return "BadIDChoice"; case 15: return "BadName"; case 16: return "BadLength"; case 17: return "BadImplementation"; case EXT_MITSHM | 0: return "BadShmSeg"; case EXT_RENDER | 0: return "BadPictFormat"; case EXT_RENDER | 1: return "BadPicture"; case EXT_RENDER | 2: return "BadPictOp"; case EXT_RENDER | 3: return "BadGlyphSet"; case EXT_RENDER | 4: return "BadGlyph"; default: return NULL; } } static void xlog_do_error(struct xlog *xl, struct request *req, const void *vdata, int len) { const unsigned char *data = (const unsigned char *)vdata; int errcode, i; const char *error; strbuf_clear(xl->textbuf); xl->overflow = false; xlog_respond_to(xl, req); xl->reqlogstate = 3; /* for things with parameters */ errcode = FETCH8(data, 1); error = NULL; if (errcode < 128) { /* Core error */ error = xlog_translate_error(errcode); if (error == NULL) strbuf_catf(xl->textbuf, "UnknownError%d", errcode); } else { /* Extension error */ for (i = 0; errcode-i >= 128; i++) if (xl->exterrors[errcode-i]) { char const *extname = xl->exterrors[errcode-i]; if (xl->extiderrors[errcode-i]) { errcode = xl->extiderrors[errcode-i] + i; error = xlog_translate_error(errcode); } if (error == NULL) strbuf_catf(xl->textbuf, "%s:UnknownError%d", extname, i); break; } if (errcode-i < 128) strbuf_catf(xl->textbuf, "UnknownError%d", errcode); } if (error) put_datastr(xl->textbuf, error); switch (errcode) { case 1: /* BadRequest */ break; case 2: /* BadValue */ put_byte(xl->textbuf, '('); xlog_param(xl, "value", HEX32, FETCH32(data, 4)); put_byte(xl->textbuf, ')'); break; case 3: /* BadWindow */ put_byte(xl->textbuf, '('); xl->reqlogstate = 3; xlog_param(xl, "window", WINDOW, FETCH32(data, 4)); put_byte(xl->textbuf, ')'); break; case 4: /* BadPixmap */ put_byte(xl->textbuf, '('); xl->reqlogstate = 3; xlog_param(xl, "pixmap", PIXMAP, FETCH32(data, 4)); put_byte(xl->textbuf, ')'); break; case 5: /* BadAtom */ put_byte(xl->textbuf, '('); xl->reqlogstate = 3; xlog_param(xl, "atom", ATOM, FETCH32(data, 4)); put_byte(xl->textbuf, ')'); break; case 6: /* BadCursor */ put_byte(xl->textbuf, '('); xl->reqlogstate = 3; xlog_param(xl, "cursor", CURSOR, FETCH32(data, 4)); put_byte(xl->textbuf, ')'); break; case 7: /* BadFont */ put_byte(xl->textbuf, '('); xl->reqlogstate = 3; xlog_param(xl, "font", FONT, FETCH32(data, 4)); put_byte(xl->textbuf, ')'); break; case 8: /* BadMatch */ break; case 9: /* BadDrawable */ put_byte(xl->textbuf, '('); xl->reqlogstate = 3; xlog_param(xl, "drawable", DRAWABLE, FETCH32(data, 4)); put_byte(xl->textbuf, ')'); break; case 10: /* BadAccess */ break; case 11: /* BadAlloc */ break; case 12: /* BadColormap */ put_byte(xl->textbuf, '('); xl->reqlogstate = 3; xlog_param(xl, "colormap", COLORMAP, FETCH32(data, 4)); put_byte(xl->textbuf, ')'); break; case 13: /* BadGContext */ put_byte(xl->textbuf, '('); xl->reqlogstate = 3; xlog_param(xl, "gc", GCONTEXT, FETCH32(data, 4)); put_byte(xl->textbuf, ')'); break; case 14: /* BadIDChoice */ put_byte(xl->textbuf, '('); xl->reqlogstate = 3; xlog_param(xl, "id", HEX32, FETCH32(data, 4)); put_byte(xl->textbuf, ')'); break; case 15: /* BadName */ break; case 16: /* BadLength */ break; case 17: /* BadImplementation */ break; default: /* UnknownError */ break; } xlog_response_done(xl->xs, req, xl->textbuf->s); /* * Don't expect any further response to this request. */ if (xl->rhead) xl->rhead->replies = 0; } static void xlog_do_event(struct xlog *xl, const void *vdata, int len) { const unsigned char *data = (const unsigned char *)vdata; int filter; strbuf_clear(xl->textbuf); xl->overflow = false; xlog_event(xl, data, len, 0, &filter); if (filter) { xlog_new_line(xl); fprintf(xl->xs->outfp, "--- %s\n", xl->textbuf->s); fflush(xl->xs->outfp); } } static void hexdump(struct xlog *xl, const void *vdata, int len, unsigned startoffset, const char *prefix) { const unsigned char *data = (const unsigned char *)vdata; unsigned lineoffset = startoffset &~ 15; char dumpbuf[128], tmpbuf[16]; int n, i; unsigned char c; for (n = -(int)(startoffset & 15); n < len; n += 16) { memset(dumpbuf, ' ', 8+2+16*3+1+16); dumpbuf[8+2+16*3+1+16] = '\n'; dumpbuf[8+2+16*3+1+16+1] = '\0'; memcpy(dumpbuf, tmpbuf, sprintf(tmpbuf, "%08X", lineoffset)); for (i = 0; i < 16; i++) { if (i + n < 0) continue; if (i + n >= len) break; c = data[i + n]; memcpy(dumpbuf+8+2+3*i, tmpbuf, sprintf(tmpbuf, "%02X", c)); dumpbuf[8+2+16*3+1+i] = (isprint(c) ? c : '.'); } dumpbuf[8+2+16*3+1+i] = '\n'; dumpbuf[8+2+16*3+1+i+1] = '\0'; xlog_new_line(xl); fputs(prefix, xl->xs->outfp); fputs(dumpbuf, xl->xs->outfp); lineoffset += 16; } } void xlog_c2s(struct xlog *xl, const void *vdata, int len) { const unsigned char *data = (const unsigned char *)vdata; /* * Remember that variables declared auto in this function may not * be used across a crReturn, and hence also crReadUpTo(). */ int i; if (xl->xs->raw_hex_dump) { hexdump(xl, vdata, len, xl->c2soff, ">>> "); xl->c2soff += len; } if (xl->error) return; crBegin(xl->c2sstate); if (xl->type == XLOG_FULL) { /* * Endianness byte and subsequent padding byte. */ strbuf_clear(xl->c2sbuf); crReadUpTo(xl->c2sbuf, 2); if (xl->c2sbuf->s[0] == 'l' || xl->c2sbuf->s[0] == 'B') { xl->endianness = xl->c2sbuf->s[0]; } else { err((xl, "initial endianness byte (0x%02X) unrecognised", (unsigned)xl->c2sbuf->u[0])); } /* * Protocol major and minor version, and authorisation * detail string lengths. * * We only log the protocol version if it doesn't match our * expectations; we definitely don't want to log the auth * data, both for security reasons and because we're * meddling with them ourselves in any case. */ strbuf_clear(xl->c2sbuf); crReadUpTo(xl->c2sbuf, 10); if ((i = READ16(xl->c2sbuf->u)) != 11) err((xl, "major protocol version (0x%04X) unrecognised", i)); if ((i = READ16(xl->c2sbuf->u + 2)) != 0) warn((xl, "minor protocol version (0x%04X) unrecognised", i)); i = READ16(xl->c2sbuf->u + 4); i = (i + 3) &~ 3; i += READ16(xl->c2sbuf->u + 6); i = (i + 3) &~ 3; strbuf_clear(xl->c2sbuf); xl->c2stmp = i; crReadUpTo(xl->c2sbuf, xl->c2stmp); strbuf_clear(xl->c2sbuf); } /* * Now we expect a steady stream of X requests. */ while (1) { strbuf_clear(xl->c2sbuf); crReadUpTo(xl->c2sbuf, 4); i = READ16(xl->c2sbuf->u + 2); if (i == 0) { /* * A zero length field means an extended request packet, * via the BIG-REQUESTS protocol extension. We must be * prepared to cope with big requests at all times: it * can't be conditional on having seen a BigReqEnable, * because in -p mode we might have tuned in after that * went past. */ crReadUpTo(xl->c2sbuf, 8); i = READ32(xl->c2sbuf->u + 4); xl->c2stmp = i*4; crReadUpTo(xl->c2sbuf, xl->c2stmp); /* * Shift the first four bytes of the packet upwards, so * as to remove the inserted extra length word. Then * pass on to xlog_do_request() as usual, which won't * mind the length field in the packet data it sees * being zero because we're passing the real length as a * separate parameter and it will look at that instead. */ memcpy(xl->c2sbuf->u + 4, xl->c2sbuf->u, 4); xlog_do_request(xl, xl->c2sbuf->u + 4, xl->c2sbuf->len - 4); } else { xl->c2stmp = i*4; crReadUpTo(xl->c2sbuf, xl->c2stmp); xlog_do_request(xl, xl->c2sbuf->u, xl->c2sbuf->len); } } crFinishV; } void xlog_s2c(struct xlog *xl, const void *vdata, int len) { const unsigned char *data = (const unsigned char *)vdata; /* * Remember that variables declared auto in this function may * not be used across a crReturn, and hence also read() or * ignore(). */ int i; if (xl->xs->raw_hex_dump) { hexdump(xl, vdata, len, xl->s2coff, "<<< "); xl->s2coff += len; } if (xl->error) return; crBegin(xl->s2cstate); if (xl->type == XLOG_FULL) { /* * Initial phase of data coming from the server is expected * to be composed of packets with an 8-byte header whose * final two bytes give the number of 4-byte words beyond * that header. */ while (1) { strbuf_clear(xl->s2cbuf); crReadUpTo(xl->s2cbuf, 8); if (xl->endianness == -1) err((xl, "server reply received before client sent endianness")); i = READ16(xl->s2cbuf->u + 6); xl->s2ctmp = 8 + i*4; crReadUpTo(xl->s2cbuf, xl->s2ctmp); /* * The byte at the front of one of these packets is 0 * for a failed authorisation, 1 for a successful * authorisation, and 2 for an incomplete authorisation * indicating more data should be sent. * * Since we proxy the X authorisation ourselves and have * a fixed set of protocols we understand of which we * know none involve type-2 packets, we never expect to * see one. 0 is also grounds for ceasing to log the * connection; that leaves 1, which terminates this loop * and we move on to the main phase of the protocol. * * (We might some day need to extend this code so that a * type-2 packet is processed and we look for another * packet of this type, which is why I've written this * as a while loop with an unconditional break at the * end instead of simple straight-through code. We would * only need to stick a 'continue' at the end of * handling a type-2 packet to make this change.) */ if (xl->s2cbuf->u[0] == 0) { ptrlen pl; pl.ptr = xl->s2cbuf->u + 8; pl.len = min(xl->s2cbuf->len - 8, xl->s2cbuf->u[1]); err((xl, "server refused authorisation, reason \"%.*s\"", PTRLEN_PRINTF(pl))); } else if (xl->s2cbuf->u[0] == 2) { err((xl, "server sent incomplete-authorisation packet, which" " is unsupported by xtruss")); } else if (xl->s2cbuf->u[0] != 1) { err((xl, "server sent unrecognised authorisation-time opcode %d", xl->s2cbuf->u[0])); } /* * Now we're sitting on a successful authorisation * packet. Optionally log it. */ if (xl->s2cbuf->len < 16) { err((xl, "server's init message was far too short\n")); } xl->clientid = READ32(xl->s2cbuf->u + 12); if (++xl->xs->num_clients_seen > 1) xl->xs->print_client_ids = true; if (xl->xs->print_server_startup) { /* variables on which the FETCH macros depend */ const unsigned char *data = xl->s2cbuf->u; int len = xl->s2cbuf->len; strbuf_clear(xl->textbuf); put_datastr(xl->textbuf, "--- server init message: "); xl->reqlogstate = 3; xlog_param(xl, "protocol-major-version", DECU, FETCH16(data, 2)); xlog_param(xl, "protocol-major-version", DECU, FETCH16(data, 4)); xlog_param(xl, "release-number", DECU, FETCH32(data, 8)); xlog_param(xl, "resource-id-base", HEX32, FETCH32(data, 12)); xlog_param(xl, "resource-id-mask", HEX32, FETCH32(data, 16)); xlog_param(xl, "motion-buffer-size", DECU, FETCH32(data, 20)); xlog_param(xl, "maximum-request-length", DECU, FETCH16(data, 26)); xlog_param(xl, "image-byte-order", ENUM | SPECVAL, FETCH8(data, 30), "LSBFirst", 0, "MSBFirst", 1, (char *)NULL); xlog_param(xl, "bitmap-bit-order", ENUM | SPECVAL, FETCH8(data, 31), "LeastSignificant", 0, "MostSignificant", 1, (char *)NULL); xlog_param(xl, "bitmap-scanline-unit", DECU, FETCH8(data, 32)); xlog_param(xl, "bitmap-scanline-pad", DECU, FETCH8(data, 33)); xlog_param(xl, "min-keycode", DECU, FETCH8(data, 34)); xlog_param(xl, "max-keycode", DECU, FETCH8(data, 35)); xlog_param(xl, "vendor", STRING, FETCH16(data, 24), STRING(data, 40, FETCH16(data, 24))); { int i, n; int pos = 40 + FETCH16(data, 24); bool printing; pos = (pos + 3) &~ 3; n = FETCH8(data, 29); printing = true; for (i = 0; i < n; i++) { if (printing) { char buf[64]; sprintf(buf, "pixmap-formats[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "depth", DECU, FETCH8(data, pos)); xlog_param(xl, "bits-per-pixel", DECU, FETCH8(data, pos+1)); xlog_param(xl, "scanline-pad", DECU, FETCH8(data, pos+2)); xlog_set_end(xl); } pos += 8; if (printing && i+1 < n && xlog_check_list_length(xl)) printing = false; } n = FETCH8(data, 28); for (i = 0; i < n; i++) { char buf[64]; int j, m; sprintf(buf, "roots[%d]", i); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "root", WINDOW, FETCH32(data, pos)); xlog_param(xl, "default-colormap", COLORMAP, FETCH32(data, pos+4)); xlog_param(xl, "white-pixel", HEX32, FETCH32(data, pos+8)); xlog_param(xl, "black-pixel", HEX32, FETCH32(data, pos+12)); xlog_param(xl, "current-input-masks", EVENTMASK, FETCH32(data, pos+16)); xlog_param(xl, "width-in-pixels", DECU, FETCH16(data, pos+20)); xlog_param(xl, "height-in-pixels", DECU, FETCH16(data, pos+22)); xlog_param(xl, "width-in-mm", DECU, FETCH16(data, pos+24)); xlog_param(xl, "height-in-mm", DECU, FETCH16(data, pos+26)); xlog_param(xl, "min-installed-maps", DECU, FETCH16(data, pos+28)); xlog_param(xl, "max-installed-maps", DECU, FETCH16(data, pos+30)); xlog_param(xl, "root-visual", VISUALID, FETCH32(data, pos+32)); xlog_param(xl, "backing-stores", ENUM | SPECVAL, FETCH8(data, pos+36), "Never", 0, "WhenMapped", 1, "Always", 2, (char *)NULL); xlog_param(xl, "save-unders", BOOLEAN, FETCH8(data, pos+37)); xlog_param(xl, "root-depth", DECU, FETCH8(data, pos+38)); m = FETCH8(data, pos+39); pos += 40; for (j = 0; j < m; j++) { char buf[64]; int k, l; sprintf(buf, "allowed-depths[%d]", j); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "depth", DECU, FETCH8(data, pos)); l = FETCH16(data, pos+2); pos += 8; for (k = 0; k < l; k++) { char buf[64]; sprintf(buf, "visuals[%d]", k); xlog_param(xl, buf, SETBEGIN); xlog_param(xl, "visual-id", VISUALID, FETCH32(data, pos)); xlog_param(xl, "class", ENUM | SPECVAL, FETCH8(data, pos + 4), "StaticGray", 0, "GrayScale", 1, "StaticColor", 2, "PseudoColor", 3, "TrueColor", 4, "DirectColor", 5, (char *)NULL); xlog_param(xl, "bits-per-rgb-value", DECU, FETCH8(data, pos + 5)); xlog_param(xl, "colormap-entries", DECU, FETCH16(data, pos + 6)); xlog_param(xl, "red-mask", HEX32, FETCH32(data, pos + 8)); xlog_param(xl, "green-mask", HEX32, FETCH32(data, pos + 12)); xlog_param(xl, "blue-mask", HEX32, FETCH32(data, pos + 16)); xlog_set_end(xl); pos += 24; if (k+1 < l && xlog_check_list_length(xl)) break; } xlog_set_end(xl); if (j+1 < m && xlog_check_list_length(xl)) break; } xlog_set_end(xl); if (i+1 < n && xlog_check_list_length(xl)) break; } } xlog_new_line(xl); fprintf(xl->xs->outfp, "%s\n", xl->textbuf->s); } /* * Find all the pixmap format information we might need * to decode PutImage and GetImage requests during the * protocol. */ xlog_use_welcome_message(xl, xl->s2cbuf->u, xl->s2cbuf->len); break; } } /* * In the main protocol phase, packets received from the server * come in three types: * * - Replies. These are distinguished by their first byte being * 1. They have a base length of 32 bytes, and at offset 4 * they contain a 32-bit length field indicating how many * more 4-byte words should be added to that base length. * - Errors. These are distinguished by their first byte being * 0, and all have a length of exactly 32 bytes. * - Events. These are distinguished by their first byte being * anything other than 0 or 1, and apart from GenericEvent all * have a length of exactly 32 bytes too. */ while (1) { /* Read the base 32 bytes of any server packet. */ strbuf_clear(xl->s2cbuf); crReadUpTo(xl->s2cbuf, 32); /* If it's a reply or a GenericEvent, read additional data if any. */ if (xl->s2cbuf->u[0] == 1 || xl->s2cbuf->u[0] == 35) { i = READ32(xl->s2cbuf->u + 4); xl->s2ctmp = 32 + i*4; crReadUpTo(xl->s2cbuf, xl->s2ctmp); } /* * All three major packet types include a sequence number, * in the same position within the packet. So our first task * is to discard outstanding requests from our stored list * until we reach the one to which this packet refers. * * The sole _known_ exception to this is the KeymapNotify * event, but we also treat extension events we don't * recognise as potential exceptions. */ if ((xl->s2cbuf->u[0] & 0x7f) != 11 && (xl->s2cbuf->u[0] < 2 || xl->extidevents[xl->s2cbuf->u[0] & 0x7f] || xlog_translate_event(xl->s2cbuf->u[0] & 0x7f))) { i = READ16(xl->s2cbuf->u + 2); while (xl->rhead && (xl->rhead->seqnum & 0xFFFF) != i) { struct request *nexthead = xl->rhead->next; if (xl->rhead->replies) { /* A request that expected a reply got none. Report that. */ xlog_do_reply(xl, xl->rhead, NULL, 0); } free_request(xl->rhead); xl->rhead = nexthead; } if (xl->rhead) xl->rhead->prev = NULL; else xl->rtail = NULL; } /* * Now we can hand off to the individual functions that * separately process the three packet types. */ if (xl->s2cbuf->u[0] == 1) { xlog_do_reply(xl, xl->rhead, xl->s2cbuf->u, xl->s2cbuf->len); } else if (xl->s2cbuf->u[0] == 0) { xlog_do_error(xl, xl->rhead, xl->s2cbuf->u, xl->s2cbuf->len); } else { xlog_do_event(xl, xl->s2cbuf->u, xl->s2cbuf->len); } } crFinishV; } void xlog_set_clientid(struct xlog *xl, unsigned clientid) { xl->clientid = clientid; } unsigned xlog_get_clientid(struct xlog *xl) { return xl->clientid; } void xlog_set_endianness(struct xlog *xl, char endian) { xl->endianness = endian; } void xlog_set_next_seq(struct xlog *xl, int seq) { xl->nextseq = seq; } void xlog_intern_atom(struct xlog *xl, char *name, unsigned long val) { internatom(xl->atoms, dupstr(name), val); } work/xtruss.c0000664000000000000000000005634114714222463010462 0ustar /* * xtruss: looks like strace, quacks like xmon. * * xtruss monitors the data sent and received between an X client * and the X server, and logs it in a format reminiscent of Linux's * strace(1). Its command-line syntax is also similar to strace: in * the simplest invocation, you just run the target X program * exactly as you normally would but prefix it with 'xtruss', e.g. * 'xtruss xterm -fn 9x15'. If your X server supports the X RECORD * extension, you can also attach xtruss to a client which is * already running, by specifying an X resource id (similarly to * xkill(1)) or by selecting a window interactively with the mouse. * * I wrote it because xmon irritated me by not being enough like * strace: a pain to set up (two confusingly cooperating processes, * no automatic handling of authorisation for the proxy display) and * unreadable in its output (half a screen per request or response, * and no way to see at a glance what request a response was a reply * to). * * This is a spinoff project from the PuTTY code base: the X proxy * code reuses the X forwarding framework from PuTTY, because that * provided for free all the code that invents new authorisation * data and checks and replaces it in the proxied connections. */ /* * Possible future work: * * - Arrange to let the network abstraction keep the peer address * of incoming connections, so that we can provide * XDM-AUTHORIZATION-1 on the proxy side at user request. * * - Decode more extensions. * * - Log connection and disconnection of clients? * * - Perhaps a command-line option to request tracing of only the * first incoming connection and just proxy the rest untraced? * * - Work out what to do about extension tracking in -p mode. * * The only thing I can think of at the moment is for our * control connection to do a ListExtensions and then lots of * QueryExtension before we even attach to the target client. * * But are we guaranteed that extension opcode indices will be * the same between all client connections? It'd certainly be * the _obvious_ way to implement an X server, but I don't * think anything in the protocol specifically requires it. * Another thing a server might choose to do would be to * allocate extension number-space sequentially from the base * but independently for each client connection, and translate * the event numbers in SendEvents between clients. The * advantage of doing this would be that the server could * support more extensions than fit into the number space, and * each client could use any subset of them that _would_ fit. * * - Command-line-configurable display format: be able to omit * parameter names for expert users? * * - Command-line configurable display format: alternative methods * of handling separated requests and responses? I definitely * like the one I've got now, but there's scope for others to be * selectable. * * Such as, for instance, "Request(params) = " followed by " ... <#xxxx> = {response}", which has * the virtue that it doesn't repeat enormous request lines in * the output. * * More radically than that, perhaps, never combine request * and response lines at all - just print a sequence number on * absolutely everything, and leave untangling it to the * reader. * * - Prettyprinting of giant data structure returns, by inserting * newlines and appropriate indentation? * * - Tracking of server state to usefully annotate the connection. * * A more radical approach to tracking atoms would be to establish * our own connection to the server and use it to _look up_ * any atom id we don't already know before we print the * request/response in which it appears. * + In order to be able to do those lookups synchronously * within do_request and friends, this would require some * tinkering with the event loop code, or alternatively * handling our own X connection entirely outside the main * event loop. The alternative is to turn do_request &c * into coroutines of some sort, but I think all the * queuing gets too hideous if we try that. * * We could try tracking currently valid window and pixmap ids * so that we can disambiguate the letter prefix on a * DRAWABLE, and likewise track fonts and graphics contexts so * we can disambiguate FONTABLE. * + Bit fiddly, this one, due to synchronisation issues * between c2s and s2c. A request which changes the current * state should immediately affect annotation of subsequent * requests, but its effect on annotation of responses * would have to be deferred until the sequence numbers in * the response stream caught up with that request. Ick. * + Not to mention the fact that tracking active window ids * is _hard_: child windows are destroyed with their * parent, so you'd have to track window hierarchy too, and * worse still windows can be unilaterally reparented by * other clients so even that isn't reliable. Even Xlib * doesn't try to track active resource ids on the client * side, hence the XC-MISC extension to get back a chunk of * its id space when trivial sequential allocation runs * out. * + So perhaps in fact this is just a silly and * overambitious idea and I'd be wiser not to try. * * - Other strace-like output options, such as prefixed timestamps * (-t, -tt, -ttt), alignment (-a), and more filtering options * under -e (e.g. filter on particular resource ids? Though that * doesn't sound _obviously_ useful...). * * - More xprop/xkill-like command line syntax for choosing a * client to trace via X RECORD? -id 0xXXX as a synonym for -p * XXX, for instance. Perhaps -name (for which we can reuse the * existing bfs loop to look for a window with the given WM_NAME * property). And should just 'xtruss' with no arguments work * like just 'xprop'? * * - Find some way of independently testing the correctness of the * vast amount of this program that I translated straight out of * the X protocol specs... * * - Clean the source code up: * + Separate the potentially cross-platform X protocol decoder * from the Unix-specific front end implementation * + Split up the giant switch-statement functions into smaller * pieces: compilers already struggle a bit with them on high * optimisation levels, and they'll only get bigger if more X * extensions become supported * + Think about how to manage the source modules cribbed from * PuTTY: want to strike a good balance between keeping them * PuTTYlike enough to be able to feed useful changes back, * and keeping them small and xtruss-specific enough for the * tarball not to look utterly stupid or include unnecessary * gunk. */ #include #include #include "putty.h" #include "ssh.h" #include "storage.h" #include "xtruss.h" #include "version.h" void read_random_seed(noise_consumer_t consumer) {} void write_random_seed(void *data, int len) {} const char usagemsg[] = " usage: xtruss [options] command [command arguments] trace a new program\n" " or: xtruss [options] -p trace an X client by resource id\n" " or: xtruss [options] -p - trace an X client selected interactively\n" " or: xtruss [options] -p all trace all clients of the X server\n" " or: xtruss [options] -p current trace clients already connected\n" " or: xtruss [options] -p future trace clients that connect in future\n" " or: xtruss [options] -P just run a logging proxy server\n" "options: -s set approximate limit on line length\n" " -o send log output to a file (default=stderr)\n" " -e [=][!][,...] filter the packets output, where:\n" " is 'requests' or 'events'\n" " is a request or event name, or 'all' or 'none'\n" " -I log X server initialisation message\n" " -R also give raw hex dump of session traffic\n" " -C unconditionally prefix client id to every line\n" " -A do not load existing atoms on startup in -p mode\n" " -display specify X display (overrides $DISPLAY)\n" " also: xtruss --version report version number\n" " xtruss --help display this help text\n" " xtruss --licence display the (MIT) licence text\n" ; void usage(FILE *fp) { fputs(usagemsg, fp); } const char licencemsg[] = "xtruss is copyright 1997-2021 Simon Tatham.\n" "\n" "Portions copyright Robert de Bath, Joris van Rantwijk, Delian\n" "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry,\n" "Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus\n" "Kuhn, Colin Watson, Christopher Staite, Lorenz Diener, Christian\n" "Brabandt, Jeff Smith, Pavel Kryukov, Maxim Kuznetsov, Svyatoslav\n" "Kuzmich, Nico Williams, Viktor Dukhovni, Josh Dersch, Lars Brinkhoff,\n" "and CORE SDI S.A.\n" "\n" "Permission is hereby granted, free of charge, to any person\n" "obtaining a copy of this software and associated documentation files\n" "(the \"Software\"), to deal in the Software without restriction,\n" "including without limitation the rights to use, copy, modify, merge,\n" "publish, distribute, sublicense, and/or sell copies of the Software,\n" "and to permit persons to whom the Software is furnished to do so,\n" "subject to the following conditions:\n" "\n" "The above copyright notice and this permission notice shall be\n" "included in all copies or substantial portions of the Software.\n" "\n" "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n" "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n" "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n" "NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE\n" "FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n" "CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n" "WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" "\n" "Except as contained in this notice, the name of the X Consortium\n" "shall not be used in advertising or otherwise to promote the sale,\n" "use or other dealings in this Software without prior written\n" "authorization from the X Consortium.\n" ; void licence(void) { fputs(licencemsg, stdout); } void version(void) { #ifdef PACKAGE_VERSION printf("xtruss, version %s\n", PACKAGE_VERSION); #endif printf("Source commit: %s\n", commitid); } const char *const appname = "xtruss"; static bool parse_hex(const char *str, unsigned *output) { int len = strlen(str); int scanned = -1; if (sscanf(str, "%x%n", output, &scanned) == 1 && scanned == len) return true; if (sscanf(str, "0x%x%n", output, &scanned) == 1 && scanned == len) return true; if (sscanf(str, "0X%x%n", output, &scanned) == 1 && scanned == len) return true; return false; } static int stringcmp(void *av, void *bv) { const char *a = (const char *)av; const char *b = (const char *)bv; return strcmp(a, b); } bool in_set(struct set *s, const char *string) { int found = (find234(s->strings, (void *)string, NULL) != NULL); if (s->include) return found; else return !found; } xtruss_state *xtruss_new(void) { xtruss_state *xs = snew(xtruss_state); memset(xs, 0, sizeof(*xs)); xs->conf = conf_new(); xs->requests_to_log.strings = newtree234(stringcmp); xs->events_to_log.strings = newtree234(stringcmp); return xs; } void xtruss_cmdline(xtruss_state *xs, int argc, char **argv) { bool doing_opts = true; char *disp = platform_get_x_display(); if (!disp) disp = dupstr(""); conf_set_str(xs->conf, CONF_x11_display, disp); sfree(disp); while (--argc > 0) { char *p = *++argv; if (doing_opts && *p == '-') { if (!strcmp(p, "--help") || !strcmp(p, "-help")) { usage(stdout); exit(0); } if (!strcmp(p, "--version") || !strcmp(p, "-version")) { version(); exit(0); } if (!strcmp(p, "--licence") || !strcmp(p, "-licence") || !strcmp(p, "--license") || !strcmp(p, "-license")) { licence(); exit(0); } if (!strcmp(p, "-display")) { char *val; if (--argc > 0) val = *++argv; else { fprintf(stderr, "xtruss: option \"%s\" expects an" " argument\n", p); exit(1); } conf_set_str(xs->conf, CONF_x11_display, val); continue; } if (p[1] == '-') { if (!p[2]) /* "--" terminates option parsing */ doing_opts = false; else { /* no GNU-style long options currently supported */ fprintf(stderr, "xtruss: unknown option '%s'\n", p); exit(1); } continue; } p++; while (*p) { int c = *p++; char *val; switch (c) { case 's': case 'o': case 'p': case 'e': /* options requiring an argument */ if (*p) { val = p; p += strlen(p); } else if (--argc > 0) { val = *++argv; } else { fprintf(stderr, "xtruss: option '-%c' expects an" " argument\n", c); exit(1); } switch (c) { case 's': if (!strcasecmp(val, "infinite") || !strcasecmp(val, "infinity") || !strcasecmp(val, "inf") || !strcasecmp(val, "unlimited") || !strcasecmp(val, "none") || !strcasecmp(val, "nolimit")) xs->sizelimit = 0; else xs->sizelimit = atoi(val); break; case 'o': xs->logfile = dupstr(val); break; case 'p': xs->xrecord = true; if (!strcmp(val, "-")) { xs->xrselectclient = true; xs->xrexit = true; } else if (!strcmp(val, "current")) { xs->xrclientid = 1; xs->print_client_ids = true; xs->xrexit = false; } else if (!strcmp(val, "future")) { xs->xrclientid = 2; xs->print_client_ids = true; xs->xrexit = false; } else if (!strcmp(val, "all")) { xs->xrclientid = 3; xs->print_client_ids = true; xs->xrexit = false; } else { if (!parse_hex(val, &xs->xrclientid)) { fprintf(stderr, "xtruss: invalid argument '%s'" " to option '-p'\n", val); exit(1); } xs->xrexit = true; } break; case 'e': { char *p; struct set *set; /* * Mimic the strace -e format: a list of * comma-separated strings, optionally * preceded by ! to indicate that those * are things _not_ to print, optionally * preceded further by a string followed * by '=' indicating that we're setting * something other than the default set * of requests to be logged. * * (Currently the only configurable set * _is_ that of requests to be logged, * but I put the machinery in place now * for there to be others since I * anticipate that there might very well * be.) */ p = strchr(val, '='); if (p) { ptrlen pl = make_ptrlen(val, p-val); if (ptrlen_eq_string(pl, "requests") || ptrlen_eq_string(pl, "request") || ptrlen_eq_string(pl, "reqs") || ptrlen_eq_string(pl, "req")) set = &xs->requests_to_log; else if (ptrlen_eq_string(pl, "events") || ptrlen_eq_string(pl, "event")) set = &xs->events_to_log; else { fprintf(stderr, "xtruss: unknown keyword" " for '-e': '%.*s'\n", PTRLEN_PRINTF(pl)); exit(1); } p++; /* skip '=' */ } else { /* In the absence of a foo= prefix, default * is to configure the set of X requests which * are printed or not printed. */ set = &xs->requests_to_log; p = val; } if (*p == '!') { set->include = false; p++; } else { set->include = true; } /* Empty the previous contents of the set if any */ while (1) { char *q = delpos234(set->strings, 0); if (!q) break; sfree(q); } while (p && *p) { char *q = strchr(p, ','); if (q) *q++ = '\0'; if (!strcmp(p, "none")) { /* just a placeholder */ } else if (!strcmp(p, "all")) { /* * Special case: everything is * included in this set, so we * have to flip the 'include' * parameter and empty the tree. */ while (1) { char *r = delpos234(set->strings, 0); if (!r) break; sfree(r); } set->include = !set->include; /* And nothing else will change this. */ break; } else { /* Just add to the set normally */ add234(set->strings, dupstr(p)); } p = q; } } break; } break; /* now options not requiring an argument */ case 'I': xs->print_server_startup = true; break; case 'R': xs->raw_hex_dump = true; break; case 'C': xs->print_client_ids = true; break; case 'P': xs->proxy_only = true; break; case 'A': xs->xrskipatoms = true; break; } } /* Configure mindisplaynum */ /* Configure proxy-side auth */ } else { xs->subcommand = argv; break; } } int nmodes = xs->xrecord + xs->proxy_only + (xs->subcommand != 0); if (nmodes == 0) { fprintf(stderr, "xtruss: must specify a command to run, or -p\n"); usage(stderr); exit(1); } if (nmodes > 1) { fprintf(stderr, "xtruss: must specify exactly one of -p, -P and" " a command\n"); usage(stderr); exit(1); } } void xtruss_start(xtruss_state *xs) { if (xs->logfile) { if (!strcmp(xs->logfile, "-")) { xs->outfp = stdout; } else { xs->outfp = fopen(xs->logfile, "w"); if (!xs->outfp) { fprintf(stderr, "xtruss: open(\"%s\"): %s\n", xs->logfile, strerror(errno)); exit(1); } } } else { xs->outfp = stderr; } const char *dispname = conf_get_str(xs->conf, CONF_x11_display); if (!dispname[0]) { fprintf(stderr, "xtruss: no X display to connect to\n"); exit(1); } char *errmsg = NULL; xs->x11disp = x11_setup_display(dispname, xs->conf, &errmsg); if (!xs->x11disp) { fprintf(stderr, "xtruss: unable to set up X display '%s': %s\n", dispname, errmsg); exit(1); } if (xs->xrecord) { xtruss_xrecord_start(xs); } else { xtruss_proxy_start(xs); if (xs->proxy_only) { printf("For sh: export DISPLAY=%s XAUTHORITY=%s\n", xs->env_disp, xs->env_auth); printf("For csh: setenv DISPLAY=%s; setenv XAUTHORITY=%s\n", xs->env_disp, xs->env_auth); } else { xtruss_start_subprocess(xs); } } xs->exit_status = -1; } work/xtruss.h0000664000000000000000000000710014714222463010454 0ustar struct set { tree234 *strings; /* sorted list of dynamically allocated "char *"s */ bool include; /* whether the tree is things to include or exclude */ }; bool in_set(struct set *s, const char *string); struct xtruss_platform; typedef struct xtruss_state xtruss_state; struct xtruss_state { Conf *conf; int sizelimit; char *logfile; bool proxy_only; bool print_server_startup, print_client_ids, raw_hex_dump; struct set requests_to_log, events_to_log; char **subcommand; const char *env_disp, *env_auth; bool xrecord, xrselectclient, xrskipatoms, xrexit; unsigned xrclientid; FILE *outfp; bool outfp_needs_closing; struct X11Display *x11disp; Socket **x11sockets; int n_x11sockets; unsigned num_clients_seen; unsigned newclientid; struct request *currreq; int exit_status; /* set to >= 0 when it's time to die */ struct xtruss_platform *platform; }; /* Provided by platform-independent xtruss code */ xtruss_state *xtruss_new(void); void xtruss_cmdline(xtruss_state *xs, int argc, char **argv); void xtruss_start(xtruss_state *xs); void xtruss_xrecord_start(xtruss_state *xs); void xtruss_proxy_start(xtruss_state *xs); /* Provided by the platform-specific module */ void xtruss_start_subprocess(xtruss_state *xs); struct xlog; typedef enum XLogType { XLOG_FULL, /* full X connection including welcome message */ XLOG_BARE, /* data from X RECORD, omitting welcome message */ } XLogType; struct xlog *xlog_new(xtruss_state *xs, XLogType type); void xlog_free(struct xlog *xl); void xlog_c2s(struct xlog *xl, const void *vdata, int len); void xlog_s2c(struct xlog *xl, const void *vdata, int len); void xlog_set_clientid(struct xlog *xl, unsigned clientid); unsigned xlog_get_clientid(struct xlog *xl); void xlog_set_endianness(struct xlog *xl, char endian); void xlog_set_next_seq(struct xlog *xl, int seq); void xlog_use_welcome_message(struct xlog *xl, const void *vdata, int len); void xlog_intern_atom(struct xlog *xl, char *name, unsigned long val); const char *xlog_translate_error(int errcode); const char *xlog_translate_event(int eventtype); /* * Macro used in coroutine-structured parts of the code. Expects the * coroutine to have a variable 'data' of char-pointer type, and 'len' * of integer type, containing the last data block passed to the * coroutine. Collects data from those variables, calling crReturnV as * necessary to fill them up, and appends it to the output strbuf * until that strbuf has at least the desired length. * * This macro isn't safe against side effects in its parameters: it * can't be, because it can't unilaterally allocate more space to * store copies of them in any structure preserved across crReturn. * * Note also that the parameters themselves must be expressions that * can survive a crReturn! */ #define crReadUpTo(sb, desired_length) do { \ while ((sb)->len < (desired_length)) { \ while (len <= 0) \ crReturnV; \ size_t Need = (desired_length) - (sb)->len; \ size_t Got = (Need < len ? Need : len); \ put_data(sb, data, Got); \ data += Got; \ len -= Got; \ } \ } while (0)