diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..ea4e6439 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: nviennot diff --git a/.github/workflows/DragonflyBSD.yml b/.github/workflows/DragonflyBSD.yml new file mode 100644 index 00000000..f014da9b --- /dev/null +++ b/.github/workflows/DragonflyBSD.yml @@ -0,0 +1,41 @@ +name: DragonflyBSD +on: + push: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/DragonflyBSD.yml' + pull_request: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/DragonflyBSD.yml' + + + +jobs: + DragonflyBSD: + runs-on: macos-12 + steps: + - uses: actions/checkout@v2 + - uses: vmactions/dragonflybsd-vm@v0 + with: + prepare: | + pkg install -y automake autoconf libtool pkgconf libevent msgpack libssh gsed + usesh: true + run: | + gsed -i "s/OK/0/g" tty-term.c + autoupdate + ./autogen.sh + ./configure + make + make install + + + diff --git a/.github/workflows/FreeBSD.yml b/.github/workflows/FreeBSD.yml new file mode 100644 index 00000000..e879618e --- /dev/null +++ b/.github/workflows/FreeBSD.yml @@ -0,0 +1,40 @@ +name: FreeBSD +on: + push: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/FreeBSD.yml' + pull_request: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/FreeBSD.yml' + + + +jobs: + FreeBSD: + runs-on: macos-12 + steps: + - uses: actions/checkout@v2 + - uses: vmactions/freebsd-vm@v0 + with: + prepare: | + pkg install -y automake autoconf libtool pkgconf libevent msgpack libssh + usesh: true + run: | + autoupdate + ./autogen.sh + ./configure + make + make install + + + diff --git a/.github/workflows/MacOS.yml b/.github/workflows/MacOS.yml new file mode 100644 index 00000000..3dc5247e --- /dev/null +++ b/.github/workflows/MacOS.yml @@ -0,0 +1,37 @@ +name: MacOS +on: + push: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/MacOS.yml' + pull_request: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/MacOS.yml' + + + +jobs: + MacOS: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: | + brew install automake msgpack libssh + autoupdate + ./autogen.sh + ./configure + make + make install + + + + diff --git a/.github/workflows/NetBSD.yml b/.github/workflows/NetBSD.yml new file mode 100644 index 00000000..0bb0924a --- /dev/null +++ b/.github/workflows/NetBSD.yml @@ -0,0 +1,40 @@ +name: NetBSD +on: + push: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/NetBSD.yml' + pull_request: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/NetBSD.yml' + + + +jobs: + NetBSD: + runs-on: macos-12 + steps: + - uses: actions/checkout@v2 + - uses: vmactions/netbsd-vm@v0 + with: + prepare: | + pkg_add automake autoconf libtool pkgconf libevent msgpack libssh + usesh: true + run: | + autoupdate + ./autogen.sh + ./configure + make + make install + + + diff --git a/.github/workflows/OpenBSD.yml b/.github/workflows/OpenBSD.yml new file mode 100644 index 00000000..8fb7901b --- /dev/null +++ b/.github/workflows/OpenBSD.yml @@ -0,0 +1,50 @@ +name: OpenBSD +on: + push: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/OpenBSD.yml' + pull_request: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/OpenBSD.yml' + + + +jobs: + OpenBSD: + runs-on: macos-12 + steps: + - uses: actions/checkout@v2 + - uses: vmactions/openbsd-vm@v0 + with: + prepare: | + pkg_add automake-1.16.3 autoconf-2.71 libtool pkgconf libevent msgpack libssh curl + usesh: true + run: | + sed -i 's,,,' *.[ch] + curl https://raw.githubusercontent.com/openbsd/ports/master/sysutils/tmate/patches/patch-Makefile_am | patch + curl https://raw.githubusercontent.com/openbsd/ports/master/sysutils/tmate/patches/patch-server_c | patch + curl https://raw.githubusercontent.com/openbsd/ports/master/sysutils/tmate/patches/patch-tmate-debug_c | patch + curl https://raw.githubusercontent.com/openbsd/ports/master/sysutils/tmate/patches/patch-tmate_h | patch + curl https://raw.githubusercontent.com/openbsd/ports/master/sysutils/tmate/patches/patch-tmux_c | patch + curl https://raw.githubusercontent.com/openbsd/ports/master/sysutils/tmate/patches/patch-tmux_h | patch + curl https://raw.githubusercontent.com/openbsd/ports/master/sysutils/tmate/patches/patch-osdep-openbsd_c | patch + export AUTOMAKE_VERSION=1.16 + export AUTOCONF_VERSION=2.71 + autoupdate + ./autogen.sh + ./configure + make + make install + + + diff --git a/.github/workflows/Ubuntu.yml b/.github/workflows/Ubuntu.yml new file mode 100644 index 00000000..cecf39b9 --- /dev/null +++ b/.github/workflows/Ubuntu.yml @@ -0,0 +1,37 @@ +name: Ubuntu +on: + push: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/Ubuntu.yml' + pull_request: + branches: + - '*' + paths: + - '**.c' + - '**.h' + - 'compat/*' + - '.github/workflows/Ubuntu.yml' + + + +jobs: + Ubuntu: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: | + sudo apt-get install -y build-essential automake libtool libevent-dev libncurses5-dev libmsgpack-dev libssh-dev pkg-config + autoupdate + ACLOCAL_PATH=/usr/share/aclocal ./autogen.sh + ./configure + make + sudo make install + + + + diff --git a/.gitignore b/.gitignore index 93153ede..bcbdfe13 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ downloads/ ext/ libssh-*/ msgpack-*/ +releases/ diff --git a/.travis.yml b/.travis.yml index a1d7e427..5a83e0ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,40 @@ language: c +services: +- docker + matrix: - include: - - compiler: gcc - - compiler: clang - env: CFLAGS="-g -O2" -before_install: - - sudo apt-get update -qq - - sudo apt-get -y install debhelper autotools-dev dh-autoreconf file libncurses5-dev libevent-dev pkg-config libutempter-dev build-essential -script: (CFLAGS= ./autogen.sh) && ./configure --enable-debug && make + include: + - arch: amd64 + env: PLATFORM=amd64 + - arch: amd64 + env: PLATFORM=i386 + - arch: arm64 + env: PLATFORM=arm32v6 + - arch: arm64 + env: PLATFORM=arm32v7 + - arch: arm64 + env: PLATFORM=arm64v8 + - arch: s390x + env: PLATFORM=s390x + - arch: ppc64le + env: PLATFORM=ppc64le + +script: +- 'docker build . --tag local-$PLATFORM/tmate-build --build-arg PLATFORM=$PLATFORM' +# On arch=arm64, some directories are not setup correctly, and 'ruby -S gem +# install dpl' required by the release push scripts fails. +- 'if [ "$TRAVIS_TAG" ]; then sudo chown -R $USER: /var/lib/gems /usr/local/bin; fi' +- 'if [ "$TRAVIS_TAG" ]; then ./build_static_release.sh $TRAVIS_TAG $PLATFORM; fi' + +deploy: + provider: releases + api_key: + secure: T2109tjjOsrVLEpJZK/uxmO0AuDGXYFdN4AAsNTmVwu/W5dcX57Kk2TCgqDuLfD21iGGXP0U/OYHM06IfBDODBWCA9P8ASHYsenS7wIiFnvCEMbfzoAFyBMrXN2kNdM2+ho3aqc0xE2lQKOKDLxpGm5FZrzujscXXzxQjWBU5Hk= + skip_cleanup: true + overwrite: true + file_glob: true + file: releases/*.tar.* + on: + repo: tmate-io/tmate + branch: master + tags: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..2f6387c5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +ARG PLATFORM=amd64 +FROM ${PLATFORM}/alpine:3.10 AS build + +WORKDIR /build + +RUN apk add --no-cache wget cmake make gcc g++ linux-headers zlib-dev openssl-dev \ + automake autoconf libevent-dev ncurses-dev msgpack-c-dev libexecinfo-dev \ + ncurses-static libexecinfo-static libevent-static msgpack-c ncurses-libs \ + libevent libexecinfo openssl zlib + +RUN set -ex; \ + mkdir -p /src/libssh/build; \ + cd /src; \ + wget -O libssh.tar.xz https://www.libssh.org/files/0.9/libssh-0.9.0.tar.xz; \ + tar -xf libssh.tar.xz -C /src/libssh --strip-components=1; \ + cd /src/libssh/build; \ + cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr \ + -DWITH_SFTP=OFF -DWITH_SERVER=OFF -DWITH_PCAP=OFF \ + -DWITH_STATIC_LIB=ON -DWITH_GSSAPI=OFF ..; \ + make -j $(nproc); \ + make install + +COPY compat ./compat +COPY *.c *.h autogen.sh Makefile.am configure.ac ./ + +RUN ./autogen.sh && ./configure --enable-static +RUN make -j $(nproc) +RUN objcopy --only-keep-debug tmate tmate.symbols && chmod -x tmate.symbols && strip tmate +RUN ./tmate -V + +FROM alpine:3.9 + +RUN apk --no-cache add bash +RUN mkdir /build +ENV PATH=/build:$PATH +COPY --from=build /build/tmate.symbols /build +COPY --from=build /build/tmate /build diff --git a/Makefile.am b/Makefile.am index 2551bce3..3da954ae 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,8 +33,8 @@ CFLAGS += -g CFLAGS += -Wno-long-long -Wall -W -Wnested-externs -Wformat=2 CFLAGS += -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations CFLAGS += -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare -CFLAGS += -Wundef -Wbad-function-cast -Winline -Wcast-align -CFLAGS += -Wdeclaration-after-statement -Wno-pointer-sign -Wno-attributes +CFLAGS += -Wundef -Wbad-function-cast -Winline +CFLAGS += -Wno-pointer-sign -Wno-attributes CPPFLAGS += -DDEBUG endif if IS_COVERAGE @@ -44,6 +44,9 @@ endif CPPFLAGS += -iquote. endif +CFLAGS += -Wno-unused-parameter -Wno-unused-variable -Wno-null-pointer-arithmetic +CFLAGS += -Wno-deprecated-declarations -Wno-format-nonliteral + # Set flags for Solaris. if IS_SUNOS if IS_GCC diff --git a/Makefile.static-build b/Makefile.static-build deleted file mode 100644 index 9bc83d92..00000000 --- a/Makefile.static-build +++ /dev/null @@ -1,61 +0,0 @@ -LIBSSH=libssh-0.7.2 -LIBSSH_URL=https://red.libssh.org/attachments/download/177/$(LIBSSH).tar.xz -LIBSSH_LIB=ext/lib/libssh.a - -MSGPACK=msgpack-1.3.0 -MSGPACK_URL=https://github.com/msgpack/msgpack-c/releases/download/cpp-1.3.0/$(MSGPACK).tar.gz -MSGPACK_LIB=ext/lib/libmsgpack.a - -TMATE_CONFIGURE=PKG_CONFIG_PATH=./ext/lib/pkgconfig - -LIBC=$(shell gcc -print-file-name=libc.a) -STATIC_LIBC_OBJECTS=fdelt_chk -STATIC_COMPAT_OBJECTS=memcpy clock_gettime - -all: tmate - -dependencies: - apt-get install build-essential cmake libssl-dev autoconf automake pkg-config libtool libevent-dev libncurses-dev zlib1g-dev - -downloads/$(notdir $(LIBSSH_URL)): - mkdir -p downloads - wget -O $@ $(LIBSSH_URL) - -$(LIBSSH)/.ready: downloads/$(notdir $(LIBSSH_URL)) - tar xf $< - touch $@ - -downloads/$(notdir $(MSGPACK_URL)): - mkdir -p downloads - wget -O $@ $(MSGPACK_URL) - -$(MSGPACK)/.ready: downloads/$(notdir $(MSGPACK_URL)) - tar xf $< - touch $@ - -$(LIBSSH_LIB): $(LIBSSH)/.ready - mkdir -p $(LIBSSH)/build - cd $(LIBSSH)/build; ([ -f Makefile ] || cmake -DCMAKE_INSTALL_PREFIX:PATH=$(shell pwd)/ext .. -DWITH_SFTP=OFF -DWITH_SERVER=OFF -DWITH_PCAP=OFF -DWITH_STATIC_LIB=ON -DWITH_GSSAPI=OFF) - +make -C $(LIBSSH)/build install - -$(MSGPACK_LIB): $(MSGPACK)/.ready - mkdir -p $(MSGPACK)/build - cd $(MSGPACK)/build; ([ -f Makefile ] || cmake -DCMAKE_INSTALL_PREFIX:PATH=$(shell pwd)/ext ..) - +make -C $(MSGPACK)/build install - -libc/%.o: - mkdir -p libc - cd libc; ar x $(LIBC) $(notdir $@) - -compat/%.o: compat/%.c - gcc -c -o $@ $< - -tmate: $(MSGPACK_LIB) $(LIBSSH_LIB) $(patsubst %,libc/%.o,$(STATIC_LIBC_OBJECTS)) $(patsubst %,compat/%.o,$(STATIC_COMPAT_OBJECTS)) - ./autogen.sh - $(TMATE_CONFIGURE) ./configure --enable-static - +make - strip tmate - -clean: - rm -rf ext libc $(LIBSSH) $(MSGPACK) - +make clean diff --git a/build_static_release.sh b/build_static_release.sh new file mode 100755 index 00000000..9cb35435 --- /dev/null +++ b/build_static_release.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -eux + +# This is invoked by .travis.yml + +VERSION=$1 +PLATFORM=$2 + +SRC_VERSION=`cat configure.ac | grep AC_INIT | sed -E 's/^AC_INIT\(tmate, (.+)\)$/\1/'` + +if [ $SRC_VERSION != $VERSION ]; then + echo "Version mismatch: $SRC_VERSION != $VERSION" + exit 1 +fi + +RELEASE_NAME=tmate-$VERSION-static-linux-$PLATFORM +echo "Building $RELEASE_NAME" + +docker build . --tag local-$PLATFORM/tmate-build --build-arg PLATFORM=$PLATFORM + +mkdir -p releases +cd releases + +rm -rf $RELEASE_NAME +mkdir -p $RELEASE_NAME +docker run --rm local-$PLATFORM/tmate-build cat /build/tmate > $RELEASE_NAME/tmate +chmod +x $RELEASE_NAME/tmate +tar -cf - $RELEASE_NAME | xz > tmate-$VERSION-static-linux-$PLATFORM.tar.xz + +rm -rf $RELEASE_NAME-symbols +mkdir -p $RELEASE_NAME-symbols +docker run --rm local-$PLATFORM/tmate-build cat /build/tmate.symbols > $RELEASE_NAME-symbols/tmate.symbols +tar -cf - $RELEASE_NAME-symbols | xz > dbg-symbols-tmate-$VERSION-static-linux-$PLATFORM.tar.xz diff --git a/cfg.c b/cfg.c index 572f8369..2410fbf9 100644 --- a/cfg.c +++ b/cfg.c @@ -71,7 +71,7 @@ start_cfg(void) cfg_add_cause("%s: %s", TMUX_CONF, strerror(errno)); if (cfg_file == NULL && (home = find_home()) != NULL) { - xasprintf(&cfg_file, "%s/.tmux.conf", home); + xasprintf(&cfg_file, "%s/.tmate.conf", home); if (access(cfg_file, R_OK) != 0 && errno == ENOENT) { free(cfg_file); cfg_file = NULL; @@ -81,20 +81,6 @@ start_cfg(void) cfg_add_cause("%s: %s", cfg_file, cause); free(cause); -#ifdef TMATE - cause = NULL; - if ((home = find_home()) != NULL) { - xasprintf(&tmate_cfg_file, "%s/.tmate.conf", home); - if (access(tmate_cfg_file, R_OK) != 0 && errno == ENOENT) { - free(tmate_cfg_file); - tmate_cfg_file = NULL; - } - } - if (tmate_cfg_file != NULL && load_cfg(tmate_cfg_file, cfg_cmd_q, &cause) == -1) - cfg_add_cause("%s: %s", cfg_file, cause); - free(cause); -#endif - cmdq_continue(cfg_cmd_q); } @@ -149,6 +135,20 @@ load_cfg(const char *path, struct cmd_q *cmdq, char **cause) return (found); } +static void print_cfg_errors(void) +{ + u_int i; + + for (i = 0; i < cfg_ncauses; i++) { + tmate_info("%s", cfg_causes[i]); + free(cfg_causes[i]); + } + + free(cfg_causes); + cfg_causes = NULL; + cfg_ncauses = 0; +} + void cfg_default_done(__unused struct cmd_q *cmdq) { @@ -157,7 +157,14 @@ cfg_default_done(__unused struct cmd_q *cmdq) cfg_finished = 1; #ifdef TMATE + /* We do it this late, this way, CLI options take precedence over cfg file */ + tmate_load_cli_options(); + tmate_session_start(); + if (tmate_foreground && cfg_ncauses) { + print_cfg_errors(); + exit(1); + } #endif if (!RB_EMPTY(&sessions)) diff --git a/client.c b/client.c index 164abdd3..c587ec0d 100644 --- a/client.c +++ b/client.c @@ -32,6 +32,7 @@ #include #include "tmux.h" +#include "tmate.h" struct tmuxproc *client_proc; struct tmuxpeer *client_peer; @@ -213,8 +214,67 @@ client_exit_message(void) #ifdef TMATE extern const struct cmd_entry cmd_attach_session_entry; extern const struct cmd_entry cmd_new_session_entry; + +/* For foreground mode */ +static int __argc; +static const char **__argv; #endif +int run_headless_command(int argc, const char **argv, int flags, void (*err_callback)(const char *)) +{ + struct cmd_q *cmd_q; + struct cmd_list *cmdlist; + char *cause; + cmd_q = cmdq_new(NULL); /* No client */ + + if ((cmdlist = cmd_list_parse(argc, (char **)argv, NULL, 0, &cause)) == NULL) { + if (err_callback) + err_callback(cause); + return -1; + } + + cmdq_run(cmd_q, cmdlist, NULL); + cmd_list_free(cmdlist); + cmdq_free(cmd_q); + + if (flags & DEFER_ERRORS_CFG) + return 0; + + /* error messages land in cfg_causes */ + int ret = cfg_ncauses ? -1 : 0; + for (u_int i = 0; i < cfg_ncauses; i++) { + if (err_callback) + err_callback(cfg_causes[i]); + free(cfg_causes[i]); + } + + free(cfg_causes); + cfg_causes = NULL; + cfg_ncauses = 0; + + return ret; +} + +static void initial_client_cmd_err_callback(const char *cause) +{ + tmate_info("%s", cause); +} + +void run_initial_client_cmd(void) +{ + int argc = __argc; + const char **argv = __argv; + + const char *default_argv[] = {"new-session"}; + if (argc == 0) { + argc = 1; + argv = default_argv; + } + + if (run_headless_command(argc, argv, 0, initial_client_cmd_err_callback) < 0) + exit(1); +} + /* Client main loop. */ int client_main(struct event_base *base, int argc, char **argv, int flags, @@ -232,6 +292,8 @@ client_main(struct event_base *base, int argc, char **argv, int flags, size_t size; #ifdef TMATE int cant_nest = 0; + __argc = argc; + __argv = (const char **)argv; #endif /* Ignore SIGCHLD now or daemon() in the server will leave a zombie. */ diff --git a/cmd-new-session.c b/cmd-new-session.c index 357ffed1..fb2f7eab 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -131,6 +131,9 @@ cmd_new_session_exec(struct cmd *self, struct cmd_q *cmdq) if (c == NULL) detached = 1; + if (tmate_foreground) + detached = 1; + /* Is this client already attached? */ already_attached = 0; if (c != NULL && c->session != NULL) diff --git a/cmd-string.c b/cmd-string.c index 757d4cdb..fdef4a82 100644 --- a/cmd-string.c +++ b/cmd-string.c @@ -307,6 +307,10 @@ cmd_string_variable(const char *s, size_t *p) free(buf); if (envent == NULL) return (xstrdup("")); +#ifdef TMATE + if (envent->value == NULL) + return (xstrdup("")); +#endif return (xstrdup(envent->value)); error: diff --git a/compat/b64_ntop.c b/compat/b64_ntop.c index 2b4dc2d4..fd8930f5 100644 --- a/compat/b64_ntop.c +++ b/compat/b64_ntop.c @@ -52,6 +52,8 @@ #include #include +#include "compat.h" + #define Assert(Cond) if (!(Cond)) abort() static const char Base64[] = @@ -122,7 +124,7 @@ static const char Pad64 = '='; */ int -b64_ntop(uint8_t const *src, size_t srclength, char *target, size_t targsize) { +b64_ntop(const char *src, size_t srclength, char *target, size_t targsize) { size_t datalength = 0; uint8_t input[3]; uint8_t output[4]; diff --git a/compat/clock_gettime.c b/compat/clock_gettime.c deleted file mode 100644 index 0b631b79..00000000 --- a/compat/clock_gettime.c +++ /dev/null @@ -1,9 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include - -int clock_gettime(clockid_t clk_id, struct timespec *tp) -{ - return syscall(SYS_clock_gettime, clk_id, tp); -} diff --git a/compat/memcpy.c b/compat/memcpy.c deleted file mode 100644 index 37092a33..00000000 --- a/compat/memcpy.c +++ /dev/null @@ -1,11 +0,0 @@ -// http://stackoverflow.com/questions/8823267/linking-against-older-symbol-version-in-a-so-file - -#include - -/* some systems do not have newest memcpy@@GLIBC_2.14 - stay with old good one */ -asm (".symver memcpy, memcpy@GLIBC_2.2.5"); - -void *__wrap_memcpy(void *dest, const void *src, size_t n) -{ - return memcpy(dest, src, n); -} diff --git a/configure.ac b/configure.ac index 02d2872b..028d5559 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,8 @@ # configure.ac -AC_INIT(tmate, 2.2.1) +AC_INIT(tmate, 2.4.0) +AM_SILENT_RULES([yes]) AC_CONFIG_AUX_DIR(etc) AM_INIT_AUTOMAKE([foreign subdir-objects]) @@ -47,10 +48,11 @@ AC_ARG_ENABLE( found_static=$enable_static ) if test "x$found_static" = xyes; then + # XXX Static build are only doable with the musl library PKG_CONFIG="pkg-config --static" CFLAGS="$CFLAGS -flto" - LDFLAGS="$LDFLAGS -flto" + LDFLAGS="$LDFLAGS -flto -static -no-pie" PKG_CHECK_MODULES([ZLIB], [zlib], [ CPPFLAGS="$ZLIB_CFLAGS $CPPFLAGS" @@ -61,7 +63,6 @@ if test "x$found_static" = xyes; then CPPFLAGS="$LIBCRYPTO_CFLAGS $CPPFLAGS" LIBS="$LIBCRYPTO_LIBS $LIBS" ]) - # See more static settings below... (search for found_static) fi # Is this gcc? @@ -102,6 +103,7 @@ AC_CHECK_HEADERS( bitstring.h \ curses.h \ dirent.h \ + execinfo.h \ fcntl.h \ inttypes.h \ libutil.h \ @@ -121,9 +123,13 @@ AC_CHECK_HEADERS( # Look for library needed for flock. AC_SEARCH_LIBS(flock, bsd) +# Look for library needed for backtrace +AC_SEARCH_LIBS(backtrace, execinfo) + # Check for some functions that are replaced or omitted. AC_CHECK_FUNCS( [ \ + backtrace \ dirfd \ flock \ setproctitle \ @@ -209,7 +215,7 @@ fi PKG_CHECK_MODULES( LIBSSH, - libssh >= 0.6.0, + libssh >= 0.8.4, [ CPPFLAGS="$LIBSSH_CFLAGS $CPPFLAGS" LIBS="$LIBSSH_LIBS $LIBS" @@ -218,7 +224,7 @@ PKG_CHECK_MODULES( found_libssh=no ) if test "x$found_libssh" = xno; then - AC_MSG_ERROR("libssh >= 0.6.0 not found") + AC_MSG_ERROR("libssh >= 0.8.4 not found") fi # Check for b64_ntop. @@ -457,17 +463,6 @@ if test "x$found_getopt" != xno; then fi AM_CONDITIONAL(NO_GETOPT, [test "x$found_getopt" = xno]) -if test "x$found_static" = xyes; then - # libc and libdl should be dynamically linked. - # but we want to lower our requirements for libc's version. - # so we extract some symobls and add them here - if test `uname -m` = x86_64; then - LIBS="compat/memcpy.o -Wl,--wrap=memcpy $LIBS" - fi - LIBS="compat/clock_gettime.o libc/fdelt_chk.o $LIBS" - LIBS="-Wl,-Bstatic ${LIBS/-ldl/} -Wl,-Bdynamic -ldl" -fi - # Check for BSD-style integer types. AC_MSG_CHECKING(for BSD-style unsigned types) AC_COMPILE_IFELSE([AC_LANG_SOURCE( diff --git a/log.c b/log.c index 018348cf..45aaea46 100644 --- a/log.c +++ b/log.c @@ -32,6 +32,11 @@ static int log_level; static void log_event_cb(int, const char *); static void log_vwrite(const char *, va_list); +static int is_log_stdout(void) +{ + return fileno(log_file) <= 2; +} + /* Log callback for libevent. */ static void log_event_cb(__unused int severity, const char *msg) @@ -53,6 +58,21 @@ log_get_level(void) return (log_level); } +void +log_open_fp(FILE *f) +{ + if (log_file == f) + return; + + if (log_file != NULL && !is_log_stdout()) + fclose(log_file); + + log_file = f; + + setvbuf(log_file, NULL, _IOLBF, 0); + event_set_log_callback(log_event_cb); +} + /* Open logging to file. */ void log_open(const char *name) @@ -62,24 +82,18 @@ log_open(const char *name) if (log_level == 0) return; - if (log_file != NULL) - fclose(log_file); - xasprintf(&path, "tmate-%s-%ld.log", name, (long)getpid()); - log_file = fopen(path, "w"); + FILE *f = fopen(path, "w"); free(path); - if (log_file == NULL) - return; - - setvbuf(log_file, NULL, _IOLBF, 0); - event_set_log_callback(log_event_cb); + if (f) + log_open_fp(f); } /* Close logging. */ void log_close(void) { - if (log_file != NULL) + if (log_file != NULL && !is_log_stdout()) fclose(log_file); log_file = NULL; @@ -87,6 +101,7 @@ log_close(void) } /* Write a log message. */ +__attribute__((__format__(__printf__, 1, 0))) static void log_vwrite(const char *msg, va_list ap) { @@ -102,9 +117,16 @@ log_vwrite(const char *msg, va_list ap) exit(1); gettimeofday(&tv, NULL); - if (fprintf(log_file, "%lld.%06d %s\n", (long long)tv.tv_sec, - (int)tv.tv_usec, out) == -1) - exit(1); + + if (is_log_stdout()) { + if (fprintf(log_file, "%s\n", out) == -1) + exit(1); + } else { + if (fprintf(log_file, "%lld.%06d %s\n", (long long)tv.tv_sec, + (int)tv.tv_usec, out) == -1) + exit(1); + } + fflush(log_file); free(out); @@ -113,16 +135,20 @@ log_vwrite(const char *msg, va_list ap) /* Log a debug message. */ void -log_debug(const char *msg, ...) +log_emit(int level, const char *msg, ...) { va_list ap; + if (log_level < level) + return; + va_start(ap, msg); log_vwrite(msg, ap); va_end(ap); } /* Log a critical error with error string and die. */ +__attribute__((__format__(__printf__, 1, 0))) __dead void fatal(const char *msg, ...) { @@ -132,11 +158,13 @@ fatal(const char *msg, ...) va_start(ap, msg); if (asprintf(&fmt, "fatal: %s: %s", msg, strerror(errno)) == -1) exit(1); - log_vwrite(fmt, ap); + msg = fmt; + log_vwrite(msg, ap); exit(1); } /* Log a critical error and die. */ +__attribute__((__format__(__printf__, 1, 0))) __dead void fatalx(const char *msg, ...) { @@ -146,6 +174,7 @@ fatalx(const char *msg, ...) va_start(ap, msg); if (asprintf(&fmt, "fatal: %s", msg) == -1) exit(1); - log_vwrite(fmt, ap); + msg = fmt; + log_vwrite(msg, ap); exit(1); } diff --git a/options-table.c b/options-table.c index 6b0c248b..e3362ba8 100644 --- a/options-table.c +++ b/options-table.c @@ -925,13 +925,19 @@ const struct options_table_entry options_table[] = { { .name = "tmate-server-rsa-fingerprint", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = "af:2d:81:c1:fe:49:70:2d:7f:09:a9:d7:4b:32:e3:be" + .default_str = "SHA256:Hthk2T/M/Ivqfk1YYUn5ijC2Att3+UPzD7Rn72P5VWs" }, { .name = "tmate-server-ecdsa-fingerprint", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = "c7:a1:51:36:d2:bb:35:4b:0a:1a:c0:43:97:74:ea:42" + .default_str = "SHA256:8GmKHYHEJ6n0TEdciHeEGkKOigQfCFuBULdt6vZIhDc" + }, + + { .name = "tmate-server-ed25519-fingerprint", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .default_str = "SHA256:jfttvoypkHiQYUqUCwKeqd9d1fJj/ZiQlFOHVl6E9sI" }, { .name = "tmate-display-time", @@ -953,6 +959,44 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .default_str = "" }, + + { .name = "tmate-api-key", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .default_str = "" + }, + + { .name = "tmate-session-name", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .default_str = "" + }, + + { .name = "tmate-session-name-ro", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .default_str = "" + }, + + { .name = "tmate-authorized-keys", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .default_str = "" + }, + + { .name = "tmate-set", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .default_str = "" + }, + + { .name = "tmate-foreground-restart", + .type = OPTIONS_TABLE_NUMBER, + .scope = OPTIONS_TABLE_SERVER, + .minimum = 0, + .maximum = 1, + .default_num = 1 + }, #endif { .name = NULL } diff --git a/osdep-darwin.c b/osdep-darwin.c index 40b18951..53b48138 100644 --- a/osdep-darwin.c +++ b/osdep-darwin.c @@ -28,7 +28,9 @@ char *osdep_get_name(int, char *); char *osdep_get_cwd(int); struct event_base *osdep_event_init(void); +#ifndef __unused #define __unused __attribute__ ((__unused__)) +#endif char * osdep_get_name(int fd, __unused char *tty) diff --git a/proc.c b/proc.c index 27880f57..edd4a6f8 100644 --- a/proc.c +++ b/proc.c @@ -172,7 +172,7 @@ proc_start(const char *name, struct event_base *base, int forkflag, struct tmuxproc *tp; struct utsname u; - if (forkflag) { + if (forkflag && !tmate_foreground) { switch (fork()) { case -1: fatal("fork failed"); @@ -189,7 +189,13 @@ proc_start(const char *name, struct event_base *base, int forkflag, fatalx("event_reinit failed"); } - log_open(name); + if (tmate_foreground) { + if (forkflag) + clear_signals(0); + log_open_fp(stdout); + } else { + log_open(name); + } #ifdef HAVE_SETPROCTITLE setproctitle("%s (%s)", name, socket_path); diff --git a/screen-redraw.c b/screen-redraw.c index 952a8515..4201b022 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -278,7 +278,7 @@ screen_redraw_draw_borders(struct client *c, int status, u_int top) struct window *w = s->curw->window; struct options *oo = w->options; struct tty *tty = &c->tty; - struct window_pane *wp; + struct window_pane *wp = NULL; struct grid_cell m_active_gc, active_gc, m_other_gc, other_gc; struct grid_cell msg_gc; u_int i, j, type, msgx = 0, msgy = 0; diff --git a/server.c b/server.c index 81334f1e..05706769 100644 --- a/server.c +++ b/server.c @@ -152,14 +152,16 @@ server_start(struct event_base *base, int lockfd, char *lockfile) { int pair[2]; - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) - fatal("socketpair failed"); + if (!tmate_foreground) + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) + fatal("socketpair failed"); server_proc = proc_start("server", base, 1, server_signal); if (server_proc == NULL) { close(pair[1]); return (pair[0]); } + close(pair[0]); if (log_get_level() > 3) @@ -190,7 +192,8 @@ server_start(struct event_base *base, int lockfd, char *lockfile) if (server_fd == -1) fatal("couldn't create socket"); server_update_socket(); - server_client_create(pair[1]); + if (!tmate_foreground) + server_client_create(pair[1]); if (lockfd >= 0) { unlink(lockfile); @@ -205,17 +208,17 @@ server_start(struct event_base *base, int lockfd, char *lockfile) status_prompt_load_history(); -#ifdef TMATE - tmate_write_ready(); -#endif - server_add_accept(0); + if (tmate_foreground) + run_initial_client_cmd(); + proc_loop(server_proc, server_loop); status_prompt_save_history(); #ifdef TMATE unlink(socket_path); #endif + exit(0); } @@ -365,6 +368,7 @@ server_signal(int sig) int fd; switch (sig) { + case SIGINT: case SIGTERM: server_exit = 1; server_send_exit(); diff --git a/session.c b/session.c index 5a3b867a..879eb657 100644 --- a/session.c +++ b/session.c @@ -207,6 +207,36 @@ session_free(__unused int fd, __unused short events, void *arg) } } +static void maybe_restart_session(void) +{ + int fg_restart = options_get_number(global_options, "tmate-foreground-restart"); + if (!fg_restart) + return; + + /* + * throttle restarts. This is a blocking sleep. It's + * simpler than using a timer, but fairly harmless + * from a blocking perspective. + */ + usleep(500*1000); + next_session_id = 0; + run_initial_client_cmd(); + + tmate_info("Session shell restarted"); + + struct session *s; + s = RB_MIN(sessions, &sessions); + if (!s) + return; + + struct window_pane *wp; + wp = s->curw->window->active; + window_pane_set_mode(wp, &window_copy_mode); + window_copy_init_for_output(wp); + window_copy_add(wp, "%s", "Session shell restarted"); + window_copy_add(wp, "%s", "Note: press the following sequence to disconnect from SSH: ~."); +} + /* Destroy a session. */ void session_destroy(struct session *s) @@ -215,10 +245,6 @@ session_destroy(struct session *s) log_debug("session %s destroyed", s->name); -#ifdef TMATE - tmate_write_fin(); -#endif - RB_REMOVE(sessions, &sessions, s); notify_session_closed(s); @@ -240,6 +266,15 @@ session_destroy(struct session *s) free((void *)s->cwd); session_unref(s); + +#ifdef TMATE + if (tmate_foreground && !server_exit) { + maybe_restart_session(); + } else { + tmate_info("Session closed"); + tmate_write_fin(); + } +#endif } /* Check a session name is valid: not empty and no colons or periods. */ @@ -586,6 +621,7 @@ session_group_index(struct session_group *sg) } fatalx("session group not found"); + for(;;); } /* diff --git a/signal.c b/signal.c index 19938638..f20a0257 100644 --- a/signal.c +++ b/signal.c @@ -24,6 +24,7 @@ #include "tmux.h" +struct event ev_sigint; struct event ev_sighup; struct event ev_sigchld; struct event ev_sigcont; @@ -40,15 +41,23 @@ set_signals(void (*handler)(int, short, void *), void *arg) sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_RESTART; sigact.sa_handler = SIG_IGN; +#ifndef TMATE if (sigaction(SIGINT, &sigact, NULL) != 0) fatal("sigaction failed"); +#endif if (sigaction(SIGPIPE, &sigact, NULL) != 0) fatal("sigaction failed"); if (sigaction(SIGUSR2, &sigact, NULL) != 0) fatal("sigaction failed"); +#ifndef TMATE if (sigaction(SIGTSTP, &sigact, NULL) != 0) fatal("sigaction failed"); +#endif +#ifdef TMATE + signal_set(&ev_sigint, SIGINT, handler, arg); + signal_add(&ev_sigint, NULL); +#endif signal_set(&ev_sighup, SIGHUP, handler, arg); signal_add(&ev_sighup, NULL); signal_set(&ev_sigchld, SIGCHLD, handler, arg); @@ -72,16 +81,24 @@ clear_signals(int after_fork) sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_RESTART; sigact.sa_handler = SIG_DFL; +#ifndef TMATE if (sigaction(SIGINT, &sigact, NULL) != 0) fatal("sigaction failed"); +#endif if (sigaction(SIGPIPE, &sigact, NULL) != 0) fatal("sigaction failed"); if (sigaction(SIGUSR2, &sigact, NULL) != 0) fatal("sigaction failed"); +#ifndef TMATE if (sigaction(SIGTSTP, &sigact, NULL) != 0) fatal("sigaction failed"); +#endif if (after_fork) { +#ifdef TMATE + if (sigaction(SIGINT, &sigact, NULL) != 0) + fatal("sigaction failed"); +#endif if (sigaction(SIGHUP, &sigact, NULL) != 0) fatal("sigaction failed"); if (sigaction(SIGCHLD, &sigact, NULL) != 0) @@ -95,6 +112,7 @@ clear_signals(int after_fork) if (sigaction(SIGWINCH, &sigact, NULL) != 0) fatal("sigaction failed"); } else { + event_del(&ev_sigint); event_del(&ev_sighup); event_del(&ev_sigchld); event_del(&ev_sigcont); diff --git a/tmate-debug.c b/tmate-debug.c index f1fd971f..81850284 100644 --- a/tmate-debug.c +++ b/tmate-debug.c @@ -1,10 +1,20 @@ +#ifdef HAVE_EXECINFO_H #include +#endif #include #include #include #include #include "tmate.h" +#ifndef HAVE_BACKTRACE + +void tmate_print_stack_trace(void) {} +void tmate_catch_sigsegv(void) {} +void tmate_preload_trace_lib(void) {} + +#else + #if DEBUG static int print_resolved_stack_frame(const char *frame) @@ -75,16 +85,28 @@ void tmate_print_stack_trace(void) free (strings); } - -static void handle_sigsegv(__unused int sig) +static void handle_crash(int sig) { /* TODO send stack trace to server */ - tmate_info("CRASH, printing stack trace"); + const char *what = sig == SIGSEGV ? "SIGSEGV" : "SIGABRT"; + tmate_info("%s printing stack trace", what); tmate_print_stack_trace(); - tmate_fatal("CRASHED"); + + /* Reraise */ + signal(sig, NULL); + kill(getpid(), sig); } void tmate_catch_sigsegv(void) { - signal(SIGSEGV, handle_sigsegv); + signal(SIGSEGV, handle_crash); + signal(SIGABRT, handle_crash); } + +void tmate_preload_trace_lib(void) +{ + void *array[1]; + backtrace(array, 1); +} + +#endif diff --git a/tmate-encoder.c b/tmate-encoder.c index c15d8921..e1373451 100644 --- a/tmate-encoder.c +++ b/tmate-encoder.c @@ -1,3 +1,4 @@ +#include #include "tmate.h" #include "tmate-protocol.h" #include "window-copy.h" @@ -12,6 +13,23 @@ void tmate_write_header(void) pack(string, VERSION); } +void tmate_write_uname(void) +{ + struct utsname name; + if (uname(&name) < 0) { + tmate_debug("uname() failed"); + return; + } + + pack(array, 6); + pack(int, TMATE_OUT_UNAME); + pack(string, name.sysname); + pack(string, name.nodename); + pack(string, name.release); + pack(string, name.version); + pack(string, name.machine); +} + void tmate_write_ready(void) { pack(array, 1); @@ -238,6 +256,14 @@ void tmate_exec_cmd_args(int argc, const char **argv) append_saved_cmd(&tmate_session, argc, argv); } +void tmate_set_val(const char *name, const char *value) +{ + char *buf; + xasprintf(&buf, "%s=%s", name, value); + tmate_exec_cmd_args(3, (const char *[]){"set-option", "tmate-set", buf}); + free(buf); +} + void tmate_exec_cmd(struct cmd *cmd) { int argc; @@ -456,6 +482,7 @@ void tmate_send_reconnection_state(struct tmate_session *session) tmate_send_reconnection_data(session); replay_saved_cmd(session); /* TODO send all option variables */ + tmate_write_uname(); tmate_write_ready(); tmate_sync_layout(); diff --git a/tmate-msg.c b/tmate-msg.c index 880e3905..4ee0a5de 100644 --- a/tmate-msg.c +++ b/tmate-msg.c @@ -50,19 +50,39 @@ static void tmate_status_message_client(struct client *c, const char *message) recalculate_sizes(); } +static void tmate_status_message_session(const char *message) +{ + if (tmate_foreground) + return; + + struct session *s; + s = RB_MIN(sessions, &sessions); + if (!s) { + cfg_add_cause("%s", message); + return; + } + + struct window_pane *wp; + wp = s->curw->window->active; + if (wp->mode == &window_copy_mode) + window_copy_add(wp, "%s", message); +} + void __tmate_status_message(const char *fmt, va_list ap) { struct client *c; char *message; xvasprintf(&message, fmt, ap); - tmate_debug("%s", message); + tmate_info("%s", message); TAILQ_FOREACH(c, &clients, entry) { if (c && !(c->flags & CLIENT_READONLY)) tmate_status_message_client(c, message); } + tmate_status_message_session(message); + free(message); } diff --git a/tmate-msgpack.c b/tmate-msgpack.c index 144cda70..c41b07ee 100644 --- a/tmate-msgpack.c +++ b/tmate-msgpack.c @@ -19,7 +19,7 @@ static int on_encoder_write(void *userdata, const char *buf, size_t len) tmate_fatal("Cannot buffer encoded data"); if (!encoder->ev_active) { - event_active(&encoder->ev_buffer, EV_READ, 0); + event_active(encoder->ev_buffer, EV_READ, 0); encoder->ev_active = true; } @@ -57,10 +57,12 @@ void tmate_encoder_init(struct tmate_encoder *encoder, if (!encoder->buffer) tmate_fatal("Can't allocate buffer"); - event_set(&encoder->ev_buffer, -1, - EV_READ | EV_PERSIST, on_encoder_buffer_ready, encoder); + encoder->ev_buffer = event_new(tmate_session.ev_base, -1, + EV_READ | EV_PERSIST, on_encoder_buffer_ready, encoder); + if (!encoder->ev_buffer) + tmate_fatal("Can't allocate event"); - event_add(&encoder->ev_buffer, NULL); + event_add(encoder->ev_buffer, NULL); encoder->ev_active = false; } @@ -69,7 +71,8 @@ void tmate_encoder_destroy(struct tmate_encoder *encoder) { /* encoder->pk doesn't need any cleanup */ evbuffer_free(encoder->buffer); - event_del(&encoder->ev_buffer); + event_del(encoder->ev_buffer); + event_free(encoder->ev_buffer); memset(encoder, 0, sizeof(*encoder)); } diff --git a/tmate-protocol.h b/tmate-protocol.h index 595b6271..81716bb2 100644 --- a/tmate-protocol.h +++ b/tmate-protocol.h @@ -56,6 +56,7 @@ enum tmate_daemon_out_msg_types { TMATE_OUT_RECONNECT, TMATE_OUT_SNAPSHOT, TMATE_OUT_EXEC_CMD, + TMATE_OUT_UNAME, }; /* @@ -77,6 +78,8 @@ enum tmate_daemon_out_msg_types { [TMATE_OUT_RECONNECT, string: reconnection_data] [TMATE_OUT_SNAPSHOT, ...] [TMATE_OUT_EXEC_CMD, string: cmd_name, ...string: args] +[TMATE_OUT_UNAME, string: name.sysname, string: name.nodename, + string: name.release, string: name.version, string: name.machine] */ enum tmate_daemon_in_msg_types { diff --git a/tmate-session.c b/tmate-session.c index b43a261b..5ba0896c 100644 --- a/tmate-session.c +++ b/tmate-session.c @@ -2,8 +2,10 @@ #include #include +#include #include +#include #include #include #include @@ -19,35 +21,53 @@ struct tmate_session tmate_session; static void lookup_and_connect(void); static void on_dns_retry(__unused evutil_socket_t fd, __unused short what, - __unused void *arg) + void *arg) { + struct tmate_session *session = arg; + + assert(session->ev_dns_retry); + event_free(session->ev_dns_retry); + session->ev_dns_retry = NULL; + lookup_and_connect(); } static void dns_cb(int errcode, struct evutil_addrinfo *addr, void *ptr) { struct evutil_addrinfo *ai; - struct timeval tv; const char *host = ptr; + evdns_base_free(tmate_session.ev_dnsbase, 0); + tmate_session.ev_dnsbase = NULL; + if (errcode) { + struct tmate_session *session = &tmate_session; + + if (session->ev_dns_retry) + return; + + struct timeval tv = { .tv_sec = TMATE_DNS_RETRY_TIMEOUT, .tv_usec = 0 }; + + session->ev_dns_retry = evtimer_new(session->ev_base, on_dns_retry, session); + if (!session->ev_dns_retry) + tmate_fatal("out of memory"); + evtimer_add(session->ev_dns_retry, &tv); + tmate_status_message("%s lookup failure. Retrying in %d seconds (%s)", host, TMATE_DNS_RETRY_TIMEOUT, evutil_gai_strerror(errcode)); - - tv.tv_sec = TMATE_DNS_RETRY_TIMEOUT; - tv.tv_usec = 0; - - evtimer_assign(&tmate_session.ev_dns_retry, tmate_session.ev_base, - on_dns_retry, NULL); - evtimer_add(&tmate_session.ev_dns_retry, &tv); - return; } tmate_status_message("Connecting to %s...", host); - for (ai = addr; ai; ai = ai->ai_next) { + int i, num_clients = 0; + for (ai = addr; ai; ai = ai->ai_next) + num_clients++; + + struct tmate_ssh_client *ssh_clients[num_clients]; + + for (ai = addr, i = 0; ai; ai = ai->ai_next, i++) { char buf[128]; const char *ip = NULL; if (ai->ai_family == AF_INET) { @@ -58,23 +78,13 @@ static void dns_cb(int errcode, struct evutil_addrinfo *addr, void *ptr) ip = evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, 128); } - tmate_debug("Trying server %s", ip); - - /* - * Note: We don't deal with the client list. Clients manage it - * and free client structs when necessary. - */ - (void)tmate_ssh_client_alloc(&tmate_session, ip); + ssh_clients[i] = tmate_ssh_client_alloc(&tmate_session, ip); } - evutil_freeaddrinfo(addr); + for (i = 0; i < num_clients; i++) + connect_ssh_client(ssh_clients[i]); - /* - * XXX For some reason, freeing the DNS resolver makes MacOSX flip out... - * not sure what's going on... - * evdns_base_free(tmate_session.ev_dnsbase, 0); - * tmate_session.ev_dnsbase = NULL; - */ + evutil_freeaddrinfo(addr); } static void lookup_and_connect(void) @@ -82,8 +92,8 @@ static void lookup_and_connect(void) struct evutil_addrinfo hints; const char *tmate_server_host; - if (!tmate_session.ev_dnsbase) - tmate_session.ev_dnsbase = evdns_base_new(tmate_session.ev_base, 1); + assert(!tmate_session.ev_dnsbase); + tmate_session.ev_dnsbase = evdns_base_new(tmate_session.ev_base, 1); if (!tmate_session.ev_dnsbase) tmate_fatal("Cannot initialize the DNS lookup service"); @@ -95,7 +105,7 @@ static void lookup_and_connect(void) tmate_server_host = options_get_string(global_options, "tmate-server-host"); - tmate_info("Looking up %s...", tmate_server_host); + tmate_debug("Looking up %s...", tmate_server_host); (void)evdns_getaddrinfo(tmate_session.ev_dnsbase, tmate_server_host, NULL, &hints, dns_cb, (void *)tmate_server_host); } @@ -128,6 +138,50 @@ void tmate_session_init(struct event_base *base) tmate_write_header(); } +static void send_authorized_keys(void) +{ + char *path; + path = options_get_string(global_options, "tmate-authorized-keys"); + if (strlen(path) == 0) + return; + + path = xstrdup(path); + tmate_info("Using %s for access control", path); + + FILE *f; + char *line; + size_t len; + + if (path[0] == '~' && path[1] == '/') { + const char *home = find_home(); + if (home) { + char *new_path; + xasprintf(&new_path, "%s%s", home, &path[1]); + free(path); + path = new_path; + } + } + + if ((f = fopen(path, "r")) == NULL) { + cfg_add_cause("%s: %s", path, strerror(errno)); + free(path); + return; + } + + while ((line = fparseln(f, &len, NULL, NULL, 0)) != NULL) { + if (len == 0) + continue; + tmate_set_val("authorized_keys", line); + free(line); + } + + if (ferror(f)) + cfg_add_cause("%s: %s", path, strerror(errno)); + + fclose(f); + free(path); +} + void tmate_session_start(void) { /* @@ -137,6 +191,19 @@ void tmate_session_start(void) * - While we are parsing the config file, we need to be able to * serialize it, and so we need a worker encoder. */ + if (tmate_foreground) { + tmate_set_val("foreground", "true"); + tmate_info("To connect to the session locally, run: tmate -S %s attach", socket_path); + } else { + cfg_add_cause("%s", "Tip: if you wish to use tmate only for remote access, run: tmate -F"); + cfg_add_cause("%s", "To see the following messages again, run in a tmate session: tmate show-messages"); + cfg_add_cause("%s", "Press or to continue"); + cfg_add_cause("%s", "---------------------------------------------------------------------"); + } + + send_authorized_keys(); + tmate_write_uname(); + tmate_write_ready(); lookup_and_connect(); } @@ -144,12 +211,18 @@ static void on_reconnect_retry(__unused evutil_socket_t fd, __unused short what, { struct tmate_session *session = arg; + assert(session->ev_connection_retry); + event_free(session->ev_connection_retry); + session->ev_connection_retry = NULL; + if (session->last_server_ip) { /* * We have a previous server ip. Let's try that again first, * but then connect to any server if it fails again. */ - (void)tmate_ssh_client_alloc(&tmate_session, session->last_server_ip); + struct tmate_ssh_client *c = tmate_ssh_client_alloc(session, + session->last_server_ip); + connect_ssh_client(c); free(session->last_server_ip); session->last_server_ip = NULL; } else { @@ -167,18 +240,21 @@ void tmate_reconnect_session(struct tmate_session *session, const char *message) */ struct timeval tv = { .tv_sec = TMATE_RECONNECT_RETRY_TIMEOUT, .tv_usec = 0 }; - evtimer_assign(&session->ev_connection_retry, session->ev_base, - on_reconnect_retry, session); - evtimer_add(&session->ev_connection_retry, &tv); + if (session->ev_connection_retry) + return; - if (message) + session->ev_connection_retry = evtimer_new(session->ev_base, on_reconnect_retry, session); + if (!session->ev_connection_retry) + tmate_fatal("out of memory"); + evtimer_add(session->ev_connection_retry, &tv); + + if (message && !tmate_foreground) tmate_status_message("Reconnecting... (%s)", message); else tmate_status_message("Reconnecting..."); /* * This says that we'll need to send a snapshot of the current state. - * Until we have persisted logs... */ session->reconnected = true; } diff --git a/tmate-ssh-client.c b/tmate-ssh-client.c index 7495d584..bed79e95 100644 --- a/tmate-ssh-client.c +++ b/tmate-ssh-client.c @@ -1,6 +1,9 @@ #include +#include #include +#include #include +#include #include #include @@ -84,7 +87,6 @@ static void on_ssh_auth_server_complete(struct tmate_ssh_client *connected_clien if (client == connected_client) continue; - assert(!client->has_encoder); kill_ssh_client(client, NULL); } } @@ -113,7 +115,7 @@ static int passphrase_callback(__unused const char *prompt, char *buf, size_t le client->tmate_session->need_passphrase = 1; if (client->tmate_session->passphrase) - strncpy(buf, client->tmate_session->passphrase, len); + strlcpy(buf, client->tmate_session->passphrase, len); else strcpy(buf, ""); @@ -165,59 +167,106 @@ static void request_passphrase(struct tmate_ssh_client *client) data->password_cb_private = client; } +#define KEEPALIVE_IDLE 30 +#define KEEPALIVE_CNT 4 +#define KEEPALIVE_INTVL 11 +#define WRITE_TIMEOUT 80 + +static void tune_socket_opts(int fd) +{ +#define SSO(level, optname, val) ({ \ + int _flag = val; \ + if (setsockopt(fd, level, optname, &(_flag), sizeof(int)) < 0) { \ + /* If the connection has been closed, we'll get EINVAL */ \ + if (errno != EINVAL) \ + tmate_info("setsockopt(" #level ", " #optname ", %d) failed %s", val, strerror(errno)); \ + } \ +}) + + SSO(IPPROTO_IP, IP_TOS, 0x10); /* IPTOS_LOWDELAY */ + SSO(IPPROTO_TCP, TCP_NODELAY, 1); + SSO(SOL_SOCKET, SO_KEEPALIVE, 1); +#ifdef TCP_KEEPALIVE + /* + * The TCP_KEEPALIVE options enable to specify the amount of time, in + * seconds, that the connection must be idle before keepalive probes + * (if enabled) are sent. + */ + SSO(IPPROTO_TCP, TCP_KEEPALIVE, KEEPALIVE_IDLE); +#endif +#ifdef TCP_KEEPIDLE + /* + * Same as TCP_KEEPALIVE, but on different systems + */ + SSO(IPPROTO_TCP, TCP_KEEPIDLE, KEEPALIVE_IDLE); +#endif +#ifdef TCP_KEEPCNT + /* + * When keepalive probes are enabled, this option will set the number + * of times a keepalive probe should be repeated if the peer is not + * responding. After this many probes, the connection will be closed. + */ + SSO(IPPROTO_TCP, TCP_KEEPCNT, KEEPALIVE_CNT); +#endif +#ifdef TCP_KEEPINTVL + /* + * When keepalive probes are enabled, this option will set the amount + * of time in seconds between successive keepalives sent to probe an + * unresponsive peer. + */ + SSO(IPPROTO_TCP, TCP_KEEPINTVL, KEEPALIVE_INTVL); +#endif +#ifdef TCP_USER_TIMEOUT + /* + * This option takes an unsigned int as an argument. When the + * value is greater than 0, it specifies the maximum amount of + * time in milliseconds that transmitted data may remain + * unacknowledged before TCP will forcibly close the + * corresponding connection and return ETIMEDOUT to the + * application. + */ + SSO(IPPROTO_TCP, TCP_USER_TIMEOUT, 1000*WRITE_TIMEOUT); +#endif +#undef SSO +} + static void init_conn_fd(struct tmate_ssh_client *client) { - if (client->has_init_conn_fd) + int fd; + + if (client->ev_ssh) return; - if (ssh_get_fd(client->session) < 0) + if ((fd = ssh_get_fd(client->session)) < 0) return; - { - int flag = 1; - setsockopt(ssh_get_fd(client->session), IPPROTO_TCP, - TCP_NODELAY, &flag, sizeof(flag)); - } + tune_socket_opts(fd); - event_set(&client->ev_ssh, ssh_get_fd(client->session), - EV_READ | EV_PERSIST, __on_ssh_client_event, client); - event_add(&client->ev_ssh, NULL); - - client->has_init_conn_fd = true; + client->ev_ssh = event_new(client->tmate_session->ev_base, fd, + EV_READ | EV_PERSIST, + __on_ssh_client_event, client); + if (!client->ev_ssh) + tmate_fatal("out of memory"); + event_add(client->ev_ssh, NULL); } static void on_ssh_client_event(struct tmate_ssh_client *client) { - char *identity; - ssh_key pubkey; - int key_type; - unsigned char *hash; - ssize_t hash_len; - char *hash_str; - const char *server_hash_str; - int match; - - int verbosity = SSH_LOG_NOLOG + log_get_level(); - int port = options_get_number(global_options, "tmate-server-port"); - ssh_session session = client->session; ssh_channel channel = client->channel; switch (client->state) { - case SSH_INIT: + case SSH_INIT: { client->session = session = ssh_new(); if (!session) { - tmate_fatal("cannot initialize"); + tmate_fatal("cannot ssh_new()"); return; } ssh_set_callbacks(session, &client->ssh_callbacks); - client->channel = channel = ssh_channel_new(session); - if (!channel) { - tmate_fatal("cannot initialize"); - return; - } + int verbosity = SSH_LOG_NOLOG + log_get_level(); + int port = options_get_number(global_options, "tmate-server-port"); ssh_set_blocking(session, 0); ssh_options_set(session, SSH_OPTIONS_HOST, client->server_ip); @@ -226,6 +275,7 @@ static void on_ssh_client_event(struct tmate_ssh_client *client) ssh_options_set(session, SSH_OPTIONS_USER, "tmate"); ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes"); + char *identity; if ((identity = get_identity())) { /* * FIXME libssh will continue with the next set of @@ -233,11 +283,15 @@ static void on_ssh_client_event(struct tmate_ssh_client *client) * regular one doesn't. */ ssh_options_set(session, SSH_OPTIONS_IDENTITY, identity); + + /* Do not use keys from ssh-agent. */ + unsetenv("SSH_AUTH_SOCK"); free(identity); } client->state = SSH_CONNECT; - /* fall through */ + } + // fall through case SSH_CONNECT: switch (ssh_connect(session)) { @@ -253,19 +307,34 @@ static void on_ssh_client_event(struct tmate_ssh_client *client) tmate_debug("Establishing connection to %s", client->server_ip); client->state = SSH_AUTH_SERVER; - /* fall through */ } + // fall through - case SSH_AUTH_SERVER: + case SSH_AUTH_SERVER: { + ssh_key pubkey; + enum ssh_keytypes_e key_type; + unsigned char *hash; + ssize_t hash_len; + char *hash_str; + const char *server_hash_str; + int match; + +#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0) + if (ssh_get_server_publickey(session, &pubkey) < 0) + tmate_fatal("ssh_get_server_publickey"); +#else if (ssh_get_publickey(session, &pubkey) < 0) tmate_fatal("ssh_get_publickey"); +#endif - if (ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, &hash, &hash_len) < 0) { - kill_ssh_client(client, "Cannot authenticate server"); + if (ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA256, + &hash, &hash_len) < 0) { + kill_ssh_client(client, "Failed to get server fingerprint"); return; } - hash_str = ssh_get_hexa(hash, hash_len); + hash_str = ssh_get_fingerprint_hash(SSH_PUBLICKEY_HASH_SHA256, + hash, hash_len); if (!hash_str) tmate_fatal("malloc failed"); @@ -277,23 +346,34 @@ static void on_ssh_client_event(struct tmate_ssh_client *client) "tmate-server-rsa-fingerprint"); break; case SSH_KEYTYPE_ECDSA: +#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0) + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#endif server_hash_str = options_get_string(global_options, "tmate-server-ecdsa-fingerprint"); break; + case SSH_KEYTYPE_ED25519: + server_hash_str = options_get_string(global_options, + "tmate-server-ed25519-fingerprint"); + break; default: server_hash_str = ""; } match = !strcmp(hash_str, server_hash_str); + if (!match) { + kill_ssh_client(client, "Server fingerprint not recognized: " + "`%s', expected `%s'", server_hash_str, hash_str); + } ssh_key_free(pubkey); ssh_clean_pubkey_hash(&hash); free(hash_str); - if (!match) { - kill_ssh_client(client, "Cannot authenticate server"); + if (!match) return; - } /* * At this point, we abort other connection attempts to the @@ -303,13 +383,31 @@ static void on_ssh_client_event(struct tmate_ssh_client *client) */ tmate_debug("Connected to %s", client->server_ip); on_ssh_auth_server_complete(client); - client->state = SSH_AUTH_CLIENT; - /* fall through */ + client->state = SSH_AUTH_CLIENT_NONE; + } + // fall through - case SSH_AUTH_CLIENT: + case SSH_AUTH_CLIENT_NONE: + switch (ssh_userauth_none(session, NULL)) { + case SSH_AUTH_AGAIN: + return; + case SSH_AUTH_ERROR: + kill_ssh_client(client, "Auth error: %s", ssh_get_error(session)); + return; + case SSH_AUTH_SUCCESS: + tmate_debug("Auth successful via none method"); + client->state = SSH_NEW_CHANNEL; + goto SSH_NEW_CHANNEL; + case SSH_AUTH_PARTIAL: + case SSH_AUTH_DENIED: + client->state = SSH_AUTH_CLIENT_PUBKEY; + } + // fall through + + case SSH_AUTH_CLIENT_PUBKEY: client->tried_passphrase = client->tmate_session->passphrase; - switch (ssh_userauth_autopubkey(session, client->tried_passphrase)) { + switch (ssh_userauth_publickey_auto(session, NULL, client->tried_passphrase)) { case SSH_AUTH_AGAIN: return; case SSH_AUTH_PARTIAL: @@ -319,7 +417,7 @@ static void on_ssh_client_event(struct tmate_ssh_client *client) request_passphrase(client); } else { kill_ssh_client(client, "SSH keys not found." - " Run 'ssh-keygen' to create keys and try again."); + " Run 'ssh-keygen' to create keys."); return; } @@ -331,10 +429,20 @@ static void on_ssh_client_event(struct tmate_ssh_client *client) kill_ssh_client(client, "Auth error: %s", ssh_get_error(session)); return; case SSH_AUTH_SUCCESS: - tmate_debug("Auth successful"); - client->state = SSH_OPEN_CHANNEL; - /* fall through */ + tmate_debug("Auth successful with pubkey"); + client->state = SSH_NEW_CHANNEL; } + // fall through + +SSH_NEW_CHANNEL: + case SSH_NEW_CHANNEL: + client->channel = channel = ssh_channel_new(session); + if (!channel) { + tmate_fatal("cannot ssh_channel_new()"); + return; + } + client->state = SSH_OPEN_CHANNEL; + // fall through case SSH_OPEN_CHANNEL: switch (ssh_channel_open_session(channel)) { @@ -345,10 +453,10 @@ static void on_ssh_client_event(struct tmate_ssh_client *client) ssh_get_error(session)); return; case SSH_OK: - tmate_debug("Session opened, initalizing tmate"); + tmate_debug("Session opened, initializing tmate"); client->state = SSH_BOOTSTRAP; - /* fall through */ } + // fall through case SSH_BOOTSTRAP: switch (ssh_channel_request_subsystem(channel, "tmate")) { @@ -376,9 +484,8 @@ static void on_ssh_client_event(struct tmate_ssh_client *client) free(client->tmate_session->last_server_ip); client->tmate_session->last_server_ip = xstrdup(client->server_ip); - - /* fall through */ } + // fall through case SSH_READY: read_channel(client); @@ -409,9 +516,10 @@ static void kill_ssh_client(struct tmate_ssh_client *client, tmate_debug("SSH client killed (%s)", client->server_ip); - if (client->has_init_conn_fd) { - event_del(&client->ev_ssh); - client->has_init_conn_fd = false; + if (client->ev_ssh) { + event_del(client->ev_ssh); + event_free(client->ev_ssh); + client->ev_ssh = NULL; } if (client->state == SSH_READY) { @@ -437,12 +545,11 @@ static void kill_ssh_client(struct tmate_ssh_client *client, free(client); } -static void connect_ssh_client(struct tmate_ssh_client *client) +void connect_ssh_client(struct tmate_ssh_client *client) { - if (!client->session) { - client->state = SSH_INIT; - on_ssh_client_event(client); - } + assert(!client->session); + client->state = SSH_INIT; + on_ssh_client_event(client); } static void ssh_log_function(int priority, const char *function, @@ -453,9 +560,11 @@ static void ssh_log_function(int priority, const char *function, struct tmate_ssh_client *tmate_ssh_client_alloc(struct tmate_session *session, const char *server_ip) + { struct tmate_ssh_client *client; client = xmalloc(sizeof(*client)); + memset(client, 0, sizeof(*client)); ssh_set_log_callback(ssh_log_function); @@ -471,11 +580,6 @@ struct tmate_ssh_client *tmate_ssh_client_alloc(struct tmate_session *session, client->state = SSH_NONE; client->session = NULL; client->channel = NULL; - client->has_encoder = 0; - - client->has_init_conn_fd = false; - - connect_ssh_client(client); return client; } diff --git a/tmate.h b/tmate.h index f096a35c..f4783dc8 100644 --- a/tmate.h +++ b/tmate.h @@ -9,10 +9,9 @@ #include "tmux.h" -#define tmate_debug(...) log_debug("[tmate] D " __VA_ARGS__) -#define tmate_warn(...) log_debug("[tmate] W " __VA_ARGS__) -#define tmate_info(...) log_debug("[tmate] I " __VA_ARGS__) -#define tmate_fatal(...) fatalx("[tmate] " __VA_ARGS__) +#define tmate_debug(...) log_emit(LOG_DEBUG, __VA_ARGS__) +#define tmate_info(...) log_emit(LOG_INFO, __VA_ARGS__) +#define tmate_fatal(...) fatalx( __VA_ARGS__) /* tmate-msgpack.c */ @@ -23,7 +22,7 @@ struct tmate_encoder { tmate_encoder_write_cb *ready_callback; void *userdata; struct evbuffer *buffer; - struct event ev_buffer; + struct event *ev_buffer; bool ev_active; }; @@ -80,10 +79,12 @@ extern void unpack_array(struct tmate_unpacker *uk, struct tmate_unpacker *neste struct tmate_session; extern void tmate_write_header(void); +extern void tmate_write_uname(void); extern void tmate_write_ready(void); extern void tmate_sync_layout(void); extern void tmate_pty_data(struct window_pane *wp, const char *buf, size_t len); extern int tmate_should_replicate_cmd(const struct cmd_entry *cmd); +extern void tmate_set_val(const char *name, const char *value); extern void tmate_exec_cmd_args(int argc, const char **argv); extern void tmate_exec_cmd(struct cmd *cmd); extern void tmate_failed_cmd(int client_id, const char *cause); @@ -106,7 +107,9 @@ enum tmate_ssh_client_state_types { SSH_INIT, SSH_CONNECT, SSH_AUTH_SERVER, - SSH_AUTH_CLIENT, + SSH_AUTH_CLIENT_NONE, + SSH_AUTH_CLIENT_PUBKEY, + SSH_NEW_CHANNEL, SSH_OPEN_CHANNEL, SSH_BOOTSTRAP, SSH_READY, @@ -127,7 +130,6 @@ struct tmate_ssh_client { char *server_ip; - int has_encoder; int state; /* @@ -139,11 +141,11 @@ struct tmate_ssh_client { ssh_session session; ssh_channel channel; - bool has_init_conn_fd; - struct event ev_ssh; + struct event *ev_ssh; }; TAILQ_HEAD(tmate_ssh_clients, tmate_ssh_client); +extern void connect_ssh_client(struct tmate_ssh_client *client); extern struct tmate_ssh_client *tmate_ssh_client_alloc(struct tmate_session *session, const char *server_ip); @@ -152,7 +154,7 @@ extern struct tmate_ssh_client *tmate_ssh_client_alloc(struct tmate_session *ses struct tmate_session { struct event_base *ev_base; struct evdns_base *ev_dnsbase; - struct event ev_dns_retry; + struct event *ev_dns_retry; struct tmate_encoder encoder; struct tmate_decoder decoder; @@ -173,7 +175,7 @@ struct tmate_session { char *passphrase; bool reconnected; - struct event ev_connection_retry; + struct event *ev_connection_retry; char *last_server_ip; char *reconnection_data; /* @@ -200,6 +202,7 @@ extern void tmate_reconnect_session(struct tmate_session *session, const char *m /* tmate-debug.c */ extern void tmate_print_stack_trace(void); extern void tmate_catch_sigsegv(void); +extern void tmate_preload_trace_lib(void); /* tmate-msg.c */ diff --git a/tmux.1 b/tmux.1 index 776b6faf..55f88c98 100644 --- a/tmux.1 +++ b/tmux.1 @@ -15,14 +15,15 @@ .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd $Mdocdate: March 25 2013 $ -.Dt TMUX 1 +.Dt TMATE 1 .Os .Sh NAME -.Nm tmux -.Nd terminal multiplexer +.Nm tmate +.Nd terminal multiplexer with instant terminal sharing .Sh SYNOPSIS -.Nm tmux +.Nm tmate .Bk -words +.Op show-messages .Op Fl 2CluvV .Op Fl c Ar shell-command .Op Fl f Ar file @@ -32,13 +33,24 @@ .Ek .Sh DESCRIPTION .Nm -is a terminal multiplexer: +is a terminal multiplexer with instant terminal sharing: it enables a number of terminals to be created, accessed, and -controlled from a single screen. +controlled from a single screen and be shared with another mates. .Nm may be detached from a screen and continue running in the background, -then later reattached. +then later reattached, like as a daemon. +.Pp +.Nm +provides an instant pairing solution, allowing you to share a terminal +with one or several teammates. Together with a voice call, it's almost like +pairing in person. The terminal sharing works by using SSH connections to +backend servers maintained by tmate upstream developers; teammates need to be +given a randomly-generated token to be able to join a session. +.Pp +.Nm +is a modified version of tmux, and uses the same configurations such as +keybindings, color schemes, etc. .Pp When .Nm @@ -47,9 +59,9 @@ is started it creates a new with a single .Em window and displays it on screen. -A status line at the bottom of the screen -shows information on the current session -and is used to enter interactive commands. +A status line at the bottom of the screen shows information +on the current session, such as ssh command to share with +your mate, and is used to enter interactive commands. .Pp A session is a single collection of .Em pseudo terminals @@ -80,7 +92,7 @@ key strokes). .Nm may be reattached using: .Pp -.Dl $ tmux attach +.Dl $ tmate attach .Pp In .Nm , @@ -98,6 +110,10 @@ The options are as follows: Force .Nm to assume the terminal supports 256 colours. +.It Fl a Ar file +Limit access to the public keys listed in the +.Ar file +given as argument. .It Fl C Start in control mode (see the .Sx CONTROL MODE @@ -126,7 +142,9 @@ By default, loads the system configuration file from .Pa @SYSCONFDIR@/tmux.conf , if present, then looks for a user configuration file at -.Pa ~/.tmux.conf . +.Pa ~/.tmux.conf +and +.Pa ~/.tmate.conf . .Pp The configuration file is a set of .Nm @@ -167,7 +185,9 @@ directories are missing). .It Fl l Behave as a login shell. This flag currently has no effect and is for compatibility with other shells -when using tmux as a login shell. +when using +.Nm +as a login shell. .It Fl S Ar socket-path Specify a full alternative path to the server socket. If @@ -597,7 +617,7 @@ to be given as multiple arguments and executed directly (without This can avoid issues with shell quoting. For example: .Bd -literal -offset indent -$ tmux new-window vi /etc/passwd +$ tmate new-window vi /etc/passwd .Ed .Pp Will run @@ -616,7 +636,7 @@ bind-key F1 set-window-option force-width 81 Or if using .Xr sh 1 : .Bd -literal -offset indent -$ tmux bind-key F1 set-window-option force-width 81 +$ tmate bind-key F1 set-window-option force-width 81 .Ed .Pp Multiple commands may be specified together as part of a @@ -648,11 +668,11 @@ bind-key R source-file ~/.tmux.conf \e; \e Or from .Xr sh 1 : .Bd -literal -offset indent -$ tmux kill-window -t :1 +$ tmate kill-window -t :1 -$ tmux new-window \e; split-window -d +$ tmate new-window \e; split-window -d -$ tmux new-session -d 'vi /etc/passwd' \e; split-window -d \e; attach +$ tmate new-session -d 'vi /etc/passwd' \e; split-window -d \e; attach .Ed .Sh CLIENTS AND SESSIONS The @@ -1236,10 +1256,10 @@ command displays the layout of each window in a form suitable for use with .Ic select-layout . For example: .Bd -literal -offset indent -$ tmux list-windows +$ tmate list-windows 0: ksh [159x48] layout: bb62,159x48,0,0{79x48,0,0,79x48,80,0} -$ tmux select-layout bb62,159x48,0,0{79x48,0,0,79x48,80,0} +$ tmate select-layout bb62,159x48,0,0{79x48,0,0,79x48,80,0} .Ed .Pp .Nm @@ -2307,8 +2327,8 @@ User options may have any name, so long as they are prefixed with and be set to any string. For example: .Bd -literal -offset indent -$ tmux setw -q @foo "abc123" -$ tmux showw -v @foo +$ tmate setw -q @foo "abc123" +$ tmate showw -v @foo abc123 .Ed .Pp @@ -2394,7 +2414,8 @@ to work correctly, this .Em must be set to .Ql screen , -.Ql tmux +.Ql tmux , +.Ql tmate or a derivative of them. .It Ic escape-time Ar time Set the time in milliseconds for which @@ -4172,18 +4193,18 @@ To create a new session running .Xr vi 1 : .Pp -.Dl $ tmux new-session vi +.Dl $ tmate new-session vi .Pp Most commands have a shorter form, known as an alias. For new-session, this is .Ic new : .Pp -.Dl $ tmux new vi +.Dl $ tmate new vi .Pp Alternatively, the shortest unambiguous form of a command is accepted. If there are several options, they are listed: .Bd -literal -offset indent -$ tmux n +$ tmate n ambiguous command: n, could be: new-session, new-window, next-window .Ed .Pp @@ -4213,7 +4234,7 @@ A session may be detached using .Xr ssh 1 disconnection) and reattached with: .Pp -.Dl $ tmux attach-session +.Dl $ tmate attach-session .Pp Typing .Ql C-b \&? diff --git a/tmux.c b/tmux.c index d55c2e1d..730c2dfc 100644 --- a/tmux.c +++ b/tmux.c @@ -48,16 +48,30 @@ __dead void usage(void); static char *make_label(const char *); #ifndef HAVE___PROGNAME -char *__progname = (char *) "tmux"; +char *__progname = (char *) "tmate"; +#endif + +#ifdef TMATE +int tmate_foreground; #endif __dead void usage(void) { fprintf(stderr, - "usage: %s [-2CluvV] [-c shell-command] [-f file] [-L socket-name]\n" - " [-S socket-path] [command [flags]]\n", - __progname); + "Usage: %s [options] [tmux-command [flags]]\n" + "\n" + "Basic options:\n" + " -n specify the session token instead of getting a random one\n" + " -r same, but for the read-only token\n" + " -k specify an api-key, necessary for using named sessions on tmate.io\n" + " -F set the foreground mode, useful for setting remote access\n" + " -f set the config file path\n" + " -S set the socket path, useful to issue commands to a running tmate instance\n" + " -a limit access to ssh public keys listed in provided file\n" + " -v set verbosity (can be repeated)\n" + " -V print version\n" + ,__progname); exit(1); } @@ -198,6 +212,29 @@ find_home(void) return (home); } +#ifdef TMATE +static char *api_key; +static char *session_name; +static char *session_name_ro; +static char *authorized_keys; + +void tmate_load_cli_options(void) +{ +#define SET_OPT(name, val) ({\ + if (val) { \ + run_headless_command(3, (const char *[]){"set-option", name, val}, DEFER_ERRORS_CFG, NULL); \ + free(val); \ + val = NULL; \ + } \ +}) + SET_OPT("tmate-api-key", api_key); + SET_OPT("tmate-session-name", session_name); + SET_OPT("tmate-session-name-ro", session_name_ro); + SET_OPT("tmate-authorized-keys", authorized_keys); +#undef SET_OPT +} +#endif + int main(int argc, char **argv) { @@ -205,12 +242,12 @@ main(int argc, char **argv) const char *s; int opt, flags, keys; - if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL) { + if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL && + setlocale(LC_CTYPE, "C.UTF-8") == NULL) { if (setlocale(LC_CTYPE, "") == NULL) errx(1, "invalid LC_ALL, LC_CTYPE or LANG"); s = nl_langinfo(CODESET); - if (strcasecmp(s, "UTF-8") != 0 && - strcasecmp(s, "UTF8") != 0) + if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0) errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s); } @@ -228,7 +265,7 @@ main(int argc, char **argv) #endif label = path = NULL; - while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUVv")) != -1) { + while ((opt = getopt(argc, argv, "h2c:CdFf:lL:qS:uUVvk:n:r:a:")) != -1) { switch (opt) { case '2': flags |= CLIENT_256COLOURS; @@ -268,6 +305,24 @@ main(int argc, char **argv) case 'v': log_add_level(); break; + case 'F': + tmate_foreground = 1; + log_add_level(); + unsetenv("TMUX"); + break; + case 'k': + api_key = xstrdup(optarg); + break; + case 'n': + session_name = xstrdup(optarg); + break; + case 'r': + session_name_ro = xstrdup(optarg); + break; + case 'a': + authorized_keys = xstrdup(optarg); + break; + case 'h': default: usage(); } diff --git a/tmux.h b/tmux.h index 1ab784b7..b38615b6 100644 --- a/tmux.h +++ b/tmux.h @@ -53,7 +53,9 @@ struct tmuxpeer; struct tmuxproc; /* Default global configuration file. */ +#ifndef TMUX_CONF #define TMUX_CONF "/etc/tmux.conf" +#endif /* * Minimum layout cell size, NOT including separator line. The scroll region @@ -1549,6 +1551,10 @@ extern struct options *global_w_options; extern struct environ *global_environ; extern struct timeval start_time; extern const char *socket_path; +#ifdef TMATE +extern int tmate_foreground; +void tmate_load_cli_options(void); +#endif const char *getshell(void); int checkshell(const char *); int areshell(const char *); @@ -1572,6 +1578,8 @@ void proc_kill_peer(struct tmuxpeer *); extern int cfg_finished; extern int cfg_references; extern struct client *cfg_client; +extern char **cfg_causes; +extern u_int cfg_ncauses; void start_cfg(void); int load_cfg(const char *, struct cmd_q *, char **); void set_cfg_file(const char *); @@ -1875,6 +1883,9 @@ void signal_waiting_clients(const char *name); void cmd_wait_for_flush(void); /* client.c */ +#define DEFER_ERRORS_CFG 1 +int run_headless_command(int argc, const char **argv, int flags, void (*err_callback)(const char *)); +void run_initial_client_cmd(void); int client_main(struct event_base *, int, char **, int, const char *); /* key-bindings.c */ @@ -1902,6 +1913,7 @@ void alerts_queue(struct window *, int); void alerts_check_session(struct session *); /* server.c */ +extern int server_exit; extern struct tmuxproc *server_proc; extern struct clients clients; extern struct cmd_find_state marked_pane; @@ -2356,9 +2368,14 @@ struct event_base *osdep_event_init(void); /* log.c */ void log_add_level(void); int log_get_level(void); +void log_open_fp(FILE *f); void log_open(const char *); void log_close(void); -void printflike(1, 2) log_debug(const char *, ...); +#define LOG_ERROR 0 +#define LOG_INFO 1 +#define LOG_DEBUG 2 +#define log_debug(...) log_emit(LOG_DEBUG+1, __VA_ARGS__) +void printflike(2, 3) log_emit(int level, const char *, ...); __dead void printflike(1, 2) fatal(const char *, ...); __dead void printflike(1, 2) fatalx(const char *, ...); diff --git a/window-copy.c b/window-copy.c index 3862a688..939d3e19 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1054,6 +1054,11 @@ window_copy_search_up(struct window_pane *wp, const char *searchstr) int n, wrapped, wrapflag, cis; const char *ptr; +#ifdef TMATE + if (!searchstr) + return; +#endif + if (*searchstr == '\0') return; wrapflag = options_get_number(wp->window->options, "wrap-search"); @@ -1120,6 +1125,11 @@ window_copy_search_down(struct window_pane *wp, const char *searchstr) int n, wrapped, wrapflag, cis; const char *ptr; +#ifdef TMATE + if (!searchstr) + return; +#endif + if (*searchstr == '\0') return; wrapflag = options_get_number(wp->window->options, "wrap-search"); diff --git a/xmalloc.c b/xmalloc.c index afa8e585..90af1e2f 100644 --- a/xmalloc.c +++ b/xmalloc.c @@ -94,6 +94,7 @@ xasprintf(char **ret, const char *fmt, ...) return i; } +__attribute__((__format__(__printf__, 2, 0))) int xvasprintf(char **ret, const char *fmt, va_list ap) { @@ -120,6 +121,7 @@ xsnprintf(char *str, size_t len, const char *fmt, ...) return i; } +__attribute__((__format__(__printf__, 3, 0))) int xvsnprintf(char *str, size_t len, const char *fmt, va_list ap) {