200 行
6.6 KiB
C
200 行
6.6 KiB
C
/* pssh sshd —— SSH2 服务端 (平替 sshd)
|
|
*
|
|
* 用户库: ~/.pssh/users.conf (可用 -u <file> 覆盖)
|
|
* 行格式: 用户名:SHA256hex(64):权限 (权限 0=受限 1=普通 2=管理员)
|
|
* 可用 pssh useradd 添加用户
|
|
*/
|
|
#include "paze/ssh.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <winsock2.h>
|
|
typedef SOCKET sock_t;
|
|
#define SOCK_INVALID INVALID_SOCKET
|
|
typedef int socklen_t;
|
|
#else
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
typedef int sock_t;
|
|
#define SOCK_INVALID (-1)
|
|
#define closesocket close
|
|
typedef socklen_t socklen_t;
|
|
#endif
|
|
|
|
#define MAX_USERS 64
|
|
|
|
static int tcp_read(void *ctx, uint8_t *buf, size_t n) {
|
|
sock_t s = *(sock_t *)ctx;
|
|
return (int)recv(s, (char *)buf, (int)n, 0);
|
|
}
|
|
static int tcp_write(void *ctx, const uint8_t *buf, size_t n) {
|
|
sock_t s = *(sock_t *)ctx;
|
|
return (int)send(s, (char *)buf, (int)n, 0);
|
|
}
|
|
|
|
static sock_t tcp_listen(int port) {
|
|
#ifdef _WIN32
|
|
WSADATA wsa;
|
|
WSAStartup(MAKEWORD(2, 2), &wsa);
|
|
#endif
|
|
sock_t s = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (s == SOCK_INVALID) return SOCK_INVALID;
|
|
int opt = 1;
|
|
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
|
|
struct sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
addr.sin_port = htons(port);
|
|
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
closesocket(s); return SOCK_INVALID;
|
|
}
|
|
if (listen(s, 5) < 0) {
|
|
closesocket(s); return SOCK_INVALID;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/* 读取整个文件到堆缓冲(末尾补 NUL);失败返回 NULL */
|
|
static char *read_file(const char *path) {
|
|
FILE *fp = fopen(path, "rb");
|
|
if (!fp) return NULL;
|
|
long sz;
|
|
if (fseek(fp, 0, SEEK_END) != 0 || (sz = ftell(fp)) < 0 ||
|
|
fseek(fp, 0, SEEK_SET) != 0) {
|
|
fclose(fp);
|
|
return NULL;
|
|
}
|
|
char *buf = (char *)malloc((size_t)sz + 1);
|
|
if (!buf) { fclose(fp); return NULL; }
|
|
size_t rd = fread(buf, 1, (size_t)sz, fp);
|
|
fclose(fp);
|
|
buf[rd] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
int pssh_cmd_sshd(int argc, char **argv) {
|
|
setvbuf(stderr, NULL, _IONBF, 0);
|
|
if (argc < 2) {
|
|
fprintf(stderr,
|
|
"用法: pssh sshd [-p <port>] [-u <users-file>] [-a <authorized_keys>]\n"
|
|
" -p <port> 监听端口 (默认 2222)\n"
|
|
" -u <file> 用户库文件 (默认 ~/.pssh/users.conf)\n"
|
|
" -a <file> authorized_keys 文件(启用公钥认证)\n"
|
|
" -v 详细日志(认证/通道调试)\n"
|
|
" (前台运行,Ctrl+C 退出)\n");
|
|
return 1;
|
|
}
|
|
int port = 2222;
|
|
int verbose = 0;
|
|
char users_path[1024] = "";
|
|
char ak_path[1024] = "";
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) port = atoi(argv[++i]);
|
|
else if (strcmp(argv[i], "-u") == 0 && i + 1 < argc) {
|
|
snprintf(users_path, sizeof(users_path), "%s", argv[++i]);
|
|
} else if (strcmp(argv[i], "-a") == 0 && i + 1 < argc) {
|
|
snprintf(ak_path, sizeof(ak_path), "%s", argv[++i]);
|
|
} else if (strcmp(argv[i], "-v") == 0) {
|
|
verbose = 1;
|
|
}
|
|
}
|
|
|
|
/* 默认用户库路径: ~/.pssh/users.conf */
|
|
if (users_path[0] == '\0') {
|
|
const char *home = getenv("USERPROFILE");
|
|
if (!home) home = getenv("HOME");
|
|
if (home) snprintf(users_path, sizeof(users_path), "%s/.pssh/users.conf", home);
|
|
else snprintf(users_path, sizeof(users_path), ".pssh/users.conf");
|
|
}
|
|
|
|
/* 加载用户库 */
|
|
static ssh_user_t users[MAX_USERS];
|
|
int nusers = 0;
|
|
char *text = read_file(users_path);
|
|
if (!text) {
|
|
fprintf(stderr,
|
|
"paze-sshd: 找不到用户库 %s\n"
|
|
" 请先创建用户, 例如: pssh useradd root -pwd <密码> -perm 2\n"
|
|
" 或指定其它文件: pssh sshd -u <file>\n",
|
|
users_path);
|
|
return 1;
|
|
}
|
|
nusers = ssh_userdb_parse(text, users, MAX_USERS);
|
|
free(text);
|
|
if (nusers < 0) {
|
|
fprintf(stderr, "paze-sshd: 用户库格式错误: %s\n", users_path);
|
|
return 1;
|
|
}
|
|
if (nusers == 0) {
|
|
fprintf(stderr, "paze-sshd: 用户库为空: %s\n", users_path);
|
|
return 1;
|
|
}
|
|
fprintf(stderr, "paze-sshd: 加载 %d 个用户 (库: %s)\n", nusers, users_path);
|
|
|
|
/* 加载 authorized_keys(可选,启用 publickey 认证) */
|
|
char *ak_text = NULL;
|
|
size_t ak_len = 0;
|
|
if (ak_path[0] != '\0') {
|
|
ak_text = read_file(ak_path);
|
|
if (!ak_text) {
|
|
fprintf(stderr, "paze-sshd: 找不到 authorized_keys %s\n", ak_path);
|
|
return 1;
|
|
}
|
|
ak_len = strlen(ak_text);
|
|
fprintf(stderr, "paze-sshd: 加载 authorized_keys %s (%zu 字节)\n",
|
|
ak_path, ak_len);
|
|
} else {
|
|
/* 默认尝试 ~/.ssh/authorized_keys */
|
|
const char *home = getenv("USERPROFILE");
|
|
if (!home) home = getenv("HOME");
|
|
char def_ak[1024];
|
|
if (home) snprintf(def_ak, sizeof(def_ak), "%s/.ssh/authorized_keys", home);
|
|
else snprintf(def_ak, sizeof(def_ak), ".ssh/authorized_keys");
|
|
ak_text = read_file(def_ak);
|
|
if (ak_text) {
|
|
ak_len = strlen(ak_text);
|
|
fprintf(stderr, "paze-sshd: 加载 authorized_keys %s (%zu 字节)\n",
|
|
def_ak, ak_len);
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "paze-sshd: listening on port %d\n", port);
|
|
sock_t listen_sock = tcp_listen(port);
|
|
if (listen_sock == SOCK_INVALID) {
|
|
fprintf(stderr, "paze-sshd: listen failed\n");
|
|
return 1;
|
|
}
|
|
|
|
for (;;) {
|
|
struct sockaddr_in client;
|
|
socklen_t clen = sizeof(client);
|
|
sock_t conn = accept(listen_sock, (struct sockaddr *)&client, &clen);
|
|
if (conn == SOCK_INVALID) continue;
|
|
|
|
fprintf(stderr, "paze-sshd: accepted connection\n");
|
|
|
|
sock_t *ps = (sock_t *)malloc(sizeof(sock_t));
|
|
*ps = conn;
|
|
ssh_session_t *s = ssh_session_new(1);
|
|
if (!s) { closesocket(conn); free(ps); continue; }
|
|
ssh_session_set_io(s, ps, tcp_read, tcp_write);
|
|
ssh_session_set_users(s, users, nusers);
|
|
ssh_session_set_peer_ip(s, inet_ntoa(client.sin_addr)); /* 供失败日志/fail2ban */
|
|
if (verbose) ssh_session_set_verbose(s, 1);
|
|
|
|
ssh_server_loop(s, (const uint8_t *)ak_text, ak_len);
|
|
|
|
ssh_session_free(s);
|
|
closesocket(conn);
|
|
free(ps);
|
|
fprintf(stderr, "paze-sshd: connection closed\n");
|
|
}
|
|
closesocket(listen_sock);
|
|
return 0;
|
|
}
|