Compare commits

...

77 commits

Author SHA1 Message Date
neil
ac919516f4 add DragonflyBSD by https://github.com/vmactions/dragonflybsd-vm 2022-08-06 21:30:02 -04:00
neil
6cb27ead4e fix NetBSD.yml 2022-08-06 21:30:02 -04:00
neil
15001bfa77 fix 2022-08-06 21:30:02 -04:00
neil
3efc8496cc fix Ubuntu.yml 2022-08-06 21:30:02 -04:00
neil
2ff8bc134d fix Ubuntu.yml 2022-08-06 21:30:02 -04:00
neil
9a1aa9ae9d add Ubuntu.yml 2022-08-06 21:30:02 -04:00
neil
17fcc8b76d fix MacOS.yml 2022-08-06 21:30:02 -04:00
neil
7c0bbf3e16 fix MacOS.yml 2022-08-06 21:30:02 -04:00
neil
05a849ba2b fix MacOS.yml 2022-08-06 21:30:02 -04:00
neil
fde4b58228 add MacOS.yml 2022-08-06 21:30:02 -04:00
neil
17d4a8a7df fix OpenBSD.yml 2022-08-06 21:30:02 -04:00
neil
ef11a7b30e fix OpenBSD.yml 2022-08-06 21:30:02 -04:00
neil
577e04df6c fix 2022-08-06 21:30:02 -04:00
neil
b1079b4393 add OpenBSD build by https://github.com/vmactions/openbsd-vm 2022-08-06 21:30:02 -04:00
neil
a258907cf8 add NetBSD test by https://github.com/vmactions/netbsd-vm 2022-08-06 21:30:02 -04:00
neil
169eb64b99 Add FreeBSD build by https://github.com/vmactions/freebsd-vm 2022-08-06 21:30:02 -04:00
Max Schmitt
be4a88507e chore: add FUNDING.yml 2022-03-05 23:28:29 -05:00
n0vember
c9ec7af632 add basic information for -a option in man page and help text 2021-03-21 15:39:14 -04:00
Sergio de Almeida Cipriano Junior
808b564564 Update manpage 2021-03-21 15:38:58 -04:00
Sergio de Almeida Cipriano Junior
e5ce3f04d2 Fix typo in ssh client 2021-03-21 15:38:58 -04:00
hpcbjdic
8123fa34f8 enable redefinition of TMUX_CONF
Without the added #ifndef / #endif it's not possible to _actually_ redefine TMUX_CONF by means of the compiler flag -DTMUX_CONF=... as done by the build system.
The same patch has also been applied to tmux, cf. master at https://github.com/tmux/tmux/blob/master/tmux.h.
2021-03-21 13:45:58 -04:00
Björn Jacke
f6a4ae6042 client: set IPTOS_LOWDELAY on TCP connection
this helps edge routers to prioritize our interactive network traffic.
2021-02-20 17:12:25 -05:00
Nicolas Viennot
cbec43f56d Better debugging when keys are not matching 2020-04-14 18:22:04 -04:00
Klemens Nanni
339e6c4357 Include <errno.h> for global errno
At least on OpenBSD the symbol `errno` is otherwise not defined;  it is
used in the `SSO()` macro and `send_authorized_keys()` function.
2020-04-12 15:17:50 -04:00
Nicolas Viennot
9e3e39d66d Avoid initializing stdout twice
Closes #190
2020-03-11 11:30:33 -04:00
Damian Szymański
ba6ac3a363 Update paths in build_static_release script (#178) 2019-12-06 06:43:04 -05:00
Nicolas Viennot
46564a0311
Merge pull request #177 from travis-ci/master
Add build on s390x and ppc64le
2019-12-04 20:32:22 -05:00
Damian Szymański
cc01f3f13a
Add build on s390x and ppc64le 2019-11-29 14:08:06 +01:00
Nicolas Viennot
f0a4707ef3 Update dockerfile for size 2019-11-28 16:35:48 -05:00
Nicolas Viennot
5e00bfa5e1 Rephrase 2019-11-16 17:09:38 -05:00
Nicolas Viennot
f895fe01b1 Rename account-key -> api-key 2019-11-10 22:27:23 -05:00
Nicolas Viennot
9fe8b32293 Add foreground tip 2019-11-10 16:40:36 -05:00
Nicolas Viennot
7e02dba7ef Minor refactor 2019-11-10 16:40:36 -05:00
Nicolas Viennot
bfa3c104d7 Refactor static builds 2019-11-10 16:12:12 -05:00
Nicolas Viennot
e5f6e68fad Unify tmate-debug.c with tmate-ssh-server 2019-11-10 16:12:12 -05:00
Nicolas Viennot
9fc6e96444 Send uname 2019-11-10 16:12:12 -05:00
Nicolas Viennot
86ec8d1ad6 Better crash messages 2019-11-10 03:59:12 -05:00
Nicolas Viennot
2b86031308 Fix keepalive bug 2019-11-10 03:58:59 -05:00
Nicolas Viennot
2b14611544 Polish session messages 2019-11-07 13:45:10 -05:00
Nicolas Viennot
d3c8808b0f Version bump 2019-11-07 11:38:54 -05:00
Nicolas Viennot
8b62c54748 Fix typo 2019-11-07 11:29:28 -05:00
Nicolas Viennot
ba860b8f45 Cleanup warnings 2019-11-07 10:14:03 -05:00
Nicolas Viennot
1600a81e58 Also add crash info on SIGABRT 2019-11-07 10:14:03 -05:00
Nicolas Viennot
0272757aa5 Clarify user message 2019-11-07 10:13:45 -05:00
Nicolas Viennot
9781946a70 Show initial message in copy mode 2019-11-05 21:39:16 -05:00
Nicolas Viennot
442143cd90 Show message when restarting shell 2019-11-05 21:39:16 -05:00
Nicolas Viennot
c71307ed5c Fix reconnection hanging bugs 2019-11-05 20:30:49 -05:00
Nicolas Viennot
fa49dc980d Provide a better CLI help (-h) 2019-11-05 20:30:49 -05:00
Nicolas Viennot
206c0f38b4 Set boot options via tmux commands 2019-11-05 20:30:40 -05:00
Nicolas Viennot
19341bc544 Add authorized_keys option -a 2019-11-05 20:30:11 -05:00
Nicolas Viennot
c78198dc59 Add command line arguments to set the account_key/session_names
-k account_key -n session_name -r session_name_ro
2019-11-04 18:36:10 -05:00
Nicolas Viennot
c63c8fbf90 Only use tmate.conf, not .tmux.conf
Fixes #108
2019-11-04 18:36:10 -05:00
Nicolas Viennot
6e84bab68c Add foreground mode with -F 2019-11-04 18:36:10 -05:00
Nicolas Viennot
7f693a97ae Add options for customizing session names (WIP) 2019-10-21 02:36:52 -04:00
Nicolas Viennot
4efe25d91d During SSH authentication, try the none auth method first 2019-10-15 03:11:26 -04:00
Nicolas Viennot
7262aead73 Bump to version 2.3.1 2019-10-12 21:10:03 -04:00
Nicolas Viennot
7153958e99 Allow the use of C.UTF-8 locale 2019-10-12 21:10:03 -04:00
Nicolas Viennot
74ff522983 Build static build releases on travis-ci 2019-10-12 20:38:36 -04:00
Nicolas Viennot
44635e752d Use docker to build static releases
Static builds are possible with the musl C library.
glibc is capricious when it comes to static linking and DNS support (and
other things).
2019-10-12 13:50:27 -04:00
Nicolas Viennot
d654ff2219 Send TMATE_READY message when the config files are done loading
This fixes bug where webhooks are not registering correctly, leading to
a disconnect and reconnect.
2019-09-18 23:35:07 -04:00
Nicolas Viennot
3e5d919b14 Bump to version 2.3.0 2019-07-31 21:02:18 -04:00
Andreas Schneider
4e7caeb536 ssh-client: Use ssh_get_server_publickey() if possible 2019-07-30 09:57:28 -04:00
Andreas Schneider
e25ab3cc8b ssh-client: Add missing ecdsa keytypes of libssh 0.9 2019-07-30 09:39:25 -04:00
Christian Hesse
299c7c670c add new channel after authentication
With libssh commit 8a885f0b ("channels: Add check if we are authenticated
before we create a channel") connection fails if channel is added before
successful authentication. So add the channel after authentication.

Fixes #154
2019-07-30 09:38:02 -04:00
Andreas Schneider
fd4ac27d59 ssh-client: Don't use keys from the ssh-agent
Fixes #138

Signed-off-by: Andreas Schneider <asn@cryptomilk.org>
2019-04-09 08:15:07 -04:00
Nicolas Viennot
32d48cbc9d Update ed25519 server key (not yet in production) 2019-04-07 10:30:41 -04:00
Andreas Schneider
b01c6ecebd configure: Require libssh >= 0.8.4
Signed-off-by: Andreas Schneider <asn@cryptomilk.org>
2019-04-07 10:30:41 -04:00
Andreas Schneider
b645ce15cb ssh-client: Add support for ed25519 keys
Signed-off-by: Andreas Schneider <asn@cryptomilk.org>
2019-04-07 10:30:41 -04:00
Andreas Schneider
2ffcbbd185 ssh-client: Use SHA256 finger prints
Signed-off-by: Andreas Schneider <asn@cryptomilk.org>
2019-04-07 10:30:41 -04:00
Nicolas Viennot
72ddb7eb08
Merge pull request #139 from teancom/update-man
Update MAN page to properly reference tmate where applicable
2018-12-01 12:28:52 -05:00
David Bishop
25f6a934cf Update MAN page to properly reference tmate where applicable
While there are still plenty of places where tmate uses tmux, including
config files and environment variables, I have updated the various
examples to use tmate where applicable.
2018-10-23 17:39:10 -06:00
Natanael Copa
3f6c6d4447 Fix building with or without backtrace(3)
backtrace(3) and execinfo.h are GNU extensions and may or may not be
available, and they may be provided via libexecinfo.

Fix detection of libexecinfo and allow building without any support of
backtrace, in which case we let kernel create core dump.

Fixes #116 #117
2017-09-28 08:56:41 -04:00
Nicolas Viennot
608763a41a Attempt to fix environment related crash
Fixes #89
2016-06-14 17:08:58 -04:00
Nicolas Viennot
b27f3bacc0 Crash fix in search prev/next match
Fixes #87
2016-06-10 18:00:50 -04:00
Nicolas Viennot
27169b7c07 Add missing incldues for FreeBSD 2016-06-05 12:54:25 -04:00
Nicolas Viennot
fe81322cc4 Keep alive the socket to make reconnections work properly 2016-04-21 16:20:27 -04:00
Nicolas Viennot
d433fe6956 nits 2016-03-29 01:42:07 -04:00
42 changed files with 1158 additions and 314 deletions

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
github: nviennot

41
.github/workflows/DragonflyBSD.yml vendored Normal file
View file

@ -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

40
.github/workflows/FreeBSD.yml vendored Normal file
View file

@ -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

37
.github/workflows/MacOS.yml vendored Normal file
View file

@ -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

40
.github/workflows/NetBSD.yml vendored Normal file
View file

@ -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

50
.github/workflows/OpenBSD.yml vendored Normal file
View file

@ -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,<event.h>,<event2/event.h>,' *.[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

37
.github/workflows/Ubuntu.yml vendored Normal file
View file

@ -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

1
.gitignore vendored
View file

@ -25,3 +25,4 @@ downloads/
ext/
libssh-*/
msgpack-*/
releases/

View file

@ -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

37
Dockerfile Normal file
View file

@ -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

View file

@ -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

View file

@ -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

33
build_static_release.sh Executable file
View file

@ -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

37
cfg.c
View file

@ -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))

View file

@ -32,6 +32,7 @@
#include <unistd.h>
#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. */

View file

@ -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)

View file

@ -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:

View file

@ -52,6 +52,8 @@
#include <stdlib.h>
#include <string.h>
#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];

View file

@ -1,9 +0,0 @@
#define _GNU_SOURCE
#include <time.h>
#include <unistd.h>
#include <sys/syscall.h>
int clock_gettime(clockid_t clk_id, struct timespec *tp)
{
return syscall(SYS_clock_gettime, clk_id, tp);
}

View file

@ -1,11 +0,0 @@
// http://stackoverflow.com/questions/8823267/linking-against-older-symbol-version-in-a-so-file
#include <string.h>
/* 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);
}

View file

@ -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(

61
log.c
View file

@ -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);
}

View file

@ -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 }

View file

@ -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)

10
proc.c
View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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: <Enter>~.");
}
/* 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(;;);
}
/*

View file

@ -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);

View file

@ -1,10 +1,20 @@
#ifdef HAVE_EXECINFO_H
#include <execinfo.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <regex.h>
#include <signal.h>
#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

View file

@ -1,3 +1,4 @@
#include <sys/utsname.h>
#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();

View file

@ -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);
}

View file

@ -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));
}

View file

@ -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 {

View file

@ -2,8 +2,10 @@
#include <event2/util.h>
#include <event2/event.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -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 <q> or <ctrl-c> 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;
}

View file

@ -1,6 +1,9 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <event.h>
#include <assert.h>
@ -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;
}

25
tmate.h
View file

@ -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 */

75
tmux.1
View file

@ -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 \&?

71
tmux.c
View file

@ -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 <name> specify the session token instead of getting a random one\n"
" -r <name> same, but for the read-only token\n"
" -k <key> 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 <path> set the config file path\n"
" -S <path> set the socket path, useful to issue commands to a running tmate instance\n"
" -a <path> 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();
}

19
tmux.h
View file

@ -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 *, ...);

View file

@ -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");

View file

@ -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)
{