最近学习ssh协议,为了方便,自己先实现一套telnet服务,以便之后套用ssh(自己进了一个深坑)。
客户端:
先从telnet客户端做起。这里先给出RFC的中文文档链接:http://oss.org.cn/man/develop/rfc/RFC854.txt 。基本上把telnet介绍得差不多了。但关于NVT的介绍太少,也没有给出一个标准。就比如说键盘的方向键ASCII映射NVT不知道是什么。网上查了很久的资料都没有找到。最终通过自己抓包权威的telnet软件才知道方向键与其他一些按键的NVT ASCII值。现在找到了一些关于NVTASCII的说明文档:http://oss.org.cn/man/develop/rfc/RFC698.txt 就上键来说,getch()收到的值是十进制:224 72 ,转换为ASCII是十进制 27 91 65 (/033[A),下是27 91 66 (/033[B)。因为没找到其他的标准,我就只处理了方向以及退格键的一些必要NVTASCII。
拿了两个权威的telnet服务器来测试,一个是我的centos服务器的telnet,一个是我的win服务器自带的的telnet。
键盘的获取使用conio.h下的getch函数,使用这个函数要注意不要阻塞IO,最好是使用_kbhit函数来检测键盘是否有输入再getch,否则可能会出现getch阻塞时,另外线程的IO也会等待getch的结束而阻塞。另外需要注意Ctrl+C、Ctrl+Z之类会直接影响到程序执行过程的输入,需要额外处理。
doc.h 头文件:
#ifndef DOC_H_INCLUDED
#define DOC_H_INCLUDED
#define BS (char)8
#define LF (char)10
#define CR (char)13
#define ESC (char)27
#define SPACE (char)32
#define MFLAG (char)91
#define MARK (char)224
#define SE (char)240
#define NOP (char)241
#define DM (char)242
#define BRK (char)243
#define IP (char)244
#define AO (char)245
#define AYT (char)246
#define EC (char)247
#define EL (char)248
#define GA (char)249
#define SB (char)250
#define WILL (char)251
#define WONT (char)252
#define DO (char)253
#define DON'T (char)254
#define IAC (char)255
#endif // DOC_H_INCLUDED
telnet客户端代码:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <conio.h>
#include <windows.h>
#include <winsock2.h>
#include "doc.h"
//简单的方向键与NVT映射
int mhash[1000];
void init()
{
mhash[72]='A';
mhash[80]='B';
mhash[77]='C';
mhash[75]='D';;
}
SOCKET sock;//唯一用来通信的SOCKET
HANDLE inputth,outputth;//两个线程句柄
UINT inputthread(LPVOID Param);//输入与发送线程
UINT outputthread(LPVOID Param);//输出与接收线程
int main(int num,char *arr[])//程序的调用参数,接受ip和port
{
if(num<2)
{
puts("no address");
return false;
}
init();
WSADATA wsadata;
if(WSAStartup(MAKEWORD(1,0),&wsadata))
{
return -1;
}
sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sock==INVALID_SOCKET)
{
return -1;
}
sockaddr_in addr;
addr.sin_family=AF_INET;
//绑定端口,默认值23端口
if(num>2)
{
addr.sin_port=htons(atoi(arr[2]));
}
else
{
addr.sin_port=htons(23);
}
//解析域名IP
hostent *hname=gethostbyname(arr[1]);
if(!hname)
{
puts("can't find address");
return -1;
}//puts(inet_ntoa(addr.sin_addr));
addr.sin_addr.S_un.S_addr=*(u_long *)hname->h_addr_list[0];
//puts(hname->h_addr_list[0]);
//puts(inet_ntoa(addr.sin_addr));
if(SOCKET_ERROR==connect(sock,(sockaddr *)&addr,sizeof(addr)))
{
return -1;
}
//启动输入线程(先启动便于建立链接)
inputth=CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)inputthread,
NULL,
0,
NULL
);
//启动输出线程
outputth=CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)outputthread,
NULL,
0,
NULL
);
DWORD ins=0,ous=0;
//循环检测telnet结束
while(1)
{
GetExitCodeThread(inputth,&ins);
GetExitCodeThread(outputth,&ous);
if(ins!=STILL_ACTIVE||ous!=STILL_ACTIVE)
{
puts("\nconnect over");
break;
}
Sleep(1);
}
WSACleanup();
return 0;
}
UINT inputthread(LPVOID Param)
{
Sleep(500);
char inchar[10];
int len;
int issend;
int cnt;
bool ismark=false;
while(1)
{
if(!_kbhit())//检测键盘输入,防止阻塞导致输入冲突
{
Sleep(10);
continue;
}
inchar[0]=getch();
len=1;
//printf("(%x)",inchar);
if(inchar[0]==MARK)//识别额外的NVT键盘
{
ismark=true;
continue;
}
if(ismark)//处理NVT(方向键)
{
if(inchar[0]<0)
{
continue;
}
inchar[2]=mhash[inchar[0]];
inchar[0]=ESC;
inchar[1]=MFLAG;
len=3;
ismark=false;
}
//printf("%x\n",inchar);
issend=SOCKET_ERROR;
cnt=0;
//发送操作信息
while(issend==SOCKET_ERROR)
{
issend=send(sock,inchar,len,0);
cnt++;
if(cnt>100)
{
puts("network error please restart");
break;
}
Sleep(100);
}
}
return 0;
}
UINT outputthread(LPVOID Param)
{
char *rdata=new char [2];
int rlen;
bool isiac=false;
bool issel=false;
char iac;
char sel;
char rebuf[3];
while(rlen=recv(sock,rdata,1,0))
{
if(rlen<=0){continue;}
//printf("%u\n",(rdata[0]|0xffffff00)^0xffffff00);
if(rdata[0]==IAC)
{
isiac=true;
}
else if(isiac==true)
{
if(issel==false)
{
iac=rdata[0];
issel=true;
}
else
{
//处理选项,该处拒绝任何选项
sel=rdata[0];
isiac=issel=false;
rebuf[0]=IAC;
rebuf[1]=WONT;
rebuf[2]=sel;
send(sock,rebuf,3,0);
}
}
else
{
//会向数据
//printf("(%x)",rdata[0]);
putch(rdata[0]);
}
}
return 0;
}
服务器:
telnet服务器不能像客户端那样,什么平台一都个模子。因为我是写了用来做ssh实验的,就做了一个win环境下的telnet服务器。
首先是关于NVT ASCII的问题,这里我就照着之前做客户端的标准,反向映射到键盘。对与命令操作,通过程序识别处理后,再将语句交给操作系统处理,将返回的信息反馈给客户端。
比如用户要查看服务器的ip地址,客户端输入“ipconfig /all”,服务器识别后将调用函数system("ipconfig /all")。但是一般system的返回数据是直接回显到控制台命令窗口,而我们需要获得这些数据并发送给客户端。如果是在linux系统下,可以使用PIPE管道来将数据流出来获取返回,但是windows要麻烦些(但是IDE是linux比不了的),我们可以将输出定向到文件的方式来获取system返回的数据,由于telnet服务器是允许多个客户端同时访问,所以用文件方式也相对能有效地区分不同链接的输出。
服务器测试:
(没有处理中文字符..)
写服务器要自己处理很多情况,比如说我要删除一个字符。BS退格,但是不能删除,也没有相应的删除ASCII。这里可以这样处理:先向客户端发送退格,再发送空格(覆盖要删除的字符),再发送退格。这样就实现了删除一个位置的字符。对于上键,需要服务器找到一个上一行命令,把改行发送给客户端。
客户端代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<conio.h>
#include<windows.h>
#include<winsock2.h>
#include<unistd.h>
#include "doc.h"
#define min(x,y) (x<y?x:y)
int mhash[1000];
void init()
{
mhash['A']=72;
mhash['B']=80;
mhash['C']=77;
mhash['D']=75;
system("md tmp");
//重定向输出
freopen("tmp\\log","w+",stdout);
}
SOCKET ssock;
HANDLE listenth;
UINT listenthread(LPVOID Param);
UINT dealthread(LPVOID Param);
UINT dealid;
int main()
{
init();
WSAData wsadata;
if(WSAStartup(MAKEWORD(1,0),&wsadata))
{
return -1;
}
//创建监听线程
listenth=CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)listenthread,
NULL,
0,
NULL
);
DWORD des;
//检测线程结束
while(1)
{
GetExitCodeThread(listenth,&des);
if(des!=STILL_ACTIVE)
{
puts("\nconnect over");
break;
}
Sleep(1);
}
WSACleanup();
return 0;
}
UINT listenthread(LPVOID Param)
{
ssock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(ssock==INVALID_SOCKET)
{
return -1;
}
sockaddr_in addr;
addr.sin_family=AF_INET;
//监听23端口
addr.sin_port=htons(23);
addr.sin_addr.S_un.S_addr=INADDR_ANY;
if(bind(ssock,(sockaddr*)&addr,sizeof(sockaddr))==SOCKET_ERROR)
{
printf("bind error\n");
return -1;
}
dealid=0;//telnet处理编号
if(SOCKET_ERROR==listen(ssock,10))
{
printf("listen error\n");
return -1;
}
int newlen=sizeof(sockaddr);
sockaddr_in newaddr;
//接收并创建独立线程处理新连接
while(ssock!=SOCKET_ERROR)
{
SOCKET newsock=accept(ssock,(sockaddr*)&newaddr,&newlen);
CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)dealthread,
&newsock,
0,
NULL
);
}
return 0;
}
//发送系统返回值
void sysreply(SOCKET sock,char *dir)
{
//读取返回文件
FILE *f=fopen(dir+3,"r");
if(f==NULL)
{
return ;
}
fseek(f,0,SEEK_END);
int len=ftell(f);
fseek(f,0,SEEK_SET);
char *rdata=new char[len+1];
fread(rdata,len,1,f);
int slen=0;
int cnt=0;
//将返回文件的值全部发送
while(slen<len)
{
slen+=send(sock,rdata+slen,min(512,len-slen),0);
cnt++;
if(cnt>len)
{
return;
}
}
}
UINT dealthread(LPVOID Param)
{
SOCKET sock=*(SOCKET *)Param;
send(sock,"welcome my telnet server\n",25,0);
//将系统返回值全定向到对应文件输出
char dir[10]=" > tmp\\";
dir[7]=(dealid+'0');
dealid++;
dir[8]='\0';
char data;
int len;
char sysdata[1024];
int syslen=0;
bool isesc=false;
bool ismflag=false;
while(sock!=SOCKET_ERROR)
{
len=recv(sock,&data,1,0);
if(len<1)
{
continue;
}
if(data==BS)//处理退格(前移光标、空格、前移光标)
{
if(syslen>0)
{
syslen--;
}
char rdata[]={BS,SPACE,BS};
send(sock,rdata,3,0);
}
else if(data==CR)//处理提交
{
send(sock,"\n",1,0);
if(syslen==0)
{
continue;
}
sysdata[syslen]='\0';
if(strcmp(sysdata,"exit")==0)//退出telnet
{
send(sock,"exit",4,0);
break;
}
strcat(sysdata,dir);
//puts(sysdata);
syslen=0;
if(system(sysdata)!=0)
{
send(sock,"error\n",6,0);
continue;
}
sysreply(sock,dir);
}
else if(data==ESC)
{
isesc=true;
}
else if(isesc==true||data==MFLAG)
{
ismflag=true;
isesc=false;
}
else if(ismflag==true)//处理额外的NVT值
{
ismflag=false;
if(data<0||data>1000)
{
continue;
}
sysdata[0]=MARK;
sysdata[1]=mhash[data];
sysdata[2]='\0';
strcat(sysdata,dir);
syslen=0;
sysreply(sock,dir);
}
else//新数据进栈
{
sysdata[syslen++]=data;
send(sock,&data,1,0);
}
//printf("%c (%d) (%x)\n",data,data,data);
}
//删除临时文件
strcpy(sysdata,"rm ");
strcat(sysdata,dir+3);
system(sysdata);
return 0;
}
一套马马虎虎的telnet就完成了。
转载于:https://blog.51cto.com/wchrt/1627262