py
复制代码
import socket
import threading
import time
import signal
from datetime import datetime
# 服务器配置
HOST = '0.0.0.0'
PORT = 2323
BUFFER_SIZE = 1024
PROMPT = "$ "
SERVER_RUNNING = True # 控制服务器运行状态
client_sockets = [] # 存储客户端连接
client_lock = threading.Lock()
# --------------------------
# 1. Ctrl+C信号处理(不变)
# --------------------------
def handle_sigint(signum, frame):
global SERVER_RUNNING
print(f"\n[!] 收到Ctrl+C信号,正在停止服务器...")
# 停止监听循环
SERVER_RUNNING = False
# 关闭所有客户端连接
with client_lock:
for sock in client_sockets:
try:
sock.sendall("\n服务器正在关闭,连接已断开。\n")
sock.close()
print(f"[-] 已关闭客户端连接: {sock.getpeername()}")
except Exception as e:
print(f"[!] 关闭客户端连接出错: {str(e)}")
client_sockets.clear()
print(f"[+] Telnet测试服务器已完全停止")
# --------------------------
# 2. 客户端交互逻辑(核心修复:删除多余回显)
# --------------------------
def handle_client(client_socket: socket.socket, client_addr: tuple):
# 加入客户端列表
with client_lock:
client_sockets.append(client_socket)
print(f"[+] 新连接来自: {client_addr}")
try:
# 1. 发送欢迎信息(保持不变)
welcome_msg = f"""
=====================================
Telnet 测试服务器 (Python)
欢迎您,连接来自: {client_addr[0]}:{client_addr[1]}
当前时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
支持命令: ls / pwd / date / whoami / help / exit
=====================================
{PROMPT}"""
client_socket.sendall(welcome_msg.encode('utf-8'))
# 2. 支持的测试命令(不变)
supported_cmds = {
'ls': lambda: "\n".join(["file1.txt", "file2.log", "docs/", "data.csv"]) + "\n",
'pwd': lambda: "/home/test/user\n",
'date': lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n",
'whoami': lambda: "test_user\n",
'help': lambda: "支持命令: ls, pwd, date, whoami, help, exit\n",
'exit': lambda: "再见!连接即将关闭...\n"
}
# 3. 客户端命令循环(核心修复:删除服务器端回显)
while SERVER_RUNNING:
client_input = b""
while b"\n" not in client_input and SERVER_RUNNING:
client_socket.settimeout(1)
try:
data = client_socket.recv(BUFFER_SIZE)
if not data: # 客户端断开
print(f"[-] 客户端 {client_addr} 主动断开连接")
return
# --------------------------
# 【核心修复1】删除服务器端回显
# 原错误:服务器主动回显客户端输入(如收到"ls"再发一次"ls")
# 修复后:仅处理输入,不回显(客户端已自行回显,避免重复)
# --------------------------
for byte in data:
if byte == 8: # 处理回退键(清除客户端输入)
if len(client_input) > 0:
client_input = client_input[:-1]
# 仅向客户端发送清除指令,不回显字符
client_socket.sendall(b"\x08 \x08")
elif byte in (13, 10): # 回车/换行统一为\n
client_input += b"\n"
elif 32 <= byte <= 126: # 可见字符:仅追加,不回显
client_input += bytes([byte])
except socket.timeout:
continue
except Exception as e:
print(f"[!] 接收客户端 {client_addr} 数据出错: {str(e)}")
return
# 4. 处理命令(不变)
cmd = client_input.decode('utf-8').strip().lower()
if not cmd: # 空输入:仅发送提示符
client_socket.sendall(PROMPT.encode('utf-8'))
continue
print(f"[*] 收到 {client_addr} 的命令: {cmd}")
# 5. 生成响应(不变)
if cmd in supported_cmds:
response = supported_cmds[cmd]()
if cmd == 'exit':
client_socket.sendall(response.encode('utf-8'))
time.sleep(0.3)
break
else:
response = f"错误: 未知命令 '{cmd}',输入 'help' 查看支持的命令\n"
# 6. 发送响应+提示符(不变)
client_socket.sendall(response.encode('utf-8'))
client_socket.sendall(PROMPT.encode('utf-8'))
except Exception as e:
if SERVER_RUNNING:
print(f"[!] 与 {client_addr} 交互出错: {str(e)}")
finally:
# 清理客户端连接
with client_lock:
if client_socket in client_sockets:
client_sockets.remove(client_socket)
client_socket.close()
print(f"[-] 与 {client_addr} 的连接已关闭")
# --------------------------
# 3. 服务器主逻辑(不变)
# --------------------------
def start_telnet_server():
signal.signal(signal.SIGINT, handle_sigint)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.setblocking(False)
try:
server_socket.bind((HOST, PORT))
server_socket.listen(5)
print(f"[+] Telnet测试服务器已启动,监听 {HOST}:{PORT}")
print(f"[+] 按 Ctrl+C 停止服务器\n")
while SERVER_RUNNING:
try:
client_socket, client_addr = server_socket.accept()
client_thread = threading.Thread(
target=handle_client,
args=(client_socket, client_addr),
daemon=True
)
client_thread.start()
except BlockingIOError:
time.sleep(0.1)
except Exception as e:
if SERVER_RUNNING:
print(f"[!] 服务器监听出错: {str(e)}")
finally:
server_socket.close()
if __name__ == "__main__":
start_telnet_server()
telnet_as_dll.go
go
复制代码
package main
// Windows require link with advapi32
/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -ladvapi32
#include <stdlib.h>
#include <string.h>
typedef struct {
void* session;
} TelnetHandle;
typedef enum {
TELNET_LINE_MODE_EDIT = 0,
TELNET_CHAR_MODE = 1
} TelnetMode;
*/
import "C"
import (
"errors"
"fmt"
"net"
"os"
"os/signal"
"sync"
"syscall"
"time"
"unsafe"
)
// 在Go侧定义对应的常量,解决未定义问题
const (
TELNET_LINE_MODE_EDIT = C.TELNET_LINE_MODE_EDIT
TELNET_CHAR_MODE = C.TELNET_CHAR_MODE
)
// 定义Go侧的TelnetMode类型
type TelnetMode int
// 纯Go Telnet会话结构体
type telnetSession struct {
conn net.Conn
mode TelnetMode
prompt string
recvBuf []byte
mu sync.Mutex
closed bool
sigChan chan os.Signal
}
// 全局会话映射
var (
sessionMap = struct {
sync.Mutex
m map[*C.TelnetHandle]*telnetSession
}{m: make(map[*C.TelnetHandle]*telnetSession)}
)
// 跨平台信号处理(Ctrl+C退出)
func init() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
sessionMap.Lock()
for _, sess := range sessionMap.m {
sess.Close()
}
sessionMap.m = make(map[*C.TelnetHandle]*telnetSession)
sessionMap.Unlock()
os.Exit(0)
}()
}
//export TelnetConnect
func TelnetConnect(host *C.char, port C.int) *C.TelnetHandle {
goHost := C.GoString(host)
goPort := int(port)
target := fmt.Sprintf("%s:%d", goHost, goPort)
conn, err := net.DialTimeout("tcp", target, 5*time.Second)
if err != nil {
fmt.Printf("[ERROR] Connect failed: %v\n", err)
return nil
}
sess := &telnetSession{
conn: conn,
mode: TELNET_LINE_MODE_EDIT,
recvBuf: make([]byte, 4096),
sigChan: make(chan os.Signal, 1),
}
cHandle := (*C.TelnetHandle)(C.malloc(C.sizeof_TelnetHandle))
sessionMap.Lock()
sessionMap.m[cHandle] = sess
sessionMap.Unlock()
return cHandle
}
//export TelnetSetMode
func TelnetSetMode(handle *C.TelnetHandle, mode C.int) C.int {
sessionMap.Lock()
sess, ok := sessionMap.m[handle]
sessionMap.Unlock()
if !ok || sess.closed {
return -1
}
sess.mu.Lock()
sess.mode = TelnetMode(mode)
if mode == C.TELNET_LINE_MODE_EDIT {
sess.conn.Write([]byte{255, 250, 34, 0, 255, 240})
}
sess.mu.Unlock()
return 0
}
//export TelnetSetTerminalType
func TelnetSetTerminalType(handle *C.TelnetHandle, termType *C.char) C.int {
sessionMap.Lock()
sess, ok := sessionMap.m[handle]
sessionMap.Unlock()
if !ok || sess.closed {
return -1
}
sess.mu.Lock()
defer sess.mu.Unlock()
// 【关键修复】Telnet终端类型协商的正确格式:
// IAC SB TERMINAL_TYPE SEND IAC SE (先发送"请求发送终端类型"的指令)
// 服务器回复后,再发送 IAC SB TERMINAL_TYPE IS <终端类型> IAC SE
// 原错误:直接发送终端类型,导致字符混入后续命令
negotiateCmd := []byte{
255, 250, // IAC SB(开始子协商)
24, // TERMINAL_TYPE(选项码24)
1, // SEND(请求服务器发送终端类型,实际应先发送此指令)
255, 240, // IAC SE(结束子协商)
}
// 发送协商指令(仅请求,不直接发送终端类型)
_, err := sess.conn.Write(negotiateCmd)
if err != nil {
return -1
}
// (可选)如果需要主动发送终端类型,需等待服务器响应后再发送,避免字符污染
// 此处简化处理,仅发送协商请求,不主动推送终端类型,解决前缀问题
return 0
}
//export TelnetSend
func TelnetSend(handle *C.TelnetHandle, data *C.char, length C.int) C.int {
sessionMap.Lock()
sess, ok := sessionMap.m[handle]
sessionMap.Unlock()
if !ok || sess.closed || length <= 0 {
return -1
}
// 【关键修复】仅发送客户端传来的数据,不额外添加换行符
goData := C.GoBytes(unsafe.Pointer(data), length)
sess.mu.Lock()
defer sess.mu.Unlock()
// 【删除】行模式下自动补换行的逻辑(客户端已处理)
if sess.mode == TELNET_LINE_MODE_EDIT && len(goData) > 0 && goData[len(goData)-1] != '\n' {
goData = append(goData, '\n')
}
n, err := sess.conn.Write(goData)
if err != nil {
return -1
}
return C.int(n)
}
//export TelnetRecv
func TelnetRecv(handle *C.TelnetHandle, buffer *C.char, bufferSize, timeoutMs C.int) C.int {
sessionMap.Lock()
sess, ok := sessionMap.m[handle]
sessionMap.Unlock()
if !ok || sess.closed || bufferSize <= 0 {
return -1
}
sess.mu.Lock()
defer sess.mu.Unlock()
sess.conn.SetReadDeadline(time.Now().Add(time.Duration(timeoutMs) * time.Millisecond))
n, err := sess.conn.Read(sess.recvBuf)
if err != nil {
if errors.Is(err, os.ErrDeadlineExceeded) {
return 0
}
if errors.Is(err, net.ErrClosed) {
return -2
}
return -1
}
copy((*[1 << 20]byte)(unsafe.Pointer(buffer))[:], sess.recvBuf[:n])
return C.int(n)
}
//export TelnetDetectPrompt
func TelnetDetectPrompt(handle *C.TelnetHandle, prompt *C.char) C.int {
sessionMap.Lock()
sess, ok := sessionMap.m[handle]
sessionMap.Unlock()
if !ok || sess.closed {
return -1
}
sess.mu.Lock()
sess.prompt = C.GoString(prompt)
sess.mu.Unlock()
return 0
}
//export TelnetClose
func TelnetClose(handle *C.TelnetHandle) {
sessionMap.Lock()
sess, ok := sessionMap.m[handle]
if ok {
sess.Close()
delete(sessionMap.m, handle)
}
sessionMap.Unlock()
C.free(unsafe.Pointer(handle))
}
// 会话关闭方法
func (s *telnetSession) Close() {
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return
}
s.closed = true
s.conn.Close()
close(s.sigChan)
signal.Stop(s.sigChan)
}
func main() {}
telnet_client.c
c
复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdbool.h>
#ifdef _WIN32
#include <windows.h>
#include <conio.h>
#else
#include <poll.h>
#include <termios.h>
#include <unistd.h>
#endif
#include "telnet.h"
TelnetHandle* g_handle = NULL;
volatile bool g_running = true;
char last_cmd[256] = {0}; // 存储纯命令(无换行)
// 信号处理(不变)
void signal_handler(int signum) {
if (signum == SIGINT || signum == SIGTERM) {
printf("\n收到退出信号,正在断开连接...\n");
g_running = false;
}
}
// 终端模式设置(不变)
void set_terminal_mode(bool raw) {
#ifdef _WIN32
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode;
GetConsoleMode(hStdin, &mode);
SetConsoleMode(hStdin, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
#else
static struct termios old_termios;
if (raw) {
tcgetattr(STDIN_FILENO, &old_termios);
struct termios new_termios = old_termios;
new_termios.c_lflag &= ~ICANON;
new_termios.c_lflag |= ECHO;
new_termios.c_cc[VMIN] = 1;
new_termios.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &new_termios);
} else {
tcsetattr(STDIN_FILENO, TCSANOW, &old_termios);
}
#endif
}
// 检查用户输入(不变)
bool has_user_input() {
#ifdef _WIN32
return _kbhit() != 0;
#else
struct pollfd pfd = {STDIN_FILENO, POLLIN, 0};
return poll(&pfd, 1, 0) > 0;
#endif
}
// 【修复1】彻底初始化输入状态,消除残留字符
int read_user_input(char* buffer, size_t max_len) {
memset(buffer, 0, max_len); // 清空缓冲区
static size_t input_pos = 0; // 静态变量确保初始化为0(编译器默认)
// 【关键】每次读取前重置输入位置(避免残留)
input_pos = 0;
#ifdef _WIN32
char c;
while (1) {
// 【修复2】读取前先清空控制台残留事件(避免读取到历史按键)
while (_kbhit()) {
c = _getch();
// 仅保留当前按键,丢弃历史残留(如引号)
if (c == '\r' || c == '\b' || c == 27 || (c >= 32 && c <= 126)) {
break;
}
}
// 处理有效按键
if (c == '\r') { // 回车:结束输入
if (input_pos > 0) {
buffer[input_pos] = '\n'; // 仅添加1个换行符
input_pos++;
printf("\n");
return input_pos;
}
c = 0; // 重置无效回车
continue;
} else if (c == '\b' && input_pos > 0) { // 退格
input_pos--;
printf("\b \b");
c = 0;
} else if (c == 27) { // ESC:退出
strcpy(buffer, "exit\n");
return strlen(buffer);
} else if (c >= 32 && c <= 126 && input_pos < max_len - 2) { // 可见字符
buffer[input_pos] = c;
input_pos++;
printf("%c", c);
c = 0;
} else {
// 过滤所有无效字符(如引号、控制符)
if (_kbhit()) {
c = _getch();
} else {
Sleep(10); // 降低CPU占用
}
}
}
#else
ssize_t n = read(STDIN_FILENO, buffer, max_len - 1);
if (n > 0) {
char* nl = strchr(buffer, '\n');
if (nl) {
*nl = '\0';
strncpy(last_cmd, buffer, sizeof(last_cmd)-1);
*nl = '\n';
}
return n;
}
return 0;
#endif
}
// 【修复3】增强响应过滤:精准匹配命令回显(避免漏删)
void filter_echo_response(char* response, int recv_len) {
if (strlen(last_cmd) == 0) return;
// 匹配两种回显格式:1. 命令+换行 2. 命令(无换行,服务器自动补)
char cmd_form1[256]; // 格式1:命令\n(如"ls\n")
char cmd_form2[256]; // 格式2:命令(如"ls")
snprintf(cmd_form1, sizeof(cmd_form1), "%s\n", last_cmd);
snprintf(cmd_form2, sizeof(cmd_form2), "%s\n", last_cmd);
int len1 = strlen(cmd_form1);
int len2 = strlen(cmd_form2);
// 优先匹配格式1(最常见)
if (recv_len >= len1 && strncmp(response, cmd_form1, len1) == 0) {
memmove(response, response + len1, recv_len - len1);
response[recv_len - len1] = '\0';
}
// 再匹配格式2(避免漏删)
else if (recv_len >= len2 && strncmp(response, cmd_form2, len2) == 0) {
memmove(response, response + len2, recv_len - len2);
response[recv_len - len2] = '\0';
}
}
int main(int argc, char* argv[]) {
char* host = "localhost";
int port = 2323;
if (argc >= 2) host = argv[1];
if (argc >= 3) port = atoi(argv[2]);
printf("Telnet 客户端 - 连接 %s:%d\n", host, port);
printf("提示:输入命令按回车发送,输入'exit'或按Ctrl+C/ESC退出\n\n");
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
set_terminal_mode(true);
g_handle = TelnetConnect(host, port);
if (!g_handle) {
fprintf(stderr, "错误:无法连接 %s:%d\n", host, port);
set_terminal_mode(false);
return 1;
}
// 【修复4】不启用行模式自动补换行(避免DLL重复添加)
TelnetSetMode(g_handle, TELNET_LINE_MODE_EDIT);
TelnetDetectPrompt(g_handle, "$ ");
char recv_buf[4096] = {0};
char send_buf[1024] = {0};
while (g_running) {
// 接收服务器数据
int recv_len = TelnetRecv(g_handle, recv_buf, sizeof(recv_buf)-1, 100);
if (recv_len > 0) {
recv_buf[recv_len] = '\0';
filter_echo_response(recv_buf, recv_len);
if (strlen(recv_buf) > 0) {
printf("%s", recv_buf);
fflush(stdout);
}
memset(recv_buf, 0, sizeof(recv_buf));
} else if (recv_len == -2) {
printf("\n错误:服务器已断开连接\n");
break;
}
// 处理用户输入
if (has_user_input()) {
int send_len = read_user_input(send_buf, sizeof(send_buf));
if (send_len > 0) {
// 保存纯命令(用于过滤)
char* nl_pos = strchr(send_buf, '\n');
if (nl_pos != NULL) {
*nl_pos = '\0';
strncpy(last_cmd, send_buf, sizeof(last_cmd)-1);
*nl_pos = '\n'; // 恢复1个换行符
}
// 发送命令(仅含用户输入+1个换行符)
TelnetSend(g_handle, send_buf, send_len);
if (strncmp(send_buf, "exit\n", 5) == 0) {
g_running = false;
}
memset(send_buf, 0, sizeof(send_buf));
}
}
}
printf("\n正在关闭连接...\n");
TelnetClose(g_handle);
set_terminal_mode(false);
printf("客户端已退出\n");
return 0;
}
build on Windows
cmd
复制代码
go build -v -buildmode=c-shared -o telnet.dll telnet_as_dll.go
gcc telnet_client.c -o telnet_client.exe -ltelnet -L.
up python server and test output
cmd
复制代码
Telnet 客户端 - 连接 localhost:2323
提示:输入命令按回车发送,输入'exit'或按Ctrl+C/ESC退出
=====================================
Telnet 测试服务器 (Python)
欢迎您,连接来自: 127.0.0.1:49857
当前时间: 2025-08-17 19:22:51
支持命令: ls / pwd / date / whoami / help / exit
=====================================
$ ls
错误: 未知命令 '"ls',输入 'help' 查看支持的命令
$ pwd
/home/test/user
$ date
2025-08-17 19:22:58
$ whoami
test_user
$ help
支持命令: ls, pwd, date, whoami, help, exit
$ exit
正在关闭连接...
客户端已退出
bug
- client ctrl + c 没退出,按esc可以退出
- 第一次ls会说没找到命令