feat: add libssh git ssh gateway demo

This commit is contained in:
xxq250 2026-06-15 15:00:01 +08:00
commit e014dca3ca
6 changed files with 1003 additions and 0 deletions

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
build/
build-*/
cmake-build-*/
ssh_host_*_key
ssh_host_*_key.pub
repositories/
repo/
*.log

25
CMakeLists.txt Normal file
View File

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.16)
project(git_ssh_gateway C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBSSH REQUIRED IMPORTED_TARGET libssh)
add_executable(git-ssh-gateway
src/git_ssh_gateway.c
)
target_compile_options(git-ssh-gateway PRIVATE
-Wall
-Wextra
-Wpedantic
)
target_link_libraries(git-ssh-gateway PRIVATE
PkgConfig::LIBSSH
)

327
README.md Normal file
View File

@ -0,0 +1,327 @@
# OpenResty stream + libssh Git SSH Gateway Demo
目标:让 `git clone git@example.com:owner/repo.git` 进入 OpenResty 的 `stream` 入口后,转发到一个基于 libssh 的 SSH gateway由 gateway 解析 Git SSH 请求并转发到真实 Git 后端。
这个仓库包含一个可编译的 C/CMake demo
```text
CMakeLists.txt
src/git_ssh_gateway.c
openresty/nginx.conf
scripts/gen-host-key.sh
```
demo 当前实现的是:
```text
git client
|
| SSH
v
OpenResty stream
|
| TCP
v
libssh gateway
|
| fork/exec
v
local git-upload-pack / git-receive-pack
```
也就是说libssh gateway 会终止 SSH、解析 Git `exec` 命令,然后在 `--repo-root` 目录下执行本机的 `git-upload-pack`、`git-receive-pack` 或 `git-upload-archive`
## 依赖
macOS:
```bash
brew install cmake pkg-config libssh
```
Ubuntu/Debian:
```bash
sudo apt-get update
sudo apt-get install -y build-essential cmake pkg-config libssh-dev git openssh-client
```
## 编译
```bash
cmake -S . -B build
cmake --build build
```
## 准备测试仓库
```bash
mkdir -p repositories/owner
git init --bare repositories/owner/repo.git
```
## 生成 SSH host key
```bash
./scripts/gen-host-key.sh ./ssh_host_ed25519_key
```
## 启动 gateway
```bash
./build/git-ssh-gateway \
--bind 127.0.0.1 \
--port 2222 \
--repo-root ./repositories \
--host-key ./ssh_host_ed25519_key
```
直接连 gateway 测试:
```bash
GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222" \
git clone git@127.0.0.1:owner/repo.git
```
经过 OpenResty 测试:
```bash
openresty -p "$PWD" -c openresty/nginx.conf
GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2220" \
git clone git@127.0.0.1:owner/repo.git
```
本 demo 为方便本地验证,认证层接受任意 publickey/password。生产环境必须改成真实的 key 校验、用户映射、仓库 ACL 和审计。
## 结论
如果只是透明 TCP 转发OpenResty `stream` 已经足够,不需要 libssh
```nginx
stream {
upstream git_ssh_backend {
server 127.0.0.1:2222;
}
server {
listen 22;
proxy_connect_timeout 5s;
proxy_timeout 1h;
proxy_pass git_ssh_backend;
}
}
```
如果需要按仓库、用户、租户、权限、审计或灰度路由,就必须终止 SSH。原因是 Git SSH 请求在 SSH 加密通道内OpenResty `stream` 的 preread 阶段最多只能看到 SSH banner例如
```text
SSH-2.0-OpenSSH_9.x
```
它看不到后续的:
```text
git-upload-pack 'owner/repo.git'
git-receive-pack 'owner/repo.git'
git-upload-archive 'owner/repo.git'
```
所以推荐架构是:
```text
git client
|
| TCP/22
v
OpenResty stream
|
| TCP/2222
v
libssh git gateway
|
| SSH or local git-shell
v
real git backend
```
## OpenResty 配置
`openresty/nginx.conf`
```nginx
worker_processes auto;
events {
worker_connections 4096;
}
stream {
log_format ssh_log '$remote_addr [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time';
access_log logs/git-ssh-access.log ssh_log;
error_log logs/git-ssh-error.log info;
limit_conn_zone $binary_remote_addr zone=ssh_ip:10m;
upstream libssh_git_gateway {
server 127.0.0.1:2222 max_fails=3 fail_timeout=10s;
keepalive 128;
}
server {
listen 22 reuseport;
proxy_connect_timeout 3s;
proxy_timeout 1h;
proxy_socket_keepalive on;
limit_conn ssh_ip 20;
proxy_pass libssh_git_gateway;
}
}
```
## libssh gateway 要做什么
gateway 是一个 SSH server不是普通 TCP proxy。核心流程
1. `ssh_bind_new` 监听 `127.0.0.1:2222`
2. 加载 host key让客户端信任的是 gateway。
3. 完成 SSH key exchange。
4. 处理认证publickey/password/token 均可,生产环境建议 publickey。
5. 接收 `session` channel。
6. 读取 `exec` 请求,解析命令:
```text
git-upload-pack 'owner/repo.git'
git-receive-pack 'owner/repo.git'
git-upload-archive 'owner/repo.git'
```
7. 做权限校验和仓库路由。
8. 转发到真实后端,二选一:
- 后端也是 SSHgateway 再作为 libssh client 连接后端,发同样的 `exec` 命令,然后双向转发 channel。
- 后端是本机仓库:直接 fork/exec `git-upload-pack``git-receive-pack`,把 SSH channel stdin/stdout/stderr 接到子进程。
## Git SSH 命令解析规则
只允许 Git 白名单命令:
```text
git-upload-pack
git-receive-pack
git-upload-archive
```
仓库路径必须规范化,避免命令注入和路径穿越:
```text
owner/repo.git
/owner/repo.git
'owner/repo.git'
"owner/repo.git"
```
建议解析后统一成:
```text
owner/repo.git
```
拒绝这些输入:
```text
../../etc/passwd
owner/repo.git; id
owner/repo.git && id
owner/../repo.git
```
## 推荐的 gateway 伪代码
```c
ssh_bind bind = ssh_bind_new();
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, "127.0.0.1");
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "2222");
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSAKEY, "/etc/git-gateway/ssh_host_rsa_key");
ssh_bind_listen(bind);
for (;;) {
ssh_session client = ssh_new();
ssh_bind_accept(bind, client);
if (ssh_handle_key_exchange(client) != SSH_OK) {
ssh_disconnect(client);
ssh_free(client);
continue;
}
authenticate_client_publickey(client);
ssh_channel chan = accept_session_channel(client);
char *exec = read_exec_request(client);
struct git_request req = parse_git_exec(exec);
authorize(req.user, req.repo, req.operation);
ssh_session backend = connect_backend(req);
ssh_channel backend_chan = open_backend_exec(backend, exec);
pump_bidirectional(chan, backend_chan);
ssh_channel_close(backend_chan);
ssh_channel_close(chan);
ssh_disconnect(backend);
ssh_disconnect(client);
}
```
## 双向转发注意事项
SSH channel 不是裸 socket不能直接 `splice`。需要循环读取两边 channel
```text
client channel stdout/stderr <-> backend channel stdout/stderr
client channel stdin <-> backend channel stdin
```
需要处理:
- EOF一侧 `ssh_channel_send_eof` 后继续读另一侧剩余数据。
- stderrGit 会通过 sideband 和 stderr 传进度信息,不能吞掉。
- exit status后端 exit status 要回传给客户端。
- backpressure不要一次性读入内存按 16KB 或 32KB chunk 转发。
- 超时clone 大仓库可能很久,读写超时要比 HTTP 长。
## 为什么不把 libssh 直接塞进 OpenResty Lua
不推荐。OpenResty `stream_lua` 可以处理 TCP但 libssh 是完整 SSH 协议栈包含握手、认证、channel、窗口、加密状态机。把它同步阻塞地放进 nginx worker 会卡 worker写成 nginx C 模块又要处理事件模型、内存池、生命周期和非阻塞 libssh复杂度远高于独立 gateway。
更稳的边界是:
```text
OpenResty = 四层入口、限流、日志、连接保护
libssh gateway = SSH 协议终止、认证、仓库路由、审计
Git backend = 实际仓库读写
```
## 测试命令
本地调试 gateway
```bash
ssh -vvv -p 2222 git@127.0.0.1 "git-upload-pack 'owner/repo.git'"
```
经过 OpenResty
```bash
GIT_SSH_COMMAND="ssh -vvv -p 22" git clone git@127.0.0.1:owner/repo.git
```
如果客户端报 host key changed说明现在 SSH 终止点换成了 gateway需要更新 known_hosts。

34
openresty/nginx.conf Normal file
View File

@ -0,0 +1,34 @@
worker_processes auto;
events {
worker_connections 4096;
}
stream {
log_format ssh_log '$remote_addr [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time';
access_log logs/git-ssh-access.log ssh_log;
error_log logs/git-ssh-error.log info;
limit_conn_zone $binary_remote_addr zone=ssh_ip:10m;
upstream libssh_git_gateway {
server 127.0.0.1:2222 max_fails=3 fail_timeout=10s;
}
server {
# Use 2220 for unprivileged local testing. Change to 22 in production.
listen 2220 reuseport;
proxy_connect_timeout 3s;
proxy_timeout 1h;
proxy_socket_keepalive on;
limit_conn ssh_ip 20;
proxy_pass libssh_git_gateway;
}
}

12
scripts/gen-host-key.sh Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env sh
set -eu
key_path="${1:-./ssh_host_ed25519_key}"
if [ -f "$key_path" ]; then
echo "host key already exists: $key_path"
exit 0
fi
ssh-keygen -t ed25519 -N "" -f "$key_path"

594
src/git_ssh_gateway.c Normal file
View File

@ -0,0 +1,594 @@
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <libssh/libssh.h>
#include <libssh/server.h>
#define DEFAULT_BIND_ADDR "127.0.0.1"
#define DEFAULT_BIND_PORT "2222"
#define DEFAULT_REPO_ROOT "./repositories"
#define DEFAULT_HOST_KEY "./ssh_host_ed25519_key"
#define IO_BUF_SIZE 32768
struct config {
const char *bind_addr;
const char *bind_port;
const char *repo_root;
const char *host_key;
};
struct git_request {
const char *program;
char repo[1024];
};
struct child_proc {
pid_t pid;
int stdin_fd;
int stdout_fd;
int stderr_fd;
};
static volatile sig_atomic_t g_stop = 0;
static void on_signal(int sig)
{
(void)sig;
g_stop = 1;
}
static void usage(const char *argv0)
{
fprintf(stderr,
"Usage: %s [--bind ADDR] [--port PORT] [--repo-root DIR] [--host-key FILE]\n"
"\n"
"Example:\n"
" %s --bind 127.0.0.1 --port 2222 --repo-root ./repositories --host-key ./ssh_host_ed25519_key\n",
argv0, argv0);
}
static struct config parse_args(int argc, char **argv)
{
struct config cfg = {
.bind_addr = DEFAULT_BIND_ADDR,
.bind_port = DEFAULT_BIND_PORT,
.repo_root = DEFAULT_REPO_ROOT,
.host_key = DEFAULT_HOST_KEY,
};
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--bind") == 0 && i + 1 < argc) {
cfg.bind_addr = argv[++i];
} else if (strcmp(argv[i], "--port") == 0 && i + 1 < argc) {
cfg.bind_port = argv[++i];
} else if (strcmp(argv[i], "--repo-root") == 0 && i + 1 < argc) {
cfg.repo_root = argv[++i];
} else if (strcmp(argv[i], "--host-key") == 0 && i + 1 < argc) {
cfg.host_key = argv[++i];
} else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
usage(argv[0]);
exit(0);
} else {
usage(argv[0]);
exit(2);
}
}
return cfg;
}
static int set_nonblock(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) {
return -1;
}
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
static void close_if_open(int *fd)
{
if (*fd >= 0) {
close(*fd);
*fd = -1;
}
}
static bool has_bad_repo_chars(const char *s)
{
for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
if (*p <= 0x20 || *p == 0x7f) {
return true;
}
switch (*p) {
case ';':
case '&':
case '|':
case '`':
case '$':
case '<':
case '>':
case '\\':
return true;
default:
break;
}
}
return false;
}
static int parse_git_exec(const char *command, struct git_request *req)
{
const char *programs[] = {
"git-upload-pack",
"git-receive-pack",
"git-upload-archive",
};
memset(req, 0, sizeof(*req));
for (size_t i = 0; i < sizeof(programs) / sizeof(programs[0]); i++) {
size_t len = strlen(programs[i]);
if (strncmp(command, programs[i], len) == 0 &&
(command[len] == ' ' || command[len] == '\t')) {
req->program = programs[i];
command += len;
break;
}
}
if (req->program == NULL) {
return -1;
}
while (*command == ' ' || *command == '\t') {
command++;
}
char quote = 0;
if (*command == '\'' || *command == '"') {
quote = *command++;
}
size_t n = 0;
while (*command) {
if (quote != 0) {
if (*command == quote) {
command++;
break;
}
} else if (*command == ' ' || *command == '\t') {
break;
}
if (n + 1 >= sizeof(req->repo)) {
return -1;
}
req->repo[n++] = *command++;
}
req->repo[n] = '\0';
while (*command == ' ' || *command == '\t') {
command++;
}
if (*command != '\0') {
return -1;
}
while (req->repo[0] == '/') {
memmove(req->repo, req->repo + 1, strlen(req->repo));
}
if (req->repo[0] == '\0' || strstr(req->repo, "..") != NULL ||
has_bad_repo_chars(req->repo)) {
return -1;
}
return 0;
}
static int authenticate_client(ssh_session session)
{
ssh_message msg;
ssh_set_auth_methods(session, SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_PASSWORD);
while ((msg = ssh_message_get(session)) != NULL) {
if (ssh_message_type(msg) == SSH_REQUEST_AUTH) {
int subtype = ssh_message_subtype(msg);
const char *user = ssh_message_auth_user(msg);
if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
if (ssh_message_auth_publickey_state(msg) == SSH_PUBLICKEY_STATE_NONE) {
ssh_message_auth_reply_pk_ok_simple(msg);
ssh_message_free(msg);
continue;
}
fprintf(stderr, "accepted publickey auth for user=%s\n", user ? user : "");
ssh_message_auth_reply_success(msg, 0);
ssh_message_free(msg);
return 0;
}
if (subtype == SSH_AUTH_METHOD_PASSWORD) {
fprintf(stderr, "accepted password auth for user=%s\n", user ? user : "");
ssh_message_auth_reply_success(msg, 0);
ssh_message_free(msg);
return 0;
}
}
ssh_message_reply_default(msg);
ssh_message_free(msg);
}
return -1;
}
static ssh_channel accept_exec_channel(ssh_session session, char *command, size_t command_len)
{
ssh_channel channel = NULL;
ssh_message msg;
while ((msg = ssh_message_get(session)) != NULL) {
if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL_OPEN &&
ssh_message_subtype(msg) == SSH_CHANNEL_SESSION) {
channel = ssh_message_channel_request_open_reply_accept(msg);
ssh_message_free(msg);
break;
}
ssh_message_reply_default(msg);
ssh_message_free(msg);
}
if (channel == NULL) {
return NULL;
}
while ((msg = ssh_message_get(session)) != NULL) {
if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL &&
ssh_message_subtype(msg) == SSH_CHANNEL_REQUEST_EXEC) {
const char *cmd = ssh_message_channel_request_command(msg);
if (cmd == NULL || strlen(cmd) + 1 > command_len) {
ssh_message_reply_default(msg);
ssh_message_free(msg);
ssh_channel_free(channel);
return NULL;
}
strcpy(command, cmd);
ssh_message_channel_request_reply_success(msg);
ssh_message_free(msg);
return channel;
}
ssh_message_reply_default(msg);
ssh_message_free(msg);
}
ssh_channel_free(channel);
return NULL;
}
static int spawn_git_child(const struct config *cfg, const struct git_request *req, struct child_proc *child)
{
int in_pipe[2] = {-1, -1};
int out_pipe[2] = {-1, -1};
int err_pipe[2] = {-1, -1};
child->pid = -1;
child->stdin_fd = -1;
child->stdout_fd = -1;
child->stderr_fd = -1;
if (pipe(in_pipe) != 0 || pipe(out_pipe) != 0 || pipe(err_pipe) != 0) {
perror("pipe");
goto fail;
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
goto fail;
}
if (pid == 0) {
dup2(in_pipe[0], STDIN_FILENO);
dup2(out_pipe[1], STDOUT_FILENO);
dup2(err_pipe[1], STDERR_FILENO);
close(in_pipe[0]);
close(in_pipe[1]);
close(out_pipe[0]);
close(out_pipe[1]);
close(err_pipe[0]);
close(err_pipe[1]);
if (chdir(cfg->repo_root) != 0) {
perror("chdir repo root");
_exit(127);
}
execlp(req->program, req->program, req->repo, (char *)NULL);
perror("execlp git program");
_exit(127);
}
close(in_pipe[0]);
close(out_pipe[1]);
close(err_pipe[1]);
child->pid = pid;
child->stdin_fd = in_pipe[1];
child->stdout_fd = out_pipe[0];
child->stderr_fd = err_pipe[0];
set_nonblock(child->stdout_fd);
set_nonblock(child->stderr_fd);
return 0;
fail:
close_if_open(&in_pipe[0]);
close_if_open(&in_pipe[1]);
close_if_open(&out_pipe[0]);
close_if_open(&out_pipe[1]);
close_if_open(&err_pipe[0]);
close_if_open(&err_pipe[1]);
return -1;
}
static int pump_channel_to_child(ssh_channel channel, int *child_stdin)
{
char buf[IO_BUF_SIZE];
for (;;) {
int n = ssh_channel_read_nonblocking(channel, buf, sizeof(buf), 0);
if (n == SSH_ERROR) {
return -1;
}
if (n == 0) {
break;
}
ssize_t off = 0;
while (off < n) {
ssize_t wr = write(*child_stdin, buf + off, (size_t)(n - off));
if (wr > 0) {
off += wr;
continue;
}
if (wr < 0 && errno == EINTR) {
continue;
}
close_if_open(child_stdin);
return -1;
}
}
if (ssh_channel_is_eof(channel)) {
close_if_open(child_stdin);
}
return 0;
}
static int pump_fd_to_channel(int *fd, ssh_channel channel, int is_stderr)
{
char buf[IO_BUF_SIZE];
for (;;) {
ssize_t n = read(*fd, buf, sizeof(buf));
if (n > 0) {
int rc = is_stderr ? ssh_channel_write_stderr(channel, buf, (uint32_t)n)
: ssh_channel_write(channel, buf, (uint32_t)n);
if (rc == SSH_ERROR) {
return -1;
}
continue;
}
if (n == 0) {
close_if_open(fd);
return 0;
}
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
return 0;
}
close_if_open(fd);
return -1;
}
}
static int relay(ssh_session session, ssh_channel channel, struct child_proc *child)
{
int exit_status = 255;
bool child_done = false;
while (!g_stop) {
struct pollfd fds[3];
nfds_t nfds = 0;
int ssh_fd = ssh_get_fd(session);
fds[nfds].fd = ssh_fd;
fds[nfds].events = POLLIN;
nfds++;
if (child->stdout_fd >= 0) {
fds[nfds].fd = child->stdout_fd;
fds[nfds].events = POLLIN;
nfds++;
}
if (child->stderr_fd >= 0) {
fds[nfds].fd = child->stderr_fd;
fds[nfds].events = POLLIN;
nfds++;
}
int rc = poll(fds, nfds, 100);
if (rc < 0 && errno != EINTR) {
perror("poll");
break;
}
if (child->stdin_fd >= 0 && pump_channel_to_child(channel, &child->stdin_fd) != 0) {
break;
}
if (child->stdout_fd >= 0 && pump_fd_to_channel(&child->stdout_fd, channel, 0) != 0) {
break;
}
if (child->stderr_fd >= 0 && pump_fd_to_channel(&child->stderr_fd, channel, 1) != 0) {
break;
}
if (!child_done) {
int status = 0;
pid_t got = waitpid(child->pid, &status, WNOHANG);
if (got == child->pid) {
child_done = true;
if (WIFEXITED(status)) {
exit_status = WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
exit_status = 128 + WTERMSIG(status);
}
}
}
if (child_done && child->stdout_fd < 0 && child->stderr_fd < 0) {
break;
}
}
close_if_open(&child->stdin_fd);
close_if_open(&child->stdout_fd);
close_if_open(&child->stderr_fd);
if (!child_done && child->pid > 0) {
int status = 0;
kill(child->pid, SIGTERM);
waitpid(child->pid, &status, 0);
}
ssh_channel_request_send_exit_status(channel, exit_status);
ssh_channel_send_eof(channel);
return exit_status;
}
static void handle_session(const struct config *cfg, ssh_session session)
{
char command[2048];
struct git_request req;
ssh_channel channel = NULL;
struct child_proc child;
memset(command, 0, sizeof(command));
if (ssh_handle_key_exchange(session) != SSH_OK) {
fprintf(stderr, "key exchange failed: %s\n", ssh_get_error(session));
return;
}
if (authenticate_client(session) != 0) {
fprintf(stderr, "authentication failed\n");
return;
}
channel = accept_exec_channel(session, command, sizeof(command));
if (channel == NULL) {
fprintf(stderr, "no exec channel\n");
return;
}
fprintf(stderr, "exec: %s\n", command);
if (parse_git_exec(command, &req) != 0) {
const char *msg = "unsupported or unsafe git ssh command\n";
ssh_channel_write_stderr(channel, msg, (uint32_t)strlen(msg));
ssh_channel_request_send_exit_status(channel, 127);
ssh_channel_send_eof(channel);
ssh_channel_close(channel);
ssh_channel_free(channel);
return;
}
fprintf(stderr, "git request: program=%s repo=%s\n", req.program, req.repo);
if (spawn_git_child(cfg, &req, &child) != 0) {
const char *msg = "failed to start git backend\n";
ssh_channel_write_stderr(channel, msg, (uint32_t)strlen(msg));
ssh_channel_request_send_exit_status(channel, 127);
} else {
relay(session, channel, &child);
}
ssh_channel_close(channel);
ssh_channel_free(channel);
}
int main(int argc, char **argv)
{
struct config cfg = parse_args(argc, argv);
ssh_bind bind = NULL;
signal(SIGINT, on_signal);
signal(SIGTERM, on_signal);
signal(SIGPIPE, SIG_IGN);
bind = ssh_bind_new();
if (bind == NULL) {
fprintf(stderr, "ssh_bind_new failed\n");
return 1;
}
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, cfg.bind_addr);
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, cfg.bind_port);
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY, cfg.host_key);
if (ssh_bind_listen(bind) != SSH_OK) {
fprintf(stderr, "listen failed: %s\n", ssh_get_error(bind));
ssh_bind_free(bind);
return 1;
}
fprintf(stderr,
"git-ssh-gateway listening on %s:%s, repo_root=%s, host_key=%s\n",
cfg.bind_addr, cfg.bind_port, cfg.repo_root, cfg.host_key);
while (!g_stop) {
ssh_session session = ssh_new();
if (session == NULL) {
continue;
}
if (ssh_bind_accept(bind, session) == SSH_ERROR) {
if (!g_stop) {
fprintf(stderr, "accept failed: %s\n", ssh_get_error(bind));
}
ssh_free(session);
continue;
}
handle_session(&cfg, session);
ssh_disconnect(session);
ssh_free(session);
}
ssh_bind_free(bind);
return 0;
}