C言語はエンドプログラム--webshell基石として実現される
10791 ワード
これまでsshがどのように実現されたのか、ネット上にも関連コードの例がなかったので、自分でしばらく研究しました.このブログでは、主にサービス側とクライアントが、クライアントを通じてサービス側にリモートでログインし、shellコマンドを実行できる2つの程度を書いています.コード実装は粗いが,基本原理は一見でわかる.
一、主な核心思想:
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を置き換える.置き換える前にdup 2システム呼び出しにより、標準入力、寸法出力、標準エラー出力にリダイレクトする必要があります.ここで、ファイル記述子はslave fdに一定です.たとえば、次のように注意してください.
3)もちろん、execファミリー関数を実行する前に、いくつかの属性設定を行う必要があります.例えば、エコーを閉じる、ウィンドウサイズを設定するなど、コードを参照することができます.
二、コンパイルして実行する
三、コード
サービス側コード:
クライアントコード:
四、最適化
このコードの書くのは比較的に粗くて、いくつかの最適化を行う必要があります.例えば、多重化技術を採用してスレッド数を減らして、退格キー、tabキー、方向キーなどをサポートします.ここではレンガを投げて玉を引いて、みんなで勉強します.
一、主な核心思想:
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を置き換える.置き換える前にdup 2システム呼び出しにより、標準入力、寸法出力、標準エラー出力にリダイレクトする必要があります.ここで、ファイル記述子は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 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
", 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
", events[n].events);
if (events[n].events & (EPOLLIN | EPOLLOUT) == (EPOLLIN | EPOLLOUT)) {
printf(">> EPOLLIN And EPOLLOUT event, socketfd = %d
", events[n].data.fd);
}
else if (events[n].events & EPOLLIN) {
printf(">> Only EPOLLIN event, socketfd = %d
", events[n].data.fd);
}
else if (events[n].events & EPOLLOUT) {
printf(">> Only EPOLLOUT, socketfd = %d
", events[n].data.fd);
}
}
}
}
クライアントコード:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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キー、方向キーなどをサポートします.ここではレンガを投げて玉を引いて、みんなで勉強します.