|
2005-9-16
QQ协议2005beta2 版协议分析
概述
网上有许多的资料关于这两种算法。由于下面会有代码分析, 所以我们这里给出我的MD5 算法和TEA 相关算法:下面是我的MD5 实现: QQ 数据报文 QQ 的数据通过UDP 方式传送,就是说每个独立的报文长度不会大于64K,发送到QQ 服务器的8000 端口(默认)。 所有的QQ 发送的数据报文格式如下: 字节 内容以及说明
下面的分析这个版本QQ 的版本号码是0x0d51 获取登录令牌 QQ2005beta2 登录的时候首先会发送一个请求,向服务器请求登录令牌,目前这令牌是 24 个字节,但是其实可以是其他的,要看服务器发回给我们的数据了。我抓的数据是13 个字节,如下:02 0d 51 00 62 1a 15 14 c5 aa ea 00 03 02 是报文的开头,0x0d51 是版本,0x0062 是请求Lgin Token 的命令我登录的QQ 号码是348498666,表示位网络字节是0x00eaaac514 0x1a15 是序号 请求格式如下: 字节 内容
目前我们抓到回应数据一般是34 字节, 回应的格式如下: 字节 内容
/* 这个函数用来获取登录令牌*/ int qq_request_login_token(struct qq_client *qc,unsigned char*token) { unsigned char buff_tx[65535]; /*64K 数据发送缓存*/ unsigned char buff_rx[65535]; /*64K 数据发送缓存*/ int len = -1; fd_set fds; struct timeval timeout; int e = -1; uint16_t tmp16 = 0; uint32_t tmp32 = 0; uint16_t seq = rand(); /*我们随机生成序号*/ /*检查传入的参数*/ if(!qc||qc->server<0||!token){ return -EFAULT; } /*清零数据是个好习惯!*/ bzero(buff_tx,sizeof(buff_tx)); bzero(buff_rx,sizeof(buff_rx)); /*构造发送数据*/ /*0x02 表示报文开始*/ buff_tx[0] = 0x02; /*QQ 版本号码 0x0d51 是QQ2005beta2 */ *((uint16_t*)&buff_tx[1]) = htons(0x0d51); /*0x0062 表示登录令牌请求*/ *((uint16_t*)&buff_tx[3]) = htons(0x0062); /*序号*/ *((uint16_t*)&buff_tx[5]) = htons(seq); /*QQ 号码*/ *((uint32_t*)&buff_tx[7]) = htonl(qc->id); /*这位设置位0*/ buff_tx[11] = 0x00; /*报文结束*/ buff_tx[12] = 0x03; /*发送这13个字节的报文到QQ服务器*/ len = write(qc->server,buff_tx,13); /*我们采用了非阻塞的套节字, 所以么设置超时, 并检查socket 事件!*/ bzero(&timeout,sizeof(timeout)); /*超时一秒*/ timeout.tv_sec = 1; timeout.tv_usec = 0; /*设置需要监视的socket*/ FD_ZERO(&fds); FD_SET(qc->server, &fds); /*检查socket 事件*/ e = select(qc->server+1,&fds,NULL,NULL,&timeout); if(e==-1||e==0){ /*超时了… */ fprintf(stderr,"request login token timeout.\n"); return -EFAULT; } /*好了!服务器回应数据出现了把接收数据放入buff_rx */ len = read(qc->server,buff_rx,sizeof(buff_rx)); if(len<=0){ /*读入数据失败*/ fprintf(stderr,"request login token error.\n"); return -EFAULT; } if(buff_rx[0] != 0x02 ){ /*不是QQ报文*/ fprintf(stderr,”not qq data .\n”); return –EFAULT; } #if 0 tmp16 = *((uint16_t*)&buff_rx[1]); printf("respond source tag x\n",ntohs(tmp16)); #endif tmp16 = *((uint16_t*)&buff_rx[3]); if(htons(tmp16) != 0x0062 ){ /*不是登录令牌回应*/ fprintf(stderr,”not login token data.\n”); return –EFAULT; } if(ntohs(*((uint16_t*)&buff_rx[5])) != seq){ /*不是我们发出的数据,因为序号不对,起码是传输出错了*/ fprintf(stderr,"request login token sequence incorrect.\n"); return -EFAULT; } /*检查是否含有令牌数据*/ if(buff_rx[7] != 0x00){ fprintf(stderr,"failed to request login token.\n"); return -EINVAL; } /*令牌的长度*/ len = buff_rx[8]; printf("login token length is %d bytes\n",len); /*复制令牌到我们的缓存*/ bcopy(&buff_rx[9],token,len); printf("login token :"); HEX_PRINT(&buff_rx[9],len); return len; } 登录 当我们获取登录令牌成功后,我们就可以开始我们的登录了过程了。 我捕获的数据长度(抛去协议数据)是460 字节 捕获的数据如下: 0x02, 协议开始 0x0d, 0x51, 版本 QQ2005beta2 0x00, 0x22, 登录请求 0x1a, 0x15, 报文序号 0x14, 0xc5, 0xaa, 0xea, QQ 号码我的号码348498666 = 0xeaaac514 0xd4,0xf3, 0x20, 0x2b, 0xc8, 0x65, 0x24, 0x55, 0xea, 0x61, 0x4a, 0xd5, 0xd3,0xae, 0x8e, 0xc8,登录数据数据密钥 16字节 …… 加密过的数据 0x03 我们除去报头的7 个字节,密钥16 字节,4 字节的号码, 还有0x03 这一个字节, 所以加密过的数据是460 -7 -16 -1 -4 = 432 其实我们在填充数据的时候,16 字节的初始密钥我们可以采用随即生成。这样安全性也许会更好。 我在自己写了个程序,采用密钥0xd4,0xf3, 0x20, 0x2b, 0xc8, 0x65, 0x24, 0x55,0xea, 0x61, 0x4a, 0xd5, 0xd3,0xae, 0x8e, 0xc8 对加密的432 字节数据解密后,还原出416 字节的登录数据,表明,那16 字节确实是初始的加密密钥。 下面说明这些数据的意义: 0 ― 15 共16 字节 0x28 ,0xb0 ,0x5f ,0xec ,0x84 ,0x96 ,0x7a ,0xea , 0x4d ,0xab ,0x72 ,0xc8 ,0xed ,0xdd ,0x14 ,0x92 , 这16字节是先把密码作两次MD5-16 运算得到一个HASH, 然后把这个结果作为密钥用TEA 加密一个任意的字串,可以是空!得到这16字节,服务器其实只是看看能不能在服务器端解密, 并不关心解密后的内容! 16 -51 共36字节 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x23 ,0xa2 ,0x10 ,0x55 ,0x97 , 0x52 ,0xd8 ,0x1e ,0xb4 ,0xd7 ,0x87 ,0x89 ,0x4a , 0x12 ,0x7d ,0xd1 ,0x01 看起来是固定的内容 52 – 52 共一个字节0x0a , 这里是登录的模式,是隐身的还是正常的0x0a 是正常模式0x28 是隐身模式 53 -68 共16字节 0x61 ,0x78 ,0x3e ,0x2b ,0x13 ,0x76 ,0x43 ,0x4a , 0xb5 ,0xdc ,0x46 ,0xce ,0x16 ,0x9b ,0x77 ,0xfc , 不知道意思 69 – 69 共一个字节 0x18 , 这里是登录令牌的长度,0x18 = 24 70 – 93 共24字节的登录令牌 0xc6 ,0x54 ,0x88 ,0x4e ,0x56 ,0xe6 ,0xbb ,0x13 , 0x90 ,0x9c ,0xb2 ,0x2a ,0xb8 ,0x0d ,0xee ,0xc0 , 0xb1 ,0x7a ,0xb4 ,0x70 ,0x38 ,0xe7 ,0x52 ,0xde , 24字节的登录令牌,这24字节刚好是我们刚才得到的! 94-94 一个字节 0x01 , 固定的0x01,目前不知道含义 95-95 一个字节0x40 , 目前不知含义,固定的 96 – 415 字节我观察到全部为0,不知道含义! 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 下面是登录请求460 字节的数据格式: 字节 内容
字节 内容
果。如果可以解密。它的回应格式如下:回应数据1:
字节 内容 67-68 未知的端口
字节 内容
如果第一个字节是0x01 也是重定向操作,0x02,0x05 是密码错误。 下面的代码展示了简单的登录: /* 登录代码*/ 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->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));
%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 = ntohl(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 qq_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; }
热门文章
推荐信息
|