编程实战:自己编写HTTP服务器(系列5:执行后台shell命令)

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。


系列入口:编程实战:自己编写HTTP服务器(系列1:概述和应答)-CSDN博客

         本文介绍执行后台命令的shell.asp的实现。

目录

一、概述

二、主体代码

三、详解

3.1 参数

3.2 设置进程组和打开管道执行命令

3.3 读取数据和返回码处理


一、概述

        这个功能就相当于一个终端,不过只能执行一个命令。有什么好处看自己,可以加入自己喜欢的特性。

        入口:

        别的不说了,主体代码是doPageShell()。

二、主体代码

        主体代码如下:

		bool doPageShell(){
#ifdef _MS_VCreturn true;
#elseFILE * fp;string changedir=m_request.GetParam("changedir");string curdir=m_request.GetParam("curdir");string cmd=m_request.GetParam("command");bool noform=(m_request.GetParam("noform")=="true");bool term=(m_request.GetParam("term")=="true");long bufsize=1024*1024;char * buf=new char[bufsize];if(NULL==buf){m_respond.AppendBody("<P><FONT color=RED>内存不足</FONT><P>");return true;}//切换路径if(0!=curdir.size()){if(0!=chdir(curdir.c_str())){m_respond.AppendBody("<P><FONT color=RED>设置初始路径出错</FONT><P>"+curdir+"<P>");return true;}}if(0!=changedir.size()){if(0!=chdir(changedir.c_str())){m_respond.AppendBody("<P><FONT color=RED>切换工作路径出错</FONT><P>"+changedir+"<P>");return true;}}//执行命令if(0==cmd.size()){m_respond.AppendBody("<P>空命令<P>");}else{if(0!=setpgid(getpid(),getpid())){m_respond.AppendBody("设置进程组ID出错<P>");}if(NULL==(fp=popen((cmd+" 2>&1").c_str(),"r"))){m_respond.AppendBody("<P><FONT color=RED>无法执行,原因:popen error</FONT><P>");if(!m_respond.Flush(m_s))return true;}else{int fd=fileno(fp);int flags;fd_set fdset;struct timeval tv;tv.tv_sec=300;tv.tv_usec=0;flags = fcntl(fd, F_GETFL, 0);flags |= O_NONBLOCK;fcntl(fd, F_SETFL, flags);char * tmpp;m_respond.AppendBody("开始执行&nbsp;");m_respond.AppendBody(CHtmlDoc::HTMLEncode(cmd));m_respond.Flush(m_s);m_respond.AppendBody("<HR></HR><CODE>");while(true){FD_ZERO(&fdset);FD_SET(fd,&fdset);
#ifdef _HPOSint selectret=select(fd+1,(int *)&fdset,NULL,NULL,&tv);
#elseint selectret=select(fd+1,&fdset,NULL,NULL,&tv);
#endifif(selectret<0){LOG<<"select error"<<ENDE;}else if(0==selectret){//超时没有数据m_respond.AppendBody("注意,长时间没有收到输出.");if(!m_respond.Flush(m_s)){LOG<<"发送失败,客户端已断开,直接退出"<<ENDI;if(term){if(0!=kill(0,SIGTERM)){LOG<<"发送停止信号出错,shell会持续执行到命令结束"<<ENDE;}}return true;}continue;}else{}bool fileend=false;while(true){tmpp=fgets(buf,int(bufsize-1),fp);if(NULL==tmpp){//正常结束if(0!=feof(fp)){fileend=true;break;}if(EWOULDBLOCK==errno || EAGAIN==errno){//无数据break;}else{//出错结束m_respond.AppendBody("<P><FONT color=RED>执行出错,原因:read error</FONT><P>");m_respond.AppendBody(strerror(errno));m_respond.Flush(m_s);fileend=true;break;}}else{m_respond.AppendBody(LogToHtml(buf,false,false));m_respond.AppendBodyHtmlScroll();if(!m_respond.Flush(m_s)){LOG<<"发送失败,客户端已断开,直接退出"<<ENDI;if(term){if(0!=kill(0,SIGTERM)){LOG<<"发送停止信号出错,shell会持续执行到命令结束"<<ENDE;}}return true;}}}if(fileend)break;}m_respond.AppendBody("</CODE><HR></HR>");int ret=pclose(fp);if(0!=ret){if(WIFEXITED(ret)){sprintf(buf,"<FONT color=RED>执行完毕,返回代码 %d 。</FONT><BR>",WEXITSTATUS(ret));//(0xFF00&ret)/256);}else if(WIFSIGNALED(ret)){sprintf(buf,"<FONT color=RED>被信号终止,信号 %d 。</FONT><BR>",WTERMSIG(ret));}else if(WCOREDUMP(ret)){sprintf(buf,"<FONT color=RED>执行失败,COREDUMP。</FONT><BR>");}else{sprintf(buf,"<FONT color=RED>未知的返回值:%d。</FONT><BR>",ret);}}else sprintf(buf,"执行完毕,返回代码 %d 。<BR>",ret);m_respond.AppendBody(buf);}}if(!noform){char cwd[1024];if(NULL!=getcwd(cwd,1024)){sprintf(buf,"<FORM ACTION=\"/shell.asp\" METHOD=\"GET\" >\n""当前路径:%s<BR>""<INPUT TYPE=\"hidden\" NAME=\"curdir\" VALUE=\"%s\" >\n""切换路径到:<INPUT TYPE=\"text\" SIZE=\"30\" NAME=\"changedir\" ><BR>\n""Shell命令: <INPUT TYPE=\"text\" SIZE=\"30\" NAME=\"command\" VALUE=\"%s\">\n""<INPUT TYPE=SUBMIT VALUE=\"执行\" >\n""</FORM>\n",cwd,cwd,cmd.c_str());}else{sprintf(buf,"获取当前工作路径出错");}m_respond.AppendBody(buf);}delete[] buf;return true;
#endif}

三、详解

3.1 参数

        最关键命令参数:command,包含要执行的命令,可以是一串命令的组合,也就是你能输到控制台运行的东西都行。

        运行命令一定需要工作目录,显然不能在服务进程的工作目录下执行,谁知道会发生什么呢。目录用curdir和changedir参数来控制,如果curdir是当前工作目录,具体就是这个页面执行命令的工作目录,changedir用于切换目录,可以是相对目录。执行时首先将目录切换到curdir(因为服务进程有自己的工作目录,和期待的执行命令的工作目录不同),然后再切换到changedir,其实就是执行两次chdir()。

        代码如下:

			//切换路径if(0!=curdir.size()){if(0!=chdir(curdir.c_str())){m_respond.AppendBody("<P><FONT color=RED>设置初始路径出错</FONT><P>"+curdir+"<P>");return true;}}if(0!=changedir.size()){if(0!=chdir(changedir.c_str())){m_respond.AppendBody("<P><FONT color=RED>切换工作路径出错</FONT><P>"+changedir+"<P>");return true;}}

3.2 设置进程组和打开管道执行命令

         启动新进程一般都要设置点东西,解除新进程和服务进程的关系,主要是进程间使用进程组广播信号的问题,新进程压根不应该知道服务进程存在。

        popen()很实用的运行命令并获取输出的方法,其内部会打开单向管道,运行命令,返回输出(文件句柄)。

        为了能看到命令的所有输出,我们需要在命令后面追加“ 2>&1”将标准出错重定向到标准输出。

        相关代码如下:

				if(0!=setpgid(getpid(),getpid())){m_respond.AppendBody("设置进程组ID出错<P>");}if(NULL==(fp=popen((cmd+" 2>&1").c_str(),"r"))){m_respond.AppendBody("<P><FONT color=RED>无法执行,原因:popen error</FONT><P>");if(!m_respond.Flush(m_s))return true;}else{

        后面就是常规的从文件描述符读取数据直到结束。当然里面混合了格式化、发送,以及命令返回码的处理。

3.3 读取数据和返回码处理

       select可以检查是否有数据,这个函数一般用于socket,但是其实是针对文件描述符的。

       用fgets()逐行读取数据,如果出错errno会有各种结果,需要分别判断。

       pclose获得返回码,具体分析和进程返回码是一样的。


(这里是结束,但不是整个系列的结束)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/706297.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【文档理解】TextMonkey:一种OCR-Free的用于文档理解的多模态大模型

背景 传统的信息提取&#xff0c;通常是从文本中提取信息&#xff0c;相关技术也比较成熟。然而对于复杂领域&#xff0c;例如图片&#xff0c;文档等形式的数据&#xff0c;想要提取出高质量的、可信的数据难度就比较大了&#xff0c;这种任务也常称为&#xff1a;视觉文档理…

职业生涯第一课---“Redis分布式锁优化:确保唯一性与效率“

前言 最近因为刚入职公司开启自己的实习生涯&#xff0c;工作和毕设论文同步进行&#xff0c;导致有段时间没更新博客了&#xff0c;今天来分享一下最近学到的一些知识。 场景介绍 BOSS让我写一些接口&#xff0c;他提出这样一个需求&#xff0c;该接口的参数有多个&#xf…

Windows下配置TortoiseGit 访问Ubuntu虚拟机下Samba共享目录

前言&#xff1a; 本文记录学习使用 Git 版本管理工具的学习笔记&#xff0c;通过阅读参考链接中的博文和实际操作&#xff0c;快速的上手使用 Git 工具。 本文参考了引用链接博文里的内容。 引用: 【TortoiseGit】TortoiseGit安装和配置详细说明-CSDN博客 Git版本管理可视…

ubuntu下安装pwndbg

安装pwndbg 如果可以科学上网 首先安装git apt install git 然后拉取git库 git clone GitHub - pwndbg/pwndbg: Exploit Development and Reverse Engineering with GDB Made Easy 进入到pwngdb的文件夹中 cd pwngdb 执行 ./setup.sh 而后输入gdb 出现红色pwndgb就是安装成功…

ALV 图标显示

前言 在ABAP ALV中&#xff0c;使用fieldcat来定义列表中每个字段的显示属性&#xff0c;包括图标&#xff08;Icon&#xff09;的显示。图标可以在ALV列表中为特定列的行或标题添加图形元素&#xff0c;以增强视觉提示或传达附加信息。 ICON查询 图标的名称用事务码”ICON“进…

析构函数详解

目录 析构函数概念特性对象的销毁顺序 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412; 个人主页 &#x1f978;&#x1f978;&#x1f978; C语言 &#x1f43f;️&#x1f43f;️&#x1f43f;️ C语言例题 &…

设计模式与软件体系结构课后练习参考答案

目录 软件设计模式第二章 创建型软件设计模式1. 工厂模式2. 生成器模式3. 单例模式 第三章 结构型软件设计模式1. 组合模式2. 适配器模式3. 外观模式4. 桥接模式 第四章 行为型软件设计模式1. 迭代器模式2. 访问者模式3. 中介者模式4. 策略模式5. 状态模式 案例分析工厂模式案例…

LED电源质量和性能测试解析

LED电源的性能对于确保照明系统的稳定性和效率至关重要。在LED技术不断进步的今天&#xff0c;对电源进行严格的测试成为了一项挑战。本文将详细探讨LED电源的测试项目&#xff0c;包括电性能、保护功能和安规测试&#xff0c;以及可靠性测试&#xff0c;旨在为测试员提供一个全…

Springboot开发 -- Postman 调试 session 验证 接口

当我们在开发Spring Boot应用时&#xff0c;经常会遇到带有Session验证的接口&#xff0c;这些接口需要用户先登录并获取到Session ID&#xff08;或称为cookie中的JSESSIONID&#xff09;&#xff0c;然后在后续的请求中携带这个Session ID来保持会话状态。下面我将以一个实际…

6---Linux下版本控制器Git的知识点

一、Linux之父与Git的故事&#xff1a; Linux之父叫做“Linus Torvalds”&#xff0c;我们简称为雷纳斯。Linux是开源项目&#xff0c;所以在Linux的早期开发中&#xff0c;许多世界各地的能力各异的程序员都参与到Linux的项目开发中。那时&#xff0c;雷纳斯每天都会收到许许…

STM32HAL库-中断篇

中断 中断简介 中断是一种事件处理机制&#xff0c;可以暂停主程序的运行&#xff0c;转而处理特定事件程序。 中断的作用和意义&#xff1a; 实时控制 在确定事件内对响应事件做出相应 故障处理 检测到故障需要第一时间处理 数据传输 如串口通信&#xff0c;不确定数…

大模型对数据库运维的赋能:智能运维的新时代

引言随着人工智能技术的飞速发展&#xff0c;大模型作为AI领域的前沿技术&#xff0c;已经开始在数据库运维(DBA)领域展现出其独特的价值。大模型的引入&#xff0c;不仅提升了数据库运维的效率&#xff0c;还极大地改善了运维的质量和智能化水平。本文将深入分析大模型在数据库…