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

TCP远程命令执行

目录

一.   命令集

二.   命令执行模块实现

三.   服务端模块实现

四.   服务端调用模块实现

五.   客户端模块实现

六.   效果展示


此篇教大家如何利用TCP进行远程命令执行。

一.   命令集

将值得信任的命令放进一个txt文件中,执行命令时,就去这个文件里面找,有就执行命令,没有就不执行。

ls -a -l
pwd
tree
whoami
who
uname -a
cat
touch

注意我们是用空格隔开的。

二.   命令执行模块实现

依然封装成类,将上述命令集写进类中。

class Command
{
public:Command(const string& cond_path):_cond_path(cond_path){}~Command(){}
private:set<string> _safe_cmd;string _cond_path;
};

有着两个成员,首先是set类型,即命令集。其次是上述文件集的路径。

 初始化路径之后,我们再来将命令写进集合中。

const string sep=" ";string PrefixCommand(const string& cmd)
{if(cmd.empty()){return string();}auto pos=cmd.find(sep);if(pos==string::npos){return cmd;}else{return cmd.substr(0,pos);}
}void LoadConf(const string& conf)
{ifstream in(conf);if(!in.is_open()){LOG(FATAL,"open %s error\n",conf);return;}string line;while(getline(in,line)){LOG(DEBUG,"load command [%s] success\n",line.c_str());_safe_cmd.insert(PrefixCommand(line));}in.close();
}

LoadConf函数即初始化集合函数,利用文件读取流打开文件,一行一行读取命令集,并且只取第一个空格前面的字符插入进集合中。下面会讲为什么。

这个操作可以在构造函数的时候实现,所以可以将这个函数加入到构造函数中。

Command(const string& cond_path)
:_cond_path(cond_path)
{LoadConf(_cond_path);
}

 此处肯定需要检测输入的命令是否在命令集中。我们只检查输入命令第一个空格前的字符是否相同即可。例如"ls -a -l"只需检测ls即可。为什么呢?

例如touch指令,后面肯定要加创建的文件名字,这样就需要连着文件名一起检查在不在命令集中,这显然是不合理的,文件名有无数个,怎么能列举完呢。所以只需检测第一个空格前面相不相同即可。

来看如何检查:

const string sep=" ";string PrefixCommand(const string& cmd)
{if(cmd.empty()){return string();}auto pos=cmd.find(sep);if(pos==string::npos){return cmd;}else{return cmd.substr(0,pos);}
}bool SafeCheck(const string& cmd)
{string prefix=PrefixCommand(cmd);auto iter=_safe_cmd.find(prefix);if(iter==_safe_cmd.end()){return false;}return true;
}string Excute(const string& cmd)
{string result;if(SafeCheck(cmd)){FILE* fp=popen(cmd.c_str(),"r");if(fp==nullptr){return "failed";}char buffer[1024];while(fgets(buffer,sizeof(buffer),fp)!=NULL){result+=buffer;}pclose(fp);}else{result="坏人\n";}return result;
}

Excute函数就是具体如何处理输入的正确指令,重点就是运用popen函数

FILE *popen(const char *command, const char *type);

popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程。 

参数说明:

command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令,比如sh -c ls

type: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。

返回值:

如果调用 fork() 或 pipe() 失败,或者不能分配内存将返回NULL,否则返回一个读或者打开文件的指针。

 最后合起来就是命令指令模块:

const string sep=" ";class Command
{
private:void LoadConf(const string& conf){ifstream in(conf);if(!in.is_open()){LOG(FATAL,"open %s error\n",conf);return;}string line;while(getline(in,line)){LOG(DEBUG,"load command [%s] success\n",line.c_str());_safe_cmd.insert(PrefixCommand(line));}in.close();}
public:Command(const string& cond_path):_cond_path(cond_path){LoadConf(_cond_path);}string PrefixCommand(const string& cmd){if(cmd.empty()){return string();}auto pos=cmd.find(sep);if(pos==string::npos){return cmd;}else{return cmd.substr(0,pos);}}bool SafeCheck(const string& cmd){string prefix=PrefixCommand(cmd);auto iter=_safe_cmd.find(prefix);if(iter==_safe_cmd.end()){return false;}return true;}string Excute(const string& cmd){string result;if(SafeCheck(cmd)){FILE* fp=popen(cmd.c_str(),"r");if(fp==nullptr){return "failed";}char buffer[1024];while(fgets(buffer,sizeof(buffer),fp)!=NULL){result+=buffer;}pclose(fp);}else{result="坏人\n";}return result;}~Command(){}
private:set<string> _safe_cmd;string _cond_path;
};

其中LOG函数是封装的日志功能:

#pragma once//日志
#include<iostream>
#include<fstream>
#include<cstdio>
#include<string>
#include<ctime>
#include<unistd.h>
#include<sys/types.h>
#include<stdarg.h>
#include<pthread.h>
#include"LockGuard.hpp"using namespace std;bool gIsSave=false;
const string logname="log.txt";void SaveFile(const string& filename,const string& message)
{ofstream out(filename,ios::app);if(!out.is_open()){return;}out<<message;out.close();
}//1.日志是有等级的
enum Level
{DEBUG=0,INFO,WARNING,ERROR,FATAL
};string LevelToString(int level)
{switch(level){case DEBUG: return "Debug";break;case INFO: return "Info";break;case WARNING: return "Warning";break;case ERROR: return "Error";break;case FATAL: return "Fatal";break;default: return "Unknown";break;}
}string GetTimeString()
{time_t curr_time=time(nullptr);struct tm* format_time=localtime(&curr_time);if(format_time==nullptr) return "None";char time_buffer[64];snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",format_time->tm_year+1900,format_time->tm_mon+1,format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return time_buffer;
}pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;//2.日志是由格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容...
void LogMessage(string filename,int line,bool issave,int level,const char* format,...)
{string levelstr=LevelToString(level);string timestr=GetTimeString();pid_t selfid=getpid();//可变参数部分处理char buffer[1024];va_list arg;va_start(arg,format);vsnprintf(buffer,sizeof(buffer),format,arg);va_end(arg);LockGuard lockguard(&lock);string message;message="["+timestr+"]"+"["+levelstr+"]"+"[pid: "+to_string(selfid)+"]"+"["+filename+"]"+"["+to_string(line)+"]"+buffer+"\n";if(!issave){cout<<message;}else{SaveFile(logname,message);}
}void Test(int num,...)
{va_list arg;va_start(arg,num);while(true){int data=va_arg(arg,int);cout<<"data: "<<data<<endl;num--;}va_end(arg);//arg==NULL
}//C99新特性 __VA_ARGS__
#define LOG(level,format,...) do {LogMessage(__FILE__,__LINE__,gIsSave,level,format,##__VA_ARGS__);} while(0)
#define EnableFile() do {gIsSave=true;} while(0)
#define EnableScreen() do {gIsSave=false;} while(0)

三.   服务端模块实现

具体的实现跟前面TCP服务端模块实现一样(点此查看)。此处我们采用多线程来实现,就不过多赘述。

#include<iostream>
#include<string>
#include<unistd.h>
#include<cstring>
#include<pthread.h>
#include<functional>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>#include"Log.hpp"
#include"InetAddr.hpp"const static int defaultsockfd=-1;
const static int gbacklog=10;class TcpServer;struct ThreadData
{
public:ThreadData(int fd,InetAddr addr,TcpServer* s):sockfd(fd),clientaddr(addr),self(s){}
public:int sockfd;InetAddr clientaddr;TcpServer* self;
};enum
{SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERROR,USAGE_ERROR
};using func_t=function<string(const string&)>;class TcpServer
{
public:TcpServer(int port,func_t func):_port(port),_listensock(defaultsockfd),_isrunning(false),_func(func){}void InitServer(){//1.创建流式套接字_listensock=::socket(AF_INET,SOCK_STREAM,0);if(_listensock<0){LOG(FATAL,"socket error\n");exit(SOCKET_ERROR);}LOG(DEBUG,"socket create success, sockfd is: %d\n",_listensock);//2.bindstruct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=INADDR_ANY;int n=::bind(_listensock,(struct sockaddr*)&local,sizeof(local));if(n<0){LOG(FATAL,"bind error\n");exit(BIND_ERROR);}LOG(DEBUG,"bind success,sockfd is: %d\n",_listensock);//3.tcp是面向连接的,所以通信之前,必须先建立连接,服务器是被连接的//tcpserver启动,未来首先要一直等待客户的连接到来n=::listen(_listensock,gbacklog);if(n<0){LOG(FATAL,"listen error\n");exit(LISTEN_ERROR);}LOG(DEBUG,"listen success,sockfd is: %d\n",_listensock);}void Service(int sockfd,InetAddr client){LOG(DEBUG,"get a new link,info %s:%d,fd: %d\n",client.Ip().c_str(),client.Port(),sockfd);string clientaddr="["+client.Ip()+":"+to_string(client.Port())+"]# ";while(true){char inbuffer[1024];ssize_t n=recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);if(n>0){inbuffer[n]=0;cout<<clientaddr<<inbuffer<<endl;string result=_func(inbuffer);send(sockfd,result.c_str(),result.size(),0);}else if(n==0){//client退出&&关闭连接了LOG(INFO,"%s quit\n",clientaddr.c_str());break;}else{LOG(ERROR,"read error\n",clientaddr.c_str());break;}}::close(sockfd);//不关闭会发生文件描述符泄露}static void* HandlerSock(void* args)//IO和业务解耦{pthread_detach(pthread_self());ThreadData* td=static_cast<ThreadData*>(args);td->self->Service(td->sockfd,td->clientaddr);delete td;return nullptr;}void Loop(){_isrunning=true;//4.不能直接接收数据,应该先获取连接while(true){struct sockaddr_in peer;socklen_t len=sizeof(peer);int sockfd=::accept(_listensock,(struct sockaddr*)&peer,&len);if(sockfd<0){LOG(WARNING,"accept error\n");continue;}//采用多线程//此处不能像多进程一样关闭文件描述符,因为多线程文件描述符表是共享的pthread_t t;ThreadData* td=new ThreadData(sockfd,InetAddr(peer),this);pthread_create(&t,nullptr,HandlerSock,td);//将线程分离}_isrunning=false;}~TcpServer(){if(_listensock>defaultsockfd){::close(_listensock);}}
private:uint16_t _port;int _listensock;bool _isrunning;func_t _func;
};

其中InetAddr.hpp文件是我们封装的。

#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>using namespace std;class InetAddr
{
private:void GetAddress(string* ip,uint16_t* port){*port=ntohs(_addr.sin_port);//网络字节序转为主机字节序*ip=inet_ntoa(_addr.sin_addr);//将网络字节序IP转为点分式十进制IP}
public:InetAddr(const struct sockaddr_in &addr):_addr(addr){GetAddress(&_ip,&_port);}string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr(){}
private:struct sockaddr_in _addr;string _ip;uint16_t _port;
};

四.   服务端调用模块实现

只需创建出服务端类的对象,依次调用InitServer和Loop函数即可。并创建出执行命令类对象。

#include"CommandExcute.hpp"
#include"TcpServer.hpp"
#include<memory>using namespace std;void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<" local_port\n"<<endl;
}// ./tcpserver port
int main(int argc,char *argv[])
{if(argc!=2){Usage(argv[0]);return 1;}EnableScreen();uint16_t port=stoi(argv[1]);Command cmd("./safe.txt");func_t cmdExec=bind(&Command::Excute,&cmd,placeholders::_1);unique_ptr<TcpServer> tsvr=make_unique<TcpServer>(port,cmdExec);tsvr->InitServer();tsvr->Loop();return 0;
}

五.   客户端模块实现

客户端还是没有变化。不懂得可看此处(点此查看)。

#include<iostream>
#include<string>
#include<unistd.h>
#include<cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>#include"Log.hpp"using namespace std;void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<" serverip serverport\n"<<endl;
}// ./tcpclient serverip serverport
int main(int argc,char *argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}string serverip=argv[1];uint16_t serverport=stoi(argv[2]);int sockfd=::socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){cerr<<"socket error\n"<<endl;exit(2);}//与udpclient一样,不需显式bind//构建目标主机的socket信息struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(serverport);server.sin_addr.s_addr=inet_addr(serverip.c_str());int n=connect(sockfd,(struct sockaddr*)&server,sizeof(server));if(n<0){cerr<<"connect error\n"<<endl;exit(3);}while(true){cout<<"Please Enter#";string outstring;getline(cin,outstring);ssize_t s=send(sockfd,outstring.c_str(),outstring.size(),0);//writeif(s>0){char inbuffer[1024];ssize_t m=recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);if(m>0){inbuffer[m]=0;cout<<inbuffer<<endl;}else{break;}}else{break;}}
}

六.   效果展示

可以看见只要在命令集中的命令都能执行。


总结:

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 详细阐述Activity的生命周期
  • 下班后做小红书第7个月,涨粉7w,累计变现5w+,我只用到五个点
  • FPGA开发:Verilog基础语法
  • 【稀疏矩阵】使用torch.sparse模块
  • ML20_贝叶斯模型平均BMA详解
  • LeetCode 61. 旋转链表
  • 再识Clip
  • clickhouse 保证幂等性
  • 9月11号作业
  • Netty权威指南:Netty总结-编解码与序列化
  • 【数据结构-二维前缀和】力扣221. 最大正方形
  • 18069 x的n次方
  • 【CSS in Depth 2 精译_029】5.2 Grid 网格布局中的网格结构剖析(上)
  • digits Social Login插件 google OAuth 2.0登录 400 redirect_uri_mismatch错误解决
  • Python 从入门到实战14(字符串相关操作)
  • docker-consul
  • iOS 颜色设置看我就够了
  • javascript面向对象之创建对象
  • laravel 用artisan创建自己的模板
  • LeetCode541. Reverse String II -- 按步长反转字符串
  • leetcode98. Validate Binary Search Tree
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • miaov-React 最佳入门
  • Tornado学习笔记(1)
  • 阿里云ubuntu14.04 Nginx反向代理Nodejs
  • 成为一名优秀的Developer的书单
  • 飞驰在Mesos的涡轮引擎上
  • 记一次删除Git记录中的大文件的过程
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 扑朔迷离的属性和特性【彻底弄清】
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 首页查询功能的一次实现过程
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • ​MySQL主从复制一致性检测
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • #每日一题合集#牛客JZ23-JZ33
  • (1) caustics\
  • (3)llvm ir转换过程
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (附源码)ssm教材管理系统 毕业设计 011229
  • (一)认识微服务
  • (原創) 博客園正式支援VHDL語法著色功能 (SOC) (VHDL)
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • (转载)PyTorch代码规范最佳实践和样式指南
  • .bat批处理出现中文乱码的情况
  • .gitignore文件_Git:.gitignore
  • .NET BackgroundWorker
  • .net core Swagger 过滤部分Api
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .NET Framework杂记
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .NET成年了,然后呢?