telnetd源码分析

  telnetd是一个telnet服务端程序

 

下载地址:http://www.busybox.net/

解压缩后进入busybox目录

make defconfig

make

make install

然后会生成 _install 目录,里面就是编译好的可执行文件

源码位于 ./networking/telnetd.c

 

程序流程图:

 

程序中非常重要的就是2个buf,位于struct tsession结构体之后

 

/*
   This is how the buffers are used. The arrows indicate data flow.

   +-------+     wridx1++     +------+     rdidx1++     +----------+
   |       | <--------------  | buf1 | <--------------  |          |
   |       |     size1--      +------+     size1++      |          |
   |  pty  |                                            |  socket  |
   |       |     rdidx2++     +------+     wridx2++     |          |
   |       |  --------------> | buf2 |  --------------> |          |
   +-------+     size2++      +------+     size2--      +----------+

   size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
   size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"

   Each session has got two buffers. Buffers are circular. If sizeN == 0,
   buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
   rdidxN == wridxN.
*/


 socket接收到远端的数据,写入count个字节到buf1中从rdidx1开始的空闲区域,然后size1 += count;rdidx1 += count;
pty可以写,从buf1中读取count个字节写入pty,然后size1 -= count;wridx1 += count;
pty可以读,写入count个字节到buf2中从rdidx2开始的空闲区域,然后size2 += count;rdidx2 += count;
socket可以发送数据到远端,从buf2中wridx2开始的位置读取count个字节,通过socket发送出去,然后size2 -= count;wridx2 += count;

 

master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
xlisten(master_fd, 1);

FD_ZERO(&rdfdset);
FD_ZERO(&wrfdset);

ts = G.sessions;
while (ts) {
    struct tsession *next = ts->next; /* in case we free ts */
    if (ts->shell_pid == -1) {
        /* Child died and we detected that */
        free_session(ts);
    } else {
        if (ts->size1 > 0)       /* can write to pty */
            FD_SET(ts->ptyfd, &wrfdset);
        if (ts->size1 < BUFSIZE) /* can read from socket */
            FD_SET(ts->sockfd_read, &rdfdset);
        if (ts->size2 > 0)       /* can write to socket */
            FD_SET(ts->sockfd_write, &wrfdset);
        if (ts->size2 < BUFSIZE) /* can read from pty */
            FD_SET(ts->ptyfd, &rdfdset);
    }
    ts = next;
}

FD_SET(master_fd, &rdfdset);
if (master_fd > G.maxfd)
    G.maxfd = master_fd;

count = select(G.maxfd + 1, &rdfdset, &wrfdset, NULL, tv_ptr);

if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
    int fd;
    struct tsession *new_ts;
    fd = accept(master_fd, NULL, NULL);
    new_ts = make_new_session(fd);
    new_ts->next = G.sessions;
    G.sessions = new_ts;
}




/* Then check for data tunneling */
ts = G.sessions;
while (ts) { /* For all sessions... */
    struct tsession *next = ts->next; /* in case we free ts */

    if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
        int num_totty;
        unsigned char *ptr;
        /* Write to pty from buffer 1 */
        ptr = remove_iacs(ts, &num_totty);
        count = safe_write(ts->ptyfd, ptr, num_totty);
        if (count < 0) {
            if (errno == EAGAIN)
                goto skip1;
            goto kill_session;
        }
        ts->size1 -= count;
        ts->wridx1 += count;
        if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
            ts->wridx1 = 0;
    }
skip1:
    if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
        /* Write to socket from buffer 2 */
        count = MIN(BUFSIZE - ts->wridx2, ts->size2);
        count = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2(ts) + ts->wridx2), count);
        if (count < 0) {
            if (errno == EAGAIN)
                goto skip2;
            goto kill_session;
        }
        ts->size2 -= count;
        ts->wridx2 += count;
        if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
            ts->wridx2 = 0;
    }
skip2:
    /* Should not be needed, but... remove_iacs is actually buggy
    * (it cannot process iacs which wrap around buffer's end)!
    * Since properly fixing it requires writing bigger code,
    * we rely instead on this code making it virtually impossible
    * to have wrapped iac (people don't type at 2k/second).
    * It also allows for bigger reads in common case. */
    if (ts->size1 == 0) {
        ts->rdidx1 = 0;
        ts->wridx1 = 0;
    }
    if (ts->size2 == 0) {
        ts->rdidx2 = 0;
        ts->wridx2 = 0;
    }

    if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
        /* Read from socket to buffer 1 */
        count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
        count = safe_read(ts->sockfd_read, TS_BUF1(ts) + ts->rdidx1, count);
        if (count <= 0) {
            if (count < 0 && errno == EAGAIN)
                goto skip3;
            goto kill_session;
        }
        /* Ignore trailing NUL if it is there */
        if (!TS_BUF1(ts)[ts->rdidx1 + count - 1]) {
            --count;
        }
        ts->size1 += count;
        ts->rdidx1 += count;
        if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
            ts->rdidx1 = 0;
    }
skip3:
    if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
        /* Read from pty to buffer 2 */
        count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
        count = safe_read(ts->ptyfd, TS_BUF2(ts) + ts->rdidx2, count);
        if (count <= 0) {
            if (count < 0 && errno == EAGAIN)
                goto skip4;
            goto kill_session;
        }
        ts->size2 += count;
        ts->rdidx2 += count;
        if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
            ts->rdidx2 = 0;
    }
skip4:
    ts = next;
    continue;
kill_session:
    if (ts->shell_pid > 0)
        update_utmp(ts->shell_pid, DEAD_PROCESS, /*tty_name:*/ NULL, /*username:*/ NULL, /*hostname:*/ NULL);
    free_session(ts);
    ts = next;
}

---------------------------------
| | | | | | | | | | | | | | | | |
---------------------------------
^                ^
|                |
ptr0             end

remove_iacs函数对buf1中从wridx1开始size1长度的缓冲区进行操作,提取出实际有效地命令行语句,移动这段字符串到首部,
更新wridx1 += ptr - totty;size1 -= ptr - totty;num_totty是实际有效地命令行语句的字节个数,返回首部地址。
static unsigned char *
remove_iacs(struct tsession *ts, int *pnum_totty)
{
    unsigned char *ptr0 = TS_BUF1(ts) + ts->wridx1;
    unsigned char *ptr = ptr0;
    unsigned char *totty = ptr;
    unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
    int num_totty;

    while (ptr < end) {

        /* 字符串处理 */
    }

    num_totty = totty - ptr0;
    *pnum_totty = num_totty;
    /* The difference between ptr and totty is number of iacs
    we removed from the stream. Adjust buf1 accordingly */
    if ((ptr - totty) == 0) /* 99.999% of cases */
        return ptr0;
    ts->wridx1 += ptr - totty;
    ts->size1 -= ptr - totty;
    /* Move chars meant for the terminal towards the end of the buffer */
    return memmove(ptr - num_totty, ptr0, num_totty);
}



free_session函数从G.sessions链表头中删除ts指向的结构,关闭pty和socket文件句柄,释放内存,更新G.maxfd
static void
free_session(struct tsession *ts)


 

make_new_session函数非常关键,它调用xgetpty打开一个伪终端,调用vfork创建一个子进程,父进程保存打开的伪终端和相关句柄
后返回,子进程调用setsid,关闭标准输入,打开伪终端,然后将0重定向到标准输出和标准错误,然后执行/bin/login,login执行
验证过程后启动shell程序。
以后只要父进程往获得的伪终端句柄里面写数据,就是把输入写到子进程启动的shell里面,shell执行之后,父进程通过read读取伪
终端句柄,就可以读取到shell的标准输出。

static struct tsession *
make_new_session
{
 struct tsession *ts = xzalloc
 fd = xgetpty(tty_name);
 ts->ptyfd = fd;
 pid =vfork();
 if(pid > 0)
 {
  //父进程
  return ts;   
 }
 
 //子进程
 setsid();                  //设置SID
 close(0);                  //关闭标准输入
 xopen(tty_name, O_RDWR);   //打开伪终端,注意这个时候默认返回的是数字号最小的句柄,也就是0
 dup2(0,1);                 //将伪终端句柄重定向到标准输出
 dup2(0,2);                 //将伪终端句柄重定向到标准错误
 execvp("/bin/login",);     //执行/bin/login
 _exit(EXIT_FAILURE);       //之后直接退出
}


 

你可能感兴趣的:(telnetd源码分析)