项目地址:Academy remote system: 一种不需要客户端ip的命令行远程工具 - Gitee.com
项目介绍:
传统的远程命令行工具如ssh,scp都需要目标服务器的ip才可以连接。
我设计的这款命令行远程工具可以基于多个中间服务器进行远程,而不需要设置目标ip。
部分代码(20240317年更新)
academy_client-aca.c:
#include <stdio.h>
#include "yamlreader.h"
#include "codereader.h"
#include <string.h>
//#define DEBUGMODE
void show_copyright(void){printf("@copyright2024 Anchengan\n");
}
void show_help(void){printf("Help document for aca software:\n");printf("aca [authkey of remote client] [path to settings.yaml] [password of remote client]\n");printf("aca listenmode [path to settings.yaml]\n");
}
int is_path_in_ld_library_path(const char *path_to_check) {const char *ld_library_path = getenv("LD_LIBRARY_PATH");if (ld_library_path == NULL) {// 如果LD_LIBRARY_PATH未设置,则直接返回0(不在其中)return 0;}// 分割LD_LIBRARY_PATH并检查每个路径char *path_copy = strdup(ld_library_path); // 复制环境变量值char *token = strtok(path_copy, ":");while (token != NULL) {// 比较当前路径和要检查的路径if (strcmp(token, path_to_check) == 0) {free(path_copy); // 释放内存return 1; // 找到路径,返回1}token = strtok(NULL, ":"); // 继续查找下一个路径}free(path_copy); // 释放内存return 0; // 未找到路径,返回0
}
char* remove_char(const char* str, char c) {int count = 0;for (const char* p = str; *p; p++) {if (*p != c) {count++;}}// 分配足够的空间来存储新字符串char* result = (char*)malloc(count + 1); // +1 为 '\0'if (!result) {return NULL; // 内存分配失败}int i = 0;for (const char* p = str; *p; p++) {if (*p != c) {result[i++] = *p;}}result[i] = '\0'; // 确保新字符串以 '\0' 结尾return result;
}
int main(int argc, char *argv[]) {// 打印传递的参数if(argc-1==0){show_copyright();printf("Enter -h or --help after aca for help.\n");printf("Example:\naca -h\naca --help\n");return 0;}for(int i=1;i<=argc-1;i++){if(strcmp(argv[i],"-h")==0 || strcmp(argv[i],"--help")==0){show_copyright();show_help();return 0;}}if(argc-1>3){printf("Unknown usage of aca...\n");show_help();return 0;}int yamllength=strlen(argv[2]);if(yamllength<5 || argv[2][yamllength-5]!='.' || argv[2][yamllength-4]!='y' || argv[2][yamllength-3]!='a' || argv[2][yamllength-2]!='m' || argv[2][yamllength-1]!='l'){printf("Please check your path to settings.yaml!\n");printf("Example:./settings.yaml\n");show_help();return 0;}int islistenmode=0;if (strcmp(argv[1],"listenmode")==0){if(argc-1>2){printf("Unknown usage of aca...\n");show_help();return 0;}printf("listenmode setup successfully...\n");islistenmode=1;}else{printf("your authkey of remote client is:%s\n",argv[1]);}printf("setting env...\n");int env_return_value;const char *path_to_check = "./"; // 要检查的路径 const char *bashrc_path = getenv("HOME"); // 获取用户主目录if (bashrc_path == NULL) {perror("Error getting HOME environment variable");return EXIT_FAILURE;}// 拼接.bashrc文件的完整路径char full_path[1024];snprintf(full_path, sizeof(full_path), "%s/.bashrc", bashrc_path);// 打开.bashrc文件FILE *file = fopen(full_path, "r");if (file == NULL) {perror("Error opening ~/.bashrc");return EXIT_FAILURE;}// 要搜索的内容const char *search_content = "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./";char line[1024]; // 假设每行不会超过1023个字符// 逐行读取并搜索内容int found = 0; // 标记是否找到内容while (fgets(line, sizeof(line), file)) {if (strstr(line, search_content) != NULL) {
#ifdef DEBUGMODEprintf("Found the content in ~/.bashrc: %s", line);
#endiffound = 1; // 找到内容,设置标记break; // 如果只需要找到一次就退出循环}}// 关闭文件fclose(file);if (!is_path_in_ld_library_path(path_to_check) && !found) { // 使用system()函数运行命令行命令env_return_value = system("echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./' >> ~/.bashrc");// 检查命令的退出码
#ifdef DEBUGMODEif (env_return_value == -1) {// system()函数调用失败// 在这种情况下,你可以查看errno来获取错误详情(需要包含errno.h头文件)perror("system() failed");} else if (WIFEXITED(env_return_value)) {// 命令正常退出,WEXITSTATUS(env_return_value)可以获取命令的退出状态printf("Command exited with status %d\n", WEXITSTATUS(env_return_value));} else if (WIFSIGNALED(env_return_value)) {// 命令因为接收到信号而终止,WTERMSIG(env_return_value)可以获取信号的编号printf("Command terminated by signal %d\n", WTERMSIG(env_return_value));}
#endifprintf("env set successfully...\n");printf("please run: source ~/.bashrc\n");return 0;}else if(!is_path_in_ld_library_path(path_to_check) && found){printf("please run: source ~/.bashrc\n");return 0;}#include "libsender.h"#include "libreceiver.h"printf("your path to settings.yaml is:%s\n",argv[2]); printf("Reading settings.yaml...\n");struct item_ret* yaml_data = get_item(argv[2]);struct ip* ip_reader;struct port* port_reader;ip_reader=yaml_data->ip_items;port_reader=yaml_data->port_items;char mac_device[20];char c = ':'; strcpy(mac_device,yaml_data->mac_device);int iplength = 0;struct ip* ip_pin = ip_reader;while (ip_pin != NULL) {iplength++;ip_pin = ip_pin->next;}char send_ip_info[iplength*100];char send_port_info[iplength*100];while (ip_reader != NULL){printf("ip:%s\n", ip_reader->ip);strcat(send_ip_info,ip_reader->ip);strcat(send_ip_info,"-");ip_reader = ip_reader->next;for(int i=0;i<2;i++){printf("port:%s\n", port_reader->port);strcat(send_port_info,port_reader->port);strcat(send_port_info,"-");port_reader = port_reader->next;}}printf("settings.yaml read completed...\n");if(islistenmode){//waiting for connection...printf("Getting connecting code of this device...\n");struct device_ret* device_data=get_device_code(mac_device);char* device_code=device_data->mac_device;char* address_code=device_data->mac_address;printf("Connecting code of this device is:%s\n",device_code);char* password = remove_char(address_code, c);printf("Your password is:%s\n",password);Receiver(device_code,password,send_ip_info,send_port_info);}else{Sender(argv[1],argv[3],send_ip_info,send_port_info);//connecting...}return 0;
}
academy_server-acade.go:
package main
import ("fmt""bufio""net""sync""io/ioutil""log""os""strconv""gopkg.in/yaml.v2"
)
type client struct {conn net.Connconnid stringname stringmessages chan string
}
type Message struct {conn net.Connconnid stringSenderName stringContent string
}
var (entering = make(chan client)leaving = make(chan client)messages = make(chan Message) // 所有接收的消息
)
type Config struct {Database struct {Host string `yaml:"host"`Port int `yaml:"port"`Port2 int `yaml:"port2"`} `yaml:"database"`
}
func broadcast() {clients := make(map[string]client) // 键是客户端的名称,值是客户端结构体实例for {select {case msg := <-messages:// 将消息广播给除了发送者以外的所有客户端for _,cli := range clients {if cli.name != msg.SenderName && cli.connid==msg.connid {cli.messages<- msg.Content}}case newClient := <-entering:clients[newClient.name] = newClientcase leavingClient := <-leaving:delete(clients, leavingClient.name)close(leavingClient.messages)}}
}
func handleConn(conn net.Conn,connid string) {input := bufio.NewScanner(conn)var wg sync.WaitGroup// 创建一个新的客户端ch := make(chan string,1) // 客户端的消息通道go clientWriter(conn, ch)cli := client{conn: conn, connid:connid,name: conn.RemoteAddr().String(), messages: ch}entering <- clifmt.Println(cli.name)wg.Add(1)go func() {defer wg.Done()for input.Scan() {messages <-Message{SenderName: cli.name, Content: input.Text(),conn:conn,connid:connid} // cli.name + ": " + input.Text()_, err := conn.Write([]byte("Received your message\n"))if err != nil {fmt.Println("Error writing to connection:", err.Error())break}}}()wg.Wait()leaving <- cliconn.Close()
}
func clientWriter(conn net.Conn, ch <-chan string) {for msg := range ch {fmt.Fprintln(conn, msg) // 注意:网络写入应该有错误处理}
}func main() {argCount := len(os.Args) - 1if argCount == 0 {fmt.Println("没有传递任何参数。")fmt.Println("acade -h")fmt.Println("acade --help")os.Exit(1) // 使用非零状态码退出,表示错误 }// 如果参数数量不符合预期(例如,你需要恰好2个参数),提前返回 if argCount != 1 {fmt.Printf("需要1个参数,但传递了%d个参数。\n", argCount)os.Exit(1) // 使用非零状态码退出,表示错误 }yamlfilename:=os.Args[1]if yamlfilename=="-h" || yamlfilename=="--help"{fmt.Println("acade [path to settings.yaml]")os.Exit(0)}// 读取YAML文件内容 yamlFile, err := ioutil.ReadFile(yamlfilename)if err != nil {log.Fatalf("无法读取YAML文件: %v", err)}// 解析YAML内容 var config Configerr = yaml.Unmarshal(yamlFile, &config)if err != nil {log.Fatalf("无法解析YAML内容: %v", err)}// 输出解析后的内容 fmt.Printf("Host: %s\n", config.Database.Host)fmt.Printf("Port: %d\n", config.Database.Port)fmt.Printf("Port2: %d\n", config.Database.Port2)Host:=config.Database.HostPort:=strconv.Itoa(config.Database.Port)Port2:=strconv.Itoa(config.Database.Port2)listener, err := net.Listen("tcp",Host+":"+Port)if err != nil {fmt.Println(err)return}listener2, err2 := net.Listen("tcp",Host+":"+Port2)if err2 != nil {fmt.Println(err2)return}go broadcast()go func(){for {conn, err := listener.Accept()if err != nil {fmt.Println(err)continue}go handleConn(conn,"1")}}()go func(){for{conn2, err2 := listener2.Accept()if err2 != nil {fmt.Println(err2)continue}go handleConn(conn2,"2")}}()for{}// 程序正常执行完毕,使用零状态码退出 os.Exit(0)
}