全心思齐网

telnet深度解析?

下面的debug信息来自与/tmp/telenet.debug文件的一部分。


是客户端按下字母e后发生的四个阶段。这篇主要分析第一阶段和第二阶段。


也就是telrcv函数的主要的功能。


td: netread 1 chars

nd: 65 e

td: ptyflush 1 chars

pd: 65 e

td: ptyread 2 chars

pd: 0065 .e

td: netflush 1 chars


下面的是telnetd.c里面最主要的一个函数,其中里面的for循环也是理解telnetd的工作机制最主要的部分。

int

telnetd_run (void)

{


...



for (;;)

{

fd_set ibits, obits, xbits;

register int c;


if (net_input_level () < 0 && pty_input_level () < 0)

break;


FD_ZERO (&ibits);

FD_ZERO (&obits);

FD_ZERO (&xbits);


/* Never look for input if there's still stuff in the corresponding

output buffer */

if (net_output_level () || pty_input_level () > 0)

FD_SET (net, &obits);

else

FD_SET (pty, &ibits);


if (pty_output_level () || net_input_level () > 0)

FD_SET (pty, &obits);

else

FD_SET (net, &ibits);


if (!SYNCHing)

FD_SET (net, &xbits);


if ((c = select (nfd, &ibits, &obits, &xbits, NULL)) <= 0)

{

if (c == -1 && errno == EINTR)

continue;

sleep (5);

continue;

}


if (FD_ISSET (net, &xbits))

SYNCHing = 1;


if (FD_ISSET (net, &ibits))

{

/* Something to read from the network... */

/*FIXME: handle !defined(SO_OOBINLINE) */

net_read (); 这里是第一阶段执行的函数

}


if (FD_ISSET (pty, &ibits))

{

/* Something to read from the pty... */

if (pty_read () <= 0)

break;


/* The first byte is now TIOCPKT data. Peek at it. */

c = pty_get_char (1);


#if defined TIOCPKT_IOCTL

if (c & TIOCPKT_IOCTL)

{

pty_get_char (0);

copy_termbuf (); /* Pty buffer is now emptied. */

localstat ();

}

#endif

if (c & TIOCPKT_FLUSHWRITE)

{

static char flushdata[] = { IAC, DM };

pty_get_char (0);

netclear (); /* clear buffer back */

net_output_datalen (flushdata, sizeof (flushdata));

set_neturg ();

DEBUG (debug_options, 1, printoption ("td: send IAC", DM));

}


if (his_state_is_will (TELOPT_LFLOW)

&& (c & (TIOCPKT_NOSTOP | TIOCPKT_DOSTOP)))

{

int newflow = (c & TIOCPKT_DOSTOP) ? 1 : 0;

if (newflow != flowmode)

{

net_output_data ("%c%c%c%c%c%c",

IAC, SB, TELOPT_LFLOW,

flowmode ? LFLOW_ON : LFLOW_OFF, IAC, SE);

}

}


pty_get_char (0); /* Discard the TIOCPKT preamble. */

}


while (pty_input_level () > 0)

{

if (net_buffer_is_full ())

break;

c = pty_get_char (0);

if (c == IAC)

net_output_byte (c);

net_output_byte (c);

if (c == '\r' && my_state_is_wont (TELOPT_BINARY))

{

if (pty_input_level () > 0 && pty_get_char (1) == '\n')

net_output_byte (pty_get_char (0));

else

net_output_byte (0);

}

}


if (FD_ISSET (net, &obits) && net_output_level () > 0)

netflush ();

if (net_input_level () > 0)

telrcv ();


if (FD_ISSET (pty, &obits) && pty_output_level () > 0)

ptyflush (); 这里是第二阶段执行的函数。


/* Attending to the child must come last in the loop,

* so as to let pending data be flushed, mainly to the

* benefit of the remote and expecting client.

*/

if (pending_sigchld) {

/* Check for pending output, independently of OBITS. */

if (net_output_level () > 0)

netflush ();


cleanup (SIGCHLD); /* Not returning from this. */

}

}




net_read函数分析。这个函数是接收来自net的一个字符。


ncc是个数,用到的netibuf,网络输入缓冲区。可以这么理解。


netip是网络输入缓冲区的指针。




int

net_read (void)

{

ncc = read (net, netibuf, sizeof (netibuf));

if (ncc < 0 && errno == EWOULDBLOCK)

ncc = 0;

else if (ncc == 0)

{

syslog (LOG_INFO, "telnetd: peer died");

cleanup (0);

/* NOT REACHED */

}

else if (ncc > 0)

{

netip = netibuf;

DEBUG (debug_report, 1,

debug_output_data ("td: netread %d chars\r\n", ncc));

DEBUG (debug_net_data, 1, printdata ("nd", netip, ncc));

}

return ncc;

}


telrcv函数是一个关键的函数,在文件state.c中定义。


和telnet协议状态机有关。


比如如果第一个字节是FF也就是IAC,那么下面的字节是命令字节。命令选项字节。


net_get_char函数和pty_output_byte函数是理解telrcv函数的主要的地方。


其他的语句都和状态机有关。这两个函数是取一个字符,函数放到pty缓冲区里。





void

telrcv (void)

{

register int c;

static int state = TS_DATA;


while ((net_input_level () > 0) & !pty_buffer_is_full ())

{

c = net_get_char (0);

#ifdef ENCRYPTION

if (decrypt_input)

c = (*decrypt_input) (c);

#endif /* ENCRYPTION */

switch (state)

{


case TS_CR:

state = TS_DATA;

/* Strip off \n or \0 after a \r */

if ((c == 0) || (c == '\n'))

break;

/* FALL THROUGH */


case TS_DATA:

if (c == IAC)

{

state = TS_IAC;

break;

}

/*

* We now map \r\n ==> \r for pragmatic reasons.

* Many client implementations send \r\n when

* the user hits the CarriageReturn key.

*

* We USED to map \r\n ==> \n, since \r\n says

* that we want to be in column 1 of the next

* printable line, and \n is the standard

* unix way of saying that (\r is only good

* if CRMOD is set, which it normally is).

*/

if ((c == '\r') && his_state_is_wont (TELOPT_BINARY))

{

int nc = net_get_char (1);

#ifdef ENCRYPTION

if (decrypt_input)

nc = (*decrypt_input) (nc & 0xff);

#endif /* ENCRYPTION */

/*

* If we are operating in linemode,

* convert to local end-of-line.

*/

if (linemode

&& net_input_level () > 0

&& (('\n' == nc) || (!nc && tty_iscrnl ())))

{

net_get_char (0); /* Remove from the buffer */

c = '\n';

}

else

{

#ifdef ENCRYPTION

if (decrypt_input)

(*decrypt_input) (-1);

#endif /* ENCRYPTION */

state = TS_CR;

}

}

pty_output_byte (c);

break;


case TS_IAC:

gotiac:

switch (c)

{


/*

* Send the process on the pty side an

* interrupt. Do this with a NULL or

* interrupt char; depending on the tty mode.

*/

case IP:

DEBUG (debug_options, 1, printoption ("td: recv IAC", c));

send_intr ();

break;


case BREAK:

DEBUG (debug_options, 1, printoption ("td: recv IAC", c));

send_brk ();

break;




int

net_get_char (int peek)

{

if (peek)

return *netip;

else if (ncc > 0)

{

ncc--;

return *netip++ & 0377;

}


return 0;

}






void

pty_output_byte (int c)

{

*pfrontp++ = c;

}





这里是第二阶段相关的函数。比较好理解。


主要的功能是把缓冲区的字符放到/dev/pty里面


a b c d e f g


| |


pbackp pfrontp


上面的pfrontp指针指向的是字母g,如果再输入一个字符h,那么pfrontp指针就指向字符h。


上面的pbackp指针指向的是字母b,如果要拿出一个字符,那么应该先拿出字符b,然后是c,再然后是d。


void


ptyflush (void)

{

int n;


if ((n = pfrontp - pbackp) > 0)

{

DEBUG (debug_report, 1,

debug_output_data ("td: ptyflush %d chars\r\n", n));

DEBUG (debug_pty_data, 1, printdata ("pd", pbackp, n));

syslog (LOG_NOTICE, "ptyflush pbackp = %s", pbackp);

n = write (pty, pbackp, n);

}


if (n < 0)

{

if (errno == EWOULDBLOCK || errno == EINTR)

return;

cleanup (0);

/* NOT REACHED */

}


pbackp += n;

if (pbackp == pfrontp)

pbackp = pfrontp = ptyobuf;

}

匿名回答于2024-06-06 10:23:32


Telnet是一种用于远程连接和交互式通信的网络协议,允许用户通过网络远程登录和执行命令。它基于客户端-服务器架构,使用端口23进行通信,通过TCP/IP协议传输数据。在telnet会话中,用户可以在远程主机上执行命令、查看文件、编辑文本等。然而由于安全性方面的弱点,telnet已经逐渐被SSH协议所取代。Telnet协议以明文传输数据,容易受到窃听和数据篡改的风险。

匿名回答于2024-06-02 20:58:59


相关知识问答