C语言实现为终端程序--webshell基石

之前对ssh一直很困惑它是如何实现的,网上也没有相关代码实例,所以自己花了一段时间研究了一下。本篇博客主要写了两个程度:服务端和客户端,通过客户端可以远程登录服务端,执行shell命令。代码实现的比较糙,但是基本原理一看就明白。

一、主要核心思想:

1)创建pty虚拟终端,即open("/dev/ptmx", O_RDWR | O_NOCTTY),关于pty的介绍网上有很多,这里简单说明一下pty类似我们管道,但是pty是全双工的。pty有master、slave,两者之间可以进行通信。当我open的时候返回的是master文件描述,那么slave如何打开呢?调用ptsname(master-fd),返回文件路径,然后在open即可。注意:ptsname入参一定是master文件描述

2)服务端需要fork一个子进程,然后用exec家族函数进行替换/bin/sh。在替换之前需要通过dup2系统调用,重定向标准输入、标注输出、标准错误输出,这里需要注意,这里文件描述符一定slave fd,例如:

    /* Duplicate pty slave to be child's stdin, stdout, and stderr */
    dup2(slave, STDIN_FILENO);
    dup2(slave, STDOUT_FILENO);
    dup2(slave, STDERR_FILENO);

3)当然在exec家族函数执行之前我们需要可以进行一些属性设置,例如:关闭回显、设置窗口大小等详细可以参考代码

二、编译并运行

[root@localhost epoll-pipe]# gcc Server.c -o ShellServer -g -lpthread
[root@localhost epoll-pipe]# 
[root@localhost epoll-pipe]# ./ShellServer 
[root@localhost epoll-pipe]# gcc -g -o client Client.c  -lpthread
[root@localhost epoll-pipe]# 
[root@localhost epoll-pipe]# ./client 127.0.0.1
sh-4.2# ifconfig 
enp0s31f6: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether d4:81:d7:c6:4a:d9  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 16  memory 0xef200000-ef220000  

三、代码

服务端代码:

#define _XOPEN_SOURCE
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <pthread.h>

#define SERVPORT 9527
#define MAXBUF 10240
#define MAXFDS 5000
#define EVENTSIZE 100

int setnonblocking(int fd)
{
    int opts;
    if( (opts = fcntl(fd, F_GETFL, 0)) == -1) {
        perror("fcntl");
        return -1;
    }

    opts = opts | O_NONBLOCK;
    if( (opts = fcntl(fd, F_SETFL, opts)) == -1) {
        perror("fcntl");
        return -1;
    }

    return 0;
}


int setcloexec(int fd)
{
    int opts;
    if( (opts = fcntl(fd, F_GETFL, 0)) == -1) {
        perror("fcntl");
        return -1;
    }

    opts = opts | FD_CLOEXEC;
    if( (opts = fcntl(fd, F_SETFL, opts)) == -1) {
        perror("fcntl");
        return -1;
    }

    return 0;
}



void* handle_child_output(void* data) {
    char buf[MAXBUF] = {0};
    int sockfd = (*(uint64_t*)data) & 0xFFFFFFF;
    int master = (int)((*(uint64_t*)data) >> 32);
    while(1) {
        int nread = read(master, buf, MAXBUF);
        send(sockfd, buf, nread, 0);
    }
}

int create_pty(char *slavename, size_t length)
{
    int master;
    char *p;

    /* Open pty master */
    master = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    if (master == -1)
        return -1;
    /* Grant access to slave pty */
    if (grantpt(master) == -1) {
        close(master);
        return -1;
    }
    /* Unlock slave pty */
    if (unlockpt(master) == -1) {
        close(master);
        return -1;
    }

    /* Get slave pty name. */
    p = ptsname(master);
    if (p == NULL) {
        close(master);
        return -1;
    }

    if (strlen(p) < length) {
        strncpy(slavename, p, length);
    } else {/* Return an error if buffer too small */
        close(master);
        return -1;
    }

    return master;
}

void child_routine(int sockfd, const char* slavename) {

    /* Start a new session */
    if (setsid() == -1) {
        exit(0);
    }

    /* Becomes controlling tty */
    int slave = open(slavename, O_RDWR);
    if (slave == -1) {
        exit(0);
    }

    /* disable ECHO attribute */
    struct termios termios;
    if (tcgetattr(slave, &termios) == -1)
        exit(0);
    termios.c_lflag &= ~(ECHO);

    if (tcsetattr(slave, TCSANOW, &termios) == -1) {
        exit(0);
    }
#if 0
    struct winsize old_wins;
    if (ioctl(slave, TIOCSWINSZ, &old_wins) == -1) {
        exit(0);
    }
#endif
    //根据客户窗口代码进行设置,以便输出结果能够优雅
    struct winsize size;
    recv(sockfd, &size, sizeof(struct winsize), 0);
    ioctl(slave, TIOCSWINSZ, &size);
    close(sockfd);

    /* Duplicate pty slave to be child's stdin, stdout, and stderr */
    dup2(slave, STDIN_FILENO);
    dup2(slave, STDOUT_FILENO);
    dup2(slave, STDERR_FILENO);

    if (slave > STDERR_FILENO)        /* Safety check */
        close(slave);                 /* No longer need this fd */

    char *args[] = {"/bin/sh", "-i", NULL};
    execv("/bin/sh", args);
    return;
}

void* service_routine(void* data) {
    int sockfd = *(int*)data;
    char slavename[256] = {0};
    int pty_master = create_pty(slavename, 256);//创建pty虚拟终端
    int pid = vfork();
    if (pid < 0) {
        exit(1);
    } else if (pid == 0) {
        // pty master fd is not useful in child-process.
        close(pty_master);
        child_routine(sockfd, slavename);
    } else {
        char buf[MAXBUF] = {0};
        pthread_t thread_id;
        uint64_t data = pty_master;
        data = data << 32 | sockfd;
        pthread_create(&thread_id, NULL, handle_child_output, &data);

        while (1) {
            int bytes = recv(sockfd, buf, MAXBUF, 0);
            printf("Recv from client: bytes = %d, errno=%d\n", bytes, errno);
            if (bytes > 0) {
                write(pty_master, buf, bytes);
            }
        }
    }
}


int main(void)
{
    char buf[MAXBUF];
    int len, n;

    struct sockaddr_in servaddr;
    int sockfd, listenfd, epollfd, nfds;

    struct epoll_event ev;
    struct epoll_event events[EVENTSIZE];

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERVPORT);

    if( (epollfd = epoll_create(MAXFDS)) == -1) {
        perror("epoll");
        exit(1);
    }
    if(setcloexec(epollfd) == -1){
        perror("setcloexec");
        exit(1);
    }

    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    if(setcloexec(listenfd) == -1){
        perror("setcloexec");
        exit(1);
    }

    if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("bind");
        exit(1);
    }

    if(listen(listenfd, 10) == -1) {
        perror("listen");
        exit(1);
    }

    // listen fd只注册EPOLLIN事件, EPOLLOUT不需要注册
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = listenfd;
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {
        perror("epoll_ctl");
        exit(1);
    }

    for( ; ; ) {
        if( (nfds = epoll_wait(epollfd, events, EVENTSIZE, -1)) == -1) {
            perror("epoll_wait");
            exit(1);
        }

        for(n = 0; n < nfds; n++) {
            if(events[n].data.fd == listenfd) {
                while( (sockfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) > 0) {
                    //创建服务线程
                    pthread_t thread_id;
                    pthread_create(&thread_id, NULL, service_routine, &sockfd);
                }
                continue;
            }
            printf("Events = 0x%x\n", events[n].events);
            if (events[n].events & (EPOLLIN | EPOLLOUT) == (EPOLLIN | EPOLLOUT)) {
                printf(">> EPOLLIN And EPOLLOUT event, socketfd = %d\n", events[n].data.fd);
            }
            else if (events[n].events & EPOLLIN) {
                printf(">> Only EPOLLIN event, socketfd = %d\n", events[n].data.fd);
            }
            else if (events[n].events & EPOLLOUT) {
                printf(">> Only EPOLLOUT, socketfd = %d\n", events[n].data.fd);
            }
        }
    }
}

客户端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <pthread.h>

#define SERVPORT 9527
#define MAXBUF 10240

int setnonblocking(int fd)
{
    int opts;
    if( (opts = fcntl(fd, F_GETFL, 0)) == -1) {
        perror("fcntl");
        return -1;
    }

    opts = opts | O_NONBLOCK;
    if( (opts = fcntl(fd, F_SETFL, opts)) == -1) {
        perror("fcntl");
        return -1;
    }

    return 0;
}


void* handle_input(void* d) {
    int fd = *(int*)d;
    char buf[MAXBUF];
    while(1) {
        //read cmd from TERMIAL and send it
        int nbytes = read(STDIN_FILENO, buf, MAXBUF);
        buf[nbytes] = '\0';
        //send
        send(fd, buf, nbytes, 0);
        buf[0] = '\0';
    }
    return NULL;
}

int main(int argc, char* argv[])
{
    char buf[MAXBUF];
    struct sockaddr_in servaddr;
    int fd;
    int n, len;

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    if (argc > 1) {
        servaddr.sin_addr.s_addr = inet_addr(argv[1]);
    } else {
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    }
    servaddr.sin_port = htons(SERVPORT);

    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    if (connect(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("connect");
        exit(1);
    }
    pthread_t thread_id;
    pthread_create(&thread_id, NULL, handle_input, &fd);

    int bytes = 0;
    struct winsize size;
    ioctl(STDIN_FILENO, TIOCGWINSZ, &size);
    bytes = send(fd, &size, sizeof(struct winsize), 0);

    while(1) {
        //resv response
        bytes = recv(fd, buf, MAXBUF, 0);
        write(STDOUT_FILENO, buf, bytes);
    }

    close(fd);// 会出发server端 EPOLLIN和EPOLLOUT
    return 0;
}

四、优化

此代码写的比较糙,需要做一些优化,例如:采用多路复用技术减少线程数,支持退格键、tab键、方向键等。这里启动抛砖引玉,大家一起学习。

展开阅读全文

Git 实用技巧

11-24
这几年越来越多的开发团队使用了Git,掌握Git的使用已经越来越重要,已经是一个开发者必备的一项技能;但很多人在刚开始学习Git的时候会遇到很多疑问,比如之前使用过SVN的开发者想不通Git提交代码为什么需要先commit然后再去push,而不是一条命令一次性搞定; 更多的开发者对Git已经入门,不过在遇到一些代码冲突、需要恢复Git代码时候就不知所措,这个时候哪些对 Git掌握得比较好的少数人,就像团队中的神一样,在队友遇到 Git 相关的问题的时候用各种流利的操作来帮助队友于水火。 我去年刚加入新团队,发现一些同事对Git的常规操作没太大问题,但对Git的理解还是比较生疏,比如说分支和分支之间的关联关系、合并代码时候的冲突解决、提交代码前未拉取新代码导致冲突问题的处理等,我在协助处理这些问题的时候也记录各种问题的解决办法,希望整理后通过教程帮助到更多对Git操作进阶的开发者。 本期教程学习方法分为“掌握基础——稳步进阶——熟悉协作”三个层次。从掌握基础的 Git的推送和拉取开始,以案例进行演示,分析每一个步骤的操作方式和原理,从理解Git 工具的操作到学会代码存储结构、演示不同场景下Git遇到问题的不同处理方案。循序渐进让同学们掌握Git工具在团队协作中的整体协作流程。 在教程中会通过大量案例进行分析,案例会模拟在工作中遇到的问题,从最基础的代码提交和拉取、代码冲突解决、代码仓库的数据维护、Git服务端搭建等。为了让同学们容易理解,对Git简单易懂,文章中详细记录了详细的操作步骤,提供大量演示截图和解析。在教程的最后部分,会从提升团队整体效率的角度对Git工具进行讲解,包括规范操作、Gitlab的搭建、钩子事件的应用等。 为了让同学们可以利用碎片化时间来灵活学习,在教程文章中大程度降低了上下文的依赖,让大家可以在工作之余进行学习与实战,并同时掌握里面涉及的Git不常见操作的相关知识,理解Git工具在工作遇到的问题解决思路和方法,相信一定会对大家的前端技能进阶大有帮助。
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值