存档

文章标签 ‘飞信’

libofetion demo以及纯命令行飞信

十二月 20th, {2010 27 条评论 25,440 人阅读过  

之前一直有用户要求写一个libofetion的demo,再加上很多用户对于纯命令行版本飞信的强烈需求,于是我昨天简单地写了一个demo,把libofetion的API也做了一些修改,使它用起来更像是一个lib,不过对于第三方开发的话还是有很多很难理解的地方,因为最初并没有想把它当做一个lib来发布。到现在我对飞信的开发又要暂时先告一段落了,周末都在openfetion和娱乐中度过的,实验室项目和论文又要开始提上日程了,OK,先把code列出来,再做下简单地说明

/***************************************************************************
 *   Copyright (C) 2010 by lwp                                             *
 *   levin108@gmail.com                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
 
#include <openfetion.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define BUFLEN 1024
 
int   password_inputed = 0;
int   mobileno_inputed = 0;
int   tono_inputed = 0;
int   message_inputed = 0;
User *user;
pthread_t th;
 
static void usage(char *argv[]);
 
int fx_login(const char *mobileno, const char *password)
{
	Config           *config;
	FetionConnection *tcp;
	FetionSip        *sip;
	char             *res;
	char             *nonce;
	char             *key;
	char             *aeskey;
	char             *response;
	int               local_group_count;
	int               local_buddy_count;
	int               group_count;
	int               buddy_count;
	int               ret;
 
	/* construct a user object */
 	user = fetion_user_new(mobileno, password);
	/* construct a config object */
	config = fetion_config_new();
	/* attach config to user */
	fetion_user_set_config(user, config);
 
	/* start ssi authencation,result string needs to be freed after use */
	res = ssi_auth_action(user);
	/* parse the ssi authencation result,if success,user's sipuri and userid
	 * are stored in user object,orelse user->loginStatus was marked failed */
	parse_ssi_auth_response(res, user);
	free(res);
 
	/* whether needs to input a confirm code,or login failed
	 * for other reason like password error */
	if(USER_AUTH_NEED_CONFIRM(user) || USER_AUTH_ERROR(user)) {
		debug_error("authencation failed");
		return 1;
	}
 
	/* initialize configuration for current user */
	if(fetion_user_init_config(user) == -1) {
		debug_error("initialize configuration");
		return 1;
	}
 
	if(fetion_config_download_configuration(user) == -1) {
		debug_error("download configuration");
		return 1;
	}
 
	/* set user's login state to be hidden */
	fetion_user_set_st(user, P_HIDDEN);
 
	/* load user information and contact list information from local host */
	fetion_user_load(user);
	fetion_contact_load(user, &local_group_count, &local_buddy_count);
 
	/* construct a tcp object and connect to the sipc proxy server */
	tcp = tcp_connection_new();
	if((ret = tcp_connection_connect(tcp, config->sipcProxyIP, config->sipcProxyPort)) == -1) {
		debug_error("connect sipc server %s:%d\n", config->sipcProxyIP, config->sipcProxyPort);
		return 1;
	}
 
	/* construct a sip object with the tcp object and attach it to user object */
	sip = fetion_sip_new(tcp, user->sId);
	fetion_user_set_sip(user, sip);
 
	/* register to sipc server */
	if(!(res = sipc_reg_action(user))) {
		debug_error("register to sipc server");
		return 1;
	}
 
	parse_sipc_reg_response(res, &nonce, &key);
	free(res);
	aeskey = generate_aes_key();
 
	response = generate_response(nonce, user->userId, user->password, key, aeskey);
	free(nonce);
	free(key);
	free(aeskey);
 
	/* sipc authencation,you can printf res to see what you received */
	if(!(res = sipc_aut_action(user, response))) {
		debug_error("sipc authencation");
		return 1;
	}
 
	if(parse_sipc_auth_response(res, user, &group_count, &buddy_count) == -1) {
		debug_error("authencation failed");
		return 1;
	}
 
	free(res);
	free(response);
 
	if(USER_AUTH_ERROR(user) || USER_AUTH_NEED_CONFIRM(user)) {
		debug_error("login failed");
		return 1;
	}
 
	/* save the user information and contact list information back to the local database */
	fetion_user_save(user);
	fetion_contact_save(user);
 
	/* these... fuck the fetion protocol */
	struct timeval tv;
	tv.tv_sec = 1;
	tv.tv_usec = 0;
	char buf[1024];
	if(setsockopt(user->sip->tcp->socketfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
		debug_error("settimeout");
		return 1;
	}
	tcp_connection_recv(user->sip->tcp, buf, sizeof(buf));
 
	return 0;
}
 
int send_message(const char *mobileno, const char *receiveno, const char *message)
{
	Conversation *conv;
	Contact      *contact;
	Contact      *contact_cur;
	Contact      *target_contact = NULL;
	int           daycount;
	int           monthcount;
 
	/* send this message to yourself */
	if(*receiveno == '\0' || strcmp(receiveno, mobileno) == 0) {
		/* construct a conversation object with the sipuri to set NULL
		 * to send a message to yourself  */
		conv = fetion_conversation_new(user, NULL, NULL);
		if(fetion_conversation_send_sms_to_myself_with_reply(conv, message) == -1) {
			debug_error("send message \"%s\" to %s", message, user->mobileno);
			return 1;
		}
	}else{
		/* get the contact detail information by mobile number,
		 * note that the result doesn't contain sipuri */
		contact = fetion_contact_get_contact_info_by_no(user, receiveno, MOBILE_NO);
		if(!contact) {
			debug_error("get contact information of %s", receiveno);
			return 1;
		}
 
		/* find the sipuri of the target user */
		foreach_contactlist(user->contactList, contact_cur) {
			if(strcmp(contact_cur->userId, contact->userId) == 0) {
				target_contact = contact_cur;
				break;
			}
		}
 
		if(!target_contact) {
			debug_error("sorry,maybe %s isn't in your contact list");
			return 1;
		}
 
		/* do what the function name says */
		conv = fetion_conversation_new(user, target_contact->sipuri, NULL);
		if(fetion_conversation_send_sms_to_phone_with_reply(conv, message, &daycount, &monthcount) == -1) {
			debug_error("send sms to %s", receiveno);
			return 1;
		}else{
			debug_info("successfully send sms to %s\nyou have sent %d messages today, %d messages this monthcount",
					receiveno, daycount, monthcount);
			return 0;
		}
	}
	return 0;
}
 
int main(int argc, char *argv[])
{
	int ch;
	char mobileno[BUFLEN];
	char password[BUFLEN];
	char receiveno[BUFLEN];
	char message[BUFLEN];
 
	memset(mobileno, 0, sizeof(mobileno));
	memset(password, 0, sizeof(password));
	memset(receiveno, 0, sizeof(receiveno));
	memset(message, 0, sizeof(message));
 
	while((ch = getopt(argc, argv, "f:p:t:d:")) != -1) {
		switch(ch) {
			case 'f':
				mobileno_inputed = 1;
				strncpy(mobileno, optarg, sizeof(mobileno) - 1);	
				break;
			case 'p':
				password_inputed = 1;
				strncpy(password, optarg, sizeof(password) - 1);
				break;
			case 't':
				tono_inputed = 1;
				strncpy(receiveno, optarg, sizeof(receiveno) - 1);
				break;
			case 'd':
				message_inputed = 1;
				strncpy(message, optarg, sizeof(message) - 1);
				break;
			default:
				break;
		}
	}
 
	if(!mobileno_inputed || !password_inputed || !message_inputed) {
		usage(argv);
		return 1;
	}
 
	if(fx_login(mobileno, password))
		return 1;
 
	if(send_message(mobileno, receiveno, message))
		return 1;
 
	fetion_user_free(user);
	return 0;
 
}
 
static void usage(char *argv[])
{
	fprintf(stderr, "Usage:%s -f mobileno -p password -t receive_mobileno -d message\n", argv[0]);
}

首先需要libofetion的支持,因为用到了最新的API,所以需要从hg中clone最新版本编译安装后才可编译该程序:

hg clone https://ofetion.googlecode.com/hg/ ofetion

编译方法如下:

gcc -o cli cli.c `pkg-config --cflags --libs ofetion`

有一个地方需要说明,请大家找到/* these… fuck the fetion protocol */这个句注释,它下面的几句话的作用是这样的,飞信在用户完成身份验证之后订阅相关信息之前会推送过来一条信令:

BN 406472150 SIP-C/4.0
N: SyncUserInfoV4
I: 2
Q: 1 BN
L: 124
 
<events><event type="SyncUserInfo"><user-info>
<score value="1261" level="2" level-score="678"/>
</user-info></event></events>

用它来更新用户的积分等级之类的玩意,像这种浮云般的信令都被我直接忽略掉了,但在这种命令行模式纯粹为了发短信的情况下,不需要新建线程监听服务器推送过来的信息,但这条推送过来的信令就有可能影响其它信令的交互,而且更让人蛋疼的是并不是每次登录都会推送这条信令,有时候有,有时候没有,所以就加了在登录完成之后加了一个recv,设定了1s的超时,来处理这句信令,如果在网络情况不好的情况下,大家可以自己把超时时间设长一些,这样也就意味着在发送一条短信时在recv那里要停留几秒钟,否则就有可能导致信息发送失败。

下载地址:fetion-demo

分类: C/C++ 标签: ,

OpenFetion V2.0正式版发布

十月 24th, {2010 74 条评论 21,379 人阅读过  


经过近一个月的修改测试,Openfetion 2.0正式版今天release出来了,相比于之前的1.x版本相比,2.0有更多的优点和特性,最重要的是2.0稳定性较之于之前版本登录速度更快,稳定性更高。

这次版本主要集中在解决历史版本中所存在的登录崩溃,运行时崩溃的问题,以及用户所普遍反映的登录速度过慢的问题,引入了SQLITE3来实现了数据的本地化,使登录过程需要下载的数据量很小,从而大大提高了用户登录速度,也因为实现了数据的本地化,新版本也增加了离线和离线登录功能。

新版本另一个重要的特性是检测网络状态,可以实现断线检测并自动重连,失败时会显示用户离线,而不是之前离线时直接退出程序。检测网络状态需要NetworkManager的支持,此依赖库为可选,如果安装时disable,则不具有自动重连功能。

另外新版本增加了空闲时间自动离开功能,空闲时间目前固定为3分钟。此功能需要XScreenSaver的支持,此依赖库也为可选。

另外新版本再一次更换了图标,新图标由@riku设计,由@xhacker修改,感谢二位对OpenFetion做出的贡献。

下面是ChangeLog:

2010-10-24 levin

* 修复了登录和运行时的崩溃问题
* 修复了群发短信时的崩溃问题
* 修复了添加好友时的崩溃问题
* 添加了数据本地化,实现了离线登录功能
* 添加了断线自动重连功能
* 添加了空闲时间自动离开功能
* 添加了关闭上线提示的功能
* 优化了登录过程,登录速度更快
* 更换了新版图标,更美观

程序添加的依赖库为libsqlite3,NetworkManager,XScreenSaver,Ubuntu下的安装方法如下:

sudo apt-get install libxml2-dev
sudo apt-get install libgtk2.0-dev
sudo apt-get install libssl-dev
sudo apt-get install libnotify-dev
sudo apt-get install libgstreamer0.10-dev
sudo apt-get install intltool
sudo apt-get install libsqlite3-dev
sudo apt-get install libnm-glib-dev
sudo apt-get install libxss-dev

ubuntu用户推荐使用happyaron制作的PPA安装,该PPA于10.24日晚将会更新到最新版本,安装方法如下:

sudo apt-add-repository ppa:happyaron/ppa
sudo apt-get update
sudo apt-get install openfetion

Fedora用户依赖库安装方法如下:

pkcon install gcc gstreamer-devel gtk2-devel libxml2-devel openssl-devel intltool 
sqlite-devel NetworkManager-devel libXScrnSaver-devel libnotify-devel

期待各方打包人士制作相关安装包

源码下载地址:
http://code.google.com/p/ofetion/downloads/list

Follow Me @levin108

分类: Linux 标签: , , ,

近期飞信开发手记(群相关介绍)

八月 14th, {2010 22 条评论 11,336 人阅读过  

实验室项目中期检查结束,两天一夜的超负载工作换来了额外两天的休息时间,时间不敢随意浪费,飞信好久没有更新了,软件总不可能没有bug,用户的需求也不可能有止境,还是得赶紧把这么长时间以来网友提出的问题和要求给解决一下,用了近三天的时间完成了飞信i18n和飞信群功能,宅在宿舍里面写代码时间长了果然会觉得无聊,闲话少说,切入正题,写一下飞信群的开发过程。

另外,现在还不打算发布新版本,还想再测试两天看看,这里就截两个图先展示一下吧

image image

其实飞信群这个功能本来也没几个人在用,分析了一下协议,没有什么特别复杂的地方,很多东西都是套路,首先是在登录的时候获取一系统群相关的信息,如群列表,群成员列表,群详细信息,群个人信息,推荐群,群话题等等,一些没用的信息像推荐群,群话题这些在windows下都不会有人去点的信息就直接给忽略了,登录的过程中发送一系统的获取信息的信令,然后再统一根据 callid从服务器返回的信息中提取各自相关的信息,获取群列表信令如下:

S fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 3
Q: 1 S
N: PGGetGroupList
L: 27

<args><group-list /></args>

获取群详细信息信令如下:

S fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 5
Q: 1 S
N: PGGetGroupInfo
L: 150

<args><groups attributes=”all”><group uri=”sip:PG9777218@fetion.com.cn;p=12205″ /><group uri=”sip:PG31809932@fetion.com.cn;p=12207″ /></groups></args>

获取群成员列表信令如下:

S fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 12
Q: 1 S
N: PGGetGroupMembers
L: 229

<args><groups attributes=”member-uri;member-nickname;member-iicnickname;member-identity;member-t6svcid”><group uri=”sip:PG9777218@fetion.com.cn;p=12205″ member-list-major-version=”" member-list-minor-version=”" /></groups></args>

这些信息发送至服务器这后,服务器会将相关的请求信息推送过来,程序编写时在发送每一条信令都记住其相关的callid,再根据返回信息中的callid来判断其是什么信息,然后再将其解析。

这些信息获取之后就需要订阅每个群的Presence信息,信令如下:

SUB fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 2
Q: 2 SUB
N: PGPresence
L: 215

<args><subscription><groups><group uri=”sip:PG9777218@fetion.com.cn;p=12205″ /></groups><presence><basic attributes=”all” /><member attributes=”all” /><management attributes=”all” /></presence></subscription></args>

之后服务器会将群成员的Presence信息推送过来,信令格式大致如下:

BN 916098834 SIP-C/4.0
N: PresenceV4
I: 1
L: 159
Q: 55 BN
<events><event type=”PresenceChanged”><contacts><c id=”464933706″><pr di=”PCCL030524118392″ b=”400″ d=”" dt=”PC” dc=”137″></pr></c></contacts></event></events>

至此,群相关的信息都已经获取到了,已经可以在界面上展示飞信群了,但要发送群信息还不够,必须还要发起群会话邀请,也就是向sip服务器发送Invite信令,格式如下:

I fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 10
Q: 1 I
T: sip:PG31809932@fetion.com.cn;p=12207
K: text/html-fragment
K: multiparty
K: nudge
K: share-background
K: fetion-show
L: 21

s=session m=message

服务器返回100 trying,之后返回200 OK,收到OK之后再向服务器发送Ack信令:

A fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 9
Q: 1 A
T: sip:PG9777218@fetion.com.cn;p=12205

这样会话邀请就已经完成了,可以发送群信息了。但在这之后我发送群信息的时候却出了一点小问题,每次发送到服务器的Message信息格式完全正确,但服务器返回的是请求失败的信令,想了好久才想明白,其实发起会话邀请这一步并不是做给服务器看的,而是要通过它来注册一个callid,Invite信令的callid必须与Message信令的callid一致,消息才能发送成功,这表示发送的群消息是属于这个会话的,而标识正是在发起邀请时所用的callid,群消息发送的信令如下:

M fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 9
Q: 2 M
T: sip:PG31809932@fetion.com.cn;p=12207
C: text/html-fragment
K: SaveHistory
L: 60

<Font Face=” Color=’-16777216′ Size=’10′>hello</Font>

然后就是群短信:

M fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 25
Q: 1 M
T: sip:PG31809932@fetion.com.cn;p=12207
N: PGSendCatSMS
L: 12

hello fetion

群短信这里的callid就无所谓啦,可以让它使用递增的全局变量,当然也只有群的超级管理员才可以发送群短信,连管理员也不能发,所以我觉得这个功能限制也太多啦。

其实关于飞信群做的事情也就这些,像其它的一些邀请好友加入群,群管理之类的功能我都没做,本来就没有几个人在用这个功能,而且linux下用的就更少了,而且我也确实拿不出再多的时间去整了,就先把基本功能都弄好了让过来提示需求的网友先凑合着吧,如果将来飞信群用的人多了我再想办法后时间来完善它。

另外,我弄这个群比飞信的官方软件里面的群帅的地方就是群成员可以显示头像,当然啦,这些也都是浮云。

对于飞信文件p2p文件传输的疑惑

四月 27th, {2010 1 条评论 9,276 人阅读过  

近期在写飞信文件传输部分的代码,又分析了一下飞信文件传输的协议。

飞信的文件传输应该有三种模式,p2p,relay和block这三种方式。block我没弄明白是怎么回事,其它两种是比较熟悉的,p2p现在应该算是一种比较成熟的技术了,年轻的时候不知道NAT是怎么回事,以为p2p简单的只需要一台主机充当server的角色,另一台主机充当client的角色,然后client向服务器发送连接(TCP)或者直接发送数据(UDP)就可以了,可当后来知道了在这个主流的IPv4世界里面,NAT的存在给p2p程序带来了很大的麻烦,两台主机之间建立连接并不像想象中地那么简单,数据流必须要能成功地穿越NAT设备,当然也有可能存在疑问,为什么连接一台web服务器的时候不需要考虑NAT的存在呢?因为web服务器一般是位于公网的,它具有一个公网的地址,任何一种NAT都可以允许它隐藏下的内网设备主动访问公网,并且会允许回传的数据流,而p2p程序面临的问题就是两台主机可能同时处于私有网络(无论是同一个私有网络还是不同的私有网络),它们直接建立连接的时候,NAT设备就很有可能会丢掉他们的数据包,导致连接失败,这个时候p2p穿越成为一种必不可少的手段。

在进行p2p穿越之前确定主机与NAT设备的相对位置是一种很好的策略,可以根据它们的相对位置来决定p2p-NAT穿越的具体策略,确定p2p和NAT设备的相对位置实际上是一种很简单的操作,IETF为我们提供了一处轻量级的协议STUN(前两篇文章做了比较详细地说明),之间对STUN这个协议的具体细节并没有了解太多,昨天读了一下rfc,然后写程序测试了一下,后来苦于在国内找不到免费的STUN服务器程序只简单了完成了Binding Request的发送和Binding Response的最简单接收,各个属性的实现原理都无法通过程序来一一检测,后来就突然想到了飞信,飞信的p2p无疑也要进行上面的一系统操作来使两台主机之间建立起p2p连接,那飞信的STUN服务器???猜测始终不如亲眼证实,打开飞信的配置文件(就是那个xml),果然在里面找到了stun-server-urls这一项,它用stun-server用的是stunserver.org,这是一个国外的免费服务器,于是便解决了我一直以来对于飞信文件输过程的一个疑惑,当我在教育网私有网络内向公网发起文件传输请求时,不管我请求的方式是不是p2p,最终进行传输所用的始终都是中继的方式,HTTP中继是一种效率多低的传输方式啊,不考虑HTTP服务器的带宽和负载,单单是上行传输的带宽限制就可以把文件传办理的速率拉低。所以我不明白的是为什么飞信不自己开发一个stun服务器,而是要用stunserver.org这个国外的免费服务器,这样对于教育网的用户来说是很无奈的。

也可能我对飞信了解还不够,但它确实用了stunserver.org,这是我不能理解的。

分类: My Life 标签: , , ,

飞信2010身份验证过程及算法详述

三月 14th, {2010 23 条评论 65,710 人阅读过  

本人还是在校学生,由于滤涉世未深,对互联网还心存敬畏,没能像当初分析2006飞信协议的大哥那样洒脱,出于各种顾虑,当初在写飞信登录协议相关的文章的时候,都没详细地阐述登录过程,那篇文章当初也只是做为自己业余爱好的一个总结,却没想会受到大家如此的关注,很多人留言或发邮件问我具体的身份验证过程及算法,我也无暇一一详细解答,后来想了想,互联网应该是一个开放的环境,大家对飞信的关注其实也是为了给更多人提供方便或者学习交流,我想我也无须吝啬,就在这里把详细地身份验证过程阐述一下,仅用于学习交流,希望大家把它用在正途。

另外,我本人也在用业余时间写了一个Linux环境下的飞信,期待大家的加入,有兴趣的可以在这里留言。

具体身份验证流程我在下面这篇文章里面说得比较详细了,有需要的话可以先阅读一下这篇文章:

http://basiccoder.com/fetion2010-auth-algorithm.html

在里面有两个重要的过程:

1.密码的处理方式。

密码并不是以明文的方式进行加密的,而是用SHA1做了散列之后才进行加密的,散列的过程与2008略有不同,文字表达能力有限,就贴几个C函数吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
char* hash_password_v1(const unsigned char* b0 , int b0len
                                   , const unsigned char* password , int psdlen) 
{
	unsigned char* dst = (unsigned char*)malloc(b0len + psdlen + 1);
	unsigned char tmp[20];
	char* res;
	memset(tmp , 0 , sizeof(tmp));
	memset(dst , 0 , b0len + psdlen + 1);
	memcpy(dst , b0 , b0len);
	memcpy(dst + b0len , password , psdlen);
	SHA_CTX ctx;
	SHA1_Init(&ctx);
	SHA1_Update(&ctx , dst , b0len + psdlen );
	SHA1_Final(tmp , &ctx);
	free(dst);
	res = hextostr(tmp , 20);
	return res;
}
char* hash_password_v2(const char* userid , const char* passwordhex) 
{
	int id = atoi(userid);
	char* res;
	unsigned char* bid = (unsigned char*)(&id);
	unsigned char ubid[4];
	int bpsd_len;
	unsigned char* bpsd = strtohex(passwordhex , &bpsd_len);
	memcpy(ubid , bid , 4);
	res = hash_password_v1(ubid , sizeof(id) , bpsd , bpsd_len);
	free(bpsd);
	return res;
}
char* hash_password_v4(const char* userid , const char* password)
{
	const char* domain = "fetion.com.cn:";
	char *res , *dst;
	unsigned char* udomain = (unsigned char*)malloc(strlen(domain));
	unsigned char* upassword = (unsigned char*)malloc(strlen(password));
	memset(udomain , 0 , strlen(domain));
	memcpy(udomain , (unsigned char*)domain , strlen(domain));
	memset(upassword , 0 , strlen(password));
	memcpy(upassword , (unsigned char*)password , strlen(password));
	res = hash_password_v1(udomain , strlen(domain) , upassword , strlen(password));
	free(udomain);
	free(upassword);
	if(userid == NULL || strlen(userid) == 0)
	{
		return res;
	}
	dst = hash_password_v2(userid , res);
	free(res);
	return dst;
}

第三个函数是用来对密码进行散列处理的函数,输入是userid和密码,这个函数返回一个40字节的16进制字符串。

另外还有两个函数是在unsigned char串和char串之间相互转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
unsigned char* strtohex(const char* in , int* len) 
{
	unsigned char* out = (unsigned char*)malloc(strlen(in)/2 );
	int i = 0 , j = 0 , k = 0 ,length = 0;
	char tmp[3] = { 0 };
	memset(out , 0 , strlen(in) / 2);
	while(i < (int)strlen(in))
	{
		tmp[k++] = in[i++];
		tmp[k] = '\0';
		if(k == 2)
		{
			out[j++] = (unsigned char)strtol(tmp , (char**)NULL , 16);
			k = 0;
			length ++;
		}
	}
	if(len != NULL )
		*len = length;
	return out;
}
char* hextostr(const unsigned char* in , int len) 
{
	char* res = (char*)malloc(len * 2 + 1);
	int i = 0;
	memset(res , 0 , len * 2 + 1);
	while(i < len)
	{
		sprintf(res + i * 2 , "%02x" , in[i]);
		i ++;
	};
	i = 0;
	while(i < (int)strlen(res))
	{
		res[i] = toupper(res[i]);
		i ++;
	};
	return res;
}

然后是计算response过程:

1
2
3
/*这当然是不符合语法的,这样写大家应该就能明白*/
unsigned char* response_tmp = (unsigned char*)nonce
          + strtohex(hashed_password) + strtohex(aedkey);

最后把response_tmp通过服务器发过来的公钥进行RSA加密后得到response,传回服务器就可以通过验证了。

具体的信令格式都是明文,大家自己抓包看就好了,我在这里也就不多说了。

分类: Protocol 标签: , ,

飞信2010身份验证算法成功破解

三月 1st, {2010 61 条评论 24,314 人阅读过  

周末简单抓包分析了一下飞信的登录协议,昨天晚上一直延续到现在都在研究它的身份验证的算法,身份验证的过程大体搞清楚了,跟旧版本的形式差不多,最大的变化是2010不再是将密码的散列值进行传输,而是使用RSA非对称加密之后再进行传输。

在SipC注册的过程中,客户端生成一个CNOUCE,然后发给服务器,服务器自己生成一对密钥,私钥自己保留,公钥放到key里面发给客户端,随同公钥一起发给客户端的还有服务器生成的key值,和一个数据签名signature。

客户端的加密过程如下:

客户端将三个字段的UTF8数组合并成一个byte[](在C里面也就是unsigned char*),然后对这个byte[]进行RSA加密,加密使用了PKCS#1 1.5的padding方式,查了一下.net只支持两种padding,相比于no padding来说更安全一些,而且它每次加密之后的结果都是不一样的,另外使用这种Padding进行加密时,要加密的数据至少要比密钥的模长短至少11个字节,否则就会加密失败。

下面说一下是将哪三个字段加密了,第一个字段是服务器发过来的nonce,第二个字段是密码,第三个字段是一个aeskey。

nonce这个没什么好说的,就是Sipc注册的时候返回来的nonce值,直接放在前面就可以了。

password是这个是困扰我时间最长的,飞信在计算response值的时候,把这三个字段当成16进制字符串,也就是类似于”ACFDF768597″这种,它将它们转换成UTF8数组,每两个字节转换成一个字节的UTF8数组,但原始密码肯定不可能是这种标准的16进制字符串,必然是做过某种处理,注意到在发送验证信令的后面附上了一个值algorithm=”SHA1-sess-v4″,然后我意识到密码可能是经过SHA1散列处理了,事实上确实如此,只不过不如我想象的那么简单。在linux下用openssl提供的加密算法来测试,返回的却一直都是Unaccpectable,一直以为是加密算法出了问题,反复修改测试各种加密算法,无数次失败后终于决定回到windows下改用.net测试,用飞信用到的.net framework里提供的加密算法进行加密,确保加密算法没有问题,庆幸自己多掌握了几门语言,虽然写得不如别人漂亮,但能看懂对我来说就足够了。

用.net写了一个sipc验证的程序,结果总是提示密码错误,也就是说密码的处理上确实存在问题,折腾了两三个小时终于弄明白飞信对密码都动了哪些手脚。它将字符串fetion.com.cn:password的utf8字符串进行SHA1做散列,然后得出一个16进制字符串,SHA1是20个字节的,16进制字符串有40个字节,然后再将这个字符串前面再附上一个user-id,再做散列,得到的字符串就是最后要加密的密码,user-id这个东西在旧版本里面我就注意到,但事实上它并没有起到什么作用,新版本里面居然把它用到身份验证里面来了,至于user-id的获取,这个连看也不用看,肯定在SSI登录的完成的时候会在返回的数据包中包含。

再就是aeskey这个字段,这真是个让我哭笑不得的字段,从名字上看上去好像是用到了AES对称加密,事实上在生成这个字段的时候也确实用到了.net关于AES算法的Rijndael类,冒然贴一段代码:

1
2
3
4
5
6
7
public static string GenerateKey()
{
     Rijndael rijndael = Rijndael.Create();
     rijndael.KeySize = 0x100;
     rijndael.GenerateKey();
     return BinaryToHex(rijndael.Key);
}

简单地说一下,第一句话创建一个Rijndael对象,第二句话指定密钥的长度为256个字节,GenerateKey()这个方法在MSDN里面解释是说生成一个随机密钥,仔细查看了.net关于Rijndael的代码,发现它在它的父类中声明,是个抽象方法,而Rijndael类并没有对这个方法进行重写,也就是说这里调用的这个方法是个空方法,我把这个方法注释掉以后再调用这个方法,仍然可以生成随机字符串。最有意思的地方是这里并于AES的只有这几句话,根本没有涉及到任何加密的代码,也就是说飞信开发人员需要一个64字节的随机字符串附在密码后面,一起加密,这样使要加密字符串更长一些,加密的强度也就更大一些,而飞信开发人员嫌麻烦,懒得再重新写一个生成这样一个随机字符串的方法,于是就借用AES里面的随机密钥,其实整个身份验证过程跟AES没有半点关系,即使我把这个aeskey换成了一个固定值如:“4A026855890197CFDF768597D07200B346F3D676411C6F87368B5C2276DCEDD2”也一样能够验证通过,而光是这个名字就给了我很大的误导。

总而言之,飞信2010身份验证算法已经搞定了,在windows下用.net测试也通过了,接下来如何移植到C+openssl就是以后的事了,并于openssl实现这些相关的算法有时间再写吧。

声明:本文纯粹用于学习,不出于任何商业利益或者有任何损坏飞信利益的行为,涉及内容比较抽象,靠此文不可能写出飞信登录程序,希望权威部门不要找我麻烦。

分类: Protocol 标签: , ,

飞信2010登录协议分析

二月 27th, {2010 17 条评论 17,670 人阅读过  

飞信2010出了有一段时间了,但到现在也只是beta版,所以就没怎么关注它,今天下载了一个分析了一下它的协议,才发现飞信迟迟不推出新版本是因为它的协议有了比较大的变动,似乎想要把我们这些山寨货赶尽杀绝,也就是传说中的V4版本的协议,很多信令后面都加上了V4标志,这也意味着想要适应2010,协议部分就必须重写。

飞信的登录协议比以前的版本有了显著的变化,最明显的变化在于它对登录信息的加密处理上,旧版本的飞信登录信息的加密让人看起来有点小儿科,把几个字段这样加起来做散列,然后再那样加起来做散列,来来回回折腾最后得出一个response传回服务器验证。

2010的登录信息处理采用了非对称加密RSA算法。先贴几个抓出来的协议包吧:
1.SSI登录
SSI登录仍然采用了https登录,登录服务器应该没变,还是uid.fetion.com.cn。登录参数变了,加上必须的参数uri如下:

https://uid.fetion.com.cn/ssiportal/SSIAppSignInV4.aspx?mobileno=1348888888&&domains=fetion.com.cn%3bm161.com.cn%3bwww.ikuwa.cn&&v4digest-type=2&v4digest=password

注意:这里的密码也不再是v3里面的明文密码了,呵呵。

其中demains里面有三个域名,第一个应该是必须的,后两个不知道是哪家第三方的公司的域名,估计可以去掉,v4digest-type是密码类型,有如下定义:

1
2
3
4
5
6
public enum PasswordTypeEnum
{
    V3,
    V4Temp,
    V4
}

v4digest就是密码了,然后还有一些可选参数像algorithm之类的我也就没细看它的功能。

2.sipC注册

R fetion.com.cn SIP-C/4.0
F: 916098834
I: 1
Q: 1 R
CN: 1CF1A05B2DD0281755997ADC70F82B16
CL: type=”pc” ,version=”3.6.1900″

这是sipC注册信息,CN就是旧版本里的cnouce , CL就是客户端信息了。服务器收到后回复信令:

SIP-C/4.0 401 Unauthoried
F: 916098834
I: 1
Q: 1 R
W: Digest algorithm=”SHA1-sess-

v4″,nonce=”389CE97D594B290366DCD4CC2DA5368D”,key=”D84CA40D849FEF3B45F02BBA6900D2A1B3010001″,signature=”62F5BA9D9146D477CDECAE1FD47098D0CABBFD31D683B5223B5D52″

为了节约篇幅key和signature的内容我都删去了一部分,key就是共享密钥,这个用来对验证信息做非对称加密用的,signature是签名,用来对密钥信息做验证的。

3.sipC验证

R fetion.com.cn SIP-C/4.0
F: 916098834
I: 1
Q: 2 R
A: Digest

response=”5041619340F10D101149E45A8E2343294E8D5F4F5086C24703EE00CAA4DF8F9D2951E9F285C458F556

7454F5C35084A8CB7C5E3AA8D31D1FC3B9DD7CF395631E5EDE73E2E683D54C9E521443D31792E18B81CA26542BF5

79F662459CD5F6E4C0EC29767B6C5F775C10AF19B544B6957AC98FF6A420C7486BE68837A576036118″,algorith

m=”SHA1-sess-v4″
AK: ak-value
L: 426

消息体里面的xml信息就不贴出来了。最关键的部分是通信sipC注册返回的信息来计算这个response,计算response共需要用到4个参数,publickey,nouce,password,aeskey.

publickey刚才注册返回的key值。

nouce 注册返回的nouce值。

password用户密码。

aeskey 用户通过 Rijndael算法生成的AES串。

response的计算规则是:

1.str = nouce + password + aeskey

2.对str进行RSA非对称加密,RSA算法具体细节我不懂,不过查看了一个微软MSDN了也大体知道是怎么工作的,它在计算的时候需要Exponent(e,公钥指数),Modulus(n , 模),D (d , 私钥指数),不过我看飞信在传递RSA参数的时候只是指定了e和n,e是截取了公钥的前256的字符,n是截取了公钥的256个以后的字符。

3.通过2计算出来的参数对str进行加密,得到的就是response值。

将验证信令发送到服务器之后服务器会返回用户的详细信息,这与之前的版本之比也是一大改进,省去了再额外构造信令去GetPersonalInfo

另外,新版本的协议在获取用户信息上改动也很大,sipC验证完成后用户个人信息和联系人列表就会随返回消息一同发过来,旧版本会先发送GetContactList信息获取用户列表,然后再构造一个庞大的Subscription去订阅这些用户的消息,新版本将这些完全简化了,发送如下一条信令就可以订阅所有用户的信息,用户信息就会被一条一条地推送过来。

SUB fetion.com.cn SIP-C/4.0
F: 916098834
I: 8
Q: 1 SUB
N: PresenceV4
L: 95

<args>
<subscription self="v4default;mail-count" buddy="v4default" version="320595567" />
</args>

格式如下:

BN 916098834 SIP-C/4.0
N: PresenceV4
I: 1
L: 190
Q: 5 BN

<events>
    <event type="PresenceChanged">
        <contacts>
            <c id="601820518"><pr di="" b="0" d="" dt="" dc="0"/></c>
            <c id="464997176"><pr di="" b="0" d="" dt="" dc="0"/></c>
        </contacts>
    </event>
</events>

将字段名称也简化了,这样有助于在数据传输时候节省字节数,效率会更高一些。

OK,重要的变化差不多也就这些了,真要动手去写的话也够我忙活一阵子的了,现在没那么多时间了,要老老实实专心做项目了,2010到现在也只是个beta版,我先静观其变吧,免得做无用功。

分类: Protocol 标签: , ,

飞信文件传输协议简述

二月 3rd, {2010 没有评论 9,526 人阅读过  

今天切回win分析了一下飞信的一些零散的功能的协议,觉得好不容易切过来就抢包看了看文件传输的协议。状态机我就不细划了,简单的地写一下几个重要的点:
当发起文件传输请求的时候,客户端向服务器发起ShareContent的请求:

fetion.com.cn SIP-C/2.0
F: 916098834
I: 3
Q: 0 O
K: ShareContent
T: sip:572003969@fetion.com.cn;p=4599
L: 460

1
2
3
4
5
6
7
8
9
<share-content id="6b984ea5-2e66-48f4-acb3-60513160162a">
<caps modes="block;relay;p2p;p2pV2;relayV2;p2pV3;scV2" max-size="2097151" />
<client outer-ip="" inner-ip="59.64.128.137:1429;" port="1428" />
<fileinfo>
<transmit type="p2p" session-id="xz4BBcV6b984ea52e6648f4acb360513160162a" />
<file name="Desktop.rar" size="526697" url="" 
md5="1a0edaa0a213be4a28d415594a150053" id="6b984ea5-2e66-48f4-acb3-60513160162a" file-type="unknown" />
</fileinfo>
</share-content>

这里面包含几个重要的字段,session-id,id,md5
md5显而易见是对要传输的文件做md5散列运算得出的字符串,用于在接收端对文件内容的完整性进行校验。
id 其实是Guid,也就是全局唯一标识符,它指在一台机器上生成的数字,在同一时空中不会有另一台机器上的数字与它相同。
官方飞信是能C#开发的,GUID 的格式为“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”,其中每个 x 是 0-9 或 a-f 范围内的一个十六进制的数字。例如:337c7f2b-7a34-4f50-9141-bab9e6478cc8 即为有效的 GUID 值。 查看了一下飞信生成Guid用了一句话:

System.Guid.NewGuid().ToString("D");

session-id的生成是这样的,把id中的‘-’去掉,然后在前面加上”xz4BBcV”这个字符串,C#代码如下:

1
2
3
4
5
6
7
public string SessionId
{
    get
    {
        return ("xz4BBcV" + this.SessionGuid.ToString().Replace("-", string.Empty));
    }
}

filetype字段的定义

1
2
3
4
5
public enum ShareContentFileType
{
    Unknown,
    DirectSendImage
}

接收端接收文件时向服务器发送如下信息:

IN 916098834 SIP-C/2.0
I: 1
Q: 4 IN
F: sip:572003969@fetion.com.cn;p=4599
L: 201

1
2
3
<share-content action="accept">
<file id="c36b9ea6-7704-4860-a323-9470db7fab74" />
<client prefer-types="FFFFFFF" inner-ip="3B408089" net-type="7" udp-inner-port="1497" tcp-port="1496" /></share-content>

id就不用说了,在同一个会话中都使用一个id,prefer-types是指接收端希望使用的传输协议,共有如下几种类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum ShareContentType
{
    All = 0xfffffff,
    None = 0,
    P2P = 0xff00,
    RelayAndBlock = 1,
    TCP = 0x100,
    UDP = 0x200,
    V2All = 0xff0000,
    V2HttpRelay = 0x200000,
    V2TcpClient = 0x20000,
    V2TcpRelay = 0x100000,
    V2TcpServer = 0x10000,
    V2Udp = 0x40000
}

在这里使用FFFFFFF表示可以使用所有的类型进行传输,后来分析了一下发现其实飞信文件传输用的一直都是P2P。
OK,这些问题搞清楚了文件传输的实现就是时间的问题了。

分类: Protocol 标签: ,