/* 登录代码*/
int qq_login(
struct qq_client qc,
/* 客户端数据结构*/
const char*id,
/*字符形式的QQ号*/
const char pass,
/*密码*/
unsigned char login_mode,
/* 登录模式,0x0a= 正常,0x28= 隐身*/
const char*local_ip,
/* 本地倾听IP, 设置为“0.0.0.0”*/
int local_port,
/*本地的端口,指定一个未用的*/
const char*server_ip,
/* 腾训的服务器*/
int server_port
/*QQ 服务器端口,一般是8000*/
)
{
struct sockaddr_in sin;
int i = 0;
int len = sizeof(struct sockaddr_in);
int len2 = 0;
uint16_t tmp16 = 0;
uint32_t tmp32 = 0;
struct in_addr in;
int e = 0;
fd_set fds;
struct timeval timeout;
unsigned char data_raw[1024];
unsigned char data_encrypted[1024+16];
unsigned char data_decrypted[1024];
unsigned char buff_tx[65535];
unsigned char buff_rx[65535];
unsigned char *p = NULL;
unsigned char login_token[256];
int login_token_len = 0;
unsigned char tmp[16];
md5_context ctx;
bzero(buff_tx,sizeof(buff_tx));
bzero(buff_rx,sizeof(buff_rx));
bzero(data_raw,sizeof(data_raw)); bzero(data_encrypted,sizeof(data_encrypted));
/*检查传入的参数*/
if(!qc||!id||!pass||!local_ip||!server_ip)
{
return -EFAULT;
}
/*是否已经登录*/
if(qc->logined)
{
return 0;
}
/*设置模式*/
qc->login_mode = login_mode;
if( qc->login_mode!=0x0a && qc->login_mode!=0x28 )
{
qc->login_mode = 0x0a;
}
/*必要设置*/
qc->id = atol(id);
snprintf(qc->pass,sizeof(qc->pass),pass);
printf("login id = %d pass = /"%s/"/n",qc->id,qc->pass);
qc->local_port = local_port;
snprintf(qc->local_ip,sizeof(qc->local_ip),local_ip);
qc->server_port = server_port;
snprintf(qc->server_ip,sizeof(qc->server_ip),server_ip);
printf("local address %s:%d/n",qc->local_ip,qc->local_port);
printf("server address %s:%d/n",qc->server_ip,qc->server_port);
/*设置加密登录数据的密钥,随即设置即可*/
/*重新设置随机种子*/
srand(time(0));
for(i=0;i<16;i++)
{
qc->init_key[i] = rand()&0xff;
}
/*创建一个套节字用来作UDP通讯*/
qc->server = socket(PF_INET,SOCK_DGRAM,0);
if(qc->server<0)
{
return -EFAULT;
}
/*设置套节字为非阻塞套节字*/
fcntl(qc->server,F_SETFL,O_NONBLOCK);
sin.sin_family = AF_INET;
sin.sin_addr.s_addr =
inet_addr(qc->local_ip);
sin.sin_port = htons(qc->local_port);
len = sizeof(sin);
/*我们需要绑定这个UDP套节字,因为我们想用固定端口通讯!*/
if(bind(qc->server,(struct sockaddr*)&sin,len)<0)
{
close(qc->server);
qc->server = -1;
return -EFAULT;
}
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr(qc->server_ip);
sin.sin_port = htons(qc->server_port);
len = sizeof(sin);
/*采用connect 后我们就可以调用read/write 系统调用了*/
/*连接到腾训服务器*/
if(connect(qc->server,(struct sockaddr*)&sin,len)<0)
{
close(qc->server);
qc->server = -1;
return -EFAULT;
}
/*请求登录令牌,重试8次*/
for(i=0;i<8;i++)
{
login_token_len = qq_request_login_token(qc,login_token);
if(login_token_len>0)
{
/*如果成功就退出*/
break;
}
}
/*看看是否请求到了登录令牌*/
if(i==8)
{
close(qc->server);
qc->server = -1;
return -EFAULT;
}
/*两轮MD5-16 加密密码,保存在qc->pass_encrypted 中*/
md5_starts(&ctx);
md5_update(&ctx,pass,strlen(pass));
md5_finish(&ctx,tmp);
md5_starts(&ctx);
md5_update(&ctx,tmp,16);
md5_finish(&ctx,qc->pass_encrypted);
/*000-015 用MD5 加密任意字符*/
qq_encrypt("LINUXQQ",7,qc->pass_encrypted,data_raw,&len);
/*016 -051 固定内容*/
bcopy(login_16_51,&data_raw[16],35);
/*52-52 登录模式*/
data_raw[52] = qc->login_mode;
/*053 -068 固定内容*/
bcopy(login_53_68,&data_raw[53],16);
/*69-69 登录令牌长度*/
data_raw[69] = login_token_len;
/*复制登录令牌*/
bcopy(login_token,&data_raw[70],login_token_len);
/*固定内容*/
data_raw[70 + login_token_len ] = 0x01;
data_raw[70 + login_token_len +1] = 0x40;
len = 70 + login_token_len +2;
/*未知内容*/ bcopy(login_unknown_fixed,&data_raw[len], sizeof(login_unknown_fixed)); len+=sizeof(login_unknown_fixed);
len = 416;
/*用TEA 加密这416 数据,密钥是我们随机得到的init_key*/
qq_encrypt(data_raw,416,qc->init_key,data_encrypted,&len);
p = buff_tx;
/*创建发送报文*/
/*所有报文用0x02 开头*/
p[0] = 0x02;
/*版本是QQ2005beta2*/
*((uint16_t*)&p[1]) = htons(0x0d51);
/*命令是0x0022 表示登录请求*/
*((uint16_t*)&p[3]) = htons(0x0022);
/*随机得到一个报文序号*/
qc->seq = rand()e535;
*((uint16_t*)&p[5]) = htons(qc->seq);
/*QQ 号码*/
*((uint32_t*)&p[7]) = htonl(qc->id);
/* 放置加密的密钥*/
bcopy(qc->init_key,&p[11],16);
/*我们加密过的登录数据*/
bcopy(data_encrypted,&p[27],len);
len = 27 + len;
/*报文结束*/
p[len] = 0x03;
/*发送登录数据*/
e = write(qc->server,buff_tx,len+1);
if(e!=(len+1))
{
close(qc->server);
qc->server = -1;
return -EFAULT;
}
/*准备接收数据*/
bzero(&timeout,sizeof(timeout));
/*1S 超时*/
timeout.tv_sec = 1;
timeout.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(qc->server, &fds);
/*我们等待8个数据包*/
for(i=0;i<8;i++)
{
e = select(qc->server+1,&fds,NULL,NULL,&timeout);
if(e==-1||e==0)
{
fprintf(stderr,"receive data timeout/n");
close(qc->server);
qc->server = -1;
qc->login_retry++;
if(qc->login_retry>16)
{
fprintf(stderr,"too many times retried./n");
return -EFAULT;
}
/*失败了重新试*/
return qq_login(qc,id,pass,login_mode, local_ip,local_port, inet_ntoa(in),ntohs(tmp16));
}
bzero(buff_rx,sizeof(buff_rx));
/*读入接收到的数据*/
len = read(qc->server,buff_rx,sizeof(buff_rx));
if(len<0)
{
close(qc->server);
qc->server = -1;
return -EFAULT;
}
p = buff_rx;
// printf("respond header tag x/n",buff_rx[0]);
// tmp16 = *((uint16_t*)&p[1]);
// printf("respond source tag x/n",ntohs(tmp16));
// tmp16 = *((uint16_t*)&p[3]);
// printf("respond command code x/n",ntohs(tmp16));
// tmp16 = *((uint16_t*)&p[5]);
// printf("respond sequence %d/n",ntohs(tmp16));
// printf("respond tail tag x/n",buff_rx[len-1]);
if(p[0]!=0x02 || p[len-1]!=0x03)
{
/*数据不对*/
fprintf(stderr,"login failed tail/header tag error./n");
/*继续等待看看能不能收到*/
continue;
}
if(htons(*((uint16_t*)&p[3])) != 0x0022)
{
fprintf(stderr,"not login respond data. cmd = %d/n",htons(*((uint16_t*)&p[3])));
/*不是登录回应,但是是其他的包,继续试试看看*/ continue;
}
if(ntohs(*((uint16_t*)&p[5])) != qc->seq)
{
fprintf(stderr,"respond sequence error seq = %d/n",htons(*((uint16_t*)&p[5])));
/*序列不对,继续等待回应*/
continue;
}
/*OK. 是回应数据*/
break;
}
len = len - (7 + 1);
bzero(data_decrypted,sizeof(data_decrypted));
len2 = len;
/*解密接收的数据,先用密码的MD5 作为密钥*/
e = qq_decrypt(&p[7],len,qc-pass_encrypted,data_decrypted,&len2);
if(e == 0)
{
/*登录成功了*/
if(data_decrypted[0] == 0x00)
{
/*001-016 会话令牌*/
bcopy(&data_decrypted[1],qc->session_token,16);
printf("session token:");
HEX_PRINT(data_decrypted,16);
//017-020: login uid
tmp32 = *((uint32_t*)&data_decrypted[17]);
tmp32 = tohl(tmp32);
// printf("user id %d/n",tmp32);
// 021-024: server detected user public IP
tmp32 = *((uint32_t*)&data_decrypted[21]);
in.s_addr = tmp32;
sprintf(qc->detected_ip,"%s",inet_ntoa(in)); printf("server detected my ip %s/n",qc->detected_ip);
// 025-026: server detected user port
tmp16 = *((uint16_t*)&data_decrypted[25]);
tmp16 = ntohs(tmp16); qc->detected_port = tmp16;
printf("server detected my port %d/n",qc->detected_port);
//027-030: server detected itself ip 127.0.0.1 ?
// 031-032: server listening port
// 033-036: login time for current session
tmp32 = *((uint32_t*)&data_decrypted[33]);
tmp32 = ntohl(tmp32);
printf("login time for current session %d/n",tmp32);
tmp32 = *((uint32_t*)&data_decrypted[123]);
in.s_addr = tmp32;
printf("last login ip %s/n",inet_ntoa(in));
// 127-130: login time of last session
// 131-138: 8 bytes unknown
//total 139 bytes
printf("id = %s pass = %s logined ok./n",id,pass);
qc->logined = 1;
return 0;
}
else if(data_decrypted[0] == 0x01)
{
printf("redirect to other server./n");
// 000-000: reply code
//printf("server reply code x/n",data[0]);
// 001-004: login uid
tmp32 = ntohl(*((uint32_t*)&data_decrypted[1]));
printf("request id %d/n",tmp32);
// 005-008: redirected new server IP
tmp32 = *((uint32_t*)&data_decrypted[5]);
in.s_addr = tmp32;
printf("new server ip %s/n",inet_ntoa(in));
// 009-010: redirected new server port
tmp16 = *((uint16_t*)&data_decrypted[9]);
printf("new server port %d/n",ntohs(tmp16));
close(qc->server);
//bzero(qc,sizeof(struct qq_client));
qc->server = -1;
qc->login_retry++;
if(qc->login_retry>16)
{
fprintf(stderr,"too many times retried./n");
return -EFAULT;
}
return qq_login(qc,id,pass,login_mode,local_ip,local_port, inet_ntoa(in),ntohs(tmp16));
}
else if(data_decrypted[0] == 0x05)
{
printf("id = %s pass = %s password error./n",id,pass);
close(qc->server);
qc->server = -1;
return -EINVAL;
}
else
{
printf("unknown error./n");
close(qc->server);
qc->server = -1;
return -EINVAL;
}
}
bzero(data_decrypted,sizeof(data_decrypted));
/*如果解密失败,试试用我们的init_key 能缶解密*/
len2 = len;
e = qq_decrypt(&p[7],len,qc->init_key,data_decrypted,&len2);
if(e == 0)
{
switch(data_decrypted[0])
{
case 0x01:
printf("redirect to other server./n");
// 000-000: reply code
//printf("server reply code x/n",data[0]);
// 001-004: login uid
tmp32 = ntohl(*((uint32_t*)&data_decrypted[1]));
printf("request id %d/n",tmp32);
// 005-008: redirected new server IP
tmp32 = *((uint32_t*)&data_decrypted[5]);
in.s_addr = tmp32;
printf("new server ip %s /n",inet_ntoa(in));
// 009-010: redirected new server port
tmp16 = *((uint16_t*)&data_decrypted[9]);
printf("new server port %d/n",ntohs(tmp16));
close(qc->server);
//bzero(qc,sizeof(struct qq_client));
qc->server = -1;
qc->login_retry++;
if(qc->login_retry>16)
{
fprintf(stderr,"too many times retried./n");
return -EFAULT;
}
return q_login(qc,id,pass,login_mode,local_ip,local_port,inet_ntoa(in),ntohs(tmp16));
break;
case 0x02:
printf("id = %s pass = %s password error./n",id,pass);
close(qc->server);
qc->server = -1;
return -EINVAL;
break;
case 0x05:
printf("id = %s pass = %s password error./n",id,pass);
close(qc->server);
qc->server = -1;
return -EINVAL;
break;
default:
printf("unknow server error./n");
close(qc->server);
qc->server = -1;
qc->login_retry++;
if(qc->login_retry>16)
{
fprintf(stderr,"too many times retried./n");
return -EFAULT;
}
return
qq_login(qc,id,pass,login_mode,local_ip,local_port,inet_ntoa(in),ntohs(tmp16));
break;
}
/*switch*/
}
else
{
printf("decrypt data error/n");
close(qc->server);
qc->server = -1;
qc->login_retry++;
if(qc->login_retry>16)
{
fprintf(stderr,"too many times retried./n");
return -EFAULT;
}
return qq_login(qc,id,pass,login_mode,local_ip, local_port,inet_ntoa(in),ntohs(tmp16));
}
qc->logined = 1;
return 0;
}