Adding upstream version 1.6.3+20210924.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
de82741bb6
commit
3a45e6c40f
32 changed files with 11509 additions and 12904 deletions
|
@ -1,3 +1,6 @@
|
||||||
BasedOnStyle: Google
|
BasedOnStyle: Google
|
||||||
Language: Cpp
|
Language: Cpp
|
||||||
ColumnLimit: 100
|
ColumnLimit: 120
|
||||||
|
IndentWidth: 2
|
||||||
|
TabWidth: 2
|
||||||
|
UseTab: Never
|
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: "/html"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
ignore:
|
||||||
|
- dependency-name: gts
|
33
.github/workflows/backend.yml
vendored
33
.github/workflows/backend.yml
vendored
|
@ -16,10 +16,10 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
lws-version: [4.1.6, 3.2.3, 2.4.2]
|
lws-version: [4.2.1, 3.2.3]
|
||||||
steps:
|
steps:
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: |
|
run: |
|
||||||
|
@ -41,10 +41,10 @@ jobs:
|
||||||
make && sudo make install
|
make && sudo make install
|
||||||
ttyd -v
|
ttyd -v
|
||||||
cross:
|
cross:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: [i686, x86_64, arm, armhf, aarch64, mips, mipsel, mips64, mips64el]
|
target: [i686, x86_64, arm, armhf, aarch64, mips, mipsel, mips64, mips64el, s390x]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
|
@ -55,7 +55,30 @@ jobs:
|
||||||
env:
|
env:
|
||||||
BUILD_TARGET: ${{ matrix.target }}
|
BUILD_TARGET: ${{ matrix.target }}
|
||||||
run: ./scripts/cross-build.sh
|
run: ./scripts/cross-build.sh
|
||||||
- uses: actions/upload-artifact@v1
|
- uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: ttyd.${{ matrix.target }}
|
name: ttyd.${{ matrix.target }}
|
||||||
path: build/ttyd
|
path: build/ttyd
|
||||||
|
win10:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: msys2/setup-msys2@v2
|
||||||
|
with:
|
||||||
|
msystem: MINGW64
|
||||||
|
install: >-
|
||||||
|
make
|
||||||
|
mingw64/mingw-w64-x86_64-gcc
|
||||||
|
mingw64/mingw-w64-x86_64-cmake
|
||||||
|
mingw64/mingw-w64-x86_64-zlib
|
||||||
|
mingw64/mingw-w64-x86_64-libuv
|
||||||
|
mingw64/mingw-w64-x86_64-mbedtls
|
||||||
|
mingw64/mingw-w64-x86_64-json-c
|
||||||
|
update: true
|
||||||
|
- name: Build ttyd
|
||||||
|
shell: msys2 {0}
|
||||||
|
run: ./scripts/mingw-build.sh
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ttyd.win10
|
||||||
|
path: build/ttyd.exe
|
||||||
|
|
59
.github/workflows/docker.yml
vendored
Normal file
59
.github/workflows/docker.yml
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
name: docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: main
|
||||||
|
tags: "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install packages
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y autoconf automake build-essential cmake curl file libtool
|
||||||
|
- name: Cross build multi-arch binary
|
||||||
|
run: |
|
||||||
|
mkdir dist
|
||||||
|
for arch in amd64 armv7 arm64 s390x; do
|
||||||
|
env BUILD_TARGET=$arch ./scripts/cross-build.sh
|
||||||
|
[ "$arch" = "armv7" ] && arch="arm"
|
||||||
|
mkdir -p dist/$arch && cp build/ttyd dist/$arch/ttyd
|
||||||
|
done
|
||||||
|
- uses: docker/setup-qemu-action@v1
|
||||||
|
- uses: docker/setup-buildx-action@v1
|
||||||
|
- uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||||
|
- name: Determine docker tags
|
||||||
|
id: docker_tag
|
||||||
|
run: |
|
||||||
|
case $GITHUB_REF in
|
||||||
|
refs/tags/*)
|
||||||
|
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||||
|
echo ::set-output name=DOCKER_TAG::tsl0922/ttyd:${TAG_NAME}
|
||||||
|
echo ::set-output name=ALPINE_TAG::tsl0922/ttyd:${TAG_NAME}-alpine
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo ::set-output name=DOCKER_TAG::tsl0922/ttyd:latest
|
||||||
|
echo ::set-output name=ALPINE_TAG::tsl0922/ttyd:alpine
|
||||||
|
esac
|
||||||
|
- name: build/push docker image
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/s390x
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.docker_tag.outputs.DOCKER_TAG }}
|
||||||
|
- name: build/push docker image (alpine)
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile.alpine
|
||||||
|
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/s390x
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.docker_tag.outputs.ALPINE_TAG }}
|
2
.github/workflows/frontend.yml
vendored
2
.github/workflows/frontend.yml
vendored
|
@ -12,7 +12,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
|
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -7,7 +7,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-20.04
|
||||||
outputs:
|
outputs:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
steps:
|
steps:
|
||||||
|
@ -23,11 +23,11 @@ jobs:
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-20.04
|
||||||
needs: release
|
needs: release
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: [i686, x86_64, arm, armhf, aarch64, mips, mipsel, mips64, mips64el]
|
target: [i686, x86_64, arm, armhf, aarch64, mips, mipsel, mips64, mips64el, s390x]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -47,5 +47,8 @@ build
|
||||||
# Clion files
|
# Clion files
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# VSCode files
|
||||||
|
.vscode/
|
||||||
|
|
||||||
# Project files
|
# Project files
|
||||||
!init.d
|
!init.d
|
||||||
|
|
|
@ -28,7 +28,7 @@ else()
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(SOURCE_FILES src/server.c src/http.c src/protocol.c src/terminal.c src/utils.c)
|
set(SOURCE_FILES src/utils.c src/pty.c src/protocol.c src/http.c src/server.c)
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
include(FindPackageHandleStandardArgs)
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ endif()
|
||||||
|
|
||||||
find_path(JSON-C_INCLUDE_DIR NAMES json.h PATH_SUFFIXES json-c)
|
find_path(JSON-C_INCLUDE_DIR NAMES json.h PATH_SUFFIXES json-c)
|
||||||
find_library(JSON-C_LIBRARY NAMES json-c)
|
find_library(JSON-C_LIBRARY NAMES json-c)
|
||||||
find_package_handle_standard_args(JSON-C REQUIRED_VARS JSON-C_INCLUDE_DIR JSON-C_LIBRARY)
|
find_package_handle_standard_args(JSON-C REQUIRED_VARS JSON-C_LIBRARY JSON-C_INCLUDE_DIR)
|
||||||
mark_as_advanced(JSON-C_INCLUDE_DIR JSON-C_LIBRARY)
|
mark_as_advanced(JSON-C_INCLUDE_DIR JSON-C_LIBRARY)
|
||||||
if(JSON-C_FOUND)
|
if(JSON-C_FOUND)
|
||||||
SET(JSON-C_INCLUDE_DIRS "${JSON-C_INCLUDE_DIR}")
|
SET(JSON-C_INCLUDE_DIRS "${JSON-C_INCLUDE_DIR}")
|
||||||
|
@ -51,26 +51,19 @@ if(JSON-C_FOUND)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
find_package(Libwebsockets 1.7.0 QUIET)
|
find_package(Libwebsockets 3.2.0 REQUIRED)
|
||||||
|
|
||||||
if(NOT Libwebsockets_FOUND) # for libwebsockets-dev on ubuntu 16.04
|
|
||||||
find_path(LIBWEBSOCKETS_INCLUDE_DIR NAMES libwebsockets.h)
|
|
||||||
find_library(LIBWEBSOCKETS_LIBRARY NAMES websockets)
|
|
||||||
find_package_handle_standard_args(Libwebsockets REQUIRED_VARS LIBWEBSOCKETS_LIBRARY LIBWEBSOCKETS_INCLUDE_DIR)
|
|
||||||
mark_as_advanced(LIBWEBSOCKETS_INCLUDE_DIR LIBWEBSOCKETS_LIBRARY)
|
|
||||||
if(Libwebsockets_FOUND)
|
|
||||||
SET(LIBWEBSOCKETS_INCLUDE_DIRS "${LIBWEBSOCKETS_INCLUDE_DIR}")
|
|
||||||
SET(LIBWEBSOCKETS_LIBRARIES "${LIBWEBSOCKETS_LIBRARY}")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(INCLUDE_DIRS ${ZLIB_INCLUDE_DIR} ${LIBWEBSOCKETS_INCLUDE_DIRS} ${JSON-C_INCLUDE_DIRS} ${LIBUV_INCLUDE_DIRS})
|
set(INCLUDE_DIRS ${ZLIB_INCLUDE_DIR} ${LIBWEBSOCKETS_INCLUDE_DIRS} ${JSON-C_INCLUDE_DIRS} ${LIBUV_INCLUDE_DIRS})
|
||||||
set(LINK_LIBS ${ZLIB_LIBRARIES} ${LIBWEBSOCKETS_LIBRARIES} ${JSON-C_LIBRARIES} ${LIBUV_LIBRARIES})
|
set(LINK_LIBS ${ZLIB_LIBRARIES} ${LIBWEBSOCKETS_LIBRARIES} ${JSON-C_LIBRARIES} ${LIBUV_LIBRARIES})
|
||||||
|
|
||||||
set (CMAKE_REQUIRED_INCLUDES ${INCLUDE_DIRS})
|
set (CMAKE_REQUIRED_INCLUDES ${INCLUDE_DIRS})
|
||||||
include(CheckSymbolExists)
|
include(CheckSymbolExists)
|
||||||
|
check_symbol_exists(LWS_WITH_LIBUV "lws_config.h" LWS_WITH_LIBUV)
|
||||||
check_symbol_exists(LWS_OPENSSL_SUPPORT "lws_config.h" LWS_OPENSSL_ENABLED)
|
check_symbol_exists(LWS_OPENSSL_SUPPORT "lws_config.h" LWS_OPENSSL_ENABLED)
|
||||||
check_symbol_exists(LWS_WITH_MBEDTLS "lws_config.h" LWS_MBEDTLS_ENABLED)
|
check_symbol_exists(LWS_WITH_MBEDTLS "lws_config.h" LWS_MBEDTLS_ENABLED)
|
||||||
|
if(NOT LWS_WITH_LIBUV)
|
||||||
|
message(FATAL_ERROR "libwebsockets was not build with libuv support (-DLWS_WITH_LIBUV=ON)")
|
||||||
|
endif()
|
||||||
if(LWS_OPENSSL_ENABLED AND NOT LWS_MBEDTLS_ENABLED)
|
if(LWS_OPENSSL_ENABLED AND NOT LWS_MBEDTLS_ENABLED)
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
list(APPEND INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR})
|
list(APPEND INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR})
|
||||||
|
@ -78,7 +71,7 @@ if(LWS_OPENSSL_ENABLED AND NOT LWS_MBEDTLS_ENABLED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
list(APPEND LINK_LIBS shell32)
|
list(APPEND LINK_LIBS shell32 ws2_32)
|
||||||
elseif(NOT APPLE)
|
elseif(NOT APPLE)
|
||||||
list(APPEND LINK_LIBS util)
|
list(APPEND LINK_LIBS util)
|
||||||
endif()
|
endif()
|
||||||
|
|
15
Dockerfile
15
Dockerfile
|
@ -1,16 +1,11 @@
|
||||||
FROM ubuntu:18.04
|
FROM ubuntu:20.04
|
||||||
RUN apt-get update && apt-get install -y autoconf automake curl cmake git libtool make \
|
|
||||||
&& git clone --depth=1 https://github.com/tsl0922/ttyd.git /ttyd \
|
|
||||||
&& cd /ttyd && env BUILD_TARGET=x86_64 WITH_SSL=true ./scripts/cross-build.sh
|
|
||||||
|
|
||||||
FROM ubuntu:18.04
|
ARG TARGETARCH
|
||||||
COPY --from=0 /ttyd/build/ttyd /usr/bin/ttyd
|
COPY ./dist/${TARGETARCH}/ttyd /usr/bin/ttyd
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/*
|
||||||
ADD https://github.com/krallin/tini/releases/download/v0.18.0/tini /sbin/tini
|
|
||||||
RUN chmod +x /sbin/tini
|
|
||||||
|
|
||||||
EXPOSE 7681
|
EXPOSE 7681
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
|
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
CMD ["ttyd", "bash"]
|
CMD ["ttyd", "bash"]
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
FROM ubuntu:18.04
|
|
||||||
RUN apt-get update && apt-get install -y autoconf automake curl cmake git libtool make \
|
|
||||||
&& git clone --depth=1 https://github.com/tsl0922/ttyd.git /ttyd \
|
|
||||||
&& cd /ttyd && env BUILD_TARGET=x86_64 WITH_SSL=true ./scripts/cross-build.sh
|
|
||||||
|
|
||||||
FROM alpine:3.12
|
|
||||||
COPY --from=0 /ttyd/build/ttyd /usr/bin/ttyd
|
|
||||||
RUN apk add --no-cache bash tini
|
|
||||||
|
|
||||||
EXPOSE 7681
|
|
||||||
WORKDIR /root
|
|
||||||
|
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
|
||||||
CMD ["ttyd", "bash"]
|
|
11
Dockerfile.alpine
Normal file
11
Dockerfile.alpine
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
FROM alpine
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
COPY ./dist/${TARGETARCH}/ttyd /usr/bin/ttyd
|
||||||
|
RUN apk add --no-cache bash tini
|
||||||
|
|
||||||
|
EXPOSE 7681
|
||||||
|
WORKDIR /root
|
||||||
|
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
|
CMD ["ttyd", "bash"]
|
|
@ -1,8 +1,10 @@
|
||||||
# ttyd - Share your terminal over the web  
|
  [](https://repology.org/project/ttyd/versions)
|
||||||
|
|
||||||
|
# ttyd - Share your terminal over the web
|
||||||
|
|
||||||
ttyd is a simple command-line tool for sharing terminal over the web.
|
ttyd is a simple command-line tool for sharing terminal over the web.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
|
@ -82,7 +84,7 @@ OPTIONS:
|
||||||
-o, --once Accept only one client and exit on disconnection
|
-o, --once Accept only one client and exit on disconnection
|
||||||
-B, --browser Open terminal with the default system browser
|
-B, --browser Open terminal with the default system browser
|
||||||
-I, --index Custom index.html path
|
-I, --index Custom index.html path
|
||||||
-b, --base-path Expected base path for requests coming from a reverse proxy (eg: /mounted/here)
|
-b, --base-path Expected base path for requests coming from a reverse proxy (eg: /mounted/here, max length: 128)
|
||||||
-P, --ping-interval Websocket ping interval(sec) (default: 300)
|
-P, --ping-interval Websocket ping interval(sec) (default: 300)
|
||||||
-6, --ipv6 Enable IPv6 support
|
-6, --ipv6 Enable IPv6 support
|
||||||
-S, --ssl Enable SSL
|
-S, --ssl Enable SSL
|
||||||
|
|
|
@ -17,60 +17,43 @@
|
||||||
"check": "gts check",
|
"check": "gts check",
|
||||||
"fix": "gts fix"
|
"fix": "gts fix"
|
||||||
},
|
},
|
||||||
"husky": {
|
|
||||||
"hooks": {
|
|
||||||
"pre-commit": "lint-staged"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint-staged": {
|
|
||||||
"src/**/*.ts": [
|
|
||||||
"gts fix",
|
|
||||||
"git add"
|
|
||||||
],
|
|
||||||
"src/**/*.scss": [
|
|
||||||
"scssfmt",
|
|
||||||
"git add"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"copy-webpack-plugin": "^6.4.1",
|
"copy-webpack-plugin": "^9.0.1",
|
||||||
"css-loader": "^5.0.1",
|
"css-loader": "^6.2.0",
|
||||||
|
"css-minimizer-webpack-plugin": "^3.0.2",
|
||||||
"gts": "^1.1.2",
|
"gts": "^1.1.2",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-clean": "^0.4.0",
|
"gulp-clean": "^0.4.0",
|
||||||
"gulp-gzip": "^1.4.2",
|
"gulp-gzip": "^1.4.2",
|
||||||
"gulp-inline-source": "^4.0.0",
|
"gulp-inline-source": "^4.0.0",
|
||||||
"gulp-rename": "^2.0.0",
|
"gulp-rename": "^2.0.0",
|
||||||
"html-webpack-plugin": "^4.5.0",
|
"html-webpack-plugin": "^5.3.2",
|
||||||
"husky": "^4.3.6",
|
"mini-css-extract-plugin": "^2.1.0",
|
||||||
"lint-staged": "^10.5.3",
|
"node-sass": "^6.0.1",
|
||||||
"mini-css-extract-plugin": "^1.3.3",
|
"sass-loader": "^12.1.0",
|
||||||
"node-sass": "^5.0.0",
|
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
|
||||||
"sass-loader": "^10.1.0",
|
|
||||||
"scssfmt": "^1.0.7",
|
"scssfmt": "^1.0.7",
|
||||||
"style-loader": "^2.0.0",
|
"style-loader": "^3.2.1",
|
||||||
"terser-webpack-plugin": "^4.2.3",
|
"terser-webpack-plugin": "^5.1.4",
|
||||||
"through2": "^4.0.2",
|
"through2": "^4.0.2",
|
||||||
"ts-loader": "^8.0.13",
|
"ts-loader": "^9.2.4",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.3",
|
||||||
"tslint-loader": "^3.5.4",
|
"tslint-loader": "^3.5.4",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^4.3.5",
|
||||||
"webpack": "4.44.2",
|
"util": "^0.12.4",
|
||||||
"webpack-cli": "^4.3.1",
|
"webpack": "^5.46.0",
|
||||||
"webpack-dev-server": "^3.11.1",
|
"webpack-cli": "^4.7.2",
|
||||||
"webpack-merge": "^5.7.3"
|
"webpack-dev-server": "^3.11.2",
|
||||||
|
"webpack-merge": "^5.8.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"backoff": "^2.5.0",
|
|
||||||
"decko": "^1.2.0",
|
"decko": "^1.2.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"preact": "^10.5.8",
|
"preact": "^10.5.14",
|
||||||
"whatwg-fetch": "^3.5.0",
|
"whatwg-fetch": "^3.6.2",
|
||||||
"xterm": "^4.9.0",
|
"xterm": "^4.13.0",
|
||||||
"xterm-addon-fit": "^0.4.0",
|
"xterm-addon-fit": "^0.5.0",
|
||||||
"xterm-addon-web-links": "^0.4.0",
|
"xterm-addon-web-links": "^0.4.0",
|
||||||
"xterm-addon-webgl": "^0.9.0",
|
"xterm-addon-webgl": "^0.11.1",
|
||||||
"zmodem.js": "^0.1.10"
|
"zmodem.js": "^0.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { bind } from 'decko';
|
import { bind } from 'decko';
|
||||||
import * as backoff from 'backoff';
|
|
||||||
import { Component, h } from 'preact';
|
import { Component, h } from 'preact';
|
||||||
import { ITerminalOptions, Terminal } from 'xterm';
|
import { ITerminalOptions, Terminal } from 'xterm';
|
||||||
import { FitAddon } from 'xterm-addon-fit';
|
import { FitAddon } from 'xterm-addon-fit';
|
||||||
|
@ -58,18 +57,17 @@ export class Xterm extends Component<Props> {
|
||||||
private fitAddon: FitAddon;
|
private fitAddon: FitAddon;
|
||||||
private overlayAddon: OverlayAddon;
|
private overlayAddon: OverlayAddon;
|
||||||
private zmodemAddon: ZmodemAddon;
|
private zmodemAddon: ZmodemAddon;
|
||||||
|
private webglAddon: WebglAddon;
|
||||||
|
|
||||||
private socket: WebSocket;
|
private socket: WebSocket;
|
||||||
private token: string;
|
private token: string;
|
||||||
|
private opened = false;
|
||||||
private title: string;
|
private title: string;
|
||||||
private titleFixed: string;
|
private titleFixed: string;
|
||||||
private resizeTimeout: number;
|
private resizeTimeout: number;
|
||||||
private resizeOverlay = true;
|
private resizeOverlay = true;
|
||||||
|
private reconnect = true;
|
||||||
private backoff: backoff.Backoff;
|
private doReconnect = true;
|
||||||
private backoffLock = false;
|
|
||||||
private doBackoff = true;
|
|
||||||
private reconnect = false;
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -78,22 +76,6 @@ export class Xterm extends Component<Props> {
|
||||||
this.textDecoder = new TextDecoder();
|
this.textDecoder = new TextDecoder();
|
||||||
this.fitAddon = new FitAddon();
|
this.fitAddon = new FitAddon();
|
||||||
this.overlayAddon = new OverlayAddon();
|
this.overlayAddon = new OverlayAddon();
|
||||||
this.backoff = backoff.exponential({
|
|
||||||
initialDelay: 100,
|
|
||||||
maxDelay: 10000,
|
|
||||||
});
|
|
||||||
this.backoff.failAfter(15);
|
|
||||||
this.backoff.on('ready', () => {
|
|
||||||
this.backoffLock = false;
|
|
||||||
this.refreshToken().then(this.connect);
|
|
||||||
});
|
|
||||||
this.backoff.on('backoff', (_, delay: number) => {
|
|
||||||
console.log(`[ttyd] will attempt to reconnect websocket in ${delay}ms`);
|
|
||||||
this.backoffLock = true;
|
|
||||||
});
|
|
||||||
this.backoff.on('fail', () => {
|
|
||||||
this.backoffLock = true; // break backoff
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
|
@ -225,8 +207,7 @@ export class Xterm extends Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
private applyOptions(options: any) {
|
private setRendererType(value: string) {
|
||||||
const { terminal, fitAddon } = this;
|
|
||||||
const isWebGL2Available = () => {
|
const isWebGL2Available = () => {
|
||||||
try {
|
try {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
|
@ -236,14 +217,38 @@ export class Xterm extends Component<Props> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { terminal } = this;
|
||||||
|
switch (value) {
|
||||||
|
case 'webgl':
|
||||||
|
if (this.webglAddon) return;
|
||||||
|
if (isWebGL2Available()) {
|
||||||
|
this.webglAddon = new WebglAddon();
|
||||||
|
terminal.loadAddon(this.webglAddon);
|
||||||
|
console.log(`[ttyd] WebGL renderer enabled`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
try {
|
||||||
|
this.webglAddon?.dispose();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
this.webglAddon = undefined;
|
||||||
|
console.log(`[ttyd] option: rendererType=${value}`);
|
||||||
|
terminal.setOption('rendererType', value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
private applyOptions(options: any) {
|
||||||
|
const { terminal, fitAddon } = this;
|
||||||
|
|
||||||
Object.keys(options).forEach(key => {
|
Object.keys(options).forEach(key => {
|
||||||
const value = options[key];
|
const value = options[key];
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'rendererType':
|
case 'rendererType':
|
||||||
if (value === 'webgl' && isWebGL2Available()) {
|
this.setRendererType(value);
|
||||||
terminal.loadAddon(new WebglAddon());
|
|
||||||
console.log(`[ttyd] WebGL renderer enabled`);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'disableLeaveAlert':
|
case 'disableLeaveAlert':
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -260,7 +265,7 @@ export class Xterm extends Component<Props> {
|
||||||
case 'disableReconnect':
|
case 'disableReconnect':
|
||||||
if (value) {
|
if (value) {
|
||||||
console.log(`[ttyd] Reconnect disabled`);
|
console.log(`[ttyd] Reconnect disabled`);
|
||||||
this.doBackoff = false;
|
this.reconnect = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'titleFixed':
|
case 'titleFixed':
|
||||||
|
@ -281,23 +286,29 @@ export class Xterm extends Component<Props> {
|
||||||
@bind
|
@bind
|
||||||
private onSocketOpen() {
|
private onSocketOpen() {
|
||||||
console.log('[ttyd] websocket connection opened');
|
console.log('[ttyd] websocket connection opened');
|
||||||
this.backoff.reset();
|
|
||||||
|
|
||||||
const { socket, textEncoder, terminal, fitAddon, overlayAddon } = this;
|
const { socket, textEncoder, terminal, fitAddon, overlayAddon } = this;
|
||||||
socket.send(textEncoder.encode(JSON.stringify({ AuthToken: this.token })));
|
|
||||||
|
|
||||||
if (this.reconnect) {
|
|
||||||
const dims = fitAddon.proposeDimensions();
|
const dims = fitAddon.proposeDimensions();
|
||||||
|
socket.send(
|
||||||
|
textEncoder.encode(
|
||||||
|
JSON.stringify({
|
||||||
|
AuthToken: this.token,
|
||||||
|
columns: dims.cols,
|
||||||
|
rows: dims.rows,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.opened) {
|
||||||
terminal.reset();
|
terminal.reset();
|
||||||
terminal.resize(dims.cols, dims.rows);
|
terminal.resize(dims.cols, dims.rows);
|
||||||
this.onTerminalResize(dims); // may not be triggered by terminal.resize
|
|
||||||
overlayAddon.showOverlay('Reconnected', 300);
|
overlayAddon.showOverlay('Reconnected', 300);
|
||||||
} else {
|
} else {
|
||||||
this.reconnect = true;
|
this.opened = true;
|
||||||
fitAddon.fit();
|
fitAddon.fit();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.applyOptions(this.props.clientOptions);
|
this.doReconnect = this.reconnect;
|
||||||
|
|
||||||
terminal.focus();
|
terminal.focus();
|
||||||
}
|
}
|
||||||
|
@ -306,22 +317,31 @@ export class Xterm extends Component<Props> {
|
||||||
private onSocketClose(event: CloseEvent) {
|
private onSocketClose(event: CloseEvent) {
|
||||||
console.log(`[ttyd] websocket connection closed with code: ${event.code}`);
|
console.log(`[ttyd] websocket connection closed with code: ${event.code}`);
|
||||||
|
|
||||||
const { backoff, doBackoff, backoffLock, overlayAddon } = this;
|
const { refreshToken, connect, doReconnect, overlayAddon } = this;
|
||||||
overlayAddon.showOverlay('Connection Closed', null);
|
overlayAddon.showOverlay('Connection Closed', null);
|
||||||
|
|
||||||
// 1000: CLOSE_NORMAL
|
// 1000: CLOSE_NORMAL
|
||||||
if (event.code !== 1000 && doBackoff && !backoffLock) {
|
if (event.code !== 1000 && doReconnect) {
|
||||||
backoff.backoff();
|
overlayAddon.showOverlay('Reconnecting...', null);
|
||||||
|
refreshToken().then(connect);
|
||||||
|
} else {
|
||||||
|
const { terminal } = this;
|
||||||
|
const keyDispose = terminal.onKey(e => {
|
||||||
|
const event = e.domEvent;
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
keyDispose.dispose();
|
||||||
|
overlayAddon.showOverlay('Reconnecting...', null);
|
||||||
|
refreshToken().then(connect);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
overlayAddon.showOverlay('Press ⏎ to Reconnect', null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
private onSocketError(event: Event) {
|
private onSocketError(event: Event) {
|
||||||
console.error('[ttyd] websocket connection error: ', event);
|
console.error('[ttyd] websocket connection error: ', event);
|
||||||
const { backoff, doBackoff, backoffLock } = this;
|
this.doReconnect = false;
|
||||||
if (doBackoff && !backoffLock) {
|
|
||||||
backoff.backoff();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
|
@ -340,7 +360,8 @@ export class Xterm extends Component<Props> {
|
||||||
document.title = this.title;
|
document.title = this.title;
|
||||||
break;
|
break;
|
||||||
case Command.SET_PREFERENCES:
|
case Command.SET_PREFERENCES:
|
||||||
this.applyOptions(JSON.parse(textDecoder.decode(data)));
|
const prefs = JSON.parse(textDecoder.decode(data));
|
||||||
|
this.applyOptions(Object.assign({}, this.props.clientOptions, prefs));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn(`[ttyd] unknown command: ${cmd}`);
|
console.warn(`[ttyd] unknown command: ${cmd}`);
|
||||||
|
|
|
@ -3,7 +3,7 @@ const { merge } = require('webpack-merge');
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
|
|
||||||
const devMode = process.env.NODE_ENV !== 'production';
|
const devMode = process.env.NODE_ENV !== 'production';
|
||||||
|
@ -15,7 +15,7 @@ const baseConfig = {
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
filename: devMode ? '[name].js' : '[name].[hash].js',
|
filename: devMode ? '[name].js' : '[name].[contenthash].js',
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
@ -49,8 +49,8 @@ const baseConfig = {
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: devMode ? '[name].css' : '[name].[hash].css',
|
filename: devMode ? '[name].css' : '[name].[contenthash].css',
|
||||||
chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
|
chunkFilename: devMode ? '[id].css' : '[id].[contenthash].css',
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
inject: false,
|
inject: false,
|
||||||
|
@ -86,17 +86,8 @@ const prodConfig = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
optimization: {
|
optimization: {
|
||||||
minimizer: [
|
minimizer: [
|
||||||
new TerserPlugin({
|
new TerserPlugin(),
|
||||||
sourceMap: true
|
new CssMinimizerPlugin(),
|
||||||
}),
|
|
||||||
new OptimizeCSSAssetsPlugin({
|
|
||||||
cssProcessorOptions: {
|
|
||||||
map: {
|
|
||||||
inline: false,
|
|
||||||
annotation: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
|
|
3424
html/yarn.lock
3424
html/yarn.lock
File diff suppressed because it is too large
Load diff
10
man/ttyd.1
10
man/ttyd.1
|
@ -46,6 +46,10 @@ Cross platform: macOS, Linux, FreeBSD/OpenBSD, OpenWrt/LEDE, Windows
|
||||||
\-c, \-\-credential USER[:PASSWORD]
|
\-c, \-\-credential USER[:PASSWORD]
|
||||||
Credential for Basic Authentication (format: username:password)
|
Credential for Basic Authentication (format: username:password)
|
||||||
|
|
||||||
|
.PP
|
||||||
|
\-H, \-\-auth\-header <name>
|
||||||
|
HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\-u, \-\-uid <uid>
|
\-u, \-\-uid <uid>
|
||||||
User id to run with
|
User id to run with
|
||||||
|
@ -58,6 +62,10 @@ Cross platform: macOS, Linux, FreeBSD/OpenBSD, OpenWrt/LEDE, Windows
|
||||||
\-s, \-\-signal <signal string>
|
\-s, \-\-signal <signal string>
|
||||||
Signal to send to the command when exit it (default: 1, SIGHUP)
|
Signal to send to the command when exit it (default: 1, SIGHUP)
|
||||||
|
|
||||||
|
.PP
|
||||||
|
\-w, \-\-cwd <path>
|
||||||
|
Working directory to be set for the child program
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\-a, \-\-url\-arg
|
\-a, \-\-url\-arg
|
||||||
Allow client to send command line arguments in URL (eg:
|
Allow client to send command line arguments in URL (eg:
|
||||||
|
@ -97,7 +105,7 @@ Cross platform: macOS, Linux, FreeBSD/OpenBSD, OpenWrt/LEDE, Windows
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\-b, \-\-base\-path
|
\-b, \-\-base\-path
|
||||||
Expected base path for requests coming from a reverse proxy (eg: /mounted/here)
|
Expected base path for requests coming from a reverse proxy (eg: /mounted/here, max length: 128)
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\-P, \-\-ping\-interval
|
\-P, \-\-ping\-interval
|
||||||
|
|
|
@ -28,6 +28,9 @@ ttyd 1 "September 2016" ttyd "User Manual"
|
||||||
-c, --credential USER[:PASSWORD]
|
-c, --credential USER[:PASSWORD]
|
||||||
Credential for Basic Authentication (format: username:password)
|
Credential for Basic Authentication (format: username:password)
|
||||||
|
|
||||||
|
-H, --auth-header <name>
|
||||||
|
HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication
|
||||||
|
|
||||||
-u, --uid <uid>
|
-u, --uid <uid>
|
||||||
User id to run with
|
User id to run with
|
||||||
|
|
||||||
|
@ -37,6 +40,9 @@ ttyd 1 "September 2016" ttyd "User Manual"
|
||||||
-s, --signal <signal string>
|
-s, --signal <signal string>
|
||||||
Signal to send to the command when exit it (default: 1, SIGHUP)
|
Signal to send to the command when exit it (default: 1, SIGHUP)
|
||||||
|
|
||||||
|
-w, --cwd <path>
|
||||||
|
Working directory to be set for the child program
|
||||||
|
|
||||||
-a, --url-arg
|
-a, --url-arg
|
||||||
Allow client to send command line arguments in URL (eg: http://localhost:7681?arg=foo&arg=bar)
|
Allow client to send command line arguments in URL (eg: http://localhost:7681?arg=foo&arg=bar)
|
||||||
|
|
||||||
|
@ -65,7 +71,7 @@ ttyd 1 "September 2016" ttyd "User Manual"
|
||||||
Custom index.html path
|
Custom index.html path
|
||||||
|
|
||||||
-b, --base-path
|
-b, --base-path
|
||||||
Expected base path for requests coming from a reverse proxy (eg: /mounted/here)
|
Expected base path for requests coming from a reverse proxy (eg: /mounted/here, max length: 128)
|
||||||
|
|
||||||
-P, --ping-interval
|
-P, --ping-interval
|
||||||
Websocket ping interval(sec) (default: 300)
|
Websocket ping interval(sec) (default: 300)
|
||||||
|
|
|
@ -14,7 +14,7 @@ ZLIB_VERSION="${ZLIB_VERSION:-1.2.11}"
|
||||||
JSON_C_VERSION="${JSON_C_VERSION:-0.15}"
|
JSON_C_VERSION="${JSON_C_VERSION:-0.15}"
|
||||||
MBEDTLS_VERSION="${MBEDTLS_VERSION:-2.16.8}"
|
MBEDTLS_VERSION="${MBEDTLS_VERSION:-2.16.8}"
|
||||||
LIBUV_VERSION="${LIBUV_VERSION:-1.40.0}"
|
LIBUV_VERSION="${LIBUV_VERSION:-1.40.0}"
|
||||||
LIBWEBSOCKETS_VERSION="${LIBWEBSOCKETS_VERSION:-4.1.6}"
|
LIBWEBSOCKETS_VERSION="${LIBWEBSOCKETS_VERSION:-4.2.1}"
|
||||||
|
|
||||||
build_zlib() {
|
build_zlib() {
|
||||||
echo "=== Building zlib-${ZLIB_VERSION} (${TARGET})..."
|
echo "=== Building zlib-${ZLIB_VERSION} (${TARGET})..."
|
||||||
|
@ -106,7 +106,6 @@ build_libwebsockets() {
|
||||||
-DLWS_WITH_LEJP=OFF \
|
-DLWS_WITH_LEJP=OFF \
|
||||||
-DLWS_WITH_LEJP_CONF=OFF \
|
-DLWS_WITH_LEJP_CONF=OFF \
|
||||||
-DLWS_WITH_LWSAC=OFF \
|
-DLWS_WITH_LWSAC=OFF \
|
||||||
-DLWS_WITH_CUSTOM_HEADERS=OFF \
|
|
||||||
-DLWS_WITH_SEQUENCER=OFF \
|
-DLWS_WITH_SEQUENCER=OFF \
|
||||||
..
|
..
|
||||||
make -j"$(nproc)" install
|
make -j"$(nproc)" install
|
||||||
|
@ -154,15 +153,24 @@ build() {
|
||||||
}
|
}
|
||||||
|
|
||||||
case ${BUILD_TARGET} in
|
case ${BUILD_TARGET} in
|
||||||
i686|x86_64|aarch64|mips|mipsel|mips64|mips64el)
|
amd64) BUILD_TARGET="x86_64" ;;
|
||||||
|
arm64) BUILD_TARGET="aarch64" ;;
|
||||||
|
armv7) BUILD_TARGET="armv7l" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case ${BUILD_TARGET} in
|
||||||
|
i686|x86_64|aarch64|mips|mipsel|mips64|mips64el|s390x)
|
||||||
build "${BUILD_TARGET}-linux-musl" "${BUILD_TARGET}"
|
build "${BUILD_TARGET}-linux-musl" "${BUILD_TARGET}"
|
||||||
;;
|
;;
|
||||||
arm)
|
arm)
|
||||||
build arm-linux-musleabi "${BUILD_TARGET}"
|
build "${BUILD_TARGET}-linux-musleabi" "${BUILD_TARGET}"
|
||||||
;;
|
;;
|
||||||
armhf)
|
armhf)
|
||||||
build arm-linux-musleabihf "${BUILD_TARGET}"
|
build arm-linux-musleabihf "${BUILD_TARGET}"
|
||||||
;;
|
;;
|
||||||
|
armv7l)
|
||||||
|
build armv7l-linux-musleabihf "${BUILD_TARGET}"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "unknown cross target: ${BUILD_TARGET}" && exit 1
|
echo "unknown cross target: ${BUILD_TARGET}" && exit 1
|
||||||
esac
|
esac
|
||||||
|
|
40
scripts/mingw-build.sh
Normal file
40
scripts/mingw-build.sh
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
TMPDIR="${TMPDIR:-/tmp}"
|
||||||
|
JSON_C_VERSION="${JSON_C_VERSION:-0.15}"
|
||||||
|
LIBWEBSOCKETS_VERSION="${LIBWEBSOCKETS_VERSION:-4.2.1}"
|
||||||
|
|
||||||
|
build_libwebsockets() {
|
||||||
|
curl -sLo- "https://github.com/warmcat/libwebsockets/archive/v${LIBWEBSOCKETS_VERSION}.tar.gz" | tar xz -f - -C "${TMPDIR}"
|
||||||
|
pushd "${TMPDIR}/libwebsockets-${LIBWEBSOCKETS_VERSION}"
|
||||||
|
sed -i 's/ websockets_shared//g' cmake/libwebsockets-config.cmake.in
|
||||||
|
sed -i '/PC_OPENSSL/d' lib/tls/CMakeLists.txt
|
||||||
|
rm -rf build && mkdir -p build && cd build
|
||||||
|
MSYS2_ARG_CONV_EXCL="-DCMAKE_INSTALL_PREFIX=" \
|
||||||
|
cmake -G"MSYS Makefiles" \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=${MINGW_PREFIX} \
|
||||||
|
-DCMAKE_BUILD_TYPE=RELEASE \
|
||||||
|
-DCMAKE_FIND_LIBRARY_SUFFIXES=".a" \
|
||||||
|
-DCMAKE_EXE_LINKER_FLAGS="-static" \
|
||||||
|
-DLWS_WITH_MBEDTLS=ON \
|
||||||
|
-DLWS_WITH_DIR=OFF \
|
||||||
|
-DLWS_WITHOUT_DAEMONIZE=ON \
|
||||||
|
-DLWS_WITHOUT_TESTAPPS=ON \
|
||||||
|
-DLWS_WITH_LIBUV=ON \
|
||||||
|
..
|
||||||
|
make -j"$(nproc)" install
|
||||||
|
popd
|
||||||
|
}
|
||||||
|
|
||||||
|
build_libwebsockets
|
||||||
|
|
||||||
|
rm -rf build && mkdir -p build && cd build
|
||||||
|
cmake -G"MSYS Makefiles" \
|
||||||
|
-DCMAKE_FIND_LIBRARY_SUFFIXES=".a" \
|
||||||
|
-DCMAKE_C_FLAGS="-Os -ffunction-sections -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables -flto" \
|
||||||
|
-DCMAKE_EXE_LINKER_FLAGS="-static -no-pie -Wl,-s -Wl,-Bsymbolic -Wl,--gc-sections" \
|
||||||
|
-DCMAKE_BUILD_TYPE=RELEASE \
|
||||||
|
..
|
||||||
|
make install
|
18378
src/html.h
generated
18378
src/html.h
generated
File diff suppressed because it is too large
Load diff
95
src/http.c
95
src/http.c
|
@ -6,61 +6,45 @@
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#if LWS_LIBRARY_VERSION_MAJOR < 2
|
|
||||||
#define HTTP_STATUS_FOUND 302
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum { AUTH_OK, AUTH_FAIL, AUTH_ERROR };
|
enum { AUTH_OK, AUTH_FAIL, AUTH_ERROR };
|
||||||
|
|
||||||
static char *html_cache = NULL;
|
static char *html_cache = NULL;
|
||||||
static size_t html_cache_len = 0;
|
static size_t html_cache_len = 0;
|
||||||
|
|
||||||
static int check_auth(struct lws *wsi, struct pss_http *pss) {
|
static int send_unauthorized(struct lws *wsi, unsigned int code, enum lws_token_indexes header) {
|
||||||
if (server->credential == NULL) return AUTH_OK;
|
|
||||||
|
|
||||||
int hdr_length = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_AUTHORIZATION);
|
|
||||||
char buf[hdr_length + 1];
|
|
||||||
int len = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
|
|
||||||
if (len > 0) {
|
|
||||||
// extract base64 text from authorization header
|
|
||||||
char *ptr = &buf[0];
|
|
||||||
char *token, *b64_text = NULL;
|
|
||||||
int i = 1;
|
|
||||||
while ((token = strsep(&ptr, " ")) != NULL) {
|
|
||||||
if (strlen(token) == 0) continue;
|
|
||||||
if (i++ == 2) {
|
|
||||||
b64_text = token;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (b64_text != NULL && !strcmp(b64_text, server->credential)) return AUTH_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char buffer[1024 + LWS_PRE], *p, *end;
|
unsigned char buffer[1024 + LWS_PRE], *p, *end;
|
||||||
p = buffer + LWS_PRE;
|
p = buffer + LWS_PRE;
|
||||||
end = p + sizeof(buffer) - LWS_PRE;
|
end = p + sizeof(buffer) - LWS_PRE;
|
||||||
|
|
||||||
char *body = strdup("401 Unauthorized\n");
|
if (lws_add_http_header_status(wsi, code, &p, end) ||
|
||||||
size_t n = strlen(body);
|
lws_add_http_header_by_token(wsi, header, (unsigned char *)"Basic realm=\"ttyd\"", 18, &p, end) ||
|
||||||
|
lws_add_http_header_content_length(wsi, 0, &p, end) || lws_finalize_http_header(wsi, &p, end) ||
|
||||||
if (lws_add_http_header_status(wsi, HTTP_STATUS_UNAUTHORIZED, &p, end) ||
|
|
||||||
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_WWW_AUTHENTICATE,
|
|
||||||
(unsigned char *)"Basic realm=\"ttyd\"", 18, &p, end) ||
|
|
||||||
lws_add_http_header_content_length(wsi, n, &p, end) ||
|
|
||||||
lws_finalize_http_header(wsi, &p, end) ||
|
|
||||||
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
|
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
|
||||||
return AUTH_ERROR;
|
|
||||||
|
|
||||||
pss->buffer = pss->ptr = body;
|
|
||||||
pss->len = n;
|
|
||||||
lws_callback_on_writable(wsi);
|
|
||||||
|
|
||||||
return AUTH_FAIL;
|
return AUTH_FAIL;
|
||||||
|
|
||||||
|
return lws_http_transaction_completed(wsi) ? AUTH_FAIL : AUTH_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int check_auth(struct lws *wsi, struct pss_http *pss) {
|
||||||
|
if (server->auth_header != NULL) {
|
||||||
|
if (lws_hdr_custom_length(wsi, server->auth_header, strlen(server->auth_header)) > 0) return AUTH_OK;
|
||||||
|
return send_unauthorized(wsi, HTTP_STATUS_PROXY_AUTH_REQUIRED, WSI_TOKEN_HTTP_PROXY_AUTHENTICATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(server->credential != NULL) {
|
||||||
|
char buf[256];
|
||||||
|
int len = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
|
||||||
|
if (len >= 7 && strstr(buf, "Basic ")) {
|
||||||
|
if (!strcmp(buf + 6, server->credential)) return AUTH_OK;
|
||||||
|
}
|
||||||
|
return send_unauthorized(wsi, HTTP_STATUS_UNAUTHORIZED, WSI_TOKEN_HTTP_WWW_AUTHENTICATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AUTH_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool accept_gzip(struct lws *wsi) {
|
static bool accept_gzip(struct lws *wsi) {
|
||||||
int hdr_length = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING);
|
char buf[256];
|
||||||
char buf[hdr_length + 1];
|
|
||||||
int len = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_ACCEPT_ENCODING);
|
int len = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_ACCEPT_ENCODING);
|
||||||
return len > 0 && strstr(buf, "gzip") != NULL;
|
return len > 0 && strstr(buf, "gzip") != NULL;
|
||||||
}
|
}
|
||||||
|
@ -102,17 +86,11 @@ static void pss_buffer_free(struct pss_http *pss) {
|
||||||
static void access_log(struct lws *wsi, const char *path) {
|
static void access_log(struct lws *wsi, const char *path) {
|
||||||
char rip[50];
|
char rip[50];
|
||||||
|
|
||||||
#if LWS_LIBRARY_VERSION_NUMBER >= 2004000
|
|
||||||
lws_get_peer_simple(lws_get_network_wsi(wsi), rip, sizeof(rip));
|
lws_get_peer_simple(lws_get_network_wsi(wsi), rip, sizeof(rip));
|
||||||
#else
|
|
||||||
char name[100];
|
|
||||||
lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), rip, sizeof(rip));
|
|
||||||
#endif
|
|
||||||
lwsl_notice("HTTP %s - %s\n", path, rip);
|
lwsl_notice("HTTP %s - %s\n", path, rip);
|
||||||
}
|
}
|
||||||
|
|
||||||
int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
|
int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
|
||||||
size_t len) {
|
|
||||||
struct pss_http *pss = (struct pss_http *)user;
|
struct pss_http *pss = (struct pss_http *)user;
|
||||||
unsigned char buffer[4096 + LWS_PRE], *p, *end;
|
unsigned char buffer[4096 + LWS_PRE], *p, *end;
|
||||||
char buf[256];
|
char buf[256];
|
||||||
|
@ -140,8 +118,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||||
size_t n = sprintf(buf, "{\"token\": \"%s\"}", credential);
|
size_t n = sprintf(buf, "{\"token\": \"%s\"}", credential);
|
||||||
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end) ||
|
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end) ||
|
||||||
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
|
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
|
||||||
(unsigned char *)"application/json;charset=utf-8", 30, &p,
|
(unsigned char *)"application/json;charset=utf-8", 30, &p, end) ||
|
||||||
end) ||
|
|
||||||
lws_add_http_header_content_length(wsi, (unsigned long)n, &p, end) ||
|
lws_add_http_header_content_length(wsi, (unsigned long)n, &p, end) ||
|
||||||
lws_finalize_http_header(wsi, &p, end) ||
|
lws_finalize_http_header(wsi, &p, end) ||
|
||||||
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
|
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
|
||||||
|
@ -156,11 +133,9 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||||
// redirects `/base-path` to `/base-path/`
|
// redirects `/base-path` to `/base-path/`
|
||||||
if (strcmp(pss->path, endpoints.parent) == 0) {
|
if (strcmp(pss->path, endpoints.parent) == 0) {
|
||||||
if (lws_add_http_header_status(wsi, HTTP_STATUS_FOUND, &p, end) ||
|
if (lws_add_http_header_status(wsi, HTTP_STATUS_FOUND, &p, end) ||
|
||||||
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION,
|
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, (unsigned char *)endpoints.index,
|
||||||
(unsigned char *)endpoints.index,
|
|
||||||
(int)strlen(endpoints.index), &p, end) ||
|
(int)strlen(endpoints.index), &p, end) ||
|
||||||
lws_add_http_header_content_length(wsi, 0, &p, end) ||
|
lws_add_http_header_content_length(wsi, 0, &p, end) || lws_finalize_http_header(wsi, &p, end) ||
|
||||||
lws_finalize_http_header(wsi, &p, end) ||
|
|
||||||
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
|
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
|
||||||
return 1;
|
return 1;
|
||||||
goto try_to_reuse;
|
goto try_to_reuse;
|
||||||
|
@ -179,15 +154,14 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||||
char *output = (char *)index_html;
|
char *output = (char *)index_html;
|
||||||
size_t output_len = index_html_len;
|
size_t output_len = index_html_len;
|
||||||
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end) ||
|
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end) ||
|
||||||
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
|
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (const unsigned char *)content_type, 9, &p,
|
||||||
(const unsigned char *)content_type, 9, &p, end))
|
end))
|
||||||
return 1;
|
return 1;
|
||||||
#ifdef LWS_WITH_HTTP_STREAM_COMPRESSION
|
#ifdef LWS_WITH_HTTP_STREAM_COMPRESSION
|
||||||
if (!uncompress_html(&output, &output_len)) return 1;
|
if (!uncompress_html(&output, &output_len)) return 1;
|
||||||
#else
|
#else
|
||||||
if (accept_gzip(wsi)) {
|
if (accept_gzip(wsi)) {
|
||||||
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING,
|
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING, (unsigned char *)"gzip", 4, &p, end))
|
||||||
(unsigned char *)"gzip", 4, &p, end))
|
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
if (!uncompress_html(&output, &output_len)) return 1;
|
if (!uncompress_html(&output, &output_len)) return 1;
|
||||||
|
@ -199,14 +173,9 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||||
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
|
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
#if LWS_LIBRARY_VERSION_MAJOR < 2
|
|
||||||
if (lws_write_http(wsi, output, output_len) < 0) return 1;
|
|
||||||
goto try_to_reuse;
|
|
||||||
#else
|
|
||||||
pss->buffer = pss->ptr = output;
|
pss->buffer = pss->ptr = output;
|
||||||
pss->len = output_len;
|
pss->len = output_len;
|
||||||
lws_callback_on_writable(wsi);
|
lws_callback_on_writable(wsi);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
333
src/protocol.c
333
src/protocol.c
|
@ -1,15 +1,13 @@
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <json.h>
|
#include <json.h>
|
||||||
#include <libwebsockets.h>
|
#include <libwebsockets.h>
|
||||||
#include <signal.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
|
#include "pty.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "terminal.h"
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
// initial message list
|
// initial message list
|
||||||
|
@ -37,34 +35,22 @@ static int send_initial_message(struct lws *wsi, int index) {
|
||||||
return lws_write(wsi, p, (size_t)n, LWS_WRITE_BINARY);
|
return lws_write(wsi, p, (size_t)n, LWS_WRITE_BINARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool parse_window_size(struct pss_tty *pss, int *cols, int *rows) {
|
static json_object *parse_window_size(const char *buf, size_t len, uint16_t *cols, uint16_t *rows) {
|
||||||
char json[pss->len];
|
json_tokener *tok = json_tokener_new();
|
||||||
strncpy(json, pss->buffer + 1, pss->len - 1);
|
json_object *obj = json_tokener_parse_ex(tok, buf, len);
|
||||||
json[pss->len - 1] = '\0';
|
|
||||||
|
|
||||||
json_object *obj = json_tokener_parse(json);
|
|
||||||
struct json_object *o = NULL;
|
struct json_object *o = NULL;
|
||||||
|
|
||||||
if (!json_object_object_get_ex(obj, "columns", &o)) {
|
if (json_object_object_get_ex(obj, "columns", &o)) *cols = (uint16_t)json_object_get_int(o);
|
||||||
lwsl_err("columns field not exists, json: %s\n", json);
|
if (json_object_object_get_ex(obj, "rows", &o)) *rows = (uint16_t)json_object_get_int(o);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*cols = json_object_get_int(o);
|
|
||||||
if (!json_object_object_get_ex(obj, "rows", &o)) {
|
|
||||||
lwsl_err("rows field not exists, json: %s\n", json);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*rows = json_object_get_int(o);
|
|
||||||
json_object_put(obj);
|
|
||||||
|
|
||||||
return true;
|
json_tokener_free(tok);
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool check_host_origin(struct lws *wsi) {
|
static bool check_host_origin(struct lws *wsi) {
|
||||||
int origin_length = lws_hdr_total_length(wsi, WSI_TOKEN_ORIGIN);
|
char buf[256];
|
||||||
char buf[origin_length + 1];
|
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
int len = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_ORIGIN);
|
int len = lws_hdr_copy(wsi, buf, (int)sizeof(buf), WSI_TOKEN_ORIGIN);
|
||||||
if (len <= 0) {
|
if (len <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -78,154 +64,123 @@ static bool check_host_origin(struct lws *wsi) {
|
||||||
sprintf(buf, "%s:%d", address, port);
|
sprintf(buf, "%s:%d", address, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
int host_length = lws_hdr_total_length(wsi, WSI_TOKEN_HOST);
|
char host_buf[256];
|
||||||
if (host_length != strlen(buf)) return false;
|
|
||||||
char host_buf[host_length + 1];
|
|
||||||
memset(host_buf, 0, sizeof(host_buf));
|
memset(host_buf, 0, sizeof(host_buf));
|
||||||
len = lws_hdr_copy(wsi, host_buf, sizeof(host_buf), WSI_TOKEN_HOST);
|
len = lws_hdr_copy(wsi, host_buf, (int)sizeof(host_buf), WSI_TOKEN_HOST);
|
||||||
|
|
||||||
return len > 0 && strcasecmp(buf, host_buf) == 0;
|
return len > 0 && strcasecmp(buf, host_buf) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void close_cb(uv_handle_t *handle) {
|
static void process_read_cb(void *ctx, pty_buf_t *buf, bool eof) {
|
||||||
struct pty_proc *proc = container_of((uv_pipe_t *)handle, struct pty_proc, pipe);
|
struct pss_tty *pss = (struct pss_tty *)ctx;
|
||||||
free(proc);
|
if (eof && !process_running(pss->process))
|
||||||
}
|
pss->lws_close_status = pss->process->exit_code == 0 ? 1000 : 1006;
|
||||||
|
else
|
||||||
static void pty_proc_free(struct pty_proc *proc) {
|
pss->pty_buf = buf;
|
||||||
uv_read_stop((uv_stream_t *)&proc->pipe);
|
|
||||||
close(proc->pty);
|
|
||||||
if (proc->pty_buffer != NULL) {
|
|
||||||
free(proc->pty_buffer);
|
|
||||||
proc->pty_buffer = NULL;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < proc->argc; i++) {
|
|
||||||
free(proc->args[i]);
|
|
||||||
}
|
|
||||||
uv_close((uv_handle_t *)&proc->pipe, close_cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
|
|
||||||
buf->base = xmalloc(suggested_size);
|
|
||||||
buf->len = suggested_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
|
|
||||||
struct pss_tty *pss = (struct pss_tty *)stream->data;
|
|
||||||
struct pty_proc *proc = pss->proc;
|
|
||||||
proc->pty_len = nread;
|
|
||||||
|
|
||||||
uv_read_stop(stream);
|
|
||||||
|
|
||||||
if (nread == UV_ENOBUFS || nread == 0) return;
|
|
||||||
if (nread > 0) {
|
|
||||||
proc->pty_buffer = xmalloc(LWS_PRE + 1 + (size_t)nread);
|
|
||||||
memcpy(proc->pty_buffer + LWS_PRE + 1, buf->base, (size_t)nread);
|
|
||||||
} else {
|
|
||||||
proc->pty_buffer = NULL;
|
|
||||||
if (nread != UV_EOF) {
|
|
||||||
proc->err_count++;
|
|
||||||
lwsl_err("read_cb: %s (%s)\n", uv_err_name(nread), uv_strerror(nread));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(buf->base);
|
|
||||||
|
|
||||||
lws_callback_on_writable(pss->wsi);
|
lws_callback_on_writable(pss->wsi);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void child_cb(uv_signal_t *handle, int signum) {
|
static void process_exit_cb(void *ctx, pty_process *process) {
|
||||||
pid_t pid;
|
struct pss_tty *pss = (struct pss_tty *)ctx;
|
||||||
int stat;
|
pss->process = NULL;
|
||||||
|
if (process->killed) {
|
||||||
struct pty_proc *proc;
|
lwsl_notice("process killed with signal %d, pid: %d\n", process->exit_signal, process->pid);
|
||||||
LIST_HEAD(proc, pty_proc) *procs = handle->data;
|
|
||||||
LIST_FOREACH(proc, procs, entry) {
|
|
||||||
do
|
|
||||||
pid = waitpid(proc->pid, &stat, WNOHANG);
|
|
||||||
while (pid == -1 && errno == EINTR);
|
|
||||||
|
|
||||||
if (pid <= 0) continue;
|
|
||||||
|
|
||||||
if (WIFEXITED(stat)) {
|
|
||||||
proc->status = WEXITSTATUS(stat);
|
|
||||||
lwsl_notice("process exited with code %d, pid: %d\n", proc->status, proc->pid);
|
|
||||||
} else if (WIFSIGNALED(stat)) {
|
|
||||||
int sig = WTERMSIG(stat);
|
|
||||||
char sig_name[20];
|
|
||||||
|
|
||||||
proc->status = 128 + sig;
|
|
||||||
get_sig_name(sig, sig_name, sizeof(sig_name));
|
|
||||||
lwsl_notice("process killed with signal %d (%s), pid: %d\n", sig, sig_name, proc->pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
LIST_REMOVE(proc, entry);
|
|
||||||
if (proc->state == STATE_KILL) {
|
|
||||||
pty_proc_free(proc);
|
|
||||||
} else {
|
} else {
|
||||||
proc->state = STATE_EXIT;
|
lwsl_notice("process exited with code %d, pid: %d\n", process->exit_code, process->pid);
|
||||||
}
|
pss->lws_close_status = process->exit_code == 0 ? 1000 : 1006;
|
||||||
|
lws_callback_on_writable(pss->wsi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int spawn_process(struct pss_tty *pss) {
|
static char **build_args(struct pss_tty *pss) {
|
||||||
struct pty_proc *proc = pss->proc;
|
|
||||||
// append url args to arguments
|
|
||||||
char *argv[server->argc + proc->argc + 1];
|
|
||||||
int i, n = 0;
|
int i, n = 0;
|
||||||
|
char **argv = xmalloc((server->argc + pss->argc + 1) * sizeof(char *));
|
||||||
|
|
||||||
for (i = 0; i < server->argc; i++) {
|
for (i = 0; i < server->argc; i++) {
|
||||||
argv[n++] = server->argv[i];
|
argv[n++] = server->argv[i];
|
||||||
}
|
}
|
||||||
for (i = 0; i < proc->argc; i++) {
|
|
||||||
argv[n++] = proc->args[i];
|
for (i = 0; i < pss->argc; i++) {
|
||||||
|
argv[n++] = pss->args[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
argv[n] = NULL;
|
argv[n] = NULL;
|
||||||
|
|
||||||
uv_signal_start(&server->watcher, child_cb, SIGCHLD);
|
return argv;
|
||||||
|
}
|
||||||
|
|
||||||
// ensure the lws socket fd close-on-exec
|
static char **build_env(struct pss_tty *pss) {
|
||||||
fd_set_cloexec(lws_get_socket_fd(pss->wsi));
|
int i = 0, n = 2;
|
||||||
|
char **envp = xmalloc(n * sizeof(char *));
|
||||||
|
|
||||||
// create process with pseudo-tty
|
// TERM
|
||||||
proc->pid = pty_fork(&proc->pty, argv[0], argv, server->terminal_type);
|
envp[i] = xmalloc(36);
|
||||||
if (proc->pid < 0) {
|
snprintf(envp[i], 36, "TERM=%s", server->terminal_type);
|
||||||
lwsl_err("pty_fork: %d (%s)\n", errno, strerror(errno));
|
i++;
|
||||||
return 1;
|
|
||||||
|
// TTYD_USER
|
||||||
|
if (strlen(pss->user) > 0) {
|
||||||
|
envp = xrealloc(envp, (++n) * sizeof(char *));
|
||||||
|
envp[i] = xmalloc(40);
|
||||||
|
snprintf(envp[i], 40, "TTYD_USER=%s", pss->user);
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
lwsl_notice("started process, pid: %d\n", proc->pid);
|
envp[i] = NULL;
|
||||||
|
|
||||||
proc->pipe.data = pss;
|
return envp;
|
||||||
uv_pipe_open(&proc->pipe, proc->pty);
|
}
|
||||||
|
|
||||||
|
static bool spawn_process(struct pss_tty *pss, uint16_t columns, uint16_t rows) {
|
||||||
|
pty_process *process = process_init((void *)pss, server->loop, build_args(pss), build_env(pss));
|
||||||
|
if (server->cwd != NULL) process->cwd = strdup(server->cwd);
|
||||||
|
if (columns > 0) process->columns = columns;
|
||||||
|
if (rows > 0) process->rows = rows;
|
||||||
|
if (pty_spawn(process, process_read_cb, process_exit_cb) != 0) {
|
||||||
|
lwsl_err("pty_spawn: %d (%s)\n", errno, strerror(errno));
|
||||||
|
process_free(process);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lwsl_notice("started process, pid: %d\n", process->pid);
|
||||||
|
pss->process = process;
|
||||||
lws_callback_on_writable(pss->wsi);
|
lws_callback_on_writable(pss->wsi);
|
||||||
|
|
||||||
return 0;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void kill_process(struct pty_proc *proc) {
|
static void wsi_output(struct lws *wsi, pty_buf_t *buf) {
|
||||||
if (proc->pid <= 0) return;
|
if (buf == NULL) return;
|
||||||
|
char *message = xmalloc(LWS_PRE + 1 + buf->len);
|
||||||
|
char *ptr = message + LWS_PRE;
|
||||||
|
|
||||||
pid_t pid = proc->pid;
|
*ptr = OUTPUT;
|
||||||
int sig = server->sig_code;
|
memcpy(ptr + 1, buf->base, buf->len);
|
||||||
char *sig_name = server->sig_name;
|
size_t n = buf->len + 1;
|
||||||
|
|
||||||
lwsl_notice("killing process %d with signal: %d (%s)\n", pid, sig, sig_name);
|
if (lws_write(wsi, (unsigned char *)ptr, n, LWS_WRITE_BINARY) < n) {
|
||||||
int pgid = getpgid(pid);
|
lwsl_err("write OUTPUT to WS\n");
|
||||||
if (uv_kill(pgid > 0 ? -pgid : pid, sig) != 0) {
|
|
||||||
lwsl_err("kill: %d, errno: %d (%s)\n", pid, errno, strerror(errno));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void write_cb(uv_write_t *req, int status) {
|
static bool check_auth(struct lws *wsi, struct pss_tty *pss) {
|
||||||
if (status != 0) lwsl_warn("uv_write callback returned status: %d\n", status);
|
if (server->auth_header != NULL) {
|
||||||
free(req->data);
|
return lws_hdr_custom_copy(wsi, pss->user, sizeof(pss->user), server->auth_header, strlen(server->auth_header)) > 0;
|
||||||
free(req);
|
}
|
||||||
|
|
||||||
|
if (server->credential != NULL) {
|
||||||
|
char buf[256];
|
||||||
|
size_t n = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
|
||||||
|
return n >= 7 && strstr(buf, "Basic ") && !strcmp(buf + 6, server->credential);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
|
int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
|
||||||
size_t len) {
|
|
||||||
struct pss_tty *pss = (struct pss_tty *)user;
|
struct pss_tty *pss = (struct pss_tty *)user;
|
||||||
struct pty_proc *proc;
|
|
||||||
char buf[256];
|
char buf[256];
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
|
|
||||||
|
@ -239,13 +194,14 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||||
lwsl_warn("refuse to serve WS client due to the --max-clients option.\n");
|
lwsl_warn("refuse to serve WS client due to the --max-clients option.\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (!check_auth(wsi, pss)) return 1;
|
||||||
|
|
||||||
n = lws_hdr_copy(wsi, pss->path, sizeof(pss->path), WSI_TOKEN_GET_URI);
|
n = lws_hdr_copy(wsi, pss->path, sizeof(pss->path), WSI_TOKEN_GET_URI);
|
||||||
#if defined(LWS_ROLE_H2)
|
#if defined(LWS_ROLE_H2)
|
||||||
if (n <= 0) n = lws_hdr_copy(wsi, pss->path, sizeof(pss->path), WSI_TOKEN_HTTP_COLON_PATH);
|
if (n <= 0) n = lws_hdr_copy(wsi, pss->path, sizeof(pss->path), WSI_TOKEN_HTTP_COLON_PATH);
|
||||||
#endif
|
#endif
|
||||||
if (strncmp(pss->path, endpoints.ws, n) != 0) {
|
if (strncmp(pss->path, endpoints.ws, n) != 0) {
|
||||||
lwsl_warn("refuse to serve WS client for illegal ws path: %s\n", buf);
|
lwsl_warn("refuse to serve WS client for illegal ws path: %s\n", pss->path);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,46 +215,31 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||||
|
|
||||||
case LWS_CALLBACK_ESTABLISHED:
|
case LWS_CALLBACK_ESTABLISHED:
|
||||||
pss->initialized = false;
|
pss->initialized = false;
|
||||||
pss->initial_cmd_index = 0;
|
|
||||||
pss->authenticated = false;
|
pss->authenticated = false;
|
||||||
pss->wsi = wsi;
|
pss->wsi = wsi;
|
||||||
pss->buffer = NULL;
|
pss->lws_close_status = LWS_CLOSE_STATUS_NOSTATUS;
|
||||||
|
|
||||||
pss->proc = proc = xmalloc(sizeof(struct pty_proc));
|
|
||||||
memset(proc, 0, sizeof(struct pty_proc));
|
|
||||||
proc->status = -1;
|
|
||||||
proc->state = STATE_INIT;
|
|
||||||
uv_pipe_init(server->loop, &proc->pipe, 0);
|
|
||||||
|
|
||||||
if (server->url_arg) {
|
if (server->url_arg) {
|
||||||
while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_URI_ARGS, n++) > 0) {
|
while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_URI_ARGS, n++) > 0) {
|
||||||
if (strncmp(buf, "arg=", 4) == 0) {
|
if (strncmp(buf, "arg=", 4) == 0) {
|
||||||
proc->args = xrealloc(proc->args, (proc->argc + 1) * sizeof(char *));
|
pss->args = xrealloc(pss->args, (pss->argc + 1) * sizeof(char *));
|
||||||
proc->args[proc->argc] = strdup(&buf[4]);
|
pss->args[pss->argc] = strdup(&buf[4]);
|
||||||
proc->argc++;
|
pss->argc++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LIST_INSERT_HEAD(&server->procs, proc, entry);
|
|
||||||
server->client_count++;
|
server->client_count++;
|
||||||
|
|
||||||
#if LWS_LIBRARY_VERSION_NUMBER >= 2004000
|
|
||||||
lws_get_peer_simple(lws_get_network_wsi(wsi), pss->address, sizeof(pss->address));
|
lws_get_peer_simple(lws_get_network_wsi(wsi), pss->address, sizeof(pss->address));
|
||||||
#else
|
|
||||||
char name[100];
|
|
||||||
lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), pss->address,
|
|
||||||
sizeof(pss->address));
|
|
||||||
#endif
|
|
||||||
lwsl_notice("WS %s - %s, clients: %d\n", pss->path, pss->address, server->client_count);
|
lwsl_notice("WS %s - %s, clients: %d\n", pss->path, pss->address, server->client_count);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LWS_CALLBACK_SERVER_WRITEABLE:
|
case LWS_CALLBACK_SERVER_WRITEABLE:
|
||||||
proc = pss->proc;
|
|
||||||
if (!pss->initialized) {
|
if (!pss->initialized) {
|
||||||
if (pss->initial_cmd_index == sizeof(initial_cmds)) {
|
if (pss->initial_cmd_index == sizeof(initial_cmds)) {
|
||||||
pss->initialized = true;
|
pss->initialized = true;
|
||||||
uv_read_start((uv_stream_t *)&proc->pipe, alloc_cb, read_cb);
|
pty_resume(pss->process);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (send_initial_message(wsi, pss->initial_cmd_index) < 0) {
|
if (send_initial_message(wsi, pss->initial_cmd_index) < 0) {
|
||||||
|
@ -311,26 +252,17 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// read error or client exited, close connection
|
if (pss->lws_close_status > LWS_CLOSE_STATUS_NOSTATUS) {
|
||||||
if (proc->status == 0 || proc->pty_len == UV_EOF) {
|
lws_close_reason(wsi, pss->lws_close_status, NULL, 0);
|
||||||
lws_close_reason(wsi, LWS_CLOSE_STATUS_NORMAL, NULL, 0);
|
|
||||||
return 1;
|
return 1;
|
||||||
} else if (proc->status > 0 || (proc->pty_len < 0 && proc->err_count == MAX_READ_RETRY)) {
|
|
||||||
lws_close_reason(wsi, LWS_CLOSE_STATUS_UNEXPECTED_CONDITION, NULL, 0);
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proc->pty_buffer != NULL && proc->pty_len > 0) {
|
if (pss->pty_buf != NULL) {
|
||||||
proc->pty_buffer[LWS_PRE] = OUTPUT;
|
wsi_output(wsi, pss->pty_buf);
|
||||||
n = (size_t)(proc->pty_len + 1);
|
pty_buf_free(pss->pty_buf);
|
||||||
if (lws_write(wsi, (unsigned char *)proc->pty_buffer + LWS_PRE, n, LWS_WRITE_BINARY) < n) {
|
pss->pty_buf = NULL;
|
||||||
lwsl_err("write OUTPUT to WS\n");
|
pty_resume(pss->process);
|
||||||
}
|
}
|
||||||
free(proc->pty_buffer);
|
|
||||||
proc->pty_buffer = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
uv_read_start((uv_stream_t *)&proc->pipe, alloc_cb, read_cb);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LWS_CALLBACK_RECEIVE:
|
case LWS_CALLBACK_RECEIVE:
|
||||||
|
@ -357,49 +289,32 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
proc = pss->proc;
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case INPUT:
|
case INPUT:
|
||||||
if (proc->pty == 0) break;
|
|
||||||
if (server->readonly) break;
|
if (server->readonly) break;
|
||||||
|
int err = pty_write(pss->process, pty_buf_init(pss->buffer + 1, pss->len - 1));
|
||||||
char *data = xmalloc(pss->len - 1);
|
|
||||||
memcpy(data, pss->buffer + 1, pss->len - 1);
|
|
||||||
|
|
||||||
uv_buf_t b = {data, pss->len - 1};
|
|
||||||
uv_write_t *req = xmalloc(sizeof(uv_write_t));
|
|
||||||
req->data = data;
|
|
||||||
|
|
||||||
int err = uv_write(req, (uv_stream_t *)&proc->pipe, &b, 1, write_cb);
|
|
||||||
if (err) {
|
if (err) {
|
||||||
lwsl_err("uv_write: %s (%s)\n", uv_err_name(err), uv_strerror(err));
|
lwsl_err("uv_write: %s (%s)\n", uv_err_name(err), uv_strerror(err));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case RESIZE_TERMINAL: {
|
case RESIZE_TERMINAL:
|
||||||
int cols, rows;
|
json_object_put(
|
||||||
if (parse_window_size(pss, &cols, &rows)) {
|
parse_window_size(pss->buffer + 1, pss->len - 1, &pss->process->columns, &pss->process->rows));
|
||||||
if (pty_resize(proc->pty, cols, rows) < 0) {
|
pty_resize(pss->process);
|
||||||
lwsl_err("pty_resize: %d (%s)\n", errno, strerror(errno));
|
break;
|
||||||
}
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case PAUSE:
|
case PAUSE:
|
||||||
if (proc->state == STATE_INIT) {
|
pty_pause(pss->process);
|
||||||
uv_read_stop((uv_stream_t *)&proc->pipe);
|
|
||||||
proc->state = STATE_PAUSE;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case RESUME:
|
case RESUME:
|
||||||
if (proc->state == STATE_PAUSE) {
|
pty_resume(pss->process);
|
||||||
uv_read_start((uv_stream_t *)&proc->pipe, alloc_cb, read_cb);
|
|
||||||
proc->state = STATE_INIT;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case JSON_DATA:
|
case JSON_DATA:
|
||||||
if (proc->pid > 0) break;
|
if (pss->process != NULL) break;
|
||||||
|
uint16_t columns = 0;
|
||||||
|
uint16_t rows = 0;
|
||||||
|
json_object *obj = parse_window_size(pss->buffer, pss->len, &columns, &rows);
|
||||||
if (server->credential != NULL) {
|
if (server->credential != NULL) {
|
||||||
json_object *obj = json_tokener_parse(pss->buffer);
|
|
||||||
struct json_object *o = NULL;
|
struct json_object *o = NULL;
|
||||||
if (json_object_object_get_ex(obj, "AuthToken", &o)) {
|
if (json_object_object_get_ex(obj, "AuthToken", &o)) {
|
||||||
const char *token = json_object_get_string(o);
|
const char *token = json_object_get_string(o);
|
||||||
|
@ -409,11 +324,13 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||||
lwsl_warn("WS authentication failed with token: %s\n", token);
|
lwsl_warn("WS authentication failed with token: %s\n", token);
|
||||||
}
|
}
|
||||||
if (!pss->authenticated) {
|
if (!pss->authenticated) {
|
||||||
|
json_object_put(obj);
|
||||||
lws_close_reason(wsi, LWS_CLOSE_STATUS_POLICY_VIOLATION, NULL, 0);
|
lws_close_reason(wsi, LWS_CLOSE_STATUS_POLICY_VIOLATION, NULL, 0);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (spawn_process(pss) != 0) return 1;
|
json_object_put(obj);
|
||||||
|
if (!spawn_process(pss, columns, rows)) return 1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
lwsl_warn("ignored unknown message type: %c\n", command);
|
lwsl_warn("ignored unknown message type: %c\n", command);
|
||||||
|
@ -434,14 +351,14 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||||
if (pss->buffer != NULL) {
|
if (pss->buffer != NULL) {
|
||||||
free(pss->buffer);
|
free(pss->buffer);
|
||||||
}
|
}
|
||||||
|
for (int i = 0; i < pss->argc; i++) {
|
||||||
|
free(pss->args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
proc = pss->proc;
|
if (process_running(pss->process)) {
|
||||||
if (proc->state == STATE_EXIT) {
|
pty_pause(pss->process);
|
||||||
pty_proc_free(proc);
|
lwsl_notice("killing process, pid: %d\n", pss->process->pid);
|
||||||
} else {
|
pty_kill(pss->process, server->sig_code);
|
||||||
proc->state = STATE_KILL;
|
|
||||||
uv_read_stop((uv_stream_t *)&proc->pipe);
|
|
||||||
kill_process(proc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server->once && server->client_count == 0) {
|
if (server->once && server->client_count == 0) {
|
||||||
|
|
505
src/pty.c
Normal file
505
src/pty.c
Normal file
|
@ -0,0 +1,505 @@
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#if defined(__OpenBSD__) || defined(__APPLE__)
|
||||||
|
#include <util.h>
|
||||||
|
#elif defined(__FreeBSD__)
|
||||||
|
#include <libutil.h>
|
||||||
|
#else
|
||||||
|
#include <pty.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#include <crt_externs.h>
|
||||||
|
#define environ (*_NSGetEnviron())
|
||||||
|
#else
|
||||||
|
extern char **environ;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "pty.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *);
|
||||||
|
HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
|
||||||
|
void (WINAPI *pClosePseudoConsole)(HPCON);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void alloc_cb(uv_handle_t *unused, size_t suggested_size, uv_buf_t *buf) {
|
||||||
|
buf->base = xmalloc(suggested_size);
|
||||||
|
buf->len = suggested_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void close_cb(uv_handle_t *handle) { free(handle); }
|
||||||
|
|
||||||
|
pty_buf_t *pty_buf_init(char *base, size_t len) {
|
||||||
|
pty_buf_t *buf = xmalloc(sizeof(pty_buf_t));
|
||||||
|
buf->base = xmalloc(len);
|
||||||
|
memcpy(buf->base, base, len);
|
||||||
|
buf->len = len;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pty_buf_free(pty_buf_t *buf) {
|
||||||
|
free(buf->base);
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void read_cb(uv_stream_t *stream, ssize_t n, const uv_buf_t *buf) {
|
||||||
|
uv_read_stop(stream);
|
||||||
|
pty_io_t *io = (pty_io_t *) stream->data;
|
||||||
|
if (n <= 0) {
|
||||||
|
if (n == UV_ENOBUFS || n == 0) return;
|
||||||
|
if (n != UV_EOF) printf("== uv_read failed with error %ld: %s\n", n, uv_strerror(n));
|
||||||
|
io->read_cb(io->ctx, NULL, true);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
io->read_cb(io->ctx, pty_buf_init(buf->base, (size_t) n), false);
|
||||||
|
|
||||||
|
done:
|
||||||
|
free(buf->base);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_cb(uv_write_t *req, int unused) {
|
||||||
|
pty_buf_t *buf = (pty_buf_t *) req->data;
|
||||||
|
pty_buf_free(buf);
|
||||||
|
free(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
static pty_io_t *pty_io_init(pty_process *process, pty_read_cb read_cb) {
|
||||||
|
pty_io_t *io = xmalloc(sizeof(pty_io_t));
|
||||||
|
io->in = xmalloc(sizeof(uv_pipe_t));
|
||||||
|
io->out = xmalloc(sizeof(uv_pipe_t));
|
||||||
|
uv_pipe_init(process->loop, io->in, 0);
|
||||||
|
uv_pipe_init(process->loop, io->out, 0);
|
||||||
|
io->paused = true;
|
||||||
|
io->read_cb = read_cb;
|
||||||
|
io->ctx = process->ctx;
|
||||||
|
return io;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pty_io_free(pty_io_t *io) {
|
||||||
|
uv_close((uv_handle_t *) io->in, close_cb);
|
||||||
|
uv_close((uv_handle_t *) io->out, close_cb);
|
||||||
|
free(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
pty_process *process_init(void *ctx, uv_loop_t *loop, char *argv[], char *envp[]) {
|
||||||
|
pty_process *process = xmalloc(sizeof(pty_process));
|
||||||
|
memset(process, 0, sizeof(pty_process));
|
||||||
|
process->ctx = ctx;
|
||||||
|
process->loop = loop;
|
||||||
|
process->argv = argv;
|
||||||
|
process->envp = envp;
|
||||||
|
process->columns = 80;
|
||||||
|
process->rows = 24;
|
||||||
|
process->exit_code = -1;
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool process_running(pty_process *process) {
|
||||||
|
return process != NULL && process->pid > 0 && uv_kill(process->pid, 0) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_free(pty_process *process) {
|
||||||
|
if (process == NULL) return;
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (process->si.lpAttributeList != NULL) {
|
||||||
|
DeleteProcThreadAttributeList(process->si.lpAttributeList);
|
||||||
|
free(process->si.lpAttributeList);
|
||||||
|
}
|
||||||
|
if (process->pty != NULL) pClosePseudoConsole(process->pty);
|
||||||
|
if (process->handle != NULL) CloseHandle(process->handle);
|
||||||
|
#else
|
||||||
|
uv_thread_join(&process->tid);
|
||||||
|
#endif
|
||||||
|
if (process->io != NULL) pty_io_free(process->io);
|
||||||
|
if (process->argv != NULL) free(process->argv);
|
||||||
|
if (process->cwd != NULL) free(process->cwd);
|
||||||
|
char **p = process->envp;
|
||||||
|
for (; *p; p++) free(*p);
|
||||||
|
free(process->envp);
|
||||||
|
free(process);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pty_pause(pty_process *process) {
|
||||||
|
if (process == NULL) return;
|
||||||
|
pty_io_t *io = process->io;
|
||||||
|
if (io->paused) return;
|
||||||
|
uv_read_stop((uv_stream_t *) io->out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pty_resume(pty_process *process) {
|
||||||
|
if (process == NULL) return;
|
||||||
|
pty_io_t *io = process->io;
|
||||||
|
if (!io->paused) return;
|
||||||
|
io->out->data = io;
|
||||||
|
uv_read_start((uv_stream_t *) io->out, alloc_cb, read_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
int pty_write(pty_process *process, pty_buf_t *buf) {
|
||||||
|
if (process == NULL) {
|
||||||
|
pty_buf_free(buf);
|
||||||
|
return UV_ESRCH;
|
||||||
|
}
|
||||||
|
pty_io_t *io = process->io;
|
||||||
|
uv_buf_t b = uv_buf_init(buf->base, buf->len);
|
||||||
|
uv_write_t *req = xmalloc(sizeof(uv_write_t));
|
||||||
|
req->data = buf;
|
||||||
|
return uv_write(req, (uv_stream_t *) io->in, &b, 1, write_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pty_resize(pty_process *process) {
|
||||||
|
if (process == NULL) return false;
|
||||||
|
if (process->columns <= 0 || process->rows <= 0) return false;
|
||||||
|
#ifdef _WIN32
|
||||||
|
COORD size = {(int16_t) process->columns, (int16_t) process->rows};
|
||||||
|
return pResizePseudoConsole(process->pty, size) == S_OK;
|
||||||
|
#else
|
||||||
|
struct winsize size = {process->rows, process->columns, 0, 0};
|
||||||
|
return ioctl(process->pty, TIOCSWINSZ, &size) == 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pty_kill(pty_process *process, int sig) {
|
||||||
|
if (process == NULL) return false;
|
||||||
|
process->killed = true;
|
||||||
|
#ifdef _WIN32
|
||||||
|
return TerminateProcess(process->handle, 1) != 0;
|
||||||
|
#else
|
||||||
|
return uv_kill(-process->pid, sig) == 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
bool conpty_init() {
|
||||||
|
uv_lib_t kernel;
|
||||||
|
if (uv_dlopen("kernel32.dll", &kernel)) {
|
||||||
|
uv_dlclose(&kernel);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static struct {
|
||||||
|
char *name;
|
||||||
|
FARPROC *ptr;
|
||||||
|
} conpty_entry[] = {{"CreatePseudoConsole", (FARPROC *) &pCreatePseudoConsole},
|
||||||
|
{"ResizePseudoConsole", (FARPROC *) &pResizePseudoConsole},
|
||||||
|
{"ClosePseudoConsole", (FARPROC *) &pClosePseudoConsole},
|
||||||
|
{NULL, NULL}};
|
||||||
|
for (int i = 0; conpty_entry[i].name != NULL && conpty_entry[i].ptr != NULL; i++) {
|
||||||
|
if (uv_dlsym(&kernel, conpty_entry[i].name, (void **) conpty_entry[i].ptr)) {
|
||||||
|
uv_dlclose(&kernel);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert argv to cmdline for CreateProcessW
|
||||||
|
static WCHAR *join_args(char **argv) {
|
||||||
|
char *args = NULL;
|
||||||
|
char **ptr = argv;
|
||||||
|
for (; *ptr; ptr++) {
|
||||||
|
char *quoted = (char *) quote_arg(*ptr);
|
||||||
|
size_t arg_len = args == NULL ? 1 : strlen(args) + 1;
|
||||||
|
size_t quoted_len = strlen(quoted);
|
||||||
|
args = xrealloc(args, arg_len + quoted_len);
|
||||||
|
if (arg_len == 1) memset(args, 0, 2);
|
||||||
|
if (arg_len != 1) strcat(args, " ");
|
||||||
|
strncat(args, quoted, quoted_len);
|
||||||
|
if (quoted != *ptr) free(quoted);
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = MultiByteToWideChar(CP_UTF8, 0, args, -1, NULL, 0);
|
||||||
|
if (len <= 0) goto failed;
|
||||||
|
WCHAR *ws = (WCHAR *) xmalloc(len * sizeof(WCHAR));
|
||||||
|
if (len != MultiByteToWideChar(CP_UTF8, 0, args, -1, ws, len)) {
|
||||||
|
free(ws);
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
return ws;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
if (args != NULL) free(args);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static WCHAR *to_utf16(char *str) {
|
||||||
|
int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
|
||||||
|
if (len <= 0) return NULL;
|
||||||
|
WCHAR *wstr = xmalloc((len + 1) * sizeof(WCHAR));
|
||||||
|
if (len != MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len)) {
|
||||||
|
free(wstr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
wstr[len] = L'\0';
|
||||||
|
return wstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool conpty_setup(HPCON *hnd, COORD size, STARTUPINFOEXW *si_ex, char **in_name, char **out_name) {
|
||||||
|
static int count = 0;
|
||||||
|
char buf[256];
|
||||||
|
HPCON pty = INVALID_HANDLE_VALUE;
|
||||||
|
SECURITY_ATTRIBUTES sa = {0};
|
||||||
|
HANDLE in_pipe = INVALID_HANDLE_VALUE;
|
||||||
|
HANDLE out_pipe = INVALID_HANDLE_VALUE;
|
||||||
|
const DWORD open_mode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE;
|
||||||
|
const DWORD pipe_mode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT;
|
||||||
|
DWORD pid = GetCurrentProcessId();
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
sa.nLength = sizeof(sa);
|
||||||
|
snprintf(buf, sizeof(buf), "\\\\.\\pipe\\ttyd-term-in-%d-%d", pid, count);
|
||||||
|
*in_name = strdup(buf);
|
||||||
|
snprintf(buf, sizeof(buf), "\\\\.\\pipe\\ttyd-term-out-%d-%d", pid, count);
|
||||||
|
*out_name = strdup(buf);
|
||||||
|
in_pipe = CreateNamedPipeA(*in_name, open_mode, pipe_mode, 1, 0, 0, 30000, &sa);
|
||||||
|
out_pipe = CreateNamedPipeA(*out_name, open_mode, pipe_mode, 1, 0, 0, 30000, &sa);
|
||||||
|
if (in_pipe == INVALID_HANDLE_VALUE || out_pipe == INVALID_HANDLE_VALUE) {
|
||||||
|
print_error("CreateNamedPipeA");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT hr = pCreatePseudoConsole(size, in_pipe, out_pipe, 0, &pty);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
print_error("CreatePseudoConsole");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
si_ex->StartupInfo.cb = sizeof(STARTUPINFOEXW);
|
||||||
|
si_ex->StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
|
||||||
|
si_ex->StartupInfo.hStdError = NULL;
|
||||||
|
si_ex->StartupInfo.hStdInput = NULL;
|
||||||
|
si_ex->StartupInfo.hStdOutput = NULL;
|
||||||
|
size_t bytes_required;
|
||||||
|
InitializeProcThreadAttributeList(NULL, 1, 0, &bytes_required);
|
||||||
|
si_ex->lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST) xmalloc(bytes_required);
|
||||||
|
if (!InitializeProcThreadAttributeList(si_ex->lpAttributeList, 1, 0, &bytes_required)) {
|
||||||
|
print_error("InitializeProcThreadAttributeList");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
if (!UpdateProcThreadAttribute(si_ex->lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, pty, sizeof(HPCON),
|
||||||
|
NULL, NULL)) {
|
||||||
|
print_error("UpdateProcThreadAttribute");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
*hnd = pty;
|
||||||
|
ret = true;
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
ret = false;
|
||||||
|
free(*in_name);
|
||||||
|
*in_name = NULL;
|
||||||
|
free(*out_name);
|
||||||
|
*out_name = NULL;
|
||||||
|
done:
|
||||||
|
if (in_pipe != INVALID_HANDLE_VALUE) CloseHandle(in_pipe);
|
||||||
|
if (out_pipe != INVALID_HANDLE_VALUE) CloseHandle(out_pipe);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void connect_cb(uv_connect_t *req, int status) { free(req); }
|
||||||
|
|
||||||
|
static void CALLBACK conpty_exit(void *context, BOOLEAN unused) {
|
||||||
|
pty_process *process = (pty_process *) context;
|
||||||
|
uv_async_send(&process->async);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void async_cb(uv_async_t *async) {
|
||||||
|
pty_process *process = (pty_process *) async->data;
|
||||||
|
UnregisterWait(process->wait);
|
||||||
|
|
||||||
|
DWORD exit_code;
|
||||||
|
GetExitCodeProcess(process->handle, &exit_code);
|
||||||
|
process->exit_code = (int) exit_code;
|
||||||
|
process->exit_signal = 1;
|
||||||
|
process->exit_cb(process->ctx, process);
|
||||||
|
|
||||||
|
uv_close((uv_handle_t *) async, NULL);
|
||||||
|
process_free(process);
|
||||||
|
}
|
||||||
|
|
||||||
|
int pty_spawn(pty_process *process, pty_read_cb read_cb, pty_exit_cb exit_cb) {
|
||||||
|
char *in_name = NULL;
|
||||||
|
char *out_name = NULL;
|
||||||
|
DWORD flags = EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT;
|
||||||
|
COORD size = {(int16_t) process->columns, (int16_t) process->rows};
|
||||||
|
|
||||||
|
if (!conpty_setup(&process->pty, size, &process->si, &in_name, &out_name)) return 1;
|
||||||
|
|
||||||
|
SetConsoleCtrlHandler(NULL, FALSE);
|
||||||
|
|
||||||
|
int status = 1;
|
||||||
|
pty_io_t *io = pty_io_init(process, read_cb);
|
||||||
|
|
||||||
|
uv_connect_t *in_req = xmalloc(sizeof(uv_connect_t));
|
||||||
|
uv_connect_t *out_req = xmalloc(sizeof(uv_connect_t));
|
||||||
|
uv_pipe_connect(in_req, io->in, in_name, connect_cb);
|
||||||
|
uv_pipe_connect(out_req, io->out, out_name, connect_cb);
|
||||||
|
|
||||||
|
PROCESS_INFORMATION pi = {0};
|
||||||
|
WCHAR *cmdline, *cwd;
|
||||||
|
cmdline = join_args(process->argv);
|
||||||
|
if (cmdline == NULL) goto cleanup;
|
||||||
|
if (process->envp != NULL) {
|
||||||
|
char **p = process->envp;
|
||||||
|
for (; *p; p++) {
|
||||||
|
WCHAR *env = to_utf16(*p);
|
||||||
|
if (env == NULL) goto cleanup;
|
||||||
|
_wputenv(env);
|
||||||
|
free(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (process->cwd != NULL) {
|
||||||
|
cwd = to_utf16(process->cwd);
|
||||||
|
if (cwd == NULL) goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CreateProcessW(NULL, cmdline, NULL, NULL, FALSE, flags, NULL, cwd, &process->si.StartupInfo, &pi)) {
|
||||||
|
print_error("CreateProcessW");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
process->pid = pi.dwProcessId;
|
||||||
|
process->handle = pi.hProcess;
|
||||||
|
process->io = io;
|
||||||
|
process->exit_cb = exit_cb;
|
||||||
|
process->async.data = process;
|
||||||
|
uv_async_init(process->loop, &process->async, async_cb);
|
||||||
|
|
||||||
|
if (!RegisterWaitForSingleObject(&process->wait, pi.hProcess, conpty_exit, process, INFINITE, WT_EXECUTEONLYONCE)) {
|
||||||
|
print_error("RegisterWaitForSingleObject");
|
||||||
|
pty_io_free(io);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = 0;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (in_name != NULL) free(in_name);
|
||||||
|
if (out_name != NULL) free(out_name);
|
||||||
|
if (cmdline != NULL) free(cmdline);
|
||||||
|
if (cwd != NULL) free(cwd);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static bool fd_set_cloexec(const int fd) {
|
||||||
|
int flags = fcntl(fd, F_GETFD);
|
||||||
|
if (flags < 0) return false;
|
||||||
|
return (flags & FD_CLOEXEC) == 0 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fd_duplicate(int fd, uv_pipe_t *pipe) {
|
||||||
|
int fd_dup = dup(fd);
|
||||||
|
if (fd_dup < 0) return false;
|
||||||
|
|
||||||
|
if (!fd_set_cloexec(fd_dup)) return false;
|
||||||
|
|
||||||
|
int status = uv_pipe_open(pipe, fd_dup);
|
||||||
|
if (status) close(fd_dup);
|
||||||
|
return status == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wait_cb(void *arg) {
|
||||||
|
pty_process *process = (pty_process *) arg;
|
||||||
|
|
||||||
|
pid_t pid;
|
||||||
|
int stat;
|
||||||
|
do
|
||||||
|
pid = waitpid(process->pid, &stat, 0);
|
||||||
|
while (pid != process->pid && errno == EINTR);
|
||||||
|
|
||||||
|
if (WIFEXITED(stat)) {
|
||||||
|
process->exit_code = WEXITSTATUS(stat);
|
||||||
|
}
|
||||||
|
if (WIFSIGNALED(stat)) {
|
||||||
|
int sig = WTERMSIG(stat);
|
||||||
|
process->exit_code = 128 + sig;
|
||||||
|
process->exit_signal = sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
uv_async_send(&process->async);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void async_cb(uv_async_t *async) {
|
||||||
|
pty_process *process = (pty_process *) async->data;
|
||||||
|
process->exit_cb(process->ctx, process);
|
||||||
|
|
||||||
|
uv_close((uv_handle_t *) async, NULL);
|
||||||
|
process_free(process);
|
||||||
|
}
|
||||||
|
|
||||||
|
int pty_spawn(pty_process *process, pty_read_cb read_cb, pty_exit_cb exit_cb) {
|
||||||
|
int status = 0;
|
||||||
|
|
||||||
|
uv_disable_stdio_inheritance();
|
||||||
|
|
||||||
|
int master, pid;
|
||||||
|
struct winsize size = {process->rows, process->columns, 0, 0};
|
||||||
|
pid = forkpty(&master, NULL, NULL, &size);
|
||||||
|
if (pid < 0) {
|
||||||
|
status = -errno;
|
||||||
|
return status;
|
||||||
|
} else if (pid == 0) {
|
||||||
|
setsid();
|
||||||
|
if (process->cwd != NULL) chdir(process->cwd);
|
||||||
|
if (process->envp != NULL) {
|
||||||
|
char **p = process->envp;
|
||||||
|
for (; *p; p++) putenv(*p);
|
||||||
|
}
|
||||||
|
int ret = execvp(process->argv[0], process->argv);
|
||||||
|
if (ret < 0) {
|
||||||
|
perror("execvp failed\n");
|
||||||
|
_exit(-errno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int flags = fcntl(master, F_GETFL);
|
||||||
|
if (flags == -1) {
|
||||||
|
status = -errno;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (fcntl(master, F_SETFD, flags | O_NONBLOCK) == -1) {
|
||||||
|
status = -errno;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (!fd_set_cloexec(master)) {
|
||||||
|
status = -errno;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
pty_io_t *io = pty_io_init(process, read_cb);
|
||||||
|
if (!fd_duplicate(master, io->in) || !fd_duplicate(master, io->out)) {
|
||||||
|
status = -errno;
|
||||||
|
pty_io_free(io);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
process->pty = master;
|
||||||
|
process->pid = pid;
|
||||||
|
process->io = io;
|
||||||
|
process->exit_cb = exit_cb;
|
||||||
|
process->async.data = process;
|
||||||
|
uv_async_init(process->loop, &process->async, async_cb);
|
||||||
|
uv_thread_create(&process->tid, wait_cb, process);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
close(master);
|
||||||
|
uv_kill(pid, SIGKILL);
|
||||||
|
waitpid(pid, NULL, 0);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
#endif
|
75
src/pty.h
Normal file
75
src/pty.h
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
#ifndef TTYD_PTY_H
|
||||||
|
#define TTYD_PTY_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <uv.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#ifndef HPCON
|
||||||
|
#define HPCON VOID *
|
||||||
|
#endif
|
||||||
|
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
|
||||||
|
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool conpty_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *base;
|
||||||
|
size_t len;
|
||||||
|
} pty_buf_t;
|
||||||
|
|
||||||
|
typedef void (*pty_read_cb)(void *, pty_buf_t *, bool);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uv_pipe_t *in;
|
||||||
|
uv_pipe_t *out;
|
||||||
|
bool paused;
|
||||||
|
|
||||||
|
pty_read_cb read_cb;
|
||||||
|
void *ctx;
|
||||||
|
} pty_io_t;
|
||||||
|
|
||||||
|
struct pty_process_;
|
||||||
|
typedef struct pty_process_ pty_process;
|
||||||
|
typedef void (*pty_exit_cb)(void *, pty_process *);
|
||||||
|
|
||||||
|
struct pty_process_ {
|
||||||
|
int pid, exit_code, exit_signal;
|
||||||
|
uint16_t columns, rows;
|
||||||
|
bool killed;
|
||||||
|
#ifdef _WIN32
|
||||||
|
STARTUPINFOEXW si;
|
||||||
|
HPCON pty;
|
||||||
|
HANDLE handle;
|
||||||
|
HANDLE wait;
|
||||||
|
#else
|
||||||
|
pid_t pty;
|
||||||
|
uv_thread_t tid;
|
||||||
|
#endif
|
||||||
|
char **argv;
|
||||||
|
char **envp;
|
||||||
|
char *cwd;
|
||||||
|
|
||||||
|
uv_loop_t *loop;
|
||||||
|
uv_async_t async;
|
||||||
|
pty_io_t *io;
|
||||||
|
pty_exit_cb exit_cb;
|
||||||
|
void *ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
pty_buf_t *pty_buf_init(char *base, size_t len);
|
||||||
|
void pty_buf_free(pty_buf_t *buf);
|
||||||
|
pty_process *process_init(void *ctx, uv_loop_t *loop, char *argv[], char *envp[]);
|
||||||
|
bool process_running(pty_process *process);
|
||||||
|
void process_free(pty_process *process);
|
||||||
|
int pty_spawn(pty_process *process, pty_read_cb read_cb, pty_exit_cb exit_cb);
|
||||||
|
void pty_pause(pty_process *process);
|
||||||
|
void pty_resume(pty_process *process);
|
||||||
|
int pty_write(pty_process *process, pty_buf_t *buf);
|
||||||
|
bool pty_resize(pty_process *process);
|
||||||
|
bool pty_kill(pty_process *process, int sig);
|
||||||
|
|
||||||
|
#endif // TTYD_PTY_H
|
574
src/queue.h
574
src/queue.h
|
@ -1,574 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 1991, 1993
|
|
||||||
* The Regents of the University of California. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions
|
|
||||||
* are met:
|
|
||||||
* 1. Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of the University nor the names of its contributors
|
|
||||||
* may be used to endorse or promote products derived from this software
|
|
||||||
* without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
||||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
||||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
||||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
||||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
||||||
* SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* @(#)queue.h 8.5 (Berkeley) 8/20/94
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _SYS_QUEUE_H_
|
|
||||||
#define _SYS_QUEUE_H_
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file defines five types of data structures: singly-linked lists,
|
|
||||||
* lists, simple queues, tail queues, and circular queues.
|
|
||||||
*
|
|
||||||
* A singly-linked list is headed by a single forward pointer. The
|
|
||||||
* elements are singly linked for minimum space and pointer manipulation
|
|
||||||
* overhead at the expense of O(n) removal for arbitrary elements. New
|
|
||||||
* elements can be added to the list after an existing element or at the
|
|
||||||
* head of the list. Elements being removed from the head of the list
|
|
||||||
* should use the explicit macro for this purpose for optimum
|
|
||||||
* efficiency. A singly-linked list may only be traversed in the forward
|
|
||||||
* direction. Singly-linked lists are ideal for applications with large
|
|
||||||
* datasets and few or no removals or for implementing a LIFO queue.
|
|
||||||
*
|
|
||||||
* A list is headed by a single forward pointer (or an array of forward
|
|
||||||
* pointers for a hash table header). The elements are doubly linked
|
|
||||||
* so that an arbitrary element can be removed without a need to
|
|
||||||
* traverse the list. New elements can be added to the list before
|
|
||||||
* or after an existing element or at the head of the list. A list
|
|
||||||
* may only be traversed in the forward direction.
|
|
||||||
*
|
|
||||||
* A simple queue is headed by a pair of pointers, one the head of the
|
|
||||||
* list and the other to the tail of the list. The elements are singly
|
|
||||||
* linked to save space, so elements can only be removed from the
|
|
||||||
* head of the list. New elements can be added to the list after
|
|
||||||
* an existing element, at the head of the list, or at the end of the
|
|
||||||
* list. A simple queue may only be traversed in the forward direction.
|
|
||||||
*
|
|
||||||
* A tail queue is headed by a pair of pointers, one to the head of the
|
|
||||||
* list and the other to the tail of the list. The elements are doubly
|
|
||||||
* linked so that an arbitrary element can be removed without a need to
|
|
||||||
* traverse the list. New elements can be added to the list before or
|
|
||||||
* after an existing element, at the head of the list, or at the end of
|
|
||||||
* the list. A tail queue may be traversed in either direction.
|
|
||||||
*
|
|
||||||
* A circle queue is headed by a pair of pointers, one to the head of the
|
|
||||||
* list and the other to the tail of the list. The elements are doubly
|
|
||||||
* linked so that an arbitrary element can be removed without a need to
|
|
||||||
* traverse the list. New elements can be added to the list before or after
|
|
||||||
* an existing element, at the head of the list, or at the end of the list.
|
|
||||||
* A circle queue may be traversed in either direction, but has a more
|
|
||||||
* complex end of list detection.
|
|
||||||
*
|
|
||||||
* For details on the use of these macros, see the queue(3) manual page.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* List definitions.
|
|
||||||
*/
|
|
||||||
#define LIST_HEAD(name, type) \
|
|
||||||
struct name { \
|
|
||||||
struct type *lh_first; /* first element */ \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define LIST_HEAD_INITIALIZER(head) \
|
|
||||||
{ NULL }
|
|
||||||
|
|
||||||
#define LIST_ENTRY(type) \
|
|
||||||
struct { \
|
|
||||||
struct type *le_next; /* next element */ \
|
|
||||||
struct type **le_prev; /* address of previous next element */ \
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* List functions.
|
|
||||||
*/
|
|
||||||
#define LIST_INIT(head) do { \
|
|
||||||
(head)->lh_first = NULL; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define LIST_INSERT_AFTER(listelm, elm, field) do { \
|
|
||||||
if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
|
|
||||||
(listelm)->field.le_next->field.le_prev = \
|
|
||||||
&(elm)->field.le_next; \
|
|
||||||
(listelm)->field.le_next = (elm); \
|
|
||||||
(elm)->field.le_prev = &(listelm)->field.le_next; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
|
|
||||||
(elm)->field.le_prev = (listelm)->field.le_prev; \
|
|
||||||
(elm)->field.le_next = (listelm); \
|
|
||||||
*(listelm)->field.le_prev = (elm); \
|
|
||||||
(listelm)->field.le_prev = &(elm)->field.le_next; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define LIST_INSERT_HEAD(head, elm, field) do { \
|
|
||||||
if (((elm)->field.le_next = (head)->lh_first) != NULL) \
|
|
||||||
(head)->lh_first->field.le_prev = &(elm)->field.le_next;\
|
|
||||||
(head)->lh_first = (elm); \
|
|
||||||
(elm)->field.le_prev = &(head)->lh_first; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define LIST_REMOVE(elm, field) do { \
|
|
||||||
if ((elm)->field.le_next != NULL) \
|
|
||||||
(elm)->field.le_next->field.le_prev = \
|
|
||||||
(elm)->field.le_prev; \
|
|
||||||
*(elm)->field.le_prev = (elm)->field.le_next; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define LIST_FOREACH(var, head, field) \
|
|
||||||
for ((var) = ((head)->lh_first); \
|
|
||||||
(var); \
|
|
||||||
(var) = ((var)->field.le_next))
|
|
||||||
|
|
||||||
/*
|
|
||||||
* List access methods.
|
|
||||||
*/
|
|
||||||
#define LIST_EMPTY(head) ((head)->lh_first == NULL)
|
|
||||||
#define LIST_FIRST(head) ((head)->lh_first)
|
|
||||||
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Singly-linked List definitions.
|
|
||||||
*/
|
|
||||||
#define SLIST_HEAD(name, type) \
|
|
||||||
struct name { \
|
|
||||||
struct type *slh_first; /* first element */ \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SLIST_HEAD_INITIALIZER(head) \
|
|
||||||
{ NULL }
|
|
||||||
|
|
||||||
#define SLIST_ENTRY(type) \
|
|
||||||
struct { \
|
|
||||||
struct type *sle_next; /* next element */ \
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Singly-linked List functions.
|
|
||||||
*/
|
|
||||||
#define SLIST_INIT(head) do { \
|
|
||||||
(head)->slh_first = NULL; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
|
|
||||||
(elm)->field.sle_next = (slistelm)->field.sle_next; \
|
|
||||||
(slistelm)->field.sle_next = (elm); \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define SLIST_INSERT_HEAD(head, elm, field) do { \
|
|
||||||
(elm)->field.sle_next = (head)->slh_first; \
|
|
||||||
(head)->slh_first = (elm); \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define SLIST_REMOVE_HEAD(head, field) do { \
|
|
||||||
(head)->slh_first = (head)->slh_first->field.sle_next; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define SLIST_REMOVE(head, elm, type, field) do { \
|
|
||||||
if ((head)->slh_first == (elm)) { \
|
|
||||||
SLIST_REMOVE_HEAD((head), field); \
|
|
||||||
} \
|
|
||||||
else { \
|
|
||||||
struct type *curelm = (head)->slh_first; \
|
|
||||||
while(curelm->field.sle_next != (elm)) \
|
|
||||||
curelm = curelm->field.sle_next; \
|
|
||||||
curelm->field.sle_next = \
|
|
||||||
curelm->field.sle_next->field.sle_next; \
|
|
||||||
} \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define SLIST_FOREACH(var, head, field) \
|
|
||||||
for((var) = (head)->slh_first; (var); (var) = (var)->field.sle_next)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Singly-linked List access methods.
|
|
||||||
*/
|
|
||||||
#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
|
|
||||||
#define SLIST_FIRST(head) ((head)->slh_first)
|
|
||||||
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Singly-linked Tail queue declarations.
|
|
||||||
*/
|
|
||||||
#define STAILQ_HEAD(name, type) \
|
|
||||||
struct name { \
|
|
||||||
struct type *stqh_first; /* first element */ \
|
|
||||||
struct type **stqh_last; /* addr of last next element */ \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define STAILQ_HEAD_INITIALIZER(head) \
|
|
||||||
{ NULL, &(head).stqh_first }
|
|
||||||
|
|
||||||
#define STAILQ_ENTRY(type) \
|
|
||||||
struct { \
|
|
||||||
struct type *stqe_next; /* next element */ \
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Singly-linked Tail queue functions.
|
|
||||||
*/
|
|
||||||
#define STAILQ_INIT(head) do { \
|
|
||||||
(head)->stqh_first = NULL; \
|
|
||||||
(head)->stqh_last = &(head)->stqh_first; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define STAILQ_INSERT_HEAD(head, elm, field) do { \
|
|
||||||
if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \
|
|
||||||
(head)->stqh_last = &(elm)->field.stqe_next; \
|
|
||||||
(head)->stqh_first = (elm); \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
|
|
||||||
(elm)->field.stqe_next = NULL; \
|
|
||||||
*(head)->stqh_last = (elm); \
|
|
||||||
(head)->stqh_last = &(elm)->field.stqe_next; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
|
|
||||||
if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\
|
|
||||||
(head)->stqh_last = &(elm)->field.stqe_next; \
|
|
||||||
(listelm)->field.stqe_next = (elm); \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define STAILQ_REMOVE_HEAD(head, field) do { \
|
|
||||||
if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \
|
|
||||||
(head)->stqh_last = &(head)->stqh_first; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define STAILQ_REMOVE(head, elm, type, field) do { \
|
|
||||||
if ((head)->stqh_first == (elm)) { \
|
|
||||||
STAILQ_REMOVE_HEAD((head), field); \
|
|
||||||
} else { \
|
|
||||||
struct type *curelm = (head)->stqh_first; \
|
|
||||||
while (curelm->field.stqe_next != (elm)) \
|
|
||||||
curelm = curelm->field.stqe_next; \
|
|
||||||
if ((curelm->field.stqe_next = \
|
|
||||||
curelm->field.stqe_next->field.stqe_next) == NULL) \
|
|
||||||
(head)->stqh_last = &(curelm)->field.stqe_next; \
|
|
||||||
} \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define STAILQ_FOREACH(var, head, field) \
|
|
||||||
for ((var) = ((head)->stqh_first); \
|
|
||||||
(var); \
|
|
||||||
(var) = ((var)->field.stqe_next))
|
|
||||||
|
|
||||||
#define STAILQ_CONCAT(head1, head2) do { \
|
|
||||||
if (!STAILQ_EMPTY((head2))) { \
|
|
||||||
*(head1)->stqh_last = (head2)->stqh_first; \
|
|
||||||
(head1)->stqh_last = (head2)->stqh_last; \
|
|
||||||
STAILQ_INIT((head2)); \
|
|
||||||
} \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Singly-linked Tail queue access methods.
|
|
||||||
*/
|
|
||||||
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
|
|
||||||
#define STAILQ_FIRST(head) ((head)->stqh_first)
|
|
||||||
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Simple queue definitions.
|
|
||||||
*/
|
|
||||||
#define SIMPLEQ_HEAD(name, type) \
|
|
||||||
struct name { \
|
|
||||||
struct type *sqh_first; /* first element */ \
|
|
||||||
struct type **sqh_last; /* addr of last next element */ \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SIMPLEQ_HEAD_INITIALIZER(head) \
|
|
||||||
{ NULL, &(head).sqh_first }
|
|
||||||
|
|
||||||
#define SIMPLEQ_ENTRY(type) \
|
|
||||||
struct { \
|
|
||||||
struct type *sqe_next; /* next element */ \
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Simple queue functions.
|
|
||||||
*/
|
|
||||||
#define SIMPLEQ_INIT(head) do { \
|
|
||||||
(head)->sqh_first = NULL; \
|
|
||||||
(head)->sqh_last = &(head)->sqh_first; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \
|
|
||||||
if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
|
|
||||||
(head)->sqh_last = &(elm)->field.sqe_next; \
|
|
||||||
(head)->sqh_first = (elm); \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \
|
|
||||||
(elm)->field.sqe_next = NULL; \
|
|
||||||
*(head)->sqh_last = (elm); \
|
|
||||||
(head)->sqh_last = &(elm)->field.sqe_next; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
|
|
||||||
if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
|
|
||||||
(head)->sqh_last = &(elm)->field.sqe_next; \
|
|
||||||
(listelm)->field.sqe_next = (elm); \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define SIMPLEQ_REMOVE_HEAD(head, field) do { \
|
|
||||||
if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
|
|
||||||
(head)->sqh_last = &(head)->sqh_first; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define SIMPLEQ_REMOVE(head, elm, type, field) do { \
|
|
||||||
if ((head)->sqh_first == (elm)) { \
|
|
||||||
SIMPLEQ_REMOVE_HEAD((head), field); \
|
|
||||||
} else { \
|
|
||||||
struct type *curelm = (head)->sqh_first; \
|
|
||||||
while (curelm->field.sqe_next != (elm)) \
|
|
||||||
curelm = curelm->field.sqe_next; \
|
|
||||||
if ((curelm->field.sqe_next = \
|
|
||||||
curelm->field.sqe_next->field.sqe_next) == NULL) \
|
|
||||||
(head)->sqh_last = &(curelm)->field.sqe_next; \
|
|
||||||
} \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define SIMPLEQ_FOREACH(var, head, field) \
|
|
||||||
for ((var) = ((head)->sqh_first); \
|
|
||||||
(var); \
|
|
||||||
(var) = ((var)->field.sqe_next))
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Simple queue access methods.
|
|
||||||
*/
|
|
||||||
#define SIMPLEQ_EMPTY(head) ((head)->sqh_first == NULL)
|
|
||||||
#define SIMPLEQ_FIRST(head) ((head)->sqh_first)
|
|
||||||
#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next)
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tail queue definitions.
|
|
||||||
*/
|
|
||||||
#define _TAILQ_HEAD(name, type, qual) \
|
|
||||||
struct name { \
|
|
||||||
qual type *tqh_first; /* first element */ \
|
|
||||||
qual type *qual *tqh_last; /* addr of last next element */ \
|
|
||||||
}
|
|
||||||
#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,)
|
|
||||||
|
|
||||||
#define TAILQ_HEAD_INITIALIZER(head) \
|
|
||||||
{ NULL, &(head).tqh_first }
|
|
||||||
|
|
||||||
#define _TAILQ_ENTRY(type, qual) \
|
|
||||||
struct { \
|
|
||||||
qual type *tqe_next; /* next element */ \
|
|
||||||
qual type *qual *tqe_prev; /* address of previous next element */\
|
|
||||||
}
|
|
||||||
#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tail queue functions.
|
|
||||||
*/
|
|
||||||
#define TAILQ_INIT(head) do { \
|
|
||||||
(head)->tqh_first = NULL; \
|
|
||||||
(head)->tqh_last = &(head)->tqh_first; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
|
|
||||||
if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
|
|
||||||
(head)->tqh_first->field.tqe_prev = \
|
|
||||||
&(elm)->field.tqe_next; \
|
|
||||||
else \
|
|
||||||
(head)->tqh_last = &(elm)->field.tqe_next; \
|
|
||||||
(head)->tqh_first = (elm); \
|
|
||||||
(elm)->field.tqe_prev = &(head)->tqh_first; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
|
|
||||||
(elm)->field.tqe_next = NULL; \
|
|
||||||
(elm)->field.tqe_prev = (head)->tqh_last; \
|
|
||||||
*(head)->tqh_last = (elm); \
|
|
||||||
(head)->tqh_last = &(elm)->field.tqe_next; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
|
|
||||||
if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
|
|
||||||
(elm)->field.tqe_next->field.tqe_prev = \
|
|
||||||
&(elm)->field.tqe_next; \
|
|
||||||
else \
|
|
||||||
(head)->tqh_last = &(elm)->field.tqe_next; \
|
|
||||||
(listelm)->field.tqe_next = (elm); \
|
|
||||||
(elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
|
|
||||||
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
|
|
||||||
(elm)->field.tqe_next = (listelm); \
|
|
||||||
*(listelm)->field.tqe_prev = (elm); \
|
|
||||||
(listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define TAILQ_REMOVE(head, elm, field) do { \
|
|
||||||
if (((elm)->field.tqe_next) != NULL) \
|
|
||||||
(elm)->field.tqe_next->field.tqe_prev = \
|
|
||||||
(elm)->field.tqe_prev; \
|
|
||||||
else \
|
|
||||||
(head)->tqh_last = (elm)->field.tqe_prev; \
|
|
||||||
*(elm)->field.tqe_prev = (elm)->field.tqe_next; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define TAILQ_FOREACH(var, head, field) \
|
|
||||||
for ((var) = ((head)->tqh_first); \
|
|
||||||
(var); \
|
|
||||||
(var) = ((var)->field.tqe_next))
|
|
||||||
|
|
||||||
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
|
|
||||||
for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last)); \
|
|
||||||
(var); \
|
|
||||||
(var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last)))
|
|
||||||
|
|
||||||
#define TAILQ_CONCAT(head1, head2, field) do { \
|
|
||||||
if (!TAILQ_EMPTY(head2)) { \
|
|
||||||
*(head1)->tqh_last = (head2)->tqh_first; \
|
|
||||||
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
|
|
||||||
(head1)->tqh_last = (head2)->tqh_last; \
|
|
||||||
TAILQ_INIT((head2)); \
|
|
||||||
} \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tail queue access methods.
|
|
||||||
*/
|
|
||||||
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
|
|
||||||
#define TAILQ_FIRST(head) ((head)->tqh_first)
|
|
||||||
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
|
|
||||||
|
|
||||||
#define TAILQ_LAST(head, headname) \
|
|
||||||
(*(((struct headname *)((head)->tqh_last))->tqh_last))
|
|
||||||
#define TAILQ_PREV(elm, headname, field) \
|
|
||||||
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Circular queue definitions.
|
|
||||||
*/
|
|
||||||
#define CIRCLEQ_HEAD(name, type) \
|
|
||||||
struct name { \
|
|
||||||
struct type *cqh_first; /* first element */ \
|
|
||||||
struct type *cqh_last; /* last element */ \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CIRCLEQ_HEAD_INITIALIZER(head) \
|
|
||||||
{ (void *)&head, (void *)&head }
|
|
||||||
|
|
||||||
#define CIRCLEQ_ENTRY(type) \
|
|
||||||
struct { \
|
|
||||||
struct type *cqe_next; /* next element */ \
|
|
||||||
struct type *cqe_prev; /* previous element */ \
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Circular queue functions.
|
|
||||||
*/
|
|
||||||
#define CIRCLEQ_INIT(head) do { \
|
|
||||||
(head)->cqh_first = (void *)(head); \
|
|
||||||
(head)->cqh_last = (void *)(head); \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
|
|
||||||
(elm)->field.cqe_next = (listelm)->field.cqe_next; \
|
|
||||||
(elm)->field.cqe_prev = (listelm); \
|
|
||||||
if ((listelm)->field.cqe_next == (void *)(head)) \
|
|
||||||
(head)->cqh_last = (elm); \
|
|
||||||
else \
|
|
||||||
(listelm)->field.cqe_next->field.cqe_prev = (elm); \
|
|
||||||
(listelm)->field.cqe_next = (elm); \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \
|
|
||||||
(elm)->field.cqe_next = (listelm); \
|
|
||||||
(elm)->field.cqe_prev = (listelm)->field.cqe_prev; \
|
|
||||||
if ((listelm)->field.cqe_prev == (void *)(head)) \
|
|
||||||
(head)->cqh_first = (elm); \
|
|
||||||
else \
|
|
||||||
(listelm)->field.cqe_prev->field.cqe_next = (elm); \
|
|
||||||
(listelm)->field.cqe_prev = (elm); \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \
|
|
||||||
(elm)->field.cqe_next = (head)->cqh_first; \
|
|
||||||
(elm)->field.cqe_prev = (void *)(head); \
|
|
||||||
if ((head)->cqh_last == (void *)(head)) \
|
|
||||||
(head)->cqh_last = (elm); \
|
|
||||||
else \
|
|
||||||
(head)->cqh_first->field.cqe_prev = (elm); \
|
|
||||||
(head)->cqh_first = (elm); \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \
|
|
||||||
(elm)->field.cqe_next = (void *)(head); \
|
|
||||||
(elm)->field.cqe_prev = (head)->cqh_last; \
|
|
||||||
if ((head)->cqh_first == (void *)(head)) \
|
|
||||||
(head)->cqh_first = (elm); \
|
|
||||||
else \
|
|
||||||
(head)->cqh_last->field.cqe_next = (elm); \
|
|
||||||
(head)->cqh_last = (elm); \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define CIRCLEQ_REMOVE(head, elm, field) do { \
|
|
||||||
if ((elm)->field.cqe_next == (void *)(head)) \
|
|
||||||
(head)->cqh_last = (elm)->field.cqe_prev; \
|
|
||||||
else \
|
|
||||||
(elm)->field.cqe_next->field.cqe_prev = \
|
|
||||||
(elm)->field.cqe_prev; \
|
|
||||||
if ((elm)->field.cqe_prev == (void *)(head)) \
|
|
||||||
(head)->cqh_first = (elm)->field.cqe_next; \
|
|
||||||
else \
|
|
||||||
(elm)->field.cqe_prev->field.cqe_next = \
|
|
||||||
(elm)->field.cqe_next; \
|
|
||||||
} while (/*CONSTCOND*/0)
|
|
||||||
|
|
||||||
#define CIRCLEQ_FOREACH(var, head, field) \
|
|
||||||
for ((var) = ((head)->cqh_first); \
|
|
||||||
(var) != (const void *)(head); \
|
|
||||||
(var) = ((var)->field.cqe_next))
|
|
||||||
|
|
||||||
#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \
|
|
||||||
for ((var) = ((head)->cqh_last); \
|
|
||||||
(var) != (const void *)(head); \
|
|
||||||
(var) = ((var)->field.cqe_prev))
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Circular queue access methods.
|
|
||||||
*/
|
|
||||||
#define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head))
|
|
||||||
#define CIRCLEQ_FIRST(head) ((head)->cqh_first)
|
|
||||||
#define CIRCLEQ_LAST(head) ((head)->cqh_last)
|
|
||||||
#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next)
|
|
||||||
#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev)
|
|
||||||
|
|
||||||
#define CIRCLEQ_LOOP_NEXT(head, elm, field) \
|
|
||||||
(((elm)->field.cqe_next == (void *)(head)) \
|
|
||||||
? ((head)->cqh_first) \
|
|
||||||
: (elm->field.cqe_next))
|
|
||||||
#define CIRCLEQ_LOOP_PREV(head, elm, field) \
|
|
||||||
(((elm)->field.cqe_prev == (void *)(head)) \
|
|
||||||
? ((head)->cqh_last) \
|
|
||||||
: (elm->field.cqe_prev))
|
|
||||||
|
|
||||||
#endif /* sys/queue.h */
|
|
244
src/server.c
244
src/server.c
|
@ -22,46 +22,43 @@ struct lws_context *context;
|
||||||
struct server *server;
|
struct server *server;
|
||||||
struct endpoints endpoints = {"/ws", "/", "/token", ""};
|
struct endpoints endpoints = {"/ws", "/", "/token", ""};
|
||||||
|
|
||||||
extern int callback_http(struct lws *wsi, enum lws_callback_reasons reason,
|
extern int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||||
void *user, void *in, size_t len);
|
extern int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||||
extern int callback_tty(struct lws *wsi, enum lws_callback_reasons reason,
|
|
||||||
void *user, void *in, size_t len);
|
|
||||||
|
|
||||||
// websocket protocols
|
// websocket protocols
|
||||||
static const struct lws_protocols protocols[] = {
|
static const struct lws_protocols protocols[] = {{"http-only", callback_http, sizeof(struct pss_http), 0},
|
||||||
{"http-only", callback_http, sizeof(struct pss_http), 0},
|
|
||||||
{"tty", callback_tty, sizeof(struct pss_tty), 0},
|
{"tty", callback_tty, sizeof(struct pss_tty), 0},
|
||||||
{NULL, NULL, 0, 0}};
|
{NULL, NULL, 0, 0}};
|
||||||
|
|
||||||
#ifndef LWS_WITHOUT_EXTENSIONS
|
#ifndef LWS_WITHOUT_EXTENSIONS
|
||||||
// websocket extensions
|
// websocket extensions
|
||||||
static const struct lws_extension extensions[] = {
|
static const struct lws_extension extensions[] = {
|
||||||
{"permessage-deflate", lws_extension_callback_pm_deflate,
|
{"permessage-deflate", lws_extension_callback_pm_deflate, "permessage-deflate"},
|
||||||
"permessage-deflate"},
|
|
||||||
{"deflate-frame", lws_extension_callback_pm_deflate, "deflate_frame"},
|
{"deflate-frame", lws_extension_callback_pm_deflate, "deflate_frame"},
|
||||||
{NULL, NULL, NULL}};
|
{NULL, NULL, NULL}};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
|
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
|
||||||
static const uint32_t backoff_ms[] = { 1000, 2000, 3000, 4000, 5000 };
|
static const uint32_t backoff_ms[] = {1000, 2000, 3000, 4000, 5000};
|
||||||
static lws_retry_bo_t retry = {
|
static lws_retry_bo_t retry = {
|
||||||
.retry_ms_table = backoff_ms,
|
.retry_ms_table = backoff_ms,
|
||||||
.retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms),
|
.retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms),
|
||||||
.conceal_count = LWS_ARRAY_SIZE(backoff_ms),
|
.conceal_count = LWS_ARRAY_SIZE(backoff_ms),
|
||||||
.secs_since_valid_ping = 300,
|
.secs_since_valid_ping = 5,
|
||||||
.secs_since_valid_hangup = 300 + 7,
|
.secs_since_valid_hangup = 10,
|
||||||
.jitter_percent = 0,
|
.jitter_percent = 0,
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// command line options
|
// command line options
|
||||||
static const struct option options[] = {
|
static const struct option options[] = {{"port", required_argument, NULL, 'p'},
|
||||||
{"port", required_argument, NULL, 'p'},
|
|
||||||
{"interface", required_argument, NULL, 'i'},
|
{"interface", required_argument, NULL, 'i'},
|
||||||
{"credential", required_argument, NULL, 'c'},
|
{"credential", required_argument, NULL, 'c'},
|
||||||
|
{"auth-header", required_argument, NULL, 'H'},
|
||||||
{"uid", required_argument, NULL, 'u'},
|
{"uid", required_argument, NULL, 'u'},
|
||||||
{"gid", required_argument, NULL, 'g'},
|
{"gid", required_argument, NULL, 'g'},
|
||||||
{"signal", required_argument, NULL, 's'},
|
{"signal", required_argument, NULL, 's'},
|
||||||
|
{"cwd", required_argument, NULL, 'w'},
|
||||||
{"index", required_argument, NULL, 'I'},
|
{"index", required_argument, NULL, 'I'},
|
||||||
{"base-path", required_argument, NULL, 'b'},
|
{"base-path", required_argument, NULL, 'b'},
|
||||||
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
|
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
|
||||||
|
@ -84,13 +81,7 @@ static const struct option options[] = {
|
||||||
{"version", no_argument, NULL, 'v'},
|
{"version", no_argument, NULL, 'v'},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{NULL, 0, 0, 0}};
|
{NULL, 0, 0, 0}};
|
||||||
|
static const char *opt_string = "p:i:c:H:u:g:s:w:I:b:P:6aSC:K:A:Rt:T:Om:oBd:vh";
|
||||||
#if LWS_LIBRARY_VERSION_NUMBER < 4000000
|
|
||||||
static const char *opt_string = "p:i:c:u:g:s:I:b:6aSC:K:A:Rt:T:Om:oBd:vh";
|
|
||||||
#endif
|
|
||||||
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
|
|
||||||
static const char *opt_string = "p:i:c:u:g:s:I:b:P:6aSC:K:A:Rt:T:Om:oBd:vh";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void print_help() {
|
static void print_help() {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
@ -102,10 +93,12 @@ static void print_help() {
|
||||||
"OPTIONS:\n"
|
"OPTIONS:\n"
|
||||||
" -p, --port Port to listen (default: 7681, use `0` for random port)\n"
|
" -p, --port Port to listen (default: 7681, use `0` for random port)\n"
|
||||||
" -i, --interface Network interface to bind (eg: eth0), or UNIX domain socket path (eg: /var/run/ttyd.sock)\n"
|
" -i, --interface Network interface to bind (eg: eth0), or UNIX domain socket path (eg: /var/run/ttyd.sock)\n"
|
||||||
" -c, --credential Credential for Basic Authentication (format: username:password)\n"
|
" -c, --credential Credential for basic authentication (format: username:password)\n"
|
||||||
|
" -H, --auth-header HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication\n"
|
||||||
" -u, --uid User id to run with\n"
|
" -u, --uid User id to run with\n"
|
||||||
" -g, --gid Group id to run with\n"
|
" -g, --gid Group id to run with\n"
|
||||||
" -s, --signal Signal to send to the command when exit it (default: 1, SIGHUP)\n"
|
" -s, --signal Signal to send to the command when exit it (default: 1, SIGHUP)\n"
|
||||||
|
" -w, --cwd Working directory to be set for the child program\n"
|
||||||
" -a, --url-arg Allow client to send command line arguments in URL (eg: http://localhost:7681?arg=foo&arg=bar)\n"
|
" -a, --url-arg Allow client to send command line arguments in URL (eg: http://localhost:7681?arg=foo&arg=bar)\n"
|
||||||
" -R, --readonly Do not allow clients to write to the TTY\n"
|
" -R, --readonly Do not allow clients to write to the TTY\n"
|
||||||
" -t, --client-option Send option to client (format: key=value), repeat to add more options\n"
|
" -t, --client-option Send option to client (format: key=value), repeat to add more options\n"
|
||||||
|
@ -115,9 +108,9 @@ static void print_help() {
|
||||||
" -o, --once Accept only one client and exit on disconnection\n"
|
" -o, --once Accept only one client and exit on disconnection\n"
|
||||||
" -B, --browser Open terminal with the default system browser\n"
|
" -B, --browser Open terminal with the default system browser\n"
|
||||||
" -I, --index Custom index.html path\n"
|
" -I, --index Custom index.html path\n"
|
||||||
" -b, --base-path Expected base path for requests coming from a reverse proxy (eg: /mounted/here)\n"
|
" -b, --base-path Expected base path for requests coming from a reverse proxy (eg: /mounted/here, max length: 128)\n"
|
||||||
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
|
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
|
||||||
" -P, --ping-interval Websocket ping interval(sec) (default: 300)\n"
|
" -P, --ping-interval Websocket ping interval(sec) (default: 5)\n"
|
||||||
#endif
|
#endif
|
||||||
#ifdef LWS_WITH_IPV6
|
#ifdef LWS_WITH_IPV6
|
||||||
" -6, --ipv6 Enable IPv6 support\n"
|
" -6, --ipv6 Enable IPv6 support\n"
|
||||||
|
@ -137,6 +130,28 @@ static void print_help() {
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void print_config() {
|
||||||
|
lwsl_notice("tty configuration:\n");
|
||||||
|
if (server->credential != NULL) lwsl_notice(" credential: %s\n", server->credential);
|
||||||
|
lwsl_notice(" start command: %s\n", server->command);
|
||||||
|
lwsl_notice(" close signal: %s (%d)\n", server->sig_name, server->sig_code);
|
||||||
|
lwsl_notice(" terminal type: %s\n", server->terminal_type);
|
||||||
|
if (endpoints.parent[0]) {
|
||||||
|
lwsl_notice("endpoints:\n");
|
||||||
|
lwsl_notice(" base-path: %s\n", endpoints.parent);
|
||||||
|
lwsl_notice(" index : %s\n", endpoints.index);
|
||||||
|
lwsl_notice(" token : %s\n", endpoints.token);
|
||||||
|
lwsl_notice(" websocket: %s\n", endpoints.ws);
|
||||||
|
}
|
||||||
|
if (server->auth_header != NULL) lwsl_notice(" auth header: %s\n", server->auth_header);
|
||||||
|
if (server->check_origin) lwsl_notice(" check origin: true\n");
|
||||||
|
if (server->url_arg) lwsl_notice(" allow url arg: true\n");
|
||||||
|
if (server->readonly) lwsl_notice(" readonly: true\n");
|
||||||
|
if (server->max_clients > 0) lwsl_notice(" max clients: %d\n", server->max_clients);
|
||||||
|
if (server->once) lwsl_notice(" once: true\n");
|
||||||
|
if (server->index != NULL) lwsl_notice(" custom index.html: %s\n", server->index);
|
||||||
|
}
|
||||||
|
|
||||||
static struct server *server_new(int argc, char **argv, int start) {
|
static struct server *server_new(int argc, char **argv, int start) {
|
||||||
struct server *ts;
|
struct server *ts;
|
||||||
size_t cmd_len = 0;
|
size_t cmd_len = 0;
|
||||||
|
@ -144,7 +159,6 @@ static struct server *server_new(int argc, char **argv, int start) {
|
||||||
ts = xmalloc(sizeof(struct server));
|
ts = xmalloc(sizeof(struct server));
|
||||||
|
|
||||||
memset(ts, 0, sizeof(struct server));
|
memset(ts, 0, sizeof(struct server));
|
||||||
LIST_INIT(&ts->procs);
|
|
||||||
ts->client_count = 0;
|
ts->client_count = 0;
|
||||||
ts->sig_code = SIGHUP;
|
ts->sig_code = SIGHUP;
|
||||||
sprintf(ts->terminal_type, "%s", "xterm-256color");
|
sprintf(ts->terminal_type, "%s", "xterm-256color");
|
||||||
|
@ -167,7 +181,8 @@ static struct server *server_new(int argc, char **argv, int start) {
|
||||||
ts->command = xmalloc(cmd_len + 1);
|
ts->command = xmalloc(cmd_len + 1);
|
||||||
char *ptr = ts->command;
|
char *ptr = ts->command;
|
||||||
for (int i = 0; i < cmd_argc; i++) {
|
for (int i = 0; i < cmd_argc; i++) {
|
||||||
ptr = stpcpy(ptr, ts->argv[i]);
|
size_t len = strlen(ts->argv[i]);
|
||||||
|
ptr = memcpy(ptr, ts->argv[i], len + 1) + len;
|
||||||
if (i != cmd_argc - 1) {
|
if (i != cmd_argc - 1) {
|
||||||
*ptr++ = ' ';
|
*ptr++ = ' ';
|
||||||
}
|
}
|
||||||
|
@ -176,8 +191,6 @@ static struct server *server_new(int argc, char **argv, int start) {
|
||||||
|
|
||||||
ts->loop = xmalloc(sizeof *ts->loop);
|
ts->loop = xmalloc(sizeof *ts->loop);
|
||||||
uv_loop_init(ts->loop);
|
uv_loop_init(ts->loop);
|
||||||
uv_signal_init(ts->loop, &ts->watcher);
|
|
||||||
ts->watcher.data = &ts->procs;
|
|
||||||
|
|
||||||
return ts;
|
return ts;
|
||||||
}
|
}
|
||||||
|
@ -185,22 +198,25 @@ static struct server *server_new(int argc, char **argv, int start) {
|
||||||
static void server_free(struct server *ts) {
|
static void server_free(struct server *ts) {
|
||||||
if (ts == NULL) return;
|
if (ts == NULL) return;
|
||||||
if (ts->credential != NULL) free(ts->credential);
|
if (ts->credential != NULL) free(ts->credential);
|
||||||
|
if (ts->auth_header != NULL) free(ts->auth_header);
|
||||||
if (ts->index != NULL) free(ts->index);
|
if (ts->index != NULL) free(ts->index);
|
||||||
|
if (ts->cwd != NULL) free(ts->cwd);
|
||||||
free(ts->command);
|
free(ts->command);
|
||||||
free(ts->prefs_json);
|
free(ts->prefs_json);
|
||||||
int i = 0;
|
|
||||||
do {
|
char **p = ts->argv;
|
||||||
free(ts->argv[i++]);
|
for (; *p; p++) free(*p);
|
||||||
} while (ts->argv[i] != NULL);
|
|
||||||
free(ts->argv);
|
free(ts->argv);
|
||||||
|
|
||||||
if (strlen(ts->socket_path) > 0) {
|
if (strlen(ts->socket_path) > 0) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (!stat(ts->socket_path, &st)) {
|
if (!stat(ts->socket_path, &st)) {
|
||||||
unlink(ts->socket_path);
|
unlink(ts->socket_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uv_signal_stop(&ts->watcher);
|
|
||||||
uv_loop_close(ts->loop);
|
uv_loop_close(ts->loop);
|
||||||
|
|
||||||
free(ts->loop);
|
free(ts->loop);
|
||||||
free(ts);
|
free(ts);
|
||||||
}
|
}
|
||||||
|
@ -212,8 +228,7 @@ static void signal_cb(uv_signal_t *watcher, int signum) {
|
||||||
case SIGINT:
|
case SIGINT:
|
||||||
case SIGTERM:
|
case SIGTERM:
|
||||||
get_sig_name(watcher->signum, sig_name, sizeof(sig_name));
|
get_sig_name(watcher->signum, sig_name, sizeof(sig_name));
|
||||||
lwsl_notice("received signal: %s (%d), exiting...\n", sig_name,
|
lwsl_notice("received signal: %s (%d), exiting...\n", sig_name, watcher->signum);
|
||||||
watcher->signum);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
signal(SIGABRT, SIG_DFL);
|
signal(SIGABRT, SIG_DFL);
|
||||||
|
@ -222,14 +237,22 @@ static void signal_cb(uv_signal_t *watcher, int signum) {
|
||||||
|
|
||||||
if (force_exit) exit(EXIT_FAILURE);
|
if (force_exit) exit(EXIT_FAILURE);
|
||||||
force_exit = true;
|
force_exit = true;
|
||||||
|
|
||||||
lws_cancel_service(context);
|
lws_cancel_service(context);
|
||||||
#if LWS_LIBRARY_VERSION_MAJOR >= 3
|
|
||||||
uv_stop(server->loop);
|
uv_stop(server->loop);
|
||||||
|
|
||||||
lwsl_notice("send ^C to force exit.\n");
|
lwsl_notice("send ^C to force exit.\n");
|
||||||
#else
|
}
|
||||||
lws_libuv_stop(context);
|
|
||||||
exit(EXIT_SUCCESS);
|
static int parse_int(char *name, char *str) {
|
||||||
#endif
|
char *endptr;
|
||||||
|
errno = 0;
|
||||||
|
long val = strtol(str, &endptr, 0);
|
||||||
|
if (errno != 0 || endptr == str) {
|
||||||
|
fprintf(stderr, "ttyd: invalid value for %s: %s\n", name, str);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
return (int)val;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int calc_command_start(int argc, char **argv) {
|
static int calc_command_start(int argc, char **argv) {
|
||||||
|
@ -274,6 +297,12 @@ int main(int argc, char **argv) {
|
||||||
print_help();
|
print_help();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!conpty_init()) {
|
||||||
|
fprintf(stderr, "ERROR: ConPTY init failed! Make sure you are on Windows 10 1809 or later.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int start = calc_command_start(argc, argv);
|
int start = calc_command_start(argc, argv);
|
||||||
server = server_new(argc, argv, start);
|
server = server_new(argc, argv, start);
|
||||||
|
@ -286,8 +315,7 @@ int main(int argc, char **argv) {
|
||||||
info.gid = -1;
|
info.gid = -1;
|
||||||
info.uid = -1;
|
info.uid = -1;
|
||||||
info.max_http_header_pool = 16;
|
info.max_http_header_pool = 16;
|
||||||
info.options = LWS_SERVER_OPTION_LIBUV | LWS_SERVER_OPTION_VALIDATE_UTF8 |
|
info.options = LWS_SERVER_OPTION_LIBUV | LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_DISABLE_IPV6;
|
||||||
LWS_SERVER_OPTION_DISABLE_IPV6;
|
|
||||||
#ifndef LWS_WITHOUT_EXTENSIONS
|
#ifndef LWS_WITHOUT_EXTENSIONS
|
||||||
info.extensions = extensions;
|
info.extensions = extensions;
|
||||||
#endif
|
#endif
|
||||||
|
@ -314,7 +342,7 @@ int main(int argc, char **argv) {
|
||||||
printf("ttyd version %s\n", TTYD_VERSION);
|
printf("ttyd version %s\n", TTYD_VERSION);
|
||||||
return 0;
|
return 0;
|
||||||
case 'd':
|
case 'd':
|
||||||
debug_level = atoi(optarg);
|
debug_level = parse_int("debug", optarg);
|
||||||
break;
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
server->url_arg = true;
|
server->url_arg = true;
|
||||||
|
@ -326,7 +354,7 @@ int main(int argc, char **argv) {
|
||||||
server->check_origin = true;
|
server->check_origin = true;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
server->max_clients = atoi(optarg);
|
server->max_clients = parse_int("max-clients", optarg);
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
server->once = true;
|
server->once = true;
|
||||||
|
@ -335,7 +363,7 @@ int main(int argc, char **argv) {
|
||||||
browser = true;
|
browser = true;
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
info.port = atoi(optarg);
|
info.port = parse_int("port", optarg);
|
||||||
if (info.port < 0) {
|
if (info.port < 0) {
|
||||||
fprintf(stderr, "ttyd: invalid port: %s\n", optarg);
|
fprintf(stderr, "ttyd: invalid port: %s\n", optarg);
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -347,18 +375,21 @@ int main(int argc, char **argv) {
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
if (strchr(optarg, ':') == NULL) {
|
if (strchr(optarg, ':') == NULL) {
|
||||||
fprintf(stderr,
|
fprintf(stderr, "ttyd: invalid credential, format: username:password\n");
|
||||||
"ttyd: invalid credential, format: username:password\n");
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
server->credential =
|
char b64_text[256];
|
||||||
base64_encode((const unsigned char *)optarg, strlen(optarg));
|
lws_b64_encode_string(optarg, strlen(optarg), b64_text, sizeof(b64_text));
|
||||||
|
server->credential = strdup(b64_text);
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
server->auth_header = strdup(optarg);
|
||||||
break;
|
break;
|
||||||
case 'u':
|
case 'u':
|
||||||
info.uid = atoi(optarg);
|
info.uid = parse_int("uid", optarg);
|
||||||
break;
|
break;
|
||||||
case 'g':
|
case 'g':
|
||||||
info.gid = atoi(optarg);
|
info.gid = parse_int("gid", optarg);
|
||||||
break;
|
break;
|
||||||
case 's': {
|
case 's': {
|
||||||
int sig = get_sig(optarg);
|
int sig = get_sig(optarg);
|
||||||
|
@ -370,6 +401,9 @@ int main(int argc, char **argv) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
case 'w':
|
||||||
|
server->cwd = strdup(optarg);
|
||||||
|
break;
|
||||||
case 'I':
|
case 'I':
|
||||||
if (!strncmp(optarg, "~/", 2)) {
|
if (!strncmp(optarg, "~/", 2)) {
|
||||||
const char *home = getenv("HOME");
|
const char *home = getenv("HOME");
|
||||||
|
@ -380,13 +414,11 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(server->index, &st) == -1) {
|
if (stat(server->index, &st) == -1) {
|
||||||
fprintf(stderr, "Can not stat index.html: %s, error: %s\n",
|
fprintf(stderr, "Can not stat index.html: %s, error: %s\n", server->index, strerror(errno));
|
||||||
server->index, strerror(errno));
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
fprintf(stderr, "Invalid index.html path: %s, is it a dir?\n",
|
fprintf(stderr, "Invalid index.html path: %s, is it a dir?\n", server->index);
|
||||||
server->index);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -403,15 +435,16 @@ int main(int argc, char **argv) {
|
||||||
#undef sc
|
#undef sc
|
||||||
} break;
|
} break;
|
||||||
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
|
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
|
||||||
case 'P':
|
case 'P': {
|
||||||
if (atoi(optarg) <= 0) {
|
int interval = parse_int("ping-interval", optarg);
|
||||||
|
if (interval <= 0) {
|
||||||
fprintf(stderr, "ttyd: invalid ping interval: %s\n", optarg);
|
fprintf(stderr, "ttyd: invalid ping interval: %s\n", optarg);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
retry.secs_since_valid_ping = atoi(optarg);
|
retry.secs_since_valid_ping = interval;
|
||||||
retry.secs_since_valid_hangup = atoi(optarg) + 7;
|
retry.secs_since_valid_hangup = interval + 7;
|
||||||
info.retry_and_idle_policy = &retry;
|
info.retry_and_idle_policy = &retry;
|
||||||
break;
|
} break;
|
||||||
#endif
|
#endif
|
||||||
case '6':
|
case '6':
|
||||||
info.options &= ~(LWS_SERVER_OPTION_DISABLE_IPV6);
|
info.options &= ~(LWS_SERVER_OPTION_DISABLE_IPV6);
|
||||||
|
@ -434,8 +467,7 @@ int main(int argc, char **argv) {
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case 'T':
|
case 'T':
|
||||||
strncpy(server->terminal_type, optarg,
|
strncpy(server->terminal_type, optarg, sizeof(server->terminal_type) - 1);
|
||||||
sizeof(server->terminal_type) - 1);
|
|
||||||
server->terminal_type[sizeof(server->terminal_type) - 1] = '\0';
|
server->terminal_type[sizeof(server->terminal_type) - 1] = '\0';
|
||||||
break;
|
break;
|
||||||
case '?':
|
case '?':
|
||||||
|
@ -443,26 +475,19 @@ int main(int argc, char **argv) {
|
||||||
case 't':
|
case 't':
|
||||||
optind--;
|
optind--;
|
||||||
for (; optind < start && *argv[optind] != '-'; optind++) {
|
for (; optind < start && *argv[optind] != '-'; optind++) {
|
||||||
char *option = strdup(optarg);
|
char *option = optarg;
|
||||||
char *key = strsep(&option, "=");
|
char *key = strsep(&option, "=");
|
||||||
if (key == NULL) {
|
if (key == NULL) {
|
||||||
fprintf(stderr,
|
fprintf(stderr, "ttyd: invalid client option: %s, format: key=value\n", optarg);
|
||||||
"ttyd: invalid client option: %s, format: key=value\n",
|
|
||||||
optarg);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
char *value = strsep(&option, "=");
|
char *value = strsep(&option, "=");
|
||||||
if (value == NULL) {
|
if (value == NULL) {
|
||||||
fprintf(stderr,
|
fprintf(stderr, "ttyd: invalid client option: %s, format: key=value\n", optarg);
|
||||||
"ttyd: invalid client option: %s, format: key=value\n",
|
|
||||||
optarg);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
free(option);
|
|
||||||
struct json_object *obj = json_tokener_parse(value);
|
struct json_object *obj = json_tokener_parse(value);
|
||||||
json_object_object_add(
|
json_object_object_add(client_prefs, key, obj != NULL ? obj : json_object_new_string(value));
|
||||||
client_prefs, key,
|
|
||||||
obj != NULL ? obj : json_object_new_string(value));
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -480,13 +505,11 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
lws_set_log_level(debug_level, NULL);
|
lws_set_log_level(debug_level, NULL);
|
||||||
|
|
||||||
#if LWS_LIBRARY_VERSION_MAJOR >= 2
|
|
||||||
char server_hdr[128] = "";
|
char server_hdr[128] = "";
|
||||||
sprintf(server_hdr, "ttyd/%s (libwebsockets/%s)", TTYD_VERSION,
|
sprintf(server_hdr, "ttyd/%s (libwebsockets/%s)", TTYD_VERSION, LWS_LIBRARY_VERSION);
|
||||||
LWS_LIBRARY_VERSION);
|
|
||||||
info.server_string = server_hdr;
|
info.server_string = server_hdr;
|
||||||
#endif
|
|
||||||
#if LWS_LIBRARY_VERSION_NUMBER >= 2001000 && LWS_LIBRARY_VERSION_NUMBER < 4000000
|
#if LWS_LIBRARY_VERSION_NUMBER < 4000000
|
||||||
info.ws_ping_pong_interval = 5;
|
info.ws_ping_pong_interval = 5;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -498,8 +521,7 @@ int main(int argc, char **argv) {
|
||||||
info.port = 0; // warmcat/libwebsockets#1985
|
info.port = 0; // warmcat/libwebsockets#1985
|
||||||
strncpy(server->socket_path, info.iface, sizeof(server->socket_path) - 1);
|
strncpy(server->socket_path, info.iface, sizeof(server->socket_path) - 1);
|
||||||
#else
|
#else
|
||||||
fprintf(stderr,
|
fprintf(stderr, "libwebsockets is not compiled with UNIX domain socket support");
|
||||||
"libwebsockets is not compiled with UNIX domain socket support");
|
|
||||||
return -1;
|
return -1;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -509,46 +531,29 @@ int main(int argc, char **argv) {
|
||||||
if (ssl) {
|
if (ssl) {
|
||||||
info.ssl_cert_filepath = cert_path;
|
info.ssl_cert_filepath = cert_path;
|
||||||
info.ssl_private_key_filepath = key_path;
|
info.ssl_private_key_filepath = key_path;
|
||||||
if (strlen(ca_path) > 0)
|
if (strlen(ca_path) > 0) {
|
||||||
info.ssl_ca_filepath = ca_path;
|
info.ssl_ca_filepath = ca_path;
|
||||||
info.options |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;
|
info.options |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;
|
||||||
#if LWS_LIBRARY_VERSION_MAJOR >= 2
|
}
|
||||||
info.options |= LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS;
|
info.options |= LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
lwsl_notice("ttyd %s (libwebsockets %s)\n", TTYD_VERSION,
|
lwsl_notice("ttyd %s (libwebsockets %s)\n", TTYD_VERSION, LWS_LIBRARY_VERSION);
|
||||||
LWS_LIBRARY_VERSION);
|
print_config();
|
||||||
lwsl_notice("tty configuration:\n");
|
|
||||||
if (server->credential != NULL)
|
// lws custom header requires lower case name, and terminating :
|
||||||
lwsl_notice(" credential: %s\n", server->credential);
|
if (server->auth_header != NULL) {
|
||||||
lwsl_notice(" start command: %s\n", server->command);
|
size_t auth_header_len = strlen(server->auth_header);
|
||||||
lwsl_notice(" close signal: %s (%d)\n", server->sig_name, server->sig_code);
|
server->auth_header = xrealloc(server->auth_header, auth_header_len + 2);
|
||||||
lwsl_notice(" terminal type: %s\n", server->terminal_type);
|
strcat(server->auth_header + auth_header_len, ":");
|
||||||
if (endpoints.parent[0]) {
|
lowercase(server->auth_header);
|
||||||
lwsl_notice("endpoints:\n");
|
|
||||||
lwsl_notice(" base-path: %s\n", endpoints.parent);
|
|
||||||
lwsl_notice(" index : %s\n", endpoints.index);
|
|
||||||
lwsl_notice(" token : %s\n", endpoints.token);
|
|
||||||
lwsl_notice(" websocket: %s\n", endpoints.ws);
|
|
||||||
}
|
|
||||||
if (server->check_origin) lwsl_notice(" check origin: true\n");
|
|
||||||
if (server->url_arg) lwsl_notice(" allow url arg: true\n");
|
|
||||||
if (server->readonly) lwsl_notice(" readonly: true\n");
|
|
||||||
if (server->max_clients > 0)
|
|
||||||
lwsl_notice(" max clients: %d\n", server->max_clients);
|
|
||||||
if (server->once) lwsl_notice(" once: true\n");
|
|
||||||
if (server->index != NULL) {
|
|
||||||
lwsl_notice(" custom index.html: %s\n", server->index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if LWS_LIBRARY_VERSION_MAJOR >= 3
|
|
||||||
void *foreign_loops[1];
|
void *foreign_loops[1];
|
||||||
foreign_loops[0] = server->loop;
|
foreign_loops[0] = server->loop;
|
||||||
info.foreign_loops = foreign_loops;
|
info.foreign_loops = foreign_loops;
|
||||||
info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
|
info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
|
||||||
#endif
|
|
||||||
|
|
||||||
context = lws_create_context(&info);
|
context = lws_create_context(&info);
|
||||||
if (context == NULL) {
|
if (context == NULL) {
|
||||||
|
@ -556,16 +561,12 @@ int main(int argc, char **argv) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if LWS_LIBRARY_VERSION_MAJOR >= 3
|
|
||||||
struct lws_vhost *vhost = lws_create_vhost(context, &info);
|
struct lws_vhost *vhost = lws_create_vhost(context, &info);
|
||||||
if (vhost == NULL) {
|
if (vhost == NULL) {
|
||||||
lwsl_err("libwebsockets vhost creation failed\n");
|
lwsl_err("libwebsockets vhost creation failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
int port = lws_get_vhost_listen_port(vhost);
|
int port = lws_get_vhost_listen_port(vhost);
|
||||||
#else
|
|
||||||
int port = info.port;
|
|
||||||
#endif
|
|
||||||
lwsl_notice(" Listening on port: %d\n", port);
|
lwsl_notice(" Listening on port: %d\n", port);
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
|
@ -574,29 +575,20 @@ int main(int argc, char **argv) {
|
||||||
open_uri(url);
|
open_uri(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if LWS_LIBRARY_VERSION_MAJOR >= 3
|
#define sig_count 2
|
||||||
int sig_nums[] = {SIGINT, SIGTERM};
|
int sig_nums[] = {SIGINT, SIGTERM};
|
||||||
int ns = sizeof(sig_nums) / sizeof(sig_nums[0]);
|
uv_signal_t signals[sig_count];
|
||||||
uv_signal_t signals[ns];
|
for (int i = 0; i < sig_count; i++) {
|
||||||
for (int i = 0; i < ns; i++) {
|
|
||||||
uv_signal_init(server->loop, &signals[i]);
|
uv_signal_init(server->loop, &signals[i]);
|
||||||
uv_signal_start(&signals[i], signal_cb, sig_nums[i]);
|
uv_signal_start(&signals[i], signal_cb, sig_nums[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
lws_service(context, 0);
|
lws_service(context, 0);
|
||||||
|
|
||||||
for (int i = 0; i < ns; i++) {
|
for (int i = 0; i < sig_count; i++) {
|
||||||
uv_signal_stop(&signals[i]);
|
uv_signal_stop(&signals[i]);
|
||||||
}
|
}
|
||||||
#else
|
#undef sig_count
|
||||||
#if LWS_LIBRARY_VERSION_MAJOR < 2
|
|
||||||
lws_uv_initloop(context, server->loop, signal_cb, 0);
|
|
||||||
#else
|
|
||||||
lws_uv_sigint_cfg(context, 1, signal_cb);
|
|
||||||
lws_uv_initloop(context, server->loop, 0);
|
|
||||||
#endif
|
|
||||||
lws_libuv_run(context, 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
lws_context_destroy(context);
|
lws_context_destroy(context);
|
||||||
|
|
||||||
|
|
40
src/server.h
40
src/server.h
|
@ -1,7 +1,8 @@
|
||||||
|
#include <libwebsockets.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <uv.h>
|
#include <uv.h>
|
||||||
|
|
||||||
#include "queue.h"
|
#include "pty.h"
|
||||||
|
|
||||||
// client message
|
// client message
|
||||||
#define INPUT '0'
|
#define INPUT '0'
|
||||||
|
@ -15,8 +16,6 @@
|
||||||
#define SET_WINDOW_TITLE '1'
|
#define SET_WINDOW_TITLE '1'
|
||||||
#define SET_PREFERENCES '2'
|
#define SET_PREFERENCES '2'
|
||||||
|
|
||||||
#define MAX_READ_RETRY 2
|
|
||||||
|
|
||||||
// url paths
|
// url paths
|
||||||
struct endpoints {
|
struct endpoints {
|
||||||
char *ws;
|
char *ws;
|
||||||
|
@ -30,8 +29,6 @@ extern struct lws_context *context;
|
||||||
extern struct server *server;
|
extern struct server *server;
|
||||||
extern struct endpoints endpoints;
|
extern struct endpoints endpoints;
|
||||||
|
|
||||||
typedef enum { STATE_INIT, STATE_PAUSE, STATE_KILL, STATE_EXIT } proc_state;
|
|
||||||
|
|
||||||
struct pss_http {
|
struct pss_http {
|
||||||
char path[128];
|
char path[128];
|
||||||
char *buffer;
|
char *buffer;
|
||||||
|
@ -39,46 +36,36 @@ struct pss_http {
|
||||||
size_t len;
|
size_t len;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct pty_proc {
|
|
||||||
char **args;
|
|
||||||
int argc;
|
|
||||||
|
|
||||||
pid_t pid;
|
|
||||||
int status;
|
|
||||||
proc_state state;
|
|
||||||
|
|
||||||
int pty;
|
|
||||||
char *pty_buffer;
|
|
||||||
ssize_t pty_len;
|
|
||||||
int err_count;
|
|
||||||
|
|
||||||
uv_pipe_t pipe;
|
|
||||||
|
|
||||||
LIST_ENTRY(pty_proc) entry;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct pss_tty {
|
struct pss_tty {
|
||||||
bool initialized;
|
bool initialized;
|
||||||
int initial_cmd_index;
|
int initial_cmd_index;
|
||||||
bool authenticated;
|
bool authenticated;
|
||||||
|
char user[30];
|
||||||
char address[50];
|
char address[50];
|
||||||
char path[20];
|
char path[128];
|
||||||
|
char **args;
|
||||||
|
int argc;
|
||||||
|
|
||||||
struct lws *wsi;
|
struct lws *wsi;
|
||||||
char *buffer;
|
char *buffer;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
struct pty_proc *proc;
|
pty_process *process;
|
||||||
|
pty_buf_t *pty_buf;
|
||||||
|
|
||||||
|
int lws_close_status;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
int client_count; // client count
|
int client_count; // client count
|
||||||
char *prefs_json; // client preferences
|
char *prefs_json; // client preferences
|
||||||
char *credential; // encoded basic auth credential
|
char *credential; // encoded basic auth credential
|
||||||
|
char *auth_header; // header name used for auth proxy
|
||||||
char *index; // custom index.html
|
char *index; // custom index.html
|
||||||
char *command; // full command line
|
char *command; // full command line
|
||||||
char **argv; // command with arguments
|
char **argv; // command with arguments
|
||||||
int argc; // command + arguments count
|
int argc; // command + arguments count
|
||||||
|
char *cwd; // working directory
|
||||||
int sig_code; // close signal
|
int sig_code; // close signal
|
||||||
char sig_name[20]; // human readable signal string
|
char sig_name[20]; // human readable signal string
|
||||||
bool url_arg; // allow client to send cli arguments in URL
|
bool url_arg; // allow client to send cli arguments in URL
|
||||||
|
@ -90,7 +77,4 @@ struct server {
|
||||||
char terminal_type[30]; // terminal type to report
|
char terminal_type[30]; // terminal type to report
|
||||||
|
|
||||||
uv_loop_t *loop; // the libuv event loop
|
uv_loop_t *loop; // the libuv event loop
|
||||||
uv_signal_t watcher; // SIGCHLD watcher
|
|
||||||
|
|
||||||
LIST_HEAD(proc, pty_proc) procs; // started process list
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#if defined(__OpenBSD__) || defined(__APPLE__)
|
|
||||||
#include <util.h>
|
|
||||||
#elif defined(__FreeBSD__)
|
|
||||||
#include <libutil.h>
|
|
||||||
#else
|
|
||||||
#include <pty.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
pid_t pty_fork(int *pty, const char *file, char *const argv[], const char *term) {
|
|
||||||
pid_t pid = forkpty(pty, NULL, NULL, NULL);
|
|
||||||
|
|
||||||
if (pid < 0) {
|
|
||||||
return pid;
|
|
||||||
} else if (pid == 0) {
|
|
||||||
setenv("TERM", term, true);
|
|
||||||
int ret = execvp(file, argv);
|
|
||||||
if (ret < 0) {
|
|
||||||
perror("execvp failed\n");
|
|
||||||
_exit(-errno);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the file descriptor non blocking
|
|
||||||
int flags = fcntl(*pty, F_GETFL);
|
|
||||||
if (flags != -1) {
|
|
||||||
fcntl(*pty, F_SETFD, flags | O_NONBLOCK);
|
|
||||||
}
|
|
||||||
// set the file descriptor close-on-exec
|
|
||||||
fd_set_cloexec(*pty);
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
int pty_resize(int pty, int cols, int rows) {
|
|
||||||
struct winsize size;
|
|
||||||
|
|
||||||
size.ws_col = (unsigned short)cols;
|
|
||||||
size.ws_row = (unsigned short)rows;
|
|
||||||
size.ws_xpixel = 0;
|
|
||||||
size.ws_ypixel = 0;
|
|
||||||
|
|
||||||
return ioctl(pty, TIOCSWINSZ, &size);
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
#ifndef TTYD_TERMINAL_H
|
|
||||||
#define TTYD_TERMINAL_H
|
|
||||||
|
|
||||||
int pty_fork(int *pty, const char *file, char *const argv[], const char *term);
|
|
||||||
|
|
||||||
int pty_resize(int pty, int cols, int rows);
|
|
||||||
|
|
||||||
#endif // TTYD_TERMINAL_H
|
|
149
src/utils.c
149
src/utils.c
|
@ -7,26 +7,20 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#if defined(__linux__) && !defined(__ANDROID__)
|
#if defined(__linux__) && !defined(__ANDROID__)
|
||||||
// https://github.com/karelzak/util-linux/blob/master/misc-utils/kill.c
|
|
||||||
const char *sys_signame[NSIG] = {
|
const char *sys_signame[NSIG] = {
|
||||||
"zero", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "UNUSED", "FPE",
|
"zero", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "UNUSED", "FPE", "KILL", "USR1",
|
||||||
"KILL", "USR1", "SEGV", "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD",
|
"SEGV", "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN",
|
||||||
"CONT", "STOP", "TSTP", "TTIN", "TTOU", "URG", "XCPU", "XFSZ", "VTALRM",
|
"TTOU", "URG", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "IO", "PWR", "SYS", NULL};
|
||||||
"PROF", "WINCH", "IO", "PWR", "SYS", NULL};
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||||
#include <shellapi.h>
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
// https://github.com/mirror/newlib-cygwin/blob/master/winsup/cygwin/strsig.cc
|
#undef NSIG
|
||||||
#ifndef NSIG
|
|
||||||
#define NSIG 33
|
#define NSIG 33
|
||||||
#endif
|
|
||||||
const char *sys_signame[NSIG] = {
|
const char *sys_signame[NSIG] = {
|
||||||
"zero", "HUP", "INT", "QUIT", "ILL", "TRAP", "IOT", "EMT", "FPE",
|
"zero", "HUP", "INT", "QUIT", "ILL", "TRAP", "IOT", "EMT", "FPE", "KILL", "BUS",
|
||||||
"KILL", "BUS", "SEGV", "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP",
|
"SEGV", "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN",
|
||||||
"TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO", "XCPU", "XFSZ", "VTALRM",
|
"TTOU", "IO", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "PWR", "USR1", "USR2", NULL};
|
||||||
"PROF", "WINCH", "PWR", "USR1", "USR2", NULL};
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void *xmalloc(size_t size) {
|
void *xmalloc(size_t size) {
|
||||||
|
@ -43,12 +37,20 @@ void *xrealloc(void *p, size_t size) {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *uppercase(char *str) {
|
char *uppercase(char *s) {
|
||||||
int i = 0;
|
while(*s) {
|
||||||
do {
|
*s = (char)toupper((int)*s);
|
||||||
str[i] = (char)toupper(str[i]);
|
s++;
|
||||||
} while (str[i++] != '\0');
|
}
|
||||||
return str;
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *lowercase(char *s) {
|
||||||
|
while(*s) {
|
||||||
|
*s = (char)tolower((int)*s);
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool endswith(const char *str, const char *suffix) {
|
bool endswith(const char *str, const char *suffix) {
|
||||||
|
@ -58,8 +60,7 @@ bool endswith(const char *str, const char *suffix) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_sig_name(int sig, char *buf, size_t len) {
|
int get_sig_name(int sig, char *buf, size_t len) {
|
||||||
int n =
|
int n = snprintf(buf, len, "SIG%s", sig < NSIG ? sys_signame[sig] : "unknown");
|
||||||
snprintf(buf, len, "SIG%s", sig < NSIG ? sys_signame[sig] : "unknown");
|
|
||||||
uppercase(buf);
|
uppercase(buf);
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
@ -67,27 +68,19 @@ int get_sig_name(int sig, char *buf, size_t len) {
|
||||||
int get_sig(const char *sig_name) {
|
int get_sig(const char *sig_name) {
|
||||||
for (int sig = 1; sig < NSIG; sig++) {
|
for (int sig = 1; sig < NSIG; sig++) {
|
||||||
const char *name = sys_signame[sig];
|
const char *name = sys_signame[sig];
|
||||||
if (name != NULL && (strcasecmp(name, sig_name) == 0 ||
|
if (name != NULL && (strcasecmp(name, sig_name) == 0 || strcasecmp(name, sig_name + 3) == 0))
|
||||||
strcasecmp(name, sig_name + 3) == 0))
|
|
||||||
return sig;
|
return sig;
|
||||||
}
|
}
|
||||||
return atoi(sig_name);
|
return atoi(sig_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fd_set_cloexec(const int fd) {
|
|
||||||
int flags = fcntl(fd, F_GETFD);
|
|
||||||
if (flags < 0) return false;
|
|
||||||
return (flags & FD_CLOEXEC) == 0 ||
|
|
||||||
fcntl(fd, F_SETFD, flags | FD_CLOEXEC) != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int open_uri(char *uri) {
|
int open_uri(char *uri) {
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
char command[256];
|
char command[256];
|
||||||
sprintf(command, "open %s > /dev/null 2>&1", uri);
|
sprintf(command, "open %s > /dev/null 2>&1", uri);
|
||||||
return system(command);
|
return system(command);
|
||||||
#elif defined(_WIN32) || defined(__CYGWIN__)
|
#elif defined(_WIN32) || defined(__CYGWIN__)
|
||||||
return ShellExecute(0, 0, uri, 0, 0, SW_SHOW) > 32 ? 0 : 1;
|
return ShellExecute(0, 0, uri, 0, 0, SW_SHOW) > (HINSTANCE)32 ? 0 : 1;
|
||||||
#else
|
#else
|
||||||
// check if X server is running
|
// check if X server is running
|
||||||
if (system("xset -q > /dev/null 2>&1")) return 1;
|
if (system("xset -q > /dev/null 2>&1")) return 1;
|
||||||
|
@ -97,28 +90,74 @@ int open_uri(char *uri) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/darkk/redsocks/blob/master/base64.c
|
#ifdef _WIN32
|
||||||
char *base64_encode(const unsigned char *buffer, size_t length) {
|
char *strsep(char **sp, char *sep) {
|
||||||
static const char b64[] =
|
char *p, *s;
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
if (sp == NULL || *sp == NULL || **sp == '\0') return (NULL);
|
||||||
char *ret, *dst;
|
s = *sp;
|
||||||
unsigned i_bits = 0;
|
p = s + strcspn(s, sep);
|
||||||
int i_shift = 0;
|
if (*p != '\0') *p++ = '\0';
|
||||||
int bytes_remaining = (int)length;
|
*sp = p;
|
||||||
|
return s;
|
||||||
ret = dst = xmalloc((size_t)(((length + 2) / 3 * 4) + 1));
|
|
||||||
while (bytes_remaining) {
|
|
||||||
i_bits = (i_bits << 8) + *buffer++;
|
|
||||||
bytes_remaining--;
|
|
||||||
i_shift += 8;
|
|
||||||
|
|
||||||
do {
|
|
||||||
*dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f];
|
|
||||||
i_shift -= 6;
|
|
||||||
} while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0));
|
|
||||||
}
|
|
||||||
while ((dst - ret) & 3) *dst++ = '=';
|
|
||||||
*dst = '\0';
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *quote_arg(const char *arg) {
|
||||||
|
int len = 0, n = 0;
|
||||||
|
int force_quotes = 0;
|
||||||
|
char *q, *d;
|
||||||
|
const char *p = arg;
|
||||||
|
if (!*p) force_quotes = 1;
|
||||||
|
while (*p) {
|
||||||
|
if (isspace(*p) || *p == '*' || *p == '?' || *p == '{' || *p == '\'')
|
||||||
|
force_quotes = 1;
|
||||||
|
else if (*p == '"')
|
||||||
|
n++;
|
||||||
|
else if (*p == '\\') {
|
||||||
|
int count = 0;
|
||||||
|
while (*p == '\\') {
|
||||||
|
count++;
|
||||||
|
p++;
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
if (*p == '"' || !*p) n += count * 2 + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
len++;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (!force_quotes && n == 0) return arg;
|
||||||
|
|
||||||
|
d = q = xmalloc(len + n + 3);
|
||||||
|
*d++ = '"';
|
||||||
|
while (*arg) {
|
||||||
|
if (*arg == '"')
|
||||||
|
*d++ = '\\';
|
||||||
|
else if (*arg == '\\') {
|
||||||
|
int count = 0;
|
||||||
|
while (*arg == '\\') {
|
||||||
|
count++;
|
||||||
|
*d++ = *arg++;
|
||||||
|
}
|
||||||
|
if (*arg == '"' || !*arg) {
|
||||||
|
while (count-- > 0) *d++ = '\\';
|
||||||
|
if (!*arg) break;
|
||||||
|
*d++ = '\\';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*d++ = *arg++;
|
||||||
|
}
|
||||||
|
*d++ = '"';
|
||||||
|
*d++ = '\0';
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_error(char *func) {
|
||||||
|
LPVOID buffer;
|
||||||
|
DWORD dw = GetLastError();
|
||||||
|
FormatMessage(
|
||||||
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&buffer, 0, NULL);
|
||||||
|
wprintf(L"== %s failed with error %d: %s", func, dw, buffer);
|
||||||
|
LocalFree(buffer);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
16
src/utils.h
16
src/utils.h
|
@ -14,7 +14,10 @@ void *xmalloc(size_t size);
|
||||||
void *xrealloc(void *p, size_t size);
|
void *xrealloc(void *p, size_t size);
|
||||||
|
|
||||||
// Convert a string to upper case
|
// Convert a string to upper case
|
||||||
char *uppercase(char *str);
|
char *uppercase(char *s);
|
||||||
|
|
||||||
|
// Convert a string to lower case
|
||||||
|
char *lowercase(char *s);
|
||||||
|
|
||||||
// Check whether str ends with suffix
|
// Check whether str ends with suffix
|
||||||
bool endswith(const char *str, const char *suffix);
|
bool endswith(const char *str, const char *suffix);
|
||||||
|
@ -25,13 +28,12 @@ int get_sig_name(int sig, char *buf, size_t len);
|
||||||
// Get signal code from string like SIGHUP
|
// Get signal code from string like SIGHUP
|
||||||
int get_sig(const char *sig_name);
|
int get_sig(const char *sig_name);
|
||||||
|
|
||||||
// Set the given file descriptor close-on-exec
|
|
||||||
bool fd_set_cloexec(const int fd);
|
|
||||||
|
|
||||||
// Open uri with the default application of system
|
// Open uri with the default application of system
|
||||||
int open_uri(char *uri);
|
int open_uri(char *uri);
|
||||||
|
|
||||||
// Encode text to base64, the caller should free the returned string
|
#ifdef _WIN32
|
||||||
char *base64_encode(const unsigned char *buffer, size_t length);
|
char *strsep(char **sp, char *sep);
|
||||||
|
const char *quote_arg(const char *arg);
|
||||||
|
void print_error(char *func);
|
||||||
|
#endif
|
||||||
#endif // TTYD_UTIL_H
|
#endif // TTYD_UTIL_H
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue