mirror of
https://github.com/tmate-io/tmate.git
synced 2026-01-23 18:36:05 +00:00
Compare commits
77 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac919516f4 | ||
|
|
6cb27ead4e | ||
|
|
15001bfa77 | ||
|
|
3efc8496cc | ||
|
|
2ff8bc134d | ||
|
|
9a1aa9ae9d | ||
|
|
17fcc8b76d | ||
|
|
7c0bbf3e16 | ||
|
|
05a849ba2b | ||
|
|
fde4b58228 | ||
|
|
17d4a8a7df | ||
|
|
ef11a7b30e | ||
|
|
577e04df6c | ||
|
|
b1079b4393 | ||
|
|
a258907cf8 | ||
|
|
169eb64b99 | ||
|
|
be4a88507e | ||
|
|
c9ec7af632 | ||
|
|
808b564564 | ||
|
|
e5ce3f04d2 | ||
|
|
8123fa34f8 | ||
|
|
f6a4ae6042 | ||
|
|
cbec43f56d | ||
|
|
339e6c4357 | ||
|
|
9e3e39d66d | ||
|
|
ba6ac3a363 | ||
|
|
46564a0311 | ||
|
|
cc01f3f13a | ||
|
|
f0a4707ef3 | ||
|
|
5e00bfa5e1 | ||
|
|
f895fe01b1 | ||
|
|
9fe8b32293 | ||
|
|
7e02dba7ef | ||
|
|
bfa3c104d7 | ||
|
|
e5f6e68fad | ||
|
|
9fc6e96444 | ||
|
|
86ec8d1ad6 | ||
|
|
2b86031308 | ||
|
|
2b14611544 | ||
|
|
d3c8808b0f | ||
|
|
8b62c54748 | ||
|
|
ba860b8f45 | ||
|
|
1600a81e58 | ||
|
|
0272757aa5 | ||
|
|
9781946a70 | ||
|
|
442143cd90 | ||
|
|
c71307ed5c | ||
|
|
fa49dc980d | ||
|
|
206c0f38b4 | ||
|
|
19341bc544 | ||
|
|
c78198dc59 | ||
|
|
c63c8fbf90 | ||
|
|
6e84bab68c | ||
|
|
7f693a97ae | ||
|
|
4efe25d91d | ||
|
|
7262aead73 | ||
|
|
7153958e99 | ||
|
|
74ff522983 | ||
|
|
44635e752d | ||
|
|
d654ff2219 | ||
|
|
3e5d919b14 | ||
|
|
4e7caeb536 | ||
|
|
e25ab3cc8b | ||
|
|
299c7c670c | ||
|
|
fd4ac27d59 | ||
|
|
32d48cbc9d | ||
|
|
b01c6ecebd | ||
|
|
b645ce15cb | ||
|
|
2ffcbbd185 | ||
|
|
72ddb7eb08 | ||
|
|
25f6a934cf | ||
|
|
3f6c6d4447 | ||
|
|
608763a41a | ||
|
|
b27f3bacc0 | ||
|
|
27169b7c07 | ||
|
|
fe81322cc4 | ||
|
|
d433fe6956 |
42 changed files with 1158 additions and 314 deletions
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
github: nviennot
|
||||
41
.github/workflows/DragonflyBSD.yml
vendored
Normal file
41
.github/workflows/DragonflyBSD.yml
vendored
Normal 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
40
.github/workflows/FreeBSD.yml
vendored
Normal 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
37
.github/workflows/MacOS.yml
vendored
Normal 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
40
.github/workflows/NetBSD.yml
vendored
Normal 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
50
.github/workflows/OpenBSD.yml
vendored
Normal 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
37
.github/workflows/Ubuntu.yml
vendored
Normal 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
1
.gitignore
vendored
|
|
@ -25,3 +25,4 @@ downloads/
|
|||
ext/
|
||||
libssh-*/
|
||||
msgpack-*/
|
||||
releases/
|
||||
|
|
|
|||
46
.travis.yml
46
.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
|
||||
|
|
|
|||
37
Dockerfile
Normal file
37
Dockerfile
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
33
build_static_release.sh
Executable 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
37
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))
|
||||
|
|
|
|||
62
client.c
62
client.c
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
27
configure.ac
27
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(
|
||||
|
|
|
|||
61
log.c
61
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
10
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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
18
server.c
18
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();
|
||||
|
|
|
|||
44
session.c
44
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: <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(;;);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
18
signal.c
18
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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
22
tmate-msg.c
22
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
144
tmate-session.c
144
tmate-session.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
25
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 */
|
||||
|
||||
|
|
|
|||
75
tmux.1
75
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 \&?
|
||||
|
|
|
|||
71
tmux.c
71
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 <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
19
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 *, ...);
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue