当前位置: 首页 > news >正文

wrap go as a telnet client lib for c to implement a simple telnet client

server.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_RUNNINGprint(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):  # 回车/换行统一为\nclient_input += b"\n"elif 32 <= byte <= 126:  # 可见字符:仅追加,不回显client_input += bytes([byte])except socket.timeout:continueexcept 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'))continueprint(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)breakelse: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

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_EDITTELNET_CHAR_MODE      = C.TELNET_CHAR_MODE
)// 定义Go侧的TelnetMode类型
type TelnetMode int// 纯Go Telnet会话结构体
type telnetSession struct {conn    net.Connmode    TelnetModeprompt  stringrecvBuf []bytemu      sync.Mutexclosed  boolsigChan chan os.Signal
}// 全局会话映射
var (sessionMap = struct {sync.Mutexm 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() {<-sigChansessionMap.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] = sesssessionMap.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 = trues.conn.Close()close(s.sigChan)signal.Stop(s.sigChan)
}func main() {}

telnet_client.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 _WIN32HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);DWORD mode;GetConsoleMode(hStdin, &mode);SetConsoleMode(hStdin, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
#elsestatic 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 _WIN32return _kbhit() != 0;
#elsestruct 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 _WIN32char 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占用}}}
#elsessize_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

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

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

  1. client ctrl + c 没退出,按esc可以退出
  2. 第一次ls会说没找到命令
http://www.dtcms.com/a/336401.html

相关文章:

  • 堆的实际应用场景
  • 【Virtual Globe 渲染技术笔记】8 顶点变换精度
  • C11期作业17(07.05)
  • Microsoft WebView2
  • AMBA-AXI and ACE协议详解(十)
  • Rust:DLL 输出对象的生命周期管理
  • 影刀初级B级考试大题2
  • STM32CUBEMX配置stm32工程
  • Linux学习-多任务(线程)
  • LangChain4j
  • 三分钟在VMware虚拟机安装winXP教程,开箱即用
  • HTTP0.9/1.0/1.1/2.0
  • linux下timerfd和posix timer为什么存在较大的抖动?
  • USB-A 3.2 和 USB-A 2.0的区别
  • 集成电路学习:什么是ORB方向性FAST和旋转BRIEF
  • 外贸电商选品方案的模型
  • 天地图应用篇: 增加缩放、比例尺控件
  • 集运业务突围:三大关键问题的智能化解决方案
  • 【数据结构与算法-Day 16】队列的应用:广度优先搜索(BFS)的基石与迷宫寻路实战
  • vulnhub-lampiao靶机渗透
  • 002.Redis 配置及数据类型
  • 安装pytorch3d后报和本机cuda不符
  • LLM、RAG、Agent知识点思维导图
  • 简单了解BeanFactory和FactoryBean的区别
  • AMBA-AXI and ACE协议详解(八)
  • Critic-V: VLM Critics Help Catch VLM Errors in Multimodal Reasoning(CVPR 2025)
  • C++零拷贝网络编程实战:从理论到生产环境的性能优化之路
  • Word和Excel的一些功能记录
  • PHP现代化全栈开发:测试驱动开发与持续交付实践
  • 重温k8s基础概念知识系列二(Pod)