星期三, 十一月 28, 2007

我们都知道在jsp中include有两种形式,分别是

< %@ include file=” ”%>

< jsp:include page=” ” flush=”true”/>

  前者是指令元素、后者是行为元素。具体它们将在何处用?如何用及它们有什么区别?这应该是很多人看到它都会想到的问题。下面一起来看看吧。

  通常当应用程序中所有的页面的某些部分(例如标题、页脚和导航栏)都相同的时候,我们就可以考虑用include。具体在哪些时候用< %@ include file=” ”%>,哪些时候用< jsp:include page=” ” flush=”true”/>.这种形式。首先要明白的是它们之间的区别。只有了解了它们用法的不同才理解该在何时去用以及如何选择。

  < %@ include file=” ”%>,jsp的include指令元素读入指定页面的内容。并把这些内容和原来的页面融合到一起。(这个过程是在翻译阶段:也就是jsp被转化成servlet的阶段进行的。

  这里对翻译阶段进行一下说明:我们知道,jsp页面不能原封不动地被传送给浏览器,所有的jsp元素都必须首先由服务器进行处理。这是通过将jsp页面转达化成servlet,然后执行这个servlet来完成的。服务器需要一个jsp容器来处理jsp页面。jsp容器通常以servlet的形式来实现,这个servlet经过配置,可以处理对jsp页面的所有请求。

  Jsp容器负责将jsp页面转化成servlet(称为jsp页面实现类?JSP Page implementation class),并编译这个servlet。这两步就构成了翻译阶段.

  由此我们就会知道:jsp页面是把include指令元素所指定的页面的实际内容(也就是代码段)加入到引入它的jsp页面中,合成一个文件后被jsp容器将它转化成servlet。可以看到这时会产生一个临时class文件和一个java文件。下面举个例子。

  服务器用tomcat,引入页面的jsp文件叫test.jsp。被引入的页面叫date.jsp.这个jsp文件里存放的是一个有关时间的jsp代码,当前的上下文根设为test

//======date.jsp的源文件=====//
<%@ page language=”java” contentType="text/html;charset=gb2312"%>
<%
java.util.Date date=new java.util.Date();
String date_cn ="";
String dateStr = "";
switch(date.getDay())
{
case 0:date_cn ="日"; break;
case 1:date_cn ="一"; break;
case 2:date_cn ="二"; break;
case 3:date_cn ="三"; break;
case 4:date_cn ="四"; break;
case 5:date_cn ="五"; break;
case 6:date_cn ="六"; break;
}
dateStr = (1900+date.getYear())+"年"+(date.getMonth()+1)+"月"+date.getDate()+
"日(星期" + date_cn + ")";
%>
document.write("<%=dateStr%>");
//======以下是test.jsp的源文件=============//
<%@ page language=”java” contentType=”text/html;charset=gb2312”%>
<html>
<head>
<title>include的两种用法</title>
<jsp:include page=”date.jsp” flush=”true”/>
<%--@ include file=”date.jsp” %-->
//我们在这里用include的两种不同形式来引入date.jsp这个文件.
<head>
<body>
<table><tr><td>
有关jsp中include的两种用法.敬请关注。
</td></tr></table>
</body>
</html>


  在test.jsp 文件中,我们只输出了一行文本“ 有关jsp中include的两种用法.敬请关注。”,现在让我们先用这种形式引入date.jsp这个文件。你想会出现什么问题了吗?此时出现了错误提示:


HTTP Status 500 ?
org.apache.jasper.JasperException: /date.jsp(0,0) Page directive:
can't have multiple occurrences of contentType


  以下还有一堆错误,但我们只要看这里就知道问题的所在了。状态码为http 500服务器内部错误。再看下面的提示。在date.jsp页面中不能指定多个contentType.

原因就在这里了。是因为在翻译阶段,date.jsp文件的代码被原封不动地加入到了test.jsp页面从而合成一个文件。合成后的文件中就会相同的:

< %@ page language=”java” contentType=”text/html;charset=gb2312”%>

  这句代码。解决的办法是把date.jsp文件中的这句删掉。刷新后再请求test.jsp页面

请求test.jsp在页面显示如下

2007年8月12日 13:12:40

有关jsp中include的两种用法.敬请关注。

  这时我们还不能发现什么。还是去查看tomcat下的临时文件吧。到那里去看看date.jsp文件的内容是否已被加入到了test.jsp文件中。

< 注.此处的tomcat装在E盘根目录下>

目录

E:tomcatworkStandalonelocalhosttest.

在这个目录下会看到

test_jsp.java和test_jsp.class两个文件。

  这里的java文件就是jsp容器将jsp转化成了servlet而得到的test_jsp.java这个文件。

  相对应的test_jsp.class这个文件就是编译test_jsp.java这个servlet文件产生的类文件了。打开所产生的servlet 文件(test_jsp.java)。此时我们会发现,在test.jsp 文件被转化成servlet文件时,在输出的< haed >之间加入了一些不是test.jsp页面里面的代码,新加入的内容就是 date.jsp里面的代码: 新加入了哪些内容或是否真的加入了新的内容请自己测试去看一下就会一目了然了.在这里不再详述.

以上就是我们用< %@ include file=”date.jsp”%>这种形式得到的结果.

下面我们换用< jsp:include page=”dae.jsp” flush=”true”/>也就是将

< %@ include file=”date.jsp”%>换成< jsp:include page=”dae.jsp” flush=”true”/>,然后请求test.jsp.

2007? ê 8??12?? 13:30:13

  有关jsp中include的两种用法.敬请关注。

  此时会在页面上看见.我们所引入date.jsp输出的日期中中文出现了乱码.什么原因?是因为include行为元素是在请求处理阶段执行的(此处要对请求处理阶段进行说明一下.Jsp容器除了上面提到的负责将jsp页面转化成servlet外,还负责调用jsp页面实现类以处理每个请求并产生应答. 这个阶段我们就称为请求处理阶段.请求处理阶段仅执行类文件)。

  所以在我们作include行为元素引入页面时,实际只是引用了 date.jsp这个文件被转化并被编译后产生的servlet类文件.既如此, date.jsp就是作为一个单独的文件在执行后才被test.jsp文件运行时调用.由于date.jsp文件中没有指定字符编码.所以出现了乱码.解决办法是在date.jsp文件中重新把刚才去掉的

  这行语句加入后刷新重新运行.此时页面显示正确,并跟用include指令正常运行时一样.再查看tomcat下的临时文件会发现.此时多出了一个 date_jsp.java文件和一个date_jsp.class文件.这两个文件得来的方式跟test_jsp.java和 test_jsp.class文件得来的方式一样.再查看一下此时test_jsp.java文件的代码会发现.此时只新增加了一句代码:

JspRuntimeLibrary.include(request, response, "date.jsp", out, true);

  它并没有把date.jsp文件的代码加入到test.jsp.

  只是在运行时引入了date.jsp页面执行后所产生的应答.这意味着我们可以指定任何能够产生应答的Web资源,(例如一个servlet或一个 jsp页面),只要这些资源所产生的类型和jsp页面产生的内容类型相同.JSP容器将通过一个内部的函数调用来执行指定的资源.因此,这些被引入的资源可以帮助处理原来的请求,所以这些资源可以访问请求作用域内的所有对象.以及所有原来的请求参数.

  由于在主页面被请求时,这些页面还没有被引入到主页面中,所以你可以对page属性使用一个请求时属性值,以便根据运行时的情况来决定要引入哪一个页面.还可以添加一些将被引入的页面读取的请求参数.

<jsp:include page=”<%=pageSelectedAtRuntime%>” flush=”true” >
<jsp:param name=”fitstParamer” value=”firstValue”>
<jsp:param name=”lastParamer” value=”lastValue”>
</jsp:include>

  如果修改了被引入的jsp页面,那么可以立刻使用该页面的最新版本,这是因为对待被引入的页面的方式与对待由浏览器直接调用的jsp页面的方式完全相同.即容器检测页面的变化,并自动进入翻译阶段,以得到页面的最新版本.

(注意,include行为元素同jsp其它元素一样,没有行为体时要以”/”结束.就像下面这样.

< jsp:include page=”” flush=”true” />

以下是对include 两种用法的区别

主要有两个方面的不同;

  1.执行时间上:

< %@ include file=”relativeURI”%> 是在翻译阶段执行

< jsp:include page=”relativeURI” flush=”true” /> 在请求处理阶段执行.

  2.引入内容的不同:

< %@ include file=”relativeURI”%>

  引入静态文本(html,jsp),在JSP页面被转化成servlet之前和它融和到一起.

< jsp:include page=”relativeURI” flush=”true” />引入执行页面或servlet所生成的应答文本.

  另外在两种用法中file和page属性都被解释为一个相对的URI.如果它以斜杠开头,那么它就是一个环境相关的路径.将根据赋给应用程序的URI的前缀进行解释,如果它不是以斜杠开头,那么就是页面相关的路径,就根据引入这个文件的页面所在的路径进行解释.更多有关URL是如何解释的请参考相关资料或书籍。(CSDN)


在中国崇尚权威的文化氛围中,名人名言占的地位是很重的,很多名家说的话,往往被人民当成指导自己人生观世界观的不二准则,但这些几千年流传下来的名言 中,也有一些话被人刻意或无知地曲解,背离了话语者本身的意旨与初衷,迷惑了天下万千受众。影不才,试举数例解析一二,以还世情真相与大众……好了我再文 诌诌地说话大家估计要扔鸡蛋,虚荣心也满足了,下面开始举例开讲。

1、以德抱怨

原句:“或曰:‘以德报怨,何如?’子曰:“何以报德?以直报怨,以德报德” ————《论语 宪问》

以德抱怨,是我们常听到的一句话了,人们通常理解的“以德抱怨”什么意思呢?就是说:孔老夫子教我们,别人欺负你了,你要忍,被打碎牙齿也要往肚子里吞, 别人来欺负你,你反而应该对他更好,要用你的爱心去感化他,用你的胸怀去感动他。这就让人感觉很有点肃然了。想想看,如果我一巴掌呼过去那个被打的人还笑 笑说打得好啊,你越打我我就要对你越好……遇到这种情况我也会感动到傻眼,这多好一孩子,多伟大的情操啊……

但事实上,我们根本曲解了孔子的原意,我当初,也万万没想到原来在孔子这句“以德抱怨”的后边还跟着另外一段话,什么话呢?子曰:“以德抱怨,何以报德? 以直报怨,以德报德!”看完以后,幡然醒悟,原来我们都被某个断章取义的孔子FANS给玩了一把!当时的真实情况是怎么样的呢?孔子的一个弟子问他说:师 傅,别人打我了,我不打他,我反而要对他好,用我的道德和教养羞死他,让他悔悟,好不好?孔子就说了,你以德抱怨,那“何以报德?”别人以德来待你的时 候,你才需要以德来回报别人。可是现在别人打了你,你就应该“以直抱怨”,拿起板砖飞他!看!就因为被人故意省略了一句话,刚烈如火的孔老夫子一下就被扭 曲成了现在这个温婉的受气包形象。

与西方文化相比,东方文明一向被认为是谦逊坚忍的,同样是被人欺负,西方的带头大哥耶稣在圣经里就叫嚣要:“以眼还眼,以牙还牙。”小弟们!别人瞪了你一 眼,你就要瞪回去,别人咬了你一口,你就要咬回来!而东方呢?佛家的精神领袖释迦牟尼说“我不入地狱,谁入地狱?”一副逆来顺受的样子。而且他可不是说说 而已的,大鹰来欺负他,要吃他的肉,他也真从自己身上一刀刀帮大鹰割下肉来,这就伟大得有点近乎BT了。说完了国外的几位大领导,再回到中国,孔老宗师的 这句话为什么会被别人有意地曲解呢?根源还是当时封建统治者的需要,他们的心思,无外乎就是要信仰孔子的万千民众成为“以德抱怨”的顺民,只有“以德抱 怨”的民众,才会老老实实地服从他们的剥削和压迫。我们再来看看影响吧,孔老先生这句被曲解了的“以德抱怨”,在中国文化史上起的压迫作用,可真是大到了 天上。皇帝残暴,我们要“以德抱怨”,地主剥削,我们要“以德抱怨”,八国联军都打到北京了,还是要“以德抱怨”,要卑躬屈膝,要割地称臣,要想尽一切办 法彰现自己的“德”,要“量中华之物力,结大国之欢心”……就是没想过反抗。试想,如果中国的文化里,没有这种把正确的思想东篡西改来为封建统治阶级服务 的恶习,如果孔子这句原话没有被刻意地曲解成这样子,我们中国人会养成这样一种懦弱的思维惯性吗?有人说西方人的骨子里本性是狼性,而东方人的骨子里的本 性是羊性,这里如月影倒想问问了,是什么原因让我们变成了这样?如果我们从古以来信奉的是西方那种“以眼还眼,以牙还牙”的训诫,近代史上的中国,会给世 界留下那么一个任人鱼肉的印象吗?

以德抱怨。

原句:以德抱怨,何以报德?以直报怨,以德报德。

歪曲程度:8

反面影响:9

2、民可使由之,不可使知之。

原句:。“子曰:兴于诗,立于礼,成于乐。子曰:民可使由之,不可使知之。”——————《论语·秦伯》

又是孔老先生的话,小小景仰一下下先。

民可使由之,不可使知之,这句话什么意思呢?是说,国家统治人民,指使驱赶他们去做事就行了,不要让他们明白他们在做什么。这句话在现在看来,绝对很明显 就是封建统治阶级几千年来一直在玩弄的愚民权术,小老百姓嘛,让他们知道那么多干什么?最好都是昏昏噩噩,只知道照着我们的意思去庸庸碌碌一辈子,这句训 诫不可谓不恶毒,它被千百年来中国的大小封建统治者奉为至宝,抹杀了多少真理与人民的创造性,但同时,我们的问题就来了,这样的一条愚民之术,真是孔子这 位致力于教化人民的教育家的本意吗?

我们知道,在春秋时代的文章是没有标点符号的,后人要研读那时侯的文章,便要再经过一个“句断”的过程,即是根据上下文意思自己在句子的适当地方加上标点 和停顿,这样才能得出一句句意通顺而连贯的话。在这里插一句,孔子的文章为什么被后人引用得非常多呢?这其一当然是孔子本身的名望使然,其二,则是因为他 的语录涵盖的范围非常广,几乎包括了社会生活的方方面面,这样一来,后来的人无论是谁,抱着什么目的,都可以从孔子的话中断章取义地引用其中的相关部分, 再加以自己个人倾向的理解和句断,用来证明自己的观点。比方说现在这句“民可使由之不可使知之”,从前文来看,我们孔老先生前边一直在讨论诗礼乐这三种东 东的教育问题,可怎么后边一下子就变成去教帝王权术的训诫了呢?原来,这又是后人别有用心地断章取义,刻意在句子的中间用一个不恰当的“句断”使这句话产 生了歧义的缘故。我们结合上下文的语境,很容易就能得出这句话正确的分句方法:“子曰:兴于诗,立于礼,成于乐。民可,使由之,不可,使知之。”孔子的整 句话就是说,诗、礼、乐这三样东西是教育民众的基础,一定要抓好,如果人民掌握了诗礼乐,好,让他们自由发挥,如果人民还玩不来这些东东,我们就要去教化 他们,让他们知道和明白这些东西。”你看,这才是“有教无类”的大教育家孔老先生的本意嘛。好好的一句话,硬是被那些别有用心的混蛋生生地坳成了孔子要愚 弄人民,要阻挠人民知书答理寻求知识的愚民之术。真不知道那些曲解这话的SB怎么想的,你要说这种P话是教育家孔子说出来的,谁信啊?都把人民当白痴来忽 悠了?

有人说古时候的中国人民是不幸的,即便是在物质丰富的盛世,他们的精神世界也贫乏得可怜,到了中近代,西方的思想领域渐渐迎来了文艺复兴,迎来了民主共 和,迎来了一个又一个推动世界的发明进步与思想启蒙,而聪明的中华民族呢?在更早的时候他们就有了伟大的四大发明,走在了世界的前边,可为什么就是这么好 的基础,到了近代我们反而远远于西方这些小字辈的民族?民可使由之,不可使知之,多恶毒的训诫啊!它象一条张开血盆大口的大蛇,狠狠地缠在中华民族的身 上,泯灭了多少先进的思维火花,束缚了多少应有所作为的苗子。但反过来看看,故意曲解这句话的封建统治者固然可恨,但我们能完全把责任都推到他们身上吗? 这本身就是多么可笑的一件事情,我们怎么就没想想,一个伟大的教育家,一个用一生置身于教化天下民众的孔子,又怎么会说出“民不可使知之”的蠢话来?更可 笑的是,这句话我们竟然就这样相信了几千年膜拜了几千年,这最后该伤心的,是孔老先生呢?还是我们呢?

民可使由之,不可使知之

原句:“子曰:兴于诗,立于礼,成于乐。民可,使由之;不可,使知之。”

歪曲程度:7

反面影响:9

3、无毒不丈夫

原句:量小非君子,无度不丈夫。——————民间谚语联对

量小非君子,无毒不丈夫,这句话绝对是中国众多以讹传讹的话中最搞笑的一个例子之一,就算是刚完成九年义务教育的初中生,也能轻易看出这其中的不妥之处, 首先,这无毒不丈夫,就跟我们认识的古人崇尚的价值观念大大背离了,大丈夫,自然是说那些坦坦荡荡胸怀宽广的男人,什么时候恶毒阴损,暗箭伤人这种前缀也 能放在前边来形容大丈夫了?

原来,这句来自民间的谚语本来应该是“量小非君子,无度不丈夫”,这本来是个很好的句子,里边充分运用了对仗。显示出了一份阳刚有力的气魄,一个胸怀坦荡 的男人形象就跃然于纸上,可惜劳动人民口耳相传的这一句话,到了朝廷上那些所谓的学高八斗的“君子”嘴里就变了个味。为什么呢?这要从古时候文人的习性说 起,在这副对联式的谚语里,“度”为仄声字,犯了孤平,念着别扭,很容易读为平声字“毒”,那些对音律美感要求甚高的学者们某天吃饱了没事儿干,便发挥他 们的专长自做主张,把这句改为“无毒不丈夫”了,于是这句话,终于成了典型的“信言不美,美言不信”的例句,成了迂腐文人的笔下的又一个牺牲品,“量小非 君子,无度不丈夫”,原话里一个君子对一个丈夫,一个度对另一个量,本来是很完美的一个句子,可经过上千年的以讹传讹,竟成了“无毒不丈夫”这句现在我们 挂在嘴边的口头禅。

这句话的整个演变过程,我宁可相信是无意的,否则也太缺德了,这不故意往咱男人身上抹黑么?但就是这无意的一个讹传,却也给我们带来了些说大不大,说小不 小的影响,我们都知道心理学上有个说法叫先入为主,潜移默化。无毒不丈夫,我们天天在耳濡目染这个被篡改了的男人形象之际,心里的价值观人生观,难免也会 受到些不良影响,天天看着电视里奸人怂恿主角干坏事的时候都用这句:“干吧!无毒不丈夫!”然后主角想想也对,于是五指并拢手掌上举,狠狠地做一个切的动 作:“无毒不丈夫,干!”你能说同样作为一个男人,这东西看久了你的思维能不收到一丁点儿的影响么?关于这种语言的影响力量,我再举个例子,比如隔壁顶撞 了我一下,这时候我朋友张三来了:用的是原版的好话劝我:“老话说,量小非君子,无度不丈夫,大如啊你是个男人,就别跟他一般计较了。”张三这样说,我这 个大丈夫当然再拉不下面子去计较这种小事,可如果另一种情况,张三来和我这么说:“这老话怎么说的?无毒不丈夫!大如,你是男人,不能怂啊!给你刀,去做 了他!” 看看,我听到他这话,除了接过刀速度冲出去乱砍以证明自己是个男人之外,还真没什么好办法,你说对不对?

无毒不丈夫

原句:量小非君子,无度不丈夫。

歪曲程度:9

反面影响:5

4、唯女子与小人难养也。

原句:唯女子与小人难养也,近之则不孙,远之则怨。——————《论语·阳货》

现今女权主义抬头,孔老夫子的这一句话便立时变成了女权精英批判者们群起而攻之的绝好靶子,看看,这就是你们中国男人歧视咱女性的最初起源,孔夫子这老混 蛋啊!害我们女性被压迫了几千年啊几千年,好不容易翻身做主人哪,我们要顶上半边天再抢他们的半边~~天!一时间,群情激愤,凶狠异常,热闹非凡。但各位 姐姐们阿姨们姑奶奶们,且听如月影说两句话,你们把这罪过都推到孔老人家身上,却是大大地冤枉了他了。

先根据当年老孔的处境,提出一个疑问:“孔老先生当时,为什么要说出“唯女子与小人难养也”这样的话?任何人说话都不会是无缘无故的,当然,除了疯子,而 我们的孔老先生应该不是疯子吧?而且这位孔老先生受《诗经》的影响很深,他说:“诗三百,一言以蔽之,思无邪”,他认为《诗经》是一部伟大的著作,而我们 再来看看这部孔子都很推崇的巨著,里边倒有很大一部分歌颂了女子的活泼美丽,大方善良,歌颂了当时男女平等的浪漫爱情氛围,事实也是这样,在春秋时代,男 女间是相当平等的,而孔夫子本人更是曾反复多次以诗经里的“妻子好合,如鼓瑟琴”来表达了自己对婚姻和女子的平等看待观点。所以,说孔子歧视妇女,不仅和 孔子的思想不符,更与当时的民间社会整个大环境对不上号,因此这个说法,实在是大大地有待商榷。

所以我们就要看看,孔子当初是在什么一种环境下说出“唯女子与小人难养也”这句话的?这话又是对谁说的?他一宗师级别的身份,不可能在路边想着想着突然心 情烦躁就开骂一八杆子打不着的妇女同志吧?在《史记·孔子世家》里,提到了孔子之前的卫国之行,孔子“居卫月余,灵公与夫人同车,宦者雍渠参乘出,使孔子 为次乘,招摇市过之。孔子曰:‘吾未见好德如好色者也。’于是丑之,去卫。”我来大致翻译一下这段话,当代全国教育劳模孔老先生受卫国国君的邀请,来到了 卫国参观学习休养,但在这期间,孔老先生突然发现自己被涮了,人家根本是拿他的身份来炫耀自己抬高自己而已,并不是真正支持他来这教化卫国民众的,尤其是 那个卫灵公的老婆,为了抬高自己的身望,公开炫耀,贬低了孔子,孔老先生那个郁闷啊,你卫灵公到底是喜欢德才多些,还是喜欢女色多些?在你心里我和你老婆 哪个重要?干醋自己吃完,依然没人鸟他,孔子怒了,说:“吾未见好德如好色者也!丢!此处不留爷,自有留爷处!GO,GO,GO!”收拾行李就离开了卫 国,离开之后,心情平复了,想起卫国公老婆那种仗着得宠,骄横跋扈乱政扰民的烂事儿,就发了感慨:“唯女子与小人难养也!近之则不孙,远之则怨。”

你看,知道了这些历史背景和人生经历,孔子这话就很好理解了。我们知道,他骂的人是那种“被养”的女人和小人,女子还好说,可想想什么人才能养小人?君主 啊!再看看孔子的卫国之行,一切都明白了,他这话断不是发神经突然开骂起包括自己老妈在内的所有女人,而是有一个特指的对象,这个对象,就是卫灵公那位老 婆南子,就是那些“近之则不孙,远之则怨”的宫廷女权,近之则不孙,远之则怨,这后一句话怎么解释呢?就是说你作为一个君主,对那些后宫的女人和没什么本 事的拍马小人太亲近了,她们就会得意忘形,忘了自己的身份,开始用你的权力胡做非为,而你疏远她们吧,她们又要埋怨,总之是非常麻烦。此前没见过什么宫廷 里女人之间的斗争的孔子说出这话是可以理解的,他的担心也不是没有道理,孔子之前,妲己误国,烽火戏诸侯,这些事例还少吗?孔子之后,宦官小人当道,皇后 外戚乱政的桥段更是屡屡上演,不可枚数。所以我们的女性同胞们,你们要清楚,孔子当时没发神经把你们全天下的女人不分青红皂白地乱骂一通,别再憋屈他老人 家了~~

最后一个问题,那么是谁误传了这句话,让我们可爱的女性同胞被压迫了几千年之久呢?俗话说冤有头,债有主,如月影要帮孔老夫子平反,自然也要再帮你们找到 一个真正的罪魁祸首,让你们发泄出那几千年积下的怨恨(怎么说着说着象鬼片了-_-!),好了,拉回正题,西汉的时候有一位所谓的“大儒”董仲舒,此人大 力主张“夫为妻纲”,就是这个家伙,借孔子的一句话断章取义,给广大的中华妇女头上戴上了千年的枷锁。看看这家伙在《基义》一书里提出的“三纲”:“君臣 父子夫妻之义,皆与诸阴阳之道。君为阳,臣为阴;父为阳,子为阴;夫为阳,妻为阴。王道之三纲,可求于天。”他这东西就是一为讨好历任统治者的马屁之作。 “君为臣纲”,为历代皇帝所接受,渐渐形成了封建统治的一套准则,而“夫为妻纲”,则为历代男人所推崇。这“纲”是什么意思?君为臣纲,则君要臣死,臣不 得不死!知道这个“纲”的厉害了吧?你想想,各位苦命的女子没嫁人前都一可人的窈窕淑女,本来身份平等,嫁了人,就得时刻提心吊胆地按着老公的这个“纲” 过日子,这三纲五常,束缚了中国多少年,害了多少代人啊!

唯女子与小人难养也。

原句:唯女子与小人难养也,近之则不孙,远之则怨。

歪曲程度:6

反面影响:9

5、吾生也有涯,而知也无涯。

原句:吾生也有涯,而知也无涯,以有涯随无涯,殆已 ——————《庄子·养生主》

这句话是庄子说的,八卦一下,庄子此人,实在是如月影非常喜欢的一个人物,他的思想和性情,都非常十分很特别对我胃口,什么大家不爱听这些?那算了,本来 想抖点猛料让大家深入了解一下我这个好同志的……好了好了别扔鸡蛋,我说正事还不行吗?好,先来看看,“吾生也有涯,而知也无涯”,这句话我们在什么地方 见得最多呢?第一,是图书馆,第二,是全国各个小学的后墙上,按照现在人们的普遍理解,这句话实在是学习励志类的不二明言警句,生也有涯,知也无涯,多崇 高的目标啊,这比什么孔老夫子的“学而时习之不亦乐乎”都来得有气势多了,古往今来,不知道多少学子在这句话的激励下凿壁偷光闻鸡起舞奋发图强追求上进读 那些永远也读不完的书,动力啊,偶像啊,庄子七老八十还能说出这么振奋人心的话,咱年轻的小辈正当青春,怎么能输给他老人家?不狠狠地读书怎么对得起他老 人家?

但真相说出来,往往会让人哭笑不得,庄子的这句话其实是这样说的,“吾生也有涯,而知也无涯,以有涯随无涯,殆已。”我庄子的生命是有限的,但我面对的知 识是无限的,要我以本来有限的生命,去追求那种永远看不到边的尽头,你当我是SB吗?这样会搞死自己的捏~~庄子是一个追崇顺其自然,清净无为的洒脱人 物,他认为人吃个饭,饱了就行,没什么必要追求奢华,穿个衣,别冻着就行,实在不需与什么人攀比。同样,学问也是如此,学够了就行,学以致用,学问学来就 是为了用,你一个人再牛B,还能全部用到这世间所有的学问?既然不能用到,那你老学老学把自己整个生命都拿来学这是干啥呢?时间都被你拿来学习了,还怎么 有时间把学到的东西拿来用呢?这不就跟吃饭是为了炫耀穿衣是为了攀比的人一样,完全本末倒置了吗?所以他老人家就在《养生主》一书中给后世陷入这个怪圈的 书呆子们提了个醒,你们这样苦学盲学乱学通学,对身体不好滴,会搞死自己滴~~

当然,庄子的这句被人误解的话,其实并没有以上那些例子引起的反面作用那么大,读书是必须的,掌握一定的知识也是必须的,但咱们要记得自己把握好,有个 度。你就算用半辈子武装得自己学富五车,却发现很多知识你根本用不上,这不浪费了么?庄子从来都不认为一个人能比自然更大,这是他所有的话中唯一的主题思 想,实际上,就算我们拿现在的世界观来衡量一部《庄子》,仍能发现他的学说里,有许多是符合辨证唯物主义思想的。可叹的是,就是这么一位崇尚自然的思想 家,他一句劝解读书人适可而止循序渐进的话却被一些叫嚷着“人定胜天”的人当成了激励年轻人拼命读书的名言来供着,科举改成了高考,八股改成了应试教育, 于是大学生一年比一年多,可素质却一年比一年低下,最后整黄了吧?你看现在大学文凭都和废纸没什么两样了,这帮人还不醒悟,还是继续读,读完大学读硕士, 读完硕士读博士,读完博士读博士后……读完出来一看,嘿,好象我已经老了捏,很多知识也用不着捏,这不自己跟自己过不去么?最后再说一句私人的牢骚话,最 近我去图书馆,总看见一帮老头老太太天天在那侯着,一见有啥免费的学习班就往里钻,也根本不管别人老师是教着什么,各位老爷爷老奶奶们,听我这个后辈说句 话,您们这不瞎捣蛋么?赶时髦啊?摆个性哪?想让我们称赞下您啊?还是真打算活到老学到老啊?自个儿细细思量一下有什么意义么?能不能腾些地方给些真正需 要的人进去?M的连“量子力学基础浅谈”都有街道的老大妈在里边占着位置,我真是无语了……

吾生也有涯,而知也无涯。

原句:吾生也有涯,而知也无涯,以有涯随无涯,殆已

歪曲程度:8

反面影响:3

6、相濡以沫

原句:相濡以沫,不如相忘于江湖——————《庄子·大宗师》

说点轻松点的吧。

沿着伟大的思想家庄老爷子的足迹,我们再来看看另外一条咱们耳熟能详的名句:“相濡以沫”。

说起这句话,其实大有来头,我想大家都听过现在流行的一个词吧?江湖,这个“江湖”是从哪儿出来的呢?很多人以为最初是古龙小说里的一句“人在江湖,身不 由己”,其实不然,江湖这个词最早的出处,便是在庄子说“相濡以沫”的这句话中。在《大宗师》篇中,庄子给我们讲了这么一个小故事:“泉涸,鱼相与处于 陆,相呴以湿,相濡以沫,不如相忘于江湖。”他说的是有一天,一眼泉水干了,两条小鱼被困在了一个小水洼,为了生存下去,它们彼此从嘴中吐出泡泡,用自己 的湿气来湿润对方的身体,互相扶持,互相依赖。但,与其在死亡边缘才这样互相扶持,还不如大家找到一条水路,开开心心地回到广阔的江河湖海,回到各自的天 地,彼此相忘,自由自在。

我们口口声声说的“江湖”就是这么来的,没想到吧?做下小广告哈,欢迎观看点评在下拙作:小说《书江湖》,不是什么特别牛B的书,主角比佐罗牛B一点而已,现正联系出版或许不日上市,先搜索来看一下也可以~~

恩,恩,回来,相濡以沫,多美的情景,虽然这并不是爱情最理想的状态。当年街道居委会的老大妈就算再没文化,这句话肯定是会说的,在我们的父辈那个婚姻靠 组织介绍的年代,这句话支撑了多少性格各异,却本没有多少感情基础的夫妻一直走到了今天,相濡以沫,多好的一个榜样啊,只是当年没曾想,只是当年不知道, 既然需要相濡以沫才能支撑,为什么却不干脆洒脱地放手,把自己,也把对方放开去找各自的幸福,去找各自的江湖呢?

相濡以沫,这句话的全部意思,又有多少人知道呢?

我有个大学的同学,是个陕西的女孩子,她在上学时找了个陕西的男朋友,她把自己的一切都给了他,当然包括了身体,可是那个懒惰的男朋友对她很不好,常打她 骂她象佣人一样地指使她,却又根本离不开她,我们惊奇的是,她竟然一直默默忍受了下来,一忍就是四年,我们忍不住问她,你们过得这么辛苦这么难受,为什么 还要在一起呢?她却这样回答:“嫁鸡随鸡,嫁狗随狗,虽然在一起不是很快乐,但这不就是相濡以沫吗?”

我无言,这能算是相濡以沫吗?为什么明明外边有更大的江湖,更宽广的天地,为什么明明知道知道自己有更好的未来,却要把双方都绑在一起,不开心地继续过下 去呢?我们都身处一个传统思维占据非常强势地位的社会,一句相濡以沫,不知道让多少爱情做了这种传统思维的牺牲品。其实想一想,如果自己都不开心,别人的 称赞又能怎么样?如果自己能找到属于自己的真正天地,别人的指责又能怎么样?唉,说大了,有点没边没际,父母也教育过我劝合不劝分的道理,这话题也到此为 止吧,劝合不劝分,相濡以沫啊,原来我也不能免俗。

只是叹息一声,我们都没曾想到,除了一句相濡以沫,原来后边,还可以相忘于江湖。

相濡以沫

原句:相濡以沫,不如相忘于江湖

歪曲程度:--

反面影响:--

7:天地不仁,以万物为刍狗

原句:天地不仁,以万物为刍狗,圣人不仁,以百姓为刍狗——————《道德经》

要说起这句话,大半的愤青都会告诉你,这话的意思是说:“天地残暴不仁,把万物都当成低贱的猪狗来看待,而那些高高在上的所谓圣人们也没两样,还不是把我 们老百姓也当成猪狗不如的东西!”标准的无政府主义宣言,标准的对社会不满。这句话在各种愤青的文章中,在当前下三流的网络YY小说里那是随处可见,主角 通常是弱小无助,饱受压迫和不公,于是有一天突然醒悟,大喊一声“天地不仁,以万物为刍狗!”立时或悟道飞升,或肉身成佛,或从一个人人可以欺负的小瘪三 变成一方霸主,等等等等,不一而足……

基本上,这些孩子的思维都可以总结为一种抑郁太久的被压迫感大爆发,就好象连老天都欠了他们个十万八万似的。可是呢?另一方面这帮无政府主义的家伙又没胆 子喊出什么“王侯将相宁有种乎”之类更牛B的话,更不敢扯出个大旗子大叫“苍天已死黄天当立!”最后,只好喊一喊这句“天地不仁,以万物为刍狗”,以表达 一种受到不公平待遇的憋屈。说实话,其实我挺同情这些人的,无政府主义没什么了不起,一个人在学生时代没有经历过无政府主义的阶段,基本上可以被认为没有 青春,我也经历过那种思想特别冲动觉得这世界上谁都跟自己过不去的时代,不同的是现在我知道了怨天尤人一点用都没有,社会是不公平,社会就是这么吊不公 平,如果你觉得社会不公平,你只有自己努力去改变这一切。

但就算我非常理解他们的青春年少,我还是有必要得跟这些后辈们说一句,你们这句话,其实恰好用错了。

天地不仁,以万物为刍狗,这根本不是你们从字面上理解的,说天地残暴不仁,把世间万物都看成猪狗,圣人不仁,以百姓为猪狗,也根本不是你们想的那样说所谓 高高在上的统治者们,把百姓看得低微卑贱。其实这句话的真正意思是说,天地不情感用事,对万物一视同仁,圣人不情感用事,对百姓一视同仁。你们推崇的这句 话,它的原意中根本没有你们所需要的压迫与歧视,却恰恰相反,它说的是一个公平的道理,我们所有的人,所有的众生在天地的眼中,都是平等的,为什么现在你 啃着干面包,别人却吃着海鲜大餐?孩子,在大叫不公平之前,为什么你不想想你是否和他流过一样的汗水?如果想不通,也行,那就别想了,为什么你不尝试通过 自己的努力,来稍微扭转一下这样的“不公”?只是大叫着不公平大叫着没天理大叫着等着天上掉个馅饼下来补偿你的委屈你的遗憾你正损失着的一去不复返的青春 年华,有用吗?

天地不仁,以万物为刍狗。

这一篇是写给那些迷茫孩子的,如果你理解错了这句话,你会陷入自己的监狱,不能自拔。如果你知道这句话的真正意思,我恭喜你,孩子,这时你才真正长大。

星期四, 十一月 01, 2007

squid filedescriptors 问题处理

最近,squid系统中的一台机器的硬盘坏了一块,所以对将squid系统切到了被分际上运行,刚开始很好,不久之后备份机的所有网络端口决绝响应!
现象描述如下,网络通,可ping到备份机,服务端口通过Telnet可以脸上,但是不响应任何输入!在运行日志中发现了如下信息!
WARNING! Your cache is running out of filedescriptors

由于主机与备份机软件系统相同,硬件配置基本相同,所以并未对这个WARNING多做关注,重新启动备份机后继续运行,不久之后又出现了上面的日志!

Google之后,初步判断这个状态与系统的工作不正常是有关联的,文件描述符被用尽了,所以不能在打开文件了(Socket与fd)是俩回事,所以可能Socket连上,但是无法响应!

处理这个问题的办法也很简单,在保证程序正确的情况下,可以通过增大系统支持的文件描述符个数来解决!

1修改系统的最大的file descriptors数

# ulimit -SHn 2048
# vi /usr/include/bits/types.h
将#define __FD_SETSIZE 1024改成#define __FD_SETSIZE 2048
然后重新编译你的squid
然后把ulimit -SHn 2048这行放到你的squid的启动脚本的最前面

vi /usr/include/bits/type.h (to change __FD_SETSIZE)

编译完后记得把types.h里的2048改回去

星期六, 十月 27, 2007

[Django]Windows下Django配置Apache示范设置

[Django]Windows下Django配置Apache示范设置
Version
Date
Creator
Description
1.0.0.1
2006-11-20
郑昀
草稿

继续阅读之前,我们假设您熟悉以下知识:
n Python
n Django
n Apache
如果不熟悉这些知识点,可以看本文最后资源列表中的文章。
本文讨论了在Windows环境下,将Django配置到Apache Web Server时所遇到的问题,以及最终解决的示范步骤。

您可以遵循后文描述的配置步骤,在此之前,我们先描述两个常见的错误现象。
[常见错误现象1]
关键词: client denied by server configuration。
表象: 访问了http://localhost/mysite/ 后,假如发现图片没有加载,或者css没有应用上,并且httpd.conf文件中Location的配置绝对路径肯定没错。

解释:
第一, 首先检查httpd.conf配置Alias的配置,
看看Alias设置的路径是否正确,
Alias /site_media c:/django/myproject/media
请保证您附加的这个路径肯定是你的图片或者css存放的路径。
其次,请检查您的django项目myproject目录下的settings.py,保证MEDIA_ROOT、STATIC_PATH、TEMPLATE_DIRS这三个参数指向的绝对路径正确无误。
最后,如果上面的参数都没错,却还是不行。那么请您检查Apache的日志文件
Apache2.2\logs\error.log
如果您发现有这样的错误提示:
错误日志
[Mon Nov 20 17:27:08 2006] [notice] Child 4172: Starting thread to listen on port 80.
[Mon Nov 20 17:27:08 2006] [error] [client 127.0.0.1] client denied by server configuration: c:/Django/myproject/media/css/global.css, referer: http://localhost:80/mysite/
那么说明是因为对这些资源文件的访问被拒绝了。
此时,请您浏览 http://localhost/site_media/css/global.css ,如果遇到了HTTP 403错误,就说明是权限问题。
此时,请到httpd.conf,增加如下配置来允许静态资源文件夹被访问:
增加的配置行
# 对需要访问的区域,可以增加正确的Directory块
# 否则会得到这样的错误:client denied by server configuration: c:/Django/myproject/media/css/global.css, referer: http:/ /localhost:80/mysite/

Order Deny,Allow
Allow from all

重启Apache服务。

[常见错误现象2]
关键词: EnvironmentError: Could not import settings。
表象: 访问了http://localhost/mysite/ 后,直接页面报告如下错误:
页面错误输出
1. Mod_python error: “PythonHandler django.core.handlers.modpython”
Traceback (most recent call last):
File “C:\Python24\Lib\site-packages\mod_python\apache.py”, line 299, in HandlerDispatchresult = object(req)
File “c:\django_src\django\core\handlers\modpython.py”, line 163, in handlerreturn ModPythonHandler()(req)
File “c:\django_src\django\core\handlers\modpython.py”, line 125, in __call__if settings.ENABLE_PSYCO:
File “c:\django_src\django\conf\__init__.py”, line 27, in __getattr__self._import_settings()
File “c:\django_src\django\conf\__init__.py”, line 54, in _import_settingsself._target = Settings(settings_module)
File “c:\django_src\django\conf\__init__.py”, line 82, in __init__raise EnvironmentError, “Could not import settings ‘%s’ (Is it on sys.path? Does it have syntax errors?): %s” % (self.SETTINGS_MODULE, e)
EnvironmentError: Could not import settings ‘myproject.settings’ (Is it on sys.path? Does it have syntax errors?): No module named myproject.settings

这是开始配置django+apache最容易遇到的问题,:D。

解释:
这是因为apache在系统目录下找不到myproject/settings.py文件。
首先检查PythonPath设置的路径是否正确:
PythonPath "sys.path+['c:/django']"
注意,这个'c:/django'路径实际是我们的项目路径c:/django/myproject的上一级目录!不要写错了。
增加的配置行
# mysite目录路径: c:/django/myproject,
# 但是对于PythonPath,必须设置成这个目录的上一级目录!
# this site url:http://localhost:80/mysite/

SetHandler python-program
PythonPath "sys.path+['c:/django']"
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE myproject.settings
PythonInterpreter mysite
PythonDebug On


配置Django到Apache的步骤:
如何在Windows环境下安装Apache和mod_Python, 这篇Blog解释得很明白,我下面的这些步骤得到了验证。1:从 http://httpd.apache.org/来获取apache_2.2.3-win32-x86-no_ssl.msi这个安装文件;运行这个msi即可顺利安装Apache。
2:
网络上有很多说可以利用Apache和mod_python源代码来编译安装mod_python的,但是如果你仅仅想快速安装,那么请从
http://www.apache.org/dist/httpd/modpython/win/3.2.10/
直接下载
mod_python-3.2.10.win32-py2.4-apache2.2.exe
文件,运行这个exe即可顺利把mod_python安装到Python2.4以及Apache2.2;
由于GFW的封锁,可能您无法访问apache网站,那么可以从这里下载:
http://www.cnblogs.com/Files/zhengyun_ustc/mod_python-3.2.10.win32-py2.4-apache2.2.rar
3:
我们用
Python django-admin.py startproject myproject
命令,在C盘的django目录下创建了一个Django项目myproject。我们把它引用的图片文件和css文件等静态资源都放在myproject文件夹下的media目录中。
此时,将myproject目录下的settings.py文件中的这三个参数修改一下,主要是为了从相对路径变成绝对路径:
修改的配置行
# Django settings for myproject project.
'''
将相对路径改为绝对路径。主要有:
MEDIA_ROOT
TEMPLATE_DIRS
STATIC_PATH
'''
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = 'c:/django/myproject'
STATIC_PATH = 'c:/django/myproject/media'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates".
# Always use forward slashes, even on Windows.
'c:/django/myproject/templates',
)
切记切记,一定要保证这三个参数是绝对路径。
4:
配置Apache的httpd.conf配置文件:
首先,在“Dynamic Shared Object (DSO) Support”的配置下增加一行
LoadModule python_module modules/mod_python.so
这个必须手动添加。
5:
我们列出此时Apache所需要的参数分别为:
项目名:myporject
试图访问的URL为:http://localhost/mysite/
静态资源文件的存放目录:c:/django/myproject/media
项目文件夹路径:c:/django/myproject
好了,此时你就应该在httpd.conf文件的最后附加这段配置,以便让Apache知道到哪里去定位myproject/settings.py文件:
增加的配置行
# mysite目录路径: c:/django/myproject,
# 但是对于PythonPath,必须设置成这个目录的上一级目录!
# this site url:http://localhost:80/mysite/

SetHandler python-program
PythonPath "sys.path+['c:/django']"
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE myproject.settings
PythonInterpreter mysite
PythonDebug On

对于上面的配置,Limodou注释道:“上面 PythonPath 主要是将 myproject的目录加入到 sys.path ,以便 Django 可以找到。需要使用绝对路径。
SetEvn 中设置的 DJANGO_SETTINGS_MODULE 就对应于你的 项目名.配置文件。因此为了能导入 项目名.配置文件,就需要前面的 PythonPath 的设置。
PythonDebug 和 PythonAutoReload 建议在生产时设为 Off 。”
为了让图片、css、script能够被Apache成功加载,还需要在httpd.conf最后附加这段配置:
增加的配置行
#Alias /site_media 是用来将 myproject的静态文件设置一个 URL 访问的别名。
Alias /site_media c:/django/myproject/media

SetHandler None


#Alias /media 是将 Django Admin 的静态文件设置一个 URL 的访问别名。
Alias /media c:/Django-0.95/django/contrib/admin/media

SetHandler None


# file types we want to serve statically
# case insensative match

SetHandler None


Limodou在Step by step中特地说“同时可以注意到 settings 我改为了 settings_apache 了。一方面将要把其中的内容有关相对路径的东西改为绝对路径,另一方面我还想保持现在的 settings.py 。”这也是一个很好的做法。
最后,修改MaxRequestsPerChild为1,这是一个可选项。Limodou是这么说的“同时如果你不想每次重启Apache 来进行测试,可以将: MaxRequestsPerChild 0. 改为: MaxRequestsPerChild 1

重启Apache服务。
6:
浏览 http://localhost/mysite/ 即可。

[参考资料]
1:《How to use Django with mod_python》Django官方文档
2:《HOWTO: Django on Windows》这篇blog给了我很大的帮助
3:《Django Step by Step (十二)》limodou的帮助文档4:《如何安装 Django

星期二, 十月 23, 2007

情爱的,你终于回来了,等你等得好辛苦!

情爱的,你终于回来了,等你等得好辛苦!
可你的兄弟 page 什么时候回来啊!

星期二, 七月 24, 2007

MySQL忘记超级用户口令怎么办

  如果MySQL正在运行,首先杀之: killall -TERM mysqld。
  启动MySQL:bin/safe_mysqld --skip-grant-tables &
  就可以不需要密码就进入MySQL了。
  然后就是
  >use mysql
  >update user set password=password("new_pass") where user="root";
  >flush privileges;
  重新杀MySQL,用正常方法启动MySQL 。

星期三, 七月 11, 2007

C++ 类构造
好久没用C++了,今天正好碰到一个函数返回对象的的问题,解决并顺便复习一下!
函数返回值是一个类,而这个类没有拷贝构造函数,而导致程序运行失败!缺省的赋值是没有办法解决那些在构造函数中的内存或文件等资源的!

class String {
private: char *data;

public:
String()
{
data = new char [10];
};
String(const String &);
String &operator = (const String &); };
String::String(const String &rs)
{
if(this != &rs) {
data = new char[10];
strcpy(this->data, rs.data);
}
}

String &String::operator=(const String & rs)
{
if(this != &rs)
{
delete[]data;
data = new char[10];
strcpy(this->data, rs.data);
}
return *this;
}

int main(int argc, char* argv[])
{
String s1; String s2(s1); //调用拷贝构造
String s3 = s1; //还是调用拷贝构造,为什么不调用赋值=
return 0;
}

String s2(s1);
String s3 = s1;
只是语法形式的不同,意义是一样的,都是定义加初始化,都调用拷贝构造函数。C++语言支持两种语法形式的初始化,就是上面的两种。
注意, String s3 = s1;
跟 String s3;
s3 = s1;
不同。这里则是首先调用默认构造函数初始化s3,然后使用拷贝赋值从s1那里进行拷贝。
而例子中s2和s3的构造过程都没有执行国默认构造函数。
至于为什么不同,不为什么,这就是语法,就好比循环要使用while和for,分支要使用if和switch一样,不能混用。

String c = b; //也是调用拷贝构造函数 而
String c; c = b;//这才是调用operator =
String s3 = s1; //还是调用拷贝构造,为什么不调用赋值= 这是因为这里的“=”不是运算符。它只是一种形式上规定的分隔符。

星期五, 七月 06, 2007

MySQL数据库插入性能的改进

最近,重新开始了对3年前一个项目的改进,该项目采用JDBC+MySQL的方式完成一些数据库操作,记得当时是每秒中可以插入70条左右的数据,由于可以满足当时的需要就没有太在意!

抱着软件质量就是持续不断的改进的态度,想把当初的JDBC+MySQL改为C API实现的方式,所以今天写了一个C程序客户端,测试中发现美妙中可以插入50条记录,感到可能时在什么地方用错了,到Google一些资料后,基本可以判断是采用了AOTOCOMMIT一引起的问题,所以对操作方式做了一些修改
SET AOTOCOMMIT=0
然后每插入1000条,就COMMIT一次,发现这个时候每秒可以插入2000条数据,比较满足。

可以看出,COMMIT是一个比较费时的过程
50*(COMMIT_COST + INSERT_COST) 基本等于 2000*INSERT_COST + 2*COMMIT_COST,此时客户端花的CPU比服务器要多。

继续改进,采用Prepare Statemode来降低INSERT语句的分析花费,每秒可以插入5000条
2000*INSERT_COST(SQL_PARSE+REAL_INSERT) + 2*COMMIT_COST基本等于5000*REAL_INSER + 5*COMMIT_COST
这个时候,客户端已经比服务器端花的时间少了,且服务器端出现了花费了更多的系统CPU,应该时磁盘IO操做便多的缘故!

星期二, 六月 26, 2007

Symbian系统

Symbian是一个实时性、多任务的纯32位操作系统,具有功耗低、内存占用少等特点,非常适合手机等移动设备使用,经过不断完善,可以支持GPRS、蓝芽、SyncML、以及3G技术。最重要的是它是一个标准化的开放式平台,任何人都可以为支持Symbian的设备开发软件。与微软产品不同的是,Symbian将移动设备的通用技术,也就是操作系统的内核,与图形用户界面技术分开,能很好的适应不同方式输入的平台,也可以使厂商可以为自己的产品制作更加友好的操作界面,符合个性化的潮流,这也是用户能见到不同样子的symbian系统的主要原因。现在为这个平台开发的java程序已经开始在互联网上盛行。用户可以通过安装这些软件,扩展手机功能。
在Symbian发展阶段,出现了三个分支:分别是Crystal、Pearl和Quarz。前两个主要针对通讯器市场,也是出现在手机上最多的,是今后智能手机操作系统的主力军。第一款基于Symabian系统的手机是2000年上市的某款爱立信手机。而真正较为成熟的同时引起人们注意的则是2001年上市的诺基亚9210,它采用了Crystal分支的系统。而2002年推出的诺基亚7650与3650则是Symbian Pearl分系的机型,其中7650是第一款基于2.5G网的智能手机产品,他们都属于Symbian的6.0版本。索尼爱立信推出的一款机型也使用了Symbian的Pearl分支,版本已经发展到7.0,是专为3G网络而开发的,可以说代表了当今最强大的手机操作系统。此外,Symbian从6.0版本就开始支持外接存储设备,如MMC,CF卡等,这让它强大的扩展能力得以充分发挥,使存放更多的软件以及各种大容量的多媒体文件成为了可能。
Symbian按版本来分,继2005年二月Symbian推出一款新的手机操作系统软件OSv9.0,到目前为止已先后有了6.0、6.1、7.0、7.0s、8.0、9.0几种版本。1999年3月Symbian推出了Symbian5.0操作系统,它的主要内核集合了网络,无线文字,电子邮件,名片薄以及个人信息助理,同时还具有支持标准网络页面的浏览器,配合java语言的支持,使得Symbian可以运行小型的应用程序。不过这个版本采用的机型甚少,基本上与EPOC没有太多的差别。
Symbian 6.0则在5.0的基础上增加了,GPRS、WAP1.2浏览器以及蓝牙技术的支持,用户可以运行第三方基于C++和J2ME开发的程序。而Symbian 6.1则是和Symbian 6.0相比主要增加了对USB的支持。Symbian 6.0的主要特点是: 支持语音通话和数据通信 ,支持Bluetooth和WAP ,配备安全性功能(SSL,HTTPS,WTLS) ,采用16bit Unicode,支持多语言显示 ,采用“PersonalJava 3.0”和“JavaPhone 1.0” 。
Symbian 7.0则支持多模式和3G手机(专区),可以让制造商们可以面向全世界推出可以运行于所有网络之上的Symbian OS手机,而且可以不对代码进行重大改动的情况下就可以重新使用许多目前已有的软件应用。7.0包含一些新的通讯、消息、联网和应用开发技术,并对一些与安全和认证相关的功能进行了改进。Symbian OS 7.0的其他功能包括:支持灵活的用户界面,例如Nokia的Series 60;支持几种音频/图像格式和许多面向游戏开发人员的API;全力的加密和认证管理,基于安全通讯协议(包括HTTPS、WTLS和SSL)及认证的应用安装;和Over-the-air(OTA)SyncML同步支持。
2004年2月,Symbian在授权LG等公司的时候,发布了Symbian8 .0版本。该版本改善了实时系统性能,提高了原有操作系统的兼容能力。此外,Symbian OS 8.0的软件工具改进了远程接入控制系统功能,运用调节装置消除手机用户使用增值服务时会受到的干扰。这个系统包含了绝对现代化的多媒体和Java设备,支持多种标准,其中包括JSR118, CLDC1.1 (JSR139),MobileMedia (JSR135),3D图像数据(JSR184), JTWI 1.0 c(JSR185)。最后,新版OS还支持SDIO。
2005年二月,英国著名手机软件制造商Symbian推出一款新的手机操作系统软件OSv9.0,它支持更高像素数码相机与三维游戏动画。该操作系统软件对运行环境要求较高,目前运行在由英国芯片开发商ARM提供的快速处理器芯片,并且需要相关设计与其它工具的支持来帮助手机生产商在开发其它版本时能够节约时间和降低成本。该软件可以处理200万像素的数字图片,甚至能够向无线耳机传送立体声音乐,更可喜的是,它不需要同步软件就能够从PC上导出MP3文件。
Series60是智能手机中应用最广泛的系统版本。Series60系统还分为6.0 OS、7.0 OS和8.0 OS三个版本。区别Series60的最直观因素在于屏幕的分辨率,Series60支持的分辨率为176*208像素,但以后S60还会支持240*320像素、352*416像素等。S60是拥有最多第三方软件或游戏的界面。
识别Series80界面最简单的办法就是看手机是否支持全键盘,例如诺基亚高端系列的9210、9300甚至9500,都采用全键盘的。识别Series90的最简便方法是:会采用触摸屏幕,分辨率也高达640*320像素。Series90应该是手机游戏、娱乐的最强平台,但因为采用Series90界面的手机型号太少,第三方软件的支持很少,所以S60依旧是玩家首选。
S40或S60是指诺基亚手机的上层平台。S60都是智能手机,所用底层操作系统是Symbian。诺基亚128*128屏幕都是S40 1.0。而128*160是S40 2.0。
另外S40中,3100支持java MIDP 1.0,所以最大支持64KB的java;6230支持java MIDP 2.0,所以最大支持128KB的java40和60,包括90,还有以前的30,都是针对与nokia不同的操作系统而言40,是nokia自行研制的手机操作系统,面对于中低端客户,支持kjava的开发,其处理器效率不高,内存偏低,屏幕大小128*128。
S60,是基于Symbian 操作系统,也就是以前的epoc,其处理器为arm9,处理效率很高,内存颇大,屏幕大小为208*176同时支持kjava和C++的开发90,属于手持式设备,也是Symbian 操作系统但是功能强劲,等同于一个PDA了。
其实技术来上说,S40跟S60是区别在系统平台上,就好像WIN98跟WIN2000,但手机不像PC,屏幕大小的确是个关口,以致于平台不容易做到向下兼容,但有的游戏也同样做出不同版本来的。
当然啦,S40比S60不单单是屏幕上的差距,更大的是性能上的差距,一般来说,S60开发出来的游戏质量都比S40高好多,但可惜,S60比S40的机子又贵上了很多啊。

星期日, 六月 03, 2007

Apache运行时库中的进程锁 -- apr_proc_mutex 命名所的扩展

Apache运行时库中的进程锁 得到一个明确的事实,那就是apr实现的进程锁其实时无名锁,只能在fork出的父子进程中互锁。


然而,通过直接进行系统调用实现命名锁的有2个比较显著的问题,1,代码移植性不够好,posix或sysv,fcntl或flock等实现在不同的系统上接口会有所不同。2,代码封装与风格问题,到现在,越来越不惜欢没有经过分装的直接操作了!

所以,将apr_proc_mutex做了几个扩展操作函数,为apr提供了命名的进程锁。

APR_LOCK_PROC_PTHREAD机制的锁没有实现完毕,会出现SIGBUS错误,其他的模式在fedora core 6上测试都是没问题的啦!
扩展的procx_mutex可以去我的网站下载了,必须需要apr库,仅仅添加了一个头文件。

#include "apr_pools.h"
#include "apr_general.h"
#include "apr_pools.h"
#include "apr_errno.h"
#include "apr_file_io.h"

#include "apr.h"
#include "apr_shm.h"
#include "apr_file_io.h"
#include "apr_proc_mutex.h"
#include "apr_errno.h"
#include "apr_general.h"
#include "apr_getopt.h"
#include "errno.h"
#include "assert.h"
#include "stdlib.h"


#define MAX_ITER 200
#define CHILDREN 6
#define MAX_COUNTER (MAX_ITER * CHILDREN)

void errlog(apr_status_t s, const char*log)
{
char errbuff[PATH_MAX];

if(log)
printf(log);
if(s!=APR_SUCCESS)
{
printf(apr_strerror(s,errbuff ,PATH_MAX));
abort();
}
}

int main(int argc, char* argv[])
{
apr_pool_t *pool;
apr_status_t rv;
const char* lockname = "/tmp/lock";
apr_lockmech_e mech = APR_LOCK_DEFAULT;
/*
APR_LOCK_FCNTL, fcntl()
APR_LOCK_FLOCK, flock()
APR_LOCK_SYSVSEM, System V Semaphores
APR_LOCK_PROC_PTHREAD, POSIX pthread process-based locking //Test failed
APR_LOCK_POSIXSEM, POSIX semaphore process-based locking
APR_LOCK_DEFAULT Use the default process lock
*/

if (mech==APR_LOCK_POSIXSEM)
lockname = "/posixlock";

static apr_proc_mutex_t *proc_lock;

apr_initialize();
apr_proc_mutex_unix_setup_lock_ex();

rv = apr_pool_create(&pool, NULL);
errlog(rv,"apr_pool_create\n");


#if USE_FORK
if(fork()!=0) {
#endif
if(argc==2 && strcmp(argv[1],"init")==0)
{
rv = apr_proc_mutex_create_ex(&proc_lock, lockname, mech, pool);
errlog(rv,"apr_proc_mutex_create_ex\n");

printf("Init OK\ndefulat=%s\nmutex=%s\nlocalfile=%s\n",apr_proc_mutex_defname(),apr_proc_mutex_name(proc_lock),apr_proc_mutex_ex_lockfile(proc_lock));

do{
errlog(0, "P Enter to lock\n");
getchar();
rv = apr_proc_mutex_lock(proc_lock);
errlog(rv, "P apr_proc_mutex_lock\n");
printf("P Enter to unlock\n");
getchar();

rv = apr_proc_mutex_unlock(proc_lock);
errlog(rv, "P apr_proc_mutex_unlock\n");
}while(1);
exit(0);
}
#if USE_FORK
}else {
#endif
printf("Child Mode\n");
// if (apr_proc_mutex_create_ex(&proc_lock, lockname, mech, pool))
rv=apr_proc_mutex_child_init_ex(&proc_lock, lockname, mech, pool);
errlog(rv,"C failed apr_proc_mutex_child_init\n");

printf("Init OK\ndefulat=%s\nmutex=%s\nlocalfile=%s\n",apr_proc_mutex_defname(),apr_proc_mutex_name(proc_lock),apr_proc_mutex_ex_lockfile(proc_lock));

do{
printf("C Enter to lock\n");
getchar();
rv = apr_proc_mutex_lock(proc_lock);
errlog(rv, "C apr_proc_mutex_lock\n");
printf("C Enter to unlock\n");
getchar();
rv = apr_proc_mutex_unlock(proc_lock);
errlog(rv, "apr_proc_mutex_unlock\n");
}while(1);

#if USE_FORK
}
#endif

exit(0);
return 0;
}

星期五, 五月 25, 2007

THe reason of iconv segment fault

#include "iconv.h"
#include "stdio.h"
#include "string.h"

int main()
{
iconv_t cd = iconv_open("UCS-2","GB2312");
char *p = "中文";
unsigned short int dst[100] = {0};
size_t leni = strlen(p);
size_t leno = sizeof(unsigned short int)*100;
char* pi = p;
char* po = (char*)&dst;

printf("Input:\t%d:%p:%s->%d:%p:%s\n", leni, p,p, leno, (char*)dst, dst);
printf("Iconv Input\t:%d:%p:%s->%d:%p:%s\n", leni, pi,pi, leno, po,po);
size_t ret = iconv(cd,π,&leni,&po,&leno);
printf("Output\t:%d:%p:%s->%d:%p:%s\n", leni, p,p, leno, (char*)dst, dst);
printf("Iconv Output\t:%d:%p:%s->%d:%p:%s\n", leni, pi,pi, leno, po,po);
printf("Ret=%d\n",ret);
iconv_close(cd);
return ret;
}
--Output
Input: 4:0x804872d:中文->200:0xbf86db84:
Iconv Input :4:0x804872d:中文->200:0xbf86db84:
Output :0:0x804872d:中文->196:0xbf86db84:-N噀
Iconv Output :0:0x8048731:->196:0xbf86db88:
Ret=0
iconv函数会造成指针的修改,如果直接操作&p,会将p直到别的地方!所以出现Segment Fault!

星期三, 五月 23, 2007

Windows 2003 R2 是什么?

R2是 Relase 2地缩写
微软日前表示,Windows Server 2003 R2是Windows Server 2003系列的最新版本,在分公司服务器管理、跨组织的身份认证、以及网络储存管理等三个领域进行了强化,并增加了许多新功能。

  下一代Windows服务器“Longhorn”前的重要版本

   依据 Windows Server 产品上市时间的规划,Windows Server 2003 R2是推进到下一代服务器“Longhorn”前的重要改版,它除了继承Windows Server 2003 SP1安全、稳定的优点之外,另外在以下的应用领域提供了新的技术与改善:

  分行服务器管理:R2 包含了新的数据压缩技术(Remote Differential Compression),更新的分布式档案系统(DFS),以及新的集中式打印机管理接口,让技术人员能更有效率的管理分公司的数据与打印机装置,透过 快速数据复写降低跨广域网络(WAN)传输所需的频宽,进而降低整体分公司的运营成本。

  身份与存取管理:新的联邦式的身份认证服务 (Active Directory Federation Service,ADFS)能在 Web Services 架构下安全地建立联邦式信任关系,在不同系统间交换身份识别及认证,协助IT人员迅速开发跨组织的身分管理机制,以及Web单一签入(Single Sign On,SSO)的解决方案。

  网络储存管理:新的档案服务器资源管理员(File Server Resource Manager,FSRM)能让系统管理者规划及最佳化储存装置的容量管制、提供过滤档案及周期性的报表产出。另外针对储域网络(SAN)R2 也提供了新的管理工具,同时支持光纤信道及iSCSI等技术,能让企业以最低的成本管理网络上的储存子系统。

  支持 64 位及与 UNIX 系统做更佳的整合

  Windows Server 2003 R2也将提供x64版本,支持日益采用的64位处理器,此外,与UNIX系统做更好的互通、通过SharePoint Services提高储存、分享及搜寻信息的能力等,都是R2改进过的重要功能。

  Windows Server 2003 R2 以 Windows Server 2003 SP1为基础,极易整合于目前Windows Server 2003 的环境中,升级不会影响既有的IT基础架构。

星期日, 五月 20, 2007

感觉最近的互联网很热闹

1. Google, MicroSoft, Yahoo广告公司收购!
2. Google与Sogou拼音输入法之争
3. MicroSoft称Linux侵权公司235专利
4. Norton错误删除用户系统文件
5. Kaspersky删除瑞信卡卡,杀的好啊!

Kaspersky申明->反思软件企业的作风,与输入法事件因该时不同的俩回事!

IT168 新闻快车】当诺顿的误杀事件还一波未平,另一边,瑞星卡巴斯基两位业界老大又因为“瑞星卡卡是否应该被列入卡巴斯基病毒查杀黑名单”一事发生了争论。5月20日下午,卡巴斯基官方通过IT168软件频道率先发布了如下声明:

2007年5月19日上午,卡巴斯基公司接到瑞星公司来电,称卡巴斯基反病毒软件将瑞星卡卡当作病毒“查杀”,要求卡巴斯基解决这一问题。卡巴斯基公司当 即表示,卡巴斯基将在保障用户安全的前提下,立即安排病毒分析工程师对瑞星卡卡在用户计算机内的行为进行全面评估。鉴于卡巴斯基在全球均奉行“用户安全第 一”的保护原则,对任何可能对计算机安全造成威胁的进程都会向用户提出警告,即使这些进程来自于商业软件。事实上,绝大多数全球领先的商业软件厂商在将其 软件推向市场时,都很重视其软件与卡巴斯基反病毒软件的兼容性。

令人吃惊的是,卡巴斯基公司负责媒体宣传的部门很快发现互联网上出现了多篇以“卡巴斯基查杀瑞星卡卡 导致大量用户无法正常升级”为题、内容完全一样的新闻稿,最早的一篇于2007年5月19日15点05分43秒发布在瑞星官方网站上。

为避免用户、公众受到不准确信息的干扰,切实保障用户网络安全,卡巴斯基公司特就瑞星公司指称卡巴斯基反病毒软件“查杀”瑞星卡卡一事,发表如下声明:

  一、卡巴斯基是全球技术领先 的安全软件厂商,卡巴斯基反病毒软件是专业而领先的安全软件,卡巴斯基将用户安全放在第一位,专注于应对病毒、木马、蠕虫、恶意程序等任何可能影响计算机 安全的威胁,并保护用户远离这些威胁。卡巴斯基并不是针对任何一款特定软件的卸载工具,也不事先假定任何软件是恶意软件或流氓软件;当然,如果某一软件包含上述任何可能对用户安全造成影响的威胁,卡巴斯基会实时的、负责任的提醒用户注意这些威胁,直到这些威胁在那些软件进行相应修正后消失。

二、卡巴斯基将传统的病毒特征码技术与最新的主动防御技术相结合,能够处理进出计算机的所有数据,包括电子邮件、互联网数据流和网络互动;能够监控在内存中运行的所有程序与进程,可在任何危险的、可疑的或隐藏的进程(例如Rootkits)出现时发出警报,阻断对系统的所有可能的有害更改,并可在恶意行为出现后将系统复原。

三、尽管卡巴斯基拥有全球范围内“百科全书”般丰富而庞大的病毒样本、恶意程序库和令人惊叹的主动防御能力,卡巴斯基依然十分尊重最终用户的选择权。在卡 巴斯基提出威胁警告后,用户可以根据自己的知识、经验或任何其它理由自主的决定是“删除”还是“跳过”这些威胁。是否“查杀”某个软件由用户来决定,如果 用户非常确信运行某个软件或进行某个操作不会产生任何威胁,卡巴斯基欢迎用户将其提交给卡巴斯基的病毒分析中心,并将由病毒分析工程师对其进行重新分析评 估。

四、作为致力于保障包括中国用户在内的全球用户计算机安全的公司,卡巴斯基愿意再次重申,卡巴斯基来中国是为了向中国用户提供更好、更完美的网络安全解决 方案,我们在全球都没有炒作概念,打口水战的传统,只懂得专注于提升技术实力以应对日益增长的安全威胁,只会低调前行。在发布这份官方声明之后,卡巴斯基 公司将不会再就此事件发表任何的评论。我们相信,在中国这样一个有着五千年文明的伟大而智慧的国度,所有用户都能看清楚所谓的“卡巴斯基查杀瑞星卡卡”事 件的真相。

鉴于网络安全形势越来越严峻,用户面临的威胁越来越多,卡巴斯基呼吁所有安全软件厂商,无论是本土的还是全球的,集中精力研发更好的产品,向用户提供尽可能准确的信息,为用户提供更好的服务。

特此声明
卡巴斯基公司

北京时间:2007年5月20日

星期五, 五月 18, 2007

win2003版本区别、四个版本的区别

-- 作者:黑鹰
-- 发布时间:2006-7-9 13:28:00

-- win2003版本区别、四个版本的区别

1.win2003版本区别

1)Windows Server 2003, Standard Edition (标准版)

针对中小型企业的核心产品,他也是支持双路处理器,4GB的内存。它除了具备 Windows Server 2003 Web Edition 所有功能外,还支持像证书服务、UDDI服务、传真服务、IAS因特网验证服务、可移动存储、RIS、智能卡、终端服务、WMS和 Services for Macintosh。

支持文件和打印机共享。 提供安全的网络联接。

2)Windows Server 2003, Enterprise Edition (企业版)

这个产品被定义为新一带高端产品,它最多能够支持8路处理器,32 GB内存,和28个节点的集群。它是 Windows Server 2003 Standard Edition 的扩展版本,增加了 Metadirectory Services Support、终端服务会话目录、集群、热添加( Hot-Add)内存和 NUMA非统一内存访问存取技术。这个版本还另外增加了一个支持64位计算的版本。

全功能的操作系统支持多达8个处理器。 提供企业级的功能例如8节点的集群,支持32GB内存。 支持英特尔 安腾Itanium 处理器。 将推出支持64位计算机的版本,可以支持8个64位处理器以及64GB的内存。

3)Windows Server 2003, Datacenter Edition (数据中心)

像以往一样,这是个一直代表微软产品最高性能的产品,他的市场对象一直定位在最高端应用上,有着极其可靠的稳定性和扩展性能。他支持高达8-32路处理器, 64GB的内存、2-8节点的集群。与 Windows Server 2003 Enterprise Edition 相比, Windows Server 2003 Datacenter Edition 增加了一套 Windows Datacenter Program 程序包。这个产品同样也为另外一个64位版本做了支持。

微软迄今为止提供的最强大、功能最为强劲的服务器操作系统。 支持32路处理器和64GB内存。 同时提供8点集群和负载均衡。 提供64位处理器平台,可支持惊人的64路处理器和512GB的内存。

4)Windows Server 2003, Web Edition (Web版)

这个版本是专门针对Web服务优化的,它支持双路处理器,2GB的内存。该产品同时支持ASP.NET、DFS分布式文件系统、EFS文件加密系统、 IIS6.0、智能镜像、ICF因特网防火墙、IPv6、Mircrosoft.Net Framework、NLB网络负载均衡、PKI、Print Services for UNIX、RDP、远程OS安装(非RIS服务)、RSoP策略的结果集、影子拷贝恢复(Shadow Copy Restore)、VPN和WMI命令行模式等功能。Windows Server 2003 Web Edition 唯一和其他版本不同的是它仅能够在AD域中做成员服务器,而不能够做DC域控制器。

可以架构各种网页应用,XML页面服务。 IIS 6.0。 轻松迅速开发各种基于 XML以及 ASP.NET服务项目的平台。


5)Windows Server 2003,64-bit Edition (64位版本)

专门针对64位处理器 安腾Itanium而开发的版本。
包括两个版本:

Windows Server 2003 Enterprise Server
64-bit Edition。

Windows Server 2003 Datacenter Server
64-bit Edition。

星期三, 五月 16, 2007

星期六, 五月 12, 2007

Fedora 7 -- what, when, and why

Fedora 7 -- what, when, and why


  • From: Max Spevack
  • To: fedora-announce-list redhat com, fedora-list redhat com
  • Cc:
  • Subject: Fedora 7 -- what, when, and why
  • Date: Tue, 8 May 2007 18:09:45 -0400 (EDT)

With the Red Hat Summit kicking off, I'd like to take a few minutes to discuss some of the recent happenings in the Fedora Project, particularly around Fedora 7. This email is being sent both to public Fedora mailing lists, and to Red Hat mailing lists, so that folks in both the external and internal Fedora communities can have a chance to read it, and people can all sort of consistently spread the same message about Fedora.

FEDORA 7

The next version of Fedora will be released on May 24th. It will be called "Fedora 7" -- not "Fedora Core 7". It's the most ambitious release of Fedora that we've undertaken, and I hope that when we look back at Fedora 7 one or two years down the road, the decisions that we made for this release will have proven to be as impactful as anything we've done in the Fedora space since the start of the Fedora Project. In one sentence: "Fedora 7 has been about improving the manner in which all future Fedora releases will be made."

(1) The entire toolchain is free. Every step in the distro creation process is free software, and can take place on hardware that is accessible both to Red Hat employees and the general Fedora community.
 Source code in an external version control system.
RPMs built on an external, open source build system.
Distributions built with an external, open source compose tool.

Why is this important? Because Fedora's ultimate goal over the past few years has been to allow proven non-Red Hat contributors to have greater influence and access to the Fedora Project. From the technical side, this goal has been pushed forward by the Fedora Extras project and the Fedora Infrastructure projects, especially. One of the Fedora Project's success metrics is building and running itself in a way such that no single entity can completely control Fedora's fate. Fedora 7 gets us there, insofar as there is no "secret sauce" in the ability to spin a Fedora distribution. Nothing is hidden. Balanced against these goals of increased openness has been the need to create systems and infrastructure that continue to allow RHEL or other Red Hat (not Fedora) branded products to be built and to be more firmly controlled by Red Hat than Fedora is. Fedora serves as an upstream for various Red Hat products, and Fedora has a responsibility to provide a good "service" to those downstream "customers".

(2) Custom spins of Fedora. The primary consequence of (1) is that customized versions of Fedora are now possible to an extent that was not available previously. User-generated Fedora, if you're looking for a buzzword. :-P
Think about some of the possibilities:

+ People in various countries *directly* managing localized spins of Fedora, customized both for language requirements and bandwidth requirements. + "Competing" spins of the Fedora Desktop, or server-ready package sets, allowing the best ones to gain popularity and be shared. + The ability for a business or a university that uses Fedora to take their own third-party RPMs and create a Fedora-derived distribution that integrates them at build time.

(3) Live CD, DVD, and USB technology. A Fedora spin can be loaded onto various forms of bootable media, which allows users to run their OS without hard disk installation, and gives users the ability to launch the installer with a simple double click. As with what is written above, the tools used for this are all free software, and therefore everything in this space is also fully customizable by users.

(4) Kernel-based Virtual Machine (KVM) technology has been integrated with the Fedora graphical virtualization manager tool. KVM provides a full virtualization solution, and users have a choice between KVM and Xen, along with Qemu, in this release.

(5) The usual set of upstream changes and improvements that are a part of any Fedora release.

RED HAT SUMMIT

Fedora will have a good presence at the Red Hat Summit, beginning on May 9th. There are four talks specifically about Fedora -- a general Q&A, a talk about Fedora Infrastructure, a talk about building custom versions of Fedora, and a talk about the Live CD technology. Furthermore, there will be a Fedora booth in the main area, where Fedora folks will be able to have general conversations with folks. If you are looking for me, this is a good place to start! Additionally, we will be able to give folks who attend the Summit a Fedora 7 Preview Live DVD. This is a special spin of Fedora that we did for the RH Summit, with custom artwork, Firefox start page, and various other goodies. We'll also have some of the bootable USB keys around for demonstration purposes, as well as demonstrations going on showing folks how to build custom spins of Fedora. I'm putting the finishing touches on this note on a plane somewhere between Raleigh and San Diego. :-)

FEDORA 7 LAUNCH

As stated earlier, the Fedora 7 release date is May 24th. This is one week before LinuxTag 2007 in Berlin, which we are using as the "European Launch" of Fedora 7. Our LinuxTag presence is being organized by Gerold Kassube, one of our Fedora Ambassadors. The community of folks in Europe who care deeply about Fedora is definitely a bright spot. For those of you in Europe who will attend LinuxTag, I look forward to seeing you there.
--
Max Spevack
+ http://fedoraproject.org/wiki/MaxSpevack
+ gpg key -- http://spevack.org/max.asc
+ fingerprint -- CD52 5E72 369B B00D 9E9A 773E 2FDB CB46 5A17 CF21

星期三, 五月 09, 2007

OpenSSL协议实现分析


1. 应用程序接口
1.1 SSL初始化
SSL_CTX* InitSSL(int server, char *cert, char *key, char *pw)
{
SSL_CTX* ctx;
SSL_METHOD *meth;
int status;
// 算法初始化
// 加载SSL错误信息
SSL_load_error_strings();
// 添加SSL的加密/HASH算法
SSLeay_add_ssl_algorithms();
// 服务器还是客户端
If(server)
meth = SSLv23_server_method();
else
meth = SSLv23_client_method();
// 建立新的SSL上下文
ctx = SSL_CTX_new (meth);
if(!ctx) return NULL;
// 设置证书文件的口令
SSL_CTX_set_default_passwd_cb_userdata(ctx, pw);
//加载本地证书文件
status=SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_ASN1);
if (status <= 0) {
frintf(stderr, "Use cert fail, status=%dn", status);
goto bad;
}
// 加载私钥文件
if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) <= 0) {
fprintf(stderr, "Use private key failn");
goto bad;
}
// 检查证书和私钥是否匹配
if (!SSL_CTX_check_private_key(ctx)) {
fprintf("Private key does not match the certificate public keyn");
goto bad;
}
fprintf("Cert and key OKn");
return ctx;
bad:
SSL_CTX_free (ctx);
return NULL;
}
1.2 建立SSL新连接
服务器:
// 建立SSL
ssl = SSL_new (ctx);
// 将SSL与TCP socket连接
SSL_set_fd (ssl, sd);
//接受新SSL连接
err = SSL_accept (ssl);
客户端:
// 建立SSL
ssl = SSL_new (ctx);
// 将SSL与TCP socket连接
SSL_set_fd (ssl, sd);
// SSL连接
err = SSL_connect (ssl);

服务器的SSL_accept()和客户端的SSL_connect()函数共同完成SSL的握手协商过程。
1.3 SSL通信
和普通的read()/write()调用一样,用下面的函数完成数据的SSL发送和接收,函数输入数据是明文,SSL自动将数据封装进SSL中:
读/接收:SSL_read()
写/发送:SSL_write()
1.4 SSL释放
SSL释放很简单:
SSL_free (ssl);
2. SSL实现分析
以下SSL源代码取自openssl-0.9.7b。

2.1 SSL_load_error_strings
该函数加载错误字符串信息:
void SSL_load_error_strings(void)
{
#ifndef OPENSSL_NO_ERR
ERR_load_crypto_strings();
ERR_load_SSL_strings();
#endif
}
最后将会进入函数:
static void err_load_strings(int lib, ERR_STRING_DATA *str)
{
while (str->error)
{
str->error|=ERR_PACK(lib,0,0);
ERRFN(err_set_item)(str);
str++;
}
}
其中:
#define ERR_PACK(l,f,r) (((((unsigned long)l)&0xffL)*0x1000000)|
((((unsigned long)f)&0xfffL)*0x1000)|
((((unsigned long)r)&0xfffL)))
#define ERRFN(a) err_fns->cb_##a
ERRFN(err_set_item)(str)的实际函数实现为:
static ERR_STRING_DATA *int_err_set_item(ERR_STRING_DATA *d)
{
ERR_STRING_DATA *p;
LHASH *hash;
err_fns_check();
hash = ERRFN(err_get)(1);
if (!hash)
return NULL;
CRYPTO_w_lock(CRYPTO_LOCK_ERR);
p = (ERR_STRING_DATA *)lh_insert(hash, d);
CRYPTO_w_unlock(CRYPTO_LOCK_ERR);
return p;
}
Lh_insert()将错误信息插入到一个链表中
如关于加密算法的错误信息:
/* crypto/err/err.c */
static ERR_STRING_DATA ERR_str_functs[]=
……
static ERR_STRING_DATA ERR_str_libraries[]=
……
static ERR_STRING_DATA ERR_str_reasons[]=
……
2.2 SSLeay_add_ssl_algorithms()
这实际是个宏:
#define OpenSSL_add_ssl_algorithms() SSL_library_init()
#define SSLeay_add_ssl_algorithms() SSL_library_init()
实际函数为SSL_library_init(),函数比较简单,就是加载各种加密和HASH算法:
/* ssl/ssl_algs.c */
int SSL_library_init(void)
{
#ifndef OPENSSL_NO_DES
EVP_add_cipher(EVP_des_cbc());
EVP_add_cipher(EVP_des_ede3_cbc());
#endif
#ifndef OPENSSL_NO_IDEA
EVP_add_cipher(EVP_idea_cbc());
#endif
#ifndef OPENSSL_NO_RC4
EVP_add_cipher(EVP_rc4());
#endif
#ifndef OPENSSL_NO_RC2
EVP_add_cipher(EVP_rc2_cbc());
#endif
#ifndef OPENSSL_NO_AES
EVP_add_cipher(EVP_aes_128_cbc());
EVP_add_cipher(EVP_aes_192_cbc());
EVP_add_cipher(EVP_aes_256_cbc());
#endif
#ifndef OPENSSL_NO_MD2
EVP_add_digest(EVP_md2());
#endif
#ifndef OPENSSL_NO_MD5
EVP_add_digest(EVP_md5());
EVP_add_digest_alias(SN_md5,"ssl2-md5");
EVP_add_digest_alias(SN_md5,"ssl3-md5");
#endif
#ifndef OPENSSL_NO_SHA
EVP_add_digest(EVP_sha1()); /* RSA with sha1 */
EVP_add_digest_alias(SN_sha1,"ssl3-sha1");
EVP_add_digest_alias(SN_sha1WithRSAEncryption,SN_sha1WithRSA);
#endif
#if !defined(OPENSSL_NO_SHA) && !defined(OPENSSL_NO_DSA)
EVP_add_digest(EVP_dss1()); /* DSA with sha1 */
EVP_add_digest_alias(SN_dsaWithSHA1,SN_dsaWithSHA1_2);
EVP_add_digest_alias(SN_dsaWithSHA1,"DSS1");
EVP_add_digest_alias(SN_dsaWithSHA1,"dss1");
#endif
/* If you want support for phased out ciphers, add the following */
#if 0
EVP_add_digest(EVP_sha());
EVP_add_digest(EVP_dss());
#endif
return(1);
}

2.3 SSL23_server_method()
建立服务器端的方法库,这是个通用函数,可动态选择SSL协议。如果想固定协议,可以只用SSLv2_server_method(), SSLv3_server_method() 等函数来初始化,该函数返回一个SSL_METHOD结构:
/* ssl/ssl.h */
/* Used to hold functions for SSLv2 or SSLv3/TLSv1 functions */
typedef struct ssl_method_st
{
int version; // 版本号
int (*ssl_new)(SSL *s); // 建立新SSL
void (*ssl_clear)(SSL *s); // 清除SSL
void (*ssl_free)(SSL *s); // 释放SSL
int (*ssl_accept)(SSL *s); // 服务器接受SSL连接
int (*ssl_connect)(SSL *s); // 客户端的SSL连接
int (*ssl_read)(SSL *s,void *buf,int len); // SSL读
int (*ssl_peek)(SSL *s,void *buf,int len); // SSL查看数据
int (*ssl_write)(SSL *s,const void *buf,int len); // SSL写
int (*ssl_shutdown)(SSL *s); // SSL半关闭
int (*ssl_renegotiate)(SSL *s); // SSL重协商
int (*ssl_renegotiate_check)(SSL *s); // SSL重协商检查
long (*ssl_ctrl)(SSL *s,int cmd,long larg,void *parg); // SSL控制
long (*ssl_ctx_ctrl)(SSL_CTX *ctx,int cmd,long larg,void *parg); //SSL上下文控制
SSL_CIPHER *(*get_cipher_by_char)(const unsigned char *ptr); // 通过名称获取SSL的算法
int (*put_cipher_by_char)(const SSL_CIPHER *cipher,unsigned char *ptr);
int (*ssl_pending)(SSL *s);
int (*num_ciphers)(void); // 算法数
SSL_CIPHER *(*get_cipher)(unsigned ncipher); // 获取算法
struct ssl_method_st *(*get_ssl_method)(int version);
long (*get_timeout)(void); // 超时
struct ssl3_enc_method *ssl3_enc; /* Extra SSLv3/TLS stuff */ // SSL3加密
int (*ssl_version)(); // SSL版本
long (*ssl_callback_ctrl)(SSL *s, int cb_id, void (*fp)()); // SSL控制回调函数
long (*ssl_ctx_callback_ctrl)(SSL_CTX *s, int cb_id, void (*fp)()); //SSL上下文控制回调函数
} SSL_METHOD;

/* ssl/s23_srvr.c */
SSL_METHOD *SSLv23_server_method(void)
{
static int init=1;
// 静态量,每个进程只初始化一次
static SSL_METHOD SSLv23_server_data;
if (init)
{
CRYPTO_w_lock(CRYPTO_LOCK_SSL_METHOD);
if (init)
{
// ssl23的基本方法结构
memcpy((char *)&SSLv23_server_data,
(char *)sslv23_base_method(),sizeof(SSL_METHOD));
// 服务器,所以要定义accept方法
SSLv23_server_data.ssl_accept=ssl23_accept;
// 根据SSL的版本设置SSL的具体方法函数
SSLv23_server_data.get_ssl_method=ssl23_get_server_method;
init=0;
}
CRYPTO_w_unlock(CRYPTO_LOCK_SSL_METHOD);
}
return(&SSLv23_server_data);
}

static SSL_METHOD *ssl23_get_server_method(int ver)
{
#ifndef OPENSSL_NO_SSL2
if (ver == SSL2_VERSION)
return(SSLv2_server_method());
#endif
if (ver == SSL3_VERSION)
return(SSLv3_server_method());
else if (ver == TLS1_VERSION)
return(TLSv1_server_method());
// 随着TLS1.1(RFC4346)的推出,估计不久将出现TLSv1_1_server_method()
else
return(NULL);
}
// SSL23的方法基本数据定义
/* ssl/s23_lib.c */
SSL_METHOD *sslv23_base_method(void)
{
return(&SSLv23_data);
}
static SSL_METHOD SSLv23_data= {
TLS1_VERSION,
tls1_new,
tls1_clear,
tls1_free,
ssl_undefined_function,
ssl_undefined_function,
ssl23_read,
ssl23_peek,
ssl23_write,
ssl_undefined_function,
ssl_undefined_function,
ssl_ok,
ssl3_ctrl,
ssl3_ctx_ctrl,
ssl23_get_cipher_by_char,
ssl23_put_cipher_by_char,
ssl_undefined_function,
ssl23_num_ciphers,
ssl23_get_cipher,
ssl_bad_method,
ssl23_default_timeout,
&ssl3_undef_enc_method,
ssl_undefined_function,
ssl3_callback_ctrl,
ssl3_ctx_callback_ctrl,
};
以SSL3的服务器方法函数为例,其他方法类似:
/* ssl/s3_srvr.c */
SSL_METHOD *SSLv3_server_method(void)
{
static int init=1;
static SSL_METHOD SSLv3_server_data;
// 只初始化一次
if (init)
{
CRYPTO_w_lock(CRYPTO_LOCK_SSL_METHOD);
if (init)
{
// ssl3的基本方法结构
memcpy((char *)&SSLv3_server_data,(char *)sslv3_base_method(),
sizeof(SSL_METHOD));
// ssl3的接受方法
SSLv3_server_data.ssl_accept=ssl3_accept;
// ssl3获取服务器的方法函数
SSLv3_server_data.get_ssl_method=ssl3_get_server_method;
init=0;
}

CRYPTO_w_unlock(CRYPTO_LOCK_SSL_METHOD);
}
return(&SSLv3_server_data);
}
// SSL3的方法基本数据定义
/* ssl/s3_lib.c */
static SSL_METHOD SSLv3_data= {
SSL3_VERSION,
ssl3_new,
ssl3_clear,
ssl3_free,
ssl_undefined_function,
ssl_undefined_function,
ssl3_read,
ssl3_peek,
ssl3_write,
ssl3_shutdown,
ssl3_renegotiate,
ssl3_renegotiate_check,
ssl3_ctrl,
ssl3_ctx_ctrl,
ssl3_get_cipher_by_char,
ssl3_put_cipher_by_char,
ssl3_pending,
ssl3_num_ciphers,
ssl3_get_cipher,
ssl_bad_method,
ssl3_default_timeout,
&SSLv3_enc_data,
ssl_undefined_function,
ssl3_callback_ctrl,
ssl3_ctx_callback_ctrl,
};
2.4 SSL23_client_method()

和服务器端的其实是相同的,只是不定义结构中的ssl_accept而是定义ssl_connnect:
SSL_METHOD *SSLv23_client_method(void)
{
static int init=1;
static SSL_METHOD SSLv23_client_data;
if (init)
{
CRYPTO_w_lock(CRYPTO_LOCK_SSL_METHOD);
if (init)
{
memcpy((char *)&SSLv23_client_data,
(char *)sslv23_base_method(),sizeof(SSL_METHOD));
SSLv23_client_data.ssl_connect=ssl23_connect;
SSLv23_client_data.get_ssl_method=ssl23_get_client_method;
init=0;
}
CRYPTO_w_unlock(CRYPTO_LOCK_SSL_METHOD);
}
return(&SSLv23_client_data);
}
2.5 SSL_CTX_new ()
该函数根据SSL方法获取一个SSL上下文结构,该结构定义为:
/* ssl/ssl.h */
struct ssl_ctx_st
{
SSL_METHOD *method;
STACK_OF(SSL_CIPHER) *cipher_list;
/* same as above but sorted for lookup */
STACK_OF(SSL_CIPHER) *cipher_list_by_id;
struct x509_store_st /* X509_STORE */ *cert_store;
struct lhash_st /* LHASH */ *sessions; /* a set of SSL_SESSIONs */
/* Most session-ids that will be cached, default is
* SSL_SESSION_CACHE_MAX_SIZE_DEFAULT. 0 is unlimited. */
unsigned long session_cache_size;
struct ssl_session_st *session_cache_head;
struct ssl_session_st *session_cache_tail;
/* This can have one of 2 values, ored together,
* SSL_SESS_CACHE_CLIENT,
* SSL_SESS_CACHE_SERVER,
* Default is SSL_SESSION_CACHE_SERVER, which means only
* SSL_accept which cache SSL_SESSIONS. */
int session_cache_mode;
/* If timeout is not 0, it is the default timeout value set
* when SSL_new() is called. This has been put in to make
* life easier to set things up */
long session_timeout;
/* If this callback is not null, it will be called each
* time a session id is added to the cache. If this function
* returns 1, it means that the callback will do a
* SSL_SESSION_free() when it has finished using it. Otherwise,
* on 0, it means the callback has finished with it.
* If remove_session_cb is not null, it will be called when
* a session-id is removed from the cache. After the call,
* OpenSSL will SSL_SESSION_free() it. */
int (*new_session_cb)(struct ssl_st *ssl,SSL_SESSION *sess);
void (*remove_session_cb)(struct ssl_ctx_st *ctx,SSL_SESSION *sess);
SSL_SESSION *(*get_session_cb)(struct ssl_st *ssl,
unsigned char *data,int len,int *copy);
struct
{
int sess_connect; /* SSL new conn - started */
int sess_connect_renegotiate;/* SSL reneg - requested */
int sess_connect_good; /* SSL new conne/reneg - finished */
int sess_accept; /* SSL new accept - started */
int sess_accept_renegotiate;/* SSL reneg - requested */
int sess_accept_good; /* SSL accept/reneg - finished */
int sess_miss; /* session lookup misses */
int sess_timeout; /* reuse attempt on timeouted session */
int sess_cache_full; /* session removed due to full cache */
int sess_hit; /* session reuse actually done */
int sess_cb_hit; /* session-id that was not
* in the cache was
* passed back via the callback. This
* indicates that the application is
* supplying session-id's from other
* processes - spooky :-) */
} stats;
int references;
/* if defined, these override the X509_verify_cert() calls */
int (*app_verify_callback)(X509_STORE_CTX *, void *);
void *app_verify_arg;
/* before OpenSSL 0.9.7, 'app_verify_arg' was ignored
* ('app_verify_callback' was called with just one argument) */
/* Default password callback. */
pem_password_cb *default_passwd_callback;
/* Default password callback user data. */
void *default_passwd_callback_userdata;
/* get client cert callback */
int (*client_cert_cb)(SSL *ssl, X509 **x509, EVP_PKEY **pkey);
CRYPTO_EX_DATA ex_data;
const EVP_MD *rsa_md5;/* For SSLv2 - name is 'ssl2-md5' */
const EVP_MD *md5; /* For SSLv3/TLSv1 'ssl3-md5' */
const EVP_MD *sha1; /* For SSLv3/TLSv1 'ssl3->sha1' */
STACK_OF(X509) *extra_certs;
STACK_OF(SSL_COMP) *comp_methods; /* stack of SSL_COMP, SSLv3/TLSv1 */

/* Default values used when no per-SSL value is defined follow */
void (*info_callback)(const SSL *ssl,int type,int val); /* used if SSL's info_callback is NULL */
/* what we put in client cert requests */
STACK_OF(X509_NAME) *client_CA;

/* Default values to use in SSL structures follow (these are copied by SSL_new) */
unsigned long options;
unsigned long mode;
long max_cert_list;
struct cert_st /* CERT */ *cert;
int read_ahead;
/* callback that allows applications to peek at protocol messages */
void (*msg_callback)(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg);
void *msg_callback_arg;
int verify_mode;
int verify_depth;
unsigned int sid_ctx_length;
unsigned char sid_ctx[SSL_MAX_SID_CTX_LENGTH];
int (*default_verify_callback)(int ok,X509_STORE_CTX *ctx); /* called 'verify_callback' in the SSL */
/* Default generate session ID callback. */
GEN_SESSION_CB generate_session_id;
int purpose; /* Purpose setting */
int trust; /* Trust setting */
int quiet_shutdown;
};

typedef struct ssl_ctx_st SSL_CTX;
/* ssl/ssl_lib.h */
SSL_CTX *SSL_CTX_new(SSL_METHOD *meth)
{
SSL_CTX *ret=NULL;

if (meth == NULL)
{
SSLerr(SSL_F_SSL_CTX_NEW,SSL_R_NULL_SSL_METHOD_PASSED);
return(NULL);
}
if (SSL_get_ex_data_X509_STORE_CTX_idx() < 0)
{
SSLerr(SSL_F_SSL_CTX_NEW,SSL_R_X509_VERIFICATION_SETUP_PROBLEMS);
goto err;
}
// 分配上下文的内存空间
ret=(SSL_CTX *)OPENSSL_malloc(sizeof(SSL_CTX));
if (ret == NULL)
goto err;
memset(ret,0,sizeof(SSL_CTX));
// 初始化上下文的结构参数
ret->method=meth;
ret->cert_store=NULL;
ret->session_cache_mode=SSL_SESS_CACHE_SERVER;
ret->session_cache_size=SSL_SESSION_CACHE_MAX_SIZE_DEFAULT;
ret->session_cache_head=NULL;
ret->session_cache_tail=NULL;
/* We take the system default */
ret->session_timeout=meth->get_timeout();
ret->new_session_cb=0;
ret->remove_session_cb=0;
ret->get_session_cb=0;
ret->generate_session_id=0;
memset((char *)&ret->stats,0,sizeof(ret->stats));
ret->references=1;
ret->quiet_shutdown=0;
/* ret->cipher=NULL;*/
/* ret->s2->challenge=NULL;
ret->master_key=NULL;
ret->key_arg=NULL;
ret->s2->conn_id=NULL; */
ret->info_callback=NULL;
ret->app_verify_callback=0;
ret->app_verify_arg=NULL;
ret->max_cert_list=SSL_MAX_CERT_LIST_DEFAULT;
ret->read_ahead=0;
ret->msg_callback=0;
ret->msg_callback_arg=NULL;
ret->verify_mode=SSL_VERIFY_NONE;
ret->verify_depth=-1; /* Don't impose a limit (but x509_lu.c does) */
ret->sid_ctx_length=0;
ret->default_verify_callback=NULL;
if ((ret->cert=ssl_cert_new()) == NULL)
goto err;
ret->default_passwd_callback=0;
ret->default_passwd_callback_userdata=NULL;
ret->client_cert_cb=0;
ret->sessions=lh_new(LHASH_HASH_FN(SSL_SESSION_hash),
LHASH_COMP_FN(SSL_SESSION_cmp));
if (ret->sessions == NULL) goto err;
ret->cert_store=X509_STORE_new();
if (ret->cert_store == NULL) goto err;
// 建立加密算法链表
ssl_create_cipher_list(ret->method,
&ret->cipher_list,&ret->cipher_list_by_id,
SSL_DEFAULT_CIPHER_LIST);
if (ret->cipher_list == NULL
|| sk_SSL_CIPHER_num(ret->cipher_list) <= 0)
{
SSLerr(SSL_F_SSL_CTX_NEW,SSL_R_LIBRARY_HAS_NO_CIPHERS);
goto err2;
}
// 定义上下文结构中HASH算法
if ((ret->rsa_md5=EVP_get_digestbyname("ssl2-md5")) == NULL)
{
SSLerr(SSL_F_SSL_CTX_NEW,SSL_R_UNABLE_TO_LOAD_SSL2_MD5_ROUTINES);
goto err2;
}
if ((ret->md5=EVP_get_digestbyname("ssl3-md5")) == NULL)
{
SSLerr(SSL_F_SSL_CTX_NEW,SSL_R_UNABLE_TO_LOAD_SSL3_MD5_ROUTINES);
goto err2;
}
if ((ret->sha1=EVP_get_digestbyname("ssl3-sha1")) == NULL)
{
SSLerr(SSL_F_SSL_CTX_NEW,SSL_R_UNABLE_TO_LOAD_SSL3_SHA1_ROUTINES);
goto err2;
}
if ((ret->client_CA=sk_X509_NAME_new_null()) == NULL)
goto err;
CRYPTO_new_ex_data(CRYPTO_EX_INDEX_SSL_CTX, ret, &ret->ex_data);
ret->extra_certs=NULL;
// 压缩算法
ret->comp_methods=SSL_COMP_get_compression_methods();
return(ret);
err:
SSLerr(SSL_F_SSL_CTX_NEW,ERR_R_MALLOC_FAILURE);
err2:
if (ret != NULL) SSL_CTX_free(ret);
return(NULL);
}
2.6 SSL_CTX_set_default_passwd_cb[_userdata]()

这个函数比较简单,就是设置SSL要加载的证书的口令,如果不设置的话加载证书时会出提示符要求输入口令的,这样在程序中使用就比较麻烦,该函数就是预先将口令保存,在读证书时自动使用。

实现该功能的有两个函数SSL_CTX_set_default_passwd_cb()和SSL_CTX_set_default_passwd_cb_userdata(),前者是定义一个口令回调函数,要获取口令时口令由该函数获取;后者是直接将口令设置好。

/* ssl/ssl_lib.c */
void SSL_CTX_set_default_passwd_cb(SSL_CTX *ctx, pem_password_cb *cb)
{
ctx->default_passwd_callback=cb;
}
void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *ctx,void *u)
{
ctx->default_passwd_callback_userdata=u;
}

举例:

static int
pass_cb(char *buf, int len, int verify, void *password)
{
snprintf(buf,len, "123456");
return strlen(buf);
}
SSL_CTX_set_default_passwd_cb(ctx, pass_cb);
等价于:
SSL_CTX_set_default_passwd_cb_userdata(ctx, "123456");
2.7 SSL_CTX_use_certificate_file()

该函数读取证书文件,证书文件通常都进行了加密保护。普及一下,证书文件里肯定是有公钥的,一般没私钥,某些情况会把私钥也包含进去,但那样作太不安全了,原则上私钥是永远不会给别人看到的,就算是进行了加密保护。
/* ssl/ssl_rsa.c */
int SSL_use_certificate_file(SSL *ssl, const char *file, int type)
{
int j;
BIO *in;
int ret=0;
X509 *x=NULL;
in=BIO_new(BIO_s_file_internal());
if (in == NULL)
{
SSLerr(SSL_F_SSL_USE_CERTIFICATE_FILE,ERR_R_BUF_LIB);
goto end;
}
if (BIO_read_filename(in,file) <= 0)
{
SSLerr(SSL_F_SSL_USE_CERTIFICATE_FILE,ERR_R_SYS_LIB);
goto end;
}
// 根据证书是PEM还是DER分别读取进行解码
// DER是二进制格式,PEM则是对DER用BASE64编码的后的文本格式
if (type == SSL_FILETYPE_ASN1)
{
j=ERR_R_ASN1_LIB;
x=d2i_X509_bio(in,NULL);
}
else if (type == SSL_FILETYPE_PEM)
{
j=ERR_R_PEM_LIB;
x=PEM_read_bio_X509(in,NULL,ssl->ctx->default_passwd_callback,ssl->ctx->default_passwd_callback_userdata);
}
else
{
SSLerr(SSL_F_SSL_USE_CERTIFICATE_FILE,SSL_R_BAD_SSL_FILETYPE);
goto end;
}
if (x == NULL)
{
SSLerr(SSL_F_SSL_USE_CERTIFICATE_FILE,j);
goto end;
}
// 加载解码后后的证书
ret=SSL_use_certificate(ssl,x);
end:
if (x != NULL) X509_free(x);
if (in != NULL) BIO_free(in);
return(ret);
}

2.8 SSL_CTX_use_PrivateKey_file()

该函数加载私钥文件,和SSL_CTX_use_certificate_file()是类似的,因为RSA算法的公钥私钥是对称的,刚生成密钥时谁作私钥都行。
SSL_CTX_use_PrivateKey_file()只加载PEM格式私钥,DER格式的用函数SSL_use_PrivateKey_ASN1()加载。

/* ssl/ssl_rsa.c */
int SSL_use_PrivateKey_file(SSL *ssl, const char *file, int type)
{
int j,ret=0;
BIO *in;
EVP_PKEY *pkey=NULL;
in=BIO_new(BIO_s_file_internal());
if (in == NULL)
{
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE,ERR_R_BUF_LIB);
goto end;
}
if (BIO_read_filename(in,file) <= 0)
{
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE,ERR_R_SYS_LIB);
goto end;
}
// 私钥只支持PEM格式
if (type == SSL_FILETYPE_PEM)
{
j=ERR_R_PEM_LIB;
pkey=PEM_read_bio_PrivateKey(in,NULL,
ssl->ctx->default_passwd_callback,ssl->ctx->default_passwd_callback_userdata);
}
else
{
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE,SSL_R_BAD_SSL_FILETYPE);
goto end;
}
if (pkey == NULL)
{
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE,j);
goto end;
}
// 加载私钥
ret=SSL_use_PrivateKey(ssl,pkey);
EVP_PKEY_free(pkey);
end:
if (in != NULL) BIO_free(in);
return(ret);
}

2.9 SSL_CTX_check_private_key()

该函数检查所用的公钥私钥是否是匹配的
int SSL_CTX_check_private_key(SSL_CTX *ctx)
{
if ( (ctx == NULL) ||
(ctx->cert == NULL) ||
(ctx->cert->key->x509 == NULL))
{
SSLerr(SSL_F_SSL_CTX_CHECK_PRIVATE_KEY,SSL_R_NO_CERTIFICATE_ASSIGNED);
return(0);
}
if (ctx->cert->key->privatekey == NULL)
{
SSLerr(SSL_F_SSL_CTX_CHECK_PRIVATE_KEY,SSL_R_NO_PRIVATE_KEY_ASSIGNED);
return(0);
}
// 这才是真正比较函数,在crypto/x509/x509_cmp.c中定义
return(X509_check_private_key(ctx->cert->key->x509, ctx->cert->key->privatekey));
}
2.10 SSL_new
该函数根据SSL_CTX实现一个SSL结构实例,SSL结构是个很复杂的结构,定义如下:

/* ssl/ssl.h */
typedef struct ssl_st SSL;
struct ssl_st
{
/* protocol version
* (one of SSL2_VERSION, SSL3_VERSION, TLS1_VERSION)
*/
int version;
int type; /* SSL_ST_CONNECT or SSL_ST_ACCEPT */
SSL_METHOD *method; /* SSLv3 */
/* There are 2 BIO's even though they are normally both the
* same. This is so data can be read and written to different
* handlers */
#ifndef OPENSSL_NO_BIO
BIO *rbio; /* used by SSL_read */
BIO *wbio; /* used by SSL_write */
BIO *bbio; /* used during session-id reuse to concatenate
* messages */
#else
char *rbio; /* used by SSL_read */
char *wbio; /* used by SSL_write */
char *bbio;
#endif
/* This holds a variable that indicates what we were doing
* when a 0 or -1 is returned. This is needed for
* non-blocking IO so we know what request needs re-doing when
* in SSL_accept or SSL_connect */
int rwstate;
/* true when we are actually in SSL_accept() or SSL_connect() */
int in_handshake;
int (*handshake_func)();
/* Imagine that here's a boolean member "init" that is
* switched as soon as SSL_set_{accept/connect}_state
* is called for the first time, so that "state" and
* "handshake_func" are properly initialized. But as
* handshake_func is == 0 until then, we use this
* test instead of an "init" member.
*/
int server; /* are we the server side? - mostly used by SSL_clear*/
int new_session;/* 1 if we are to use a new session.
* 2 if we are a server and are inside a handshake
* (i.e. not just sending a HelloRequest)
* NB: For servers, the 'new' session may actually be a previously
* cached session or even the previous session unless
* SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION is set */
int quiet_shutdown;/* don't send shutdown packets */
int shutdown; /* we have shut things down, 0x01 sent, 0x02
* for received */
int state; /* where we are */
int rstate; /* where we are when reading */
BUF_MEM *init_buf; /* buffer used during init */
void *init_msg; /* pointer to handshake message body, set by ssl3_get_message() */
int init_num; /* amount read/written */
int init_off; /* amount read/written */
/* used internally to point at a raw packet */
unsigned char *packet;
unsigned int packet_length;
struct ssl2_state_st *s2; /* SSLv2 variables */
struct ssl3_state_st *s3; /* SSLv3 variables */
int read_ahead; /* Read as many input bytes as possible
* (for non-blocking reads) */
/* callback that allows applications to peek at protocol messages */
void (*msg_callback)(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg);
void *msg_callback_arg;
int hit; /* reusing a previous session */
int purpose; /* Purpose setting */
int trust; /* Trust setting */
/* crypto */
STACK_OF(SSL_CIPHER) *cipher_list;
STACK_OF(SSL_CIPHER) *cipher_list_by_id;
/* These are the ones being used, the ones in SSL_SESSION are
* the ones to be 'copied' into these ones */
EVP_CIPHER_CTX *enc_read_ctx; /* cryptographic state */
const EVP_MD *read_hash; /* used for mac generation */
#ifndef OPENSSL_NO_COMP
COMP_CTX *expand; /* uncompress */
#else
char *expand;
#endif
EVP_CIPHER_CTX *enc_write_ctx; /* cryptographic state */
const EVP_MD *write_hash; /* used for mac generation */
#ifndef OPENSSL_NO_COMP
COMP_CTX *compress; /* compression */
#else
char *compress;
#endif
/* session info */
/* client cert? */
/* This is used to hold the server certificate used */
struct cert_st /* CERT */ *cert;
/* the session_id_context is used to ensure sessions are only reused
* in the appropriate context */
unsigned int sid_ctx_length;
unsigned char sid_ctx[SSL_MAX_SID_CTX_LENGTH];
/* This can also be in the session once a session is established */
SSL_SESSION *session;
/* Default generate session ID callback. */
GEN_SESSION_CB generate_session_id;
/* Used in SSL2 and SSL3 */
int verify_mode; /* 0 don't care about verify failure.
* 1 fail if verify fails */
int verify_depth;
int (*verify_callback)(int ok,X509_STORE_CTX *ctx); /* fail if callback returns 0 */
void (*info_callback)(const SSL *ssl,int type,int val); /* optional informational callback */
int error; /* error bytes to be written */
int error_code; /* actual code */
#ifndef OPENSSL_NO_KRB5
KSSL_CTX *kssl_ctx; /* Kerberos 5 context */
#endif /* OPENSSL_NO_KRB5 */
SSL_CTX *ctx;
/* set this flag to 1 and a sleep(1) is put into all SSL_read()
* and SSL_write() calls, good for nbio debuging :-) */
int debug;
/* extra application data */
long verify_result;
CRYPTO_EX_DATA ex_data;
/* for server side, keep the list of CA_dn we can use */
STACK_OF(X509_NAME) *client_CA;
int references;
unsigned long options; /* protocol behaviour */
unsigned long mode; /* API behaviour */
long max_cert_list;
int first_packet;
int client_version; /* what was passed, used for
* SSLv3/TLS rollback check */
};

/* ssl/ssl_lib.c */
SSL *SSL_new(SSL_CTX *ctx)
{
SSL *s;
// 一些必要检查
if (ctx == NULL)
{
SSLerr(SSL_F_SSL_NEW,SSL_R_NULL_SSL_CTX);
return(NULL);
}
if (ctx->method == NULL)
{
SSLerr(SSL_F_SSL_NEW,SSL_R_SSL_CTX_HAS_NO_DEFAULT_SSL_VERSION);
return(NULL);
}
// 分配SSL实例的空间
s=(SSL *)OPENSSL_malloc(sizeof(SSL));
if (s == NULL) goto err;
memset(s,0,sizeof(SSL));
// 初始化SSL结构参数
#ifndef OPENSSL_NO_KRB5
s->kssl_ctx = kssl_ctx_new();
#endif /* OPENSSL_NO_KRB5 */
s->options=ctx->options;
s->mode=ctx->mode;
s->max_cert_list=ctx->max_cert_list;
if (ctx->cert != NULL)
{
/* Earlier library versions used to copy the pointer to
* the CERT, not its contents; only when setting new
* parameters for the per-SSL copy, ssl_cert_new would be
* called (and the direct reference to the per-SSL_CTX
* settings would be lost, but those still were indirectly
* accessed for various purposes, and for that reason they
* used to be known as s->ctx->default_cert).
* Now we don't look at the SSL_CTX's CERT after having
* duplicated it once. */
s->cert = ssl_cert_dup(ctx->cert);
if (s->cert == NULL)
goto err;
}
else
s->cert=NULL; /* Cannot really happen (see SSL_CTX_new) */
s->read_ahead=ctx->read_ahead;
s->msg_callback=ctx->msg_callback;
s->msg_callback_arg=ctx->msg_callback_arg;
s->verify_mode=ctx->verify_mode;
s->verify_depth=ctx->verify_depth;
s->sid_ctx_length=ctx->sid_ctx_length;
OPENSSL_assert(s->sid_ctx_length <= sizeof s->sid_ctx);
memcpy(&s->sid_ctx,&ctx->sid_ctx,sizeof(s->sid_ctx));
s->verify_callback=ctx->default_verify_callback;
s->generate_session_id=ctx->generate_session_id;
s->purpose = ctx->purpose;
s->trust = ctx->trust;
s->quiet_shutdown=ctx->quiet_shutdown;
CRYPTO_add(&ctx->references,1,CRYPTO_LOCK_SSL_CTX);
s->ctx=ctx;
s->verify_result=X509_V_OK;
s->method=ctx->method;
if (!s->method->ssl_new(s))
goto err;
s->references=1;
s->server=(ctx->method->ssl_accept == ssl_undefined_function)?0:1;
SSL_clear(s);
CRYPTO_new_ex_data(CRYPTO_EX_INDEX_SSL, s, &s->ex_data);
return(s);
err:
if (s != NULL)
{
if (s->cert != NULL)
ssl_cert_free(s->cert);
if (s->ctx != NULL)
SSL_CTX_free(s->ctx); /* decrement reference count */
OPENSSL_free(s);
}
SSLerr(SSL_F_SSL_NEW,ERR_R_MALLOC_FAILURE);
return(NULL);
}

2.11 SSL_set_fd

SSL_set_fd()函数将建立的SSL结构与TCP套接字联系,使SSL结构对套接字中的TCP数据进行SSL封装。
/* ssl/ssl_lib.c */
int SSL_set_fd(SSL *s,int fd)
{
int ret=0;
BIO *bio=NULL;
// 建立一个BIO,BIO是OpenSSL提供的用来进行算法封装的处理结构,还可以将多个算法
// 串联起来,这样可以很方便地实现数据的封装
bio=BIO_new(BIO_s_socket());
if (bio == NULL)
{
SSLerr(SSL_F_SSL_SET_FD,ERR_R_BUF_LIB);
goto err;
}
// 把套接字和BIO联系
BIO_set_fd(bio,fd,BIO_NOCLOSE);
// 把SSL和BIO联系起来,包括读写操作
SSL_set_bio(s,bio,bio);
ret=1;
err:
return(ret);
}

void SSL_set_bio(SSL *s,BIO *rbio,BIO *wbio)
{
/* If the output buffering BIO is still in place, remove it
*/
if (s->bbio != NULL)
{
if (s->wbio == s->bbio)
{
s->wbio=s->wbio->next_bio;
s->bbio->next_bio=NULL;
}
}
if ((s->rbio != NULL) && (s->rbio != rbio))
BIO_free_all(s->rbio);
if ((s->wbio != NULL) && (s->wbio != wbio) && (s->rbio != s->wbio))
BIO_free_all(s->wbio);
// 设置SSL读写BIO
s->rbio=rbio;
s->wbio=wbio;
}

SSL_set_fd()还有两个类似函数:
SSL_set_wfd():对写的数据进行SSL封装
SSL_set_rfd():对都的数据进行SSL封装
不过一般情况下用得比较少。
2.12 SSL_accept
SSL_accept()函数完成SSL协商的服务器端操作:
/* ssl/ssl_lib.c */
int SSL_accept(SSL *s)
{
if (s->handshake_func == 0)
/* Not properly initialized yet */
SSL_set_accept_state(s);
return(s->method->ssl_accept(s));
}
其中SSL_set_accept_state(s)函数初始化SSL协商处理:
void SSL_set_accept_state(SSL *s)
{
// 服务器端
s->server=1;
s->shutdown=0;
// 初始化服务器端状态值
s->state=SSL_ST_ACCEPT|SSL_ST_BEFORE;
// 握手函数即是ssl_accept函数
s->handshake_func=s->method->ssl_accept;
/* clear the current cipher */
// 清除SSL读写加密算法上下文
ssl_clear_cipher_ctx(s);
}
因此最重要的就是ssl_accept()这个成员函数,是前面SSLv[2][3]_server_method()中定义的,如对于 SSLv23方法,处理函数分别为ssl23_accept()函数,其它SSLv2和SSLv3方法分别对应ssl2_accept()和 ssl3_accept(),后两者就没有协商过程了,ssl23_accept()实际在协商确定协议版本后也是调用ssl2[3]_accept ()。

SSL很多状态都分A,B两种,A状态表示刚进入该状态还没有收发数据,B状态表示进行的收发数据处理但还没完成善后操作。

/* ssl/s23_srvr.c */
int ssl23_accept(SSL *s)
{
BUF_MEM *buf;
unsigned long Time=time(NULL);
void (*cb)(const SSL *ssl,int type,int val)=NULL;
int ret= -1;
int new_state,state;
// 用当前时间作为随机种子
RAND_add(&Time,sizeof(Time),0);
ERR_clear_error();
clear_sys_error();
// 在SSL_new()函数中,s->info_callback并没有定义
// 是通过SSL_set_info_callback()函数单独定义的
if (s->info_callback != NULL)
cb=s->info_callback;
// SSL_CTX_new()函数中,ctx->info_callback也没定义
// 是通过SSL_CTX_set_info_callback()宏单独定义的
else if (s->ctx->info_callback != NULL)
cb=s->ctx->info_callback;
// 握手计数
s->in_handshake++;
// 如果SSL已用,清除SSL原来的值
if (!SSL_in_init(s) || SSL_in_before(s)) SSL_clear(s);
for (;;)
{
// 保存SSL当前状态
state=s->state;
// 在SSL_set_accept_state中s->state被初始化为SSL_ST_ACCEPT|SSL_ST_BEFORE
switch(s->state)
{
case SSL_ST_BEFORE:
case SSL_ST_ACCEPT:
case SSL_ST_BEFORE|SSL_ST_ACCEPT:
case SSL_ST_OK|SSL_ST_ACCEPT:
s->server=1;
if (cb != NULL) cb(s,SSL_CB_HANDSHAKE_START,1);
/* s->version=SSL3_VERSION; */
s->type=SSL_ST_ACCEPT;
if (s->init_buf == NULL)
{
// 生成一个SSL缓冲区
if ((buf=BUF_MEM_new()) == NULL)
{
ret= -1;
goto end;
}
if (!BUF_MEM_grow(buf,SSL3_RT_MAX_PLAIN_LENGTH))
{
ret= -1;
goto end;
}
s->init_buf=buf;
}
// 初始化认证码MAC
ssl3_init_finished_mac(s);
// SSL状态设置为SSL23_ST_SR_CLNT_HELLO_A,进入客户端的HELLO A状态
s->state=SSL23_ST_SR_CLNT_HELLO_A;
// 接受的SSL会话统计
s->ctx->stats.sess_accept++;
s->init_num=0;
// 重新进行循环接收客户端数据
break;
case SSL23_ST_SR_CLNT_HELLO_A:
case SSL23_ST_SR_CLNT_HELLO_B:
s->shutdown=0;
// 获取对方的HELLO信息,也就是进行SSL握手协议
ret=ssl23_get_client_hello(s);
if (ret >= 0) cb=NULL;
goto end;
/* break; */
default:
SSLerr(SSL_F_SSL23_ACCEPT,SSL_R_UNKNOWN_STATE);
ret= -1;
goto end;
/* break; */
}
// 如果SSL状态改变,而又定义了信息回调函数,执行之
if ((cb != NULL) && (s->state != state))
{
new_state=s->state;
s->state=state;
cb(s,SSL_CB_ACCEPT_LOOP,1);
s->state=new_state;
}
}
end:
s->in_handshake--;
if (cb != NULL)
cb(s,SSL_CB_ACCEPT_EXIT,ret);
return(ret);
}

可见,SSL握手协议是在ssl23_get_client_hello(s)函数中完成,也算个很复杂的函数:

int ssl23_get_client_hello(SSL *s)
{
//
// SSL握手协议头首部空间,11字节
// 客户端发出的HELLO,如果第一字节最高位为1
// 头两字节是包长度,不包括第一字节的第一位;
// 第3字节是握手类型类型,取值如下:
// enum {
// hello_request(0), client_hello(1), server_hello(2),
// certificate(11), server_key_exchange (12), certificate_request(13),
// server_done(14), certificate_verify(15), client_key_exchange(16),
// finished(20), (255)
// } HandshakeType;
// 第4,5字节是版本类型,TLS1为0301,SSL3为0300,SSL2为0002
// 第6,7字节是加密算法部分(cipher_specs)信息长度
// 第8,9字节是会话ID(session id)
// 第10,11字节是挑战信息长度(challenge)
//
//
// 如果第一字节最高位不为1或者非客户端发出的HELLO
// 第一字节为类型,取值为:
// enum {
// change_cipher_spec(20), alert(21), handshake(22),
// application_data(23), (255)
// } ContentType
// 第2,3字节是服务器端SSL版本类型,TLS1为0301,SSL3为0300,SSL2为0002
// 第4,5字节为握手部分长度
// 第6字节为消息类型
// 第7,8,9字节为握手信息长度
// 第10,11字节为客户端SSL版本
//
// 本函数的主要功能是识别客户端SSL版本,根据服务器自身支持的SSL版本,选定合适的SSL
// 版本进行下一步的accept,即ssl2_accept或ssl3_accept
//
char buf_space[11]; /* Request this many bytes in initial read.
* We can detect SSL 3.0/TLS 1.0 Client Hellos
* ('type == 3') correctly only when the following
* is in a single record, which is not guaranteed by
* the protocol specification:
* Byte Content
* 0 type
* 1/2 version > record header
* 3/4 length /
* 5 msg_type
* 6-8 length > Client Hello message
* 9/10 client_version /
*/
char *buf= &(buf_space[0]);
unsigned char *p,*d,*d_len,*dd;
unsigned int i;
unsigned int csl,sil,cl;
int n=0,j;
int type=0;
int v[2];
#ifndef OPENSSL_NO_RSA
int use_sslv2_strong=0;
#endif
if (s->state == SSL23_ST_SR_CLNT_HELLO_A)
{
/* read the initial header */
v[0]=v[1]=0;
if (!ssl3_setup_buffers(s)) goto err;
// 读取首部空间长度的数据
n=ssl23_read_bytes(s, sizeof buf_space);
if (n != sizeof buf_space) return(n); /* n == -1 || n == 0 */
// 数据保存在s->packet缓冲区中
p=s->packet;
// 拷贝到buf_space
memcpy(buf,p,n);
if ((p[0] & 0x80) && (p[2] == SSL2_MT_CLIENT_HELLO))
{
/*
* SSLv2 header
*/
if ((p[3] == 0x00) && (p[4] == 0x02))
{
// 客户端为SSLv2
v[0]=p[3]; v[1]=p[4];
/* SSLv2 */
if (!(s->options & SSL_OP_NO_SSLv2))
type=1;
}
else if (p[3] == SSL3_VERSION_MAJOR)
{
// 客户端主版本SSLv3
v[0]=p[3]; v[1]=p[4];
/* SSLv3/TLSv1 */
if (p[4] >= TLS1_VERSION_MINOR)
{
// 次版本表明是客户端TLS1.0, 服务器为SSL3或TLS1时type设为2,为SSL2时设为1
if (!(s->options & SSL_OP_NO_TLSv1))
{
// 服务器支持TLS1.0,SSL类型设置为TLS1
s->version=TLS1_VERSION;
/* type=2; */ /* done later to survive restarts */
s->state=SSL23_ST_SR_CLNT_HELLO_B;
}
else if (!(s->options & SSL_OP_NO_SSLv3))
{
// 服务器不支持TLS,支持SSL3,SSL类型设置为SSL3
s->version=SSL3_VERSION;
/* type=2; */
s->state=SSL23_ST_SR_CLNT_HELLO_B;
}
else if (!(s->options & SSL_OP_NO_SSLv2))
{
// 服务器这边不支持SSL3,TLS1,协商为SSL2, type为1
type=1;
}
}
else if (!(s->options & SSL_OP_NO_SSLv3))
{
// 次版本号表明客户端是SSLv3
s->version=SSL3_VERSION;
/* type=2; */
s->state=SSL23_ST_SR_CLNT_HELLO_B;
}
else if (!(s->options & SSL_OP_NO_SSLv2))
type=1;
}
}
else if ((p[0] == SSL3_RT_HANDSHAKE) &&
// p[1]为SSL3主版本号
(p[1] == SSL3_VERSION_MAJOR) &amp;&
// p[5]为消息类型
(p[5] == SSL3_MT_CLIENT_HELLO) &amp;&
// p[3],p[4]为握手部分长度,如果只是记录头部分,长度小于5,
((p[3] == 0 && p[4] < 5 /* silly record length? */)
// p[9]是客户端主版本号
|| (p[9] == p[1])))
{
/*
* SSLv3 or tls1 header
*/
// 主版本为SSL3
v[0]=p[1]; /* major version (= SSL3_VERSION_MAJOR) */
/* We must look at client_version inside the Client Hello message
* to get the correct minor version.
* However if we have only a pathologically small fragment of the
* Client Hello message, this would be difficult, and we'd have
* to read more records to find out.
* No known SSL 3.0 client fragments ClientHello like this,
* so we simply assume TLS 1.0 to avoid protocol version downgrade
* attacks. */
if (p[3] == 0 && p[4] < 6)
{
// 如果握手长度小于6认为就是TLS1
#if 0
SSLerr(SSL_F_SSL23_GET_CLIENT_HELLO,SSL_R_RECORD_TOO_SMALL);
goto err;
#else
v[1] = TLS1_VERSION_MINOR;
#endif
}
else
v[1]=p[10]; /* minor version according to client_version */
if (v[1] >= TLS1_VERSION_MINOR)
{
// 客户端为TLS1.0,按上面相同的方法设置服务器端的版本
// 注意这时的type设置为3
if (!(s->options & SSL_OP_NO_TLSv1))
{
s->version=TLS1_VERSION;
type=3;
}
else if (!(s->options & SSL_OP_NO_SSLv3))
{
s->version=SSL3_VERSION;
type=3;
}
}
else
{
/* client requests SSL 3.0 */
// 客户端为SSL3,设置服务器段SSL版本
// type为3
if (!(s->options & SSL_OP_NO_SSLv3))
{
s->version=SSL3_VERSION;
type=3;
}
else if (!(s->options & SSL_OP_NO_TLSv1))
{
/* we won't be able to use TLS of course,
* but this will send an appropriate alert */
s->version=TLS1_VERSION;
type=3;
}
}
}
else if ((strncmp("GET ", (char *)p,4) == 0) ||
(strncmp("POST ",(char *)p,5) == 0) ||
(strncmp("HEAD ",(char *)p,5) == 0) ||
(strncmp("PUT ", (char *)p,4) == 0))
{
// 在SSL通道中走HTTP的明文数据,出错
SSLerr(SSL_F_SSL23_GET_CLIENT_HELLO,SSL_R_HTTP_REQUEST);
goto err;
}
else if (strncmp("CONNECT",(char *)p,7) == 0)
{
SSLerr(SSL_F_SSL23_GET_CLIENT_HELLO,SSL_R_HTTPS_PROXY_REQUEST);
goto err;
}
}
// 进入HELLO B状态,也就是客户端数据是SSL3或TLS,而且(p[0] & 0x80) &&
// (p[2] == SSL2_MT_CLIENT_HELLO),已经找出服务器端的对应版本
if (s->state == SSL23_ST_SR_CLNT_HELLO_B)
{
/* we have SSLv3/TLSv1 in an SSLv2 header
* (other cases skip this state) */
// 服务器是SSL3或TLS1,类型为2
type=2;
p=s->packet;
v[0] = p[3]; /* == SSL3_VERSION_MAJOR */
v[1] = p[4];
// p[0],p[1]是HELLO包长
n=((p[0]&0x7f)<<8)|p[1];
if (n > (1024*4))
{
// 一个SSL段不能超过4096字节
SSLerr(SSL_F_SSL23_GET_CLIENT_HELLO,SSL_R_RECORD_TOO_LARGE);
goto err;
}
// 读取整个包长数据,"2"是因为p[0],p[1]表示包长不包括自身长度(2字节)
// 这个读操作数据初始指针是不移动的,注意前面已经用这函数读了11字节了
j=ssl23_read_bytes(s,n+2);
if (j <= 0) return(j);
// MAC认证
ssl3_finish_mac(s, s->packet+2, s->packet_length-2);
if (s->msg_callback)
s->msg_callback(0, SSL2_VERSION, 0, s->packet+2, s->packet_length-2, s, s->msg_callback_arg); /* CLIENT-HELLO */

// 回到数据头
p=s->packet;
// 跳过前面的5字节,长度、类型、版本信息
p+=5;
// cipher_specs的长度
n2s(p,csl);
// session id
n2s(p,sil);
// challenge长度
n2s(p,cl);
// SSL缓冲区头
d=(unsigned char *)s->init_buf->data;
if ((csl+sil+cl+11) != s->packet_length)
{
// 检查包长是否正确
SSLerr(SSL_F_SSL23_GET_CLIENT_HELLO,SSL_R_RECORD_LENGTH_MISMATCH);
goto err;
}
// 以下开始填充作为ssl3_accept定义的客户端SSL握手包
/* record header: msg_type ... */
// 数据类型
*(d++) = SSL3_MT_CLIENT_HELLO;
/* ... and length (actual value will be written later) */
d_len = d;
// 数据类型1字节,长度2字节
d += 3;
/* client_version */
// 版本号
*(d++) = SSL3_VERSION_MAJOR; /* == v[0] */
*(d++) = v[1];
/* lets populate the random area */
/* get the challenge_length */
// 拷贝挑战信息,最多SSL3_RANDOM_SIZE(32)
i=(cl > SSL3_RANDOM_SIZE)?SSL3_RANDOM_SIZE:cl;
memset(d,0,SSL3_RANDOM_SIZE);
// 如果挑战信息长度不到SSL3_RANDOM_SIZE,相当于前面多余字节补0,不是在后面
memcpy(&(d[SSL3_RANDOM_SIZE-i]),&(p[csl+sil]),i);
d+=SSL3_RANDOM_SIZE;
/* no session-id reuse */
// 会话ID没用
*(d++)=0;
/* ciphers */
// cipher_specs域
j=0;
// 头指针备份
dd=d;
// 留出长度空间
d+=2;
for (i=0; i<csl; i+=3)
{
// p[0]位置现在是收到包中cipher_specs数据头
if (p[i] != 0) continue;
// 每3字节为一个单位,拷贝后两字节,第1字节忽略
*(d++)=p[i+1];
*(d++)=p[i+2];
j+=2;
}
// 写cipher_specs长度,网络序
s2n(j,dd);
/* COMPRESSION */
*(d++)=1;
*(d++)=0;
// 实际数据长度
i = (d-(unsigned char *)s->init_buf->data) - 4;
l2n3((long)i, d_len);
/* get the data reused from the init_buf */
s->s3->tmp.reuse_message=1;
s->s3->tmp.message_type=SSL3_MT_CLIENT_HELLO;
s->s3->tmp.message_size=i;
}
/* imaginary new state (for program structure): */
/* s->state = SSL23_SR_CLNT_HELLO_C */
if (type == 1)
{
// 服务器只支持SSL2的情况,实际已经很少见了
#ifdef OPENSSL_NO_SSL2
SSLerr(SSL_F_SSL23_GET_CLIENT_HELLO,SSL_R_UNSUPPORTED_PROTOCOL);
goto err;
#else
/* we are talking sslv2 */
/* we need to clean up the SSLv3/TLSv1 setup and put in the
* sslv2 stuff. */
if (s->s2 == NULL)
{
// 新分配一个SSL2结构
if (!ssl2_new(s))
goto err;
}
else
ssl2_clear(s);
// 释放SSL3结构
if (s->s3 != NULL) ssl3_free(s);
// 将缓冲区扩到SSL2的最大记录情况
if (!BUF_MEM_grow_clean(s->init_buf,
SSL2_MAX_RECORD_LENGTH_3_BYTE_HEADER))
{
goto err;
}
// 这个状态是"SSL2_ST"系列(SSL2服务器端)的
s->state=SSL2_ST_GET_CLIENT_HELLO_A;
if ((s->options & SSL_OP_MSIE_SSLV2_RSA_PADDING) ||
use_sslv2_strong ||
(s->options & SSL_OP_NO_TLSv1 &amp;& s->options & SSL_OP_NO_SSLv3))
s->s2->ssl2_rollback=0;
else
/* reject SSL 2.0 session if client supports SSL 3.0 or TLS 1.0
* (SSL 3.0 draft/RFC 2246, App. E.2) */
s->s2->ssl2_rollback=1;
/* setup the n bytes we have read so we get them from
* the sslv2 buffer */
s->rstate=SSL_ST_READ_HEADER;
s->packet_length=n;
s->packet= &(s->s2->rbuf[0]);
// buf是接收数据缓冲区头,n正常的话是11
memcpy(s->packet,buf,n);
s->s2->rbuf_left=n;
s->s2->rbuf_offs=0;
// SSL封装方法是SSL2
s->method=SSLv2_server_method();
// 实际函数为ssl2_accept
s->handshake_func=s->method->ssl_accept;
#endif
}
if ((type == 2) || (type == 3))
{
// 服务器自身可以支持SSL3或TLS1
/* we have SSLv3/TLSv1 (type 2: SSL2 style, type 3: SSL3/TLS style) */
// 初始化写缓冲区
if (!ssl_init_wbio_buffer(s,1)) goto err;
/* we are in this state */
// SSL3_ST类,SSL3服务器收到客户端的HELLO的A状态
s->state=SSL3_ST_SR_CLNT_HELLO_A;
// 进行一些初始化操作
if (type == 3)
{
/* put the 'n' bytes we have read into the input buffer
* for SSLv3 */
s->rstate=SSL_ST_READ_HEADER;
s->packet_length=n;
s->packet= &(s->s3->rbuf.buf[0]);
memcpy(s->packet,buf,n);
s->s3->rbuf.left=n;
s->s3->rbuf.offset=0;
}
else
{
s->packet_length=0;
s->s3->rbuf.left=0;
s->s3->rbuf.offset=0;
}
if (s->version == TLS1_VERSION)
// 实际上TLS1中的accept方法也就是ssl3_accept
s->method = TLSv1_server_method();
else
// 就是ssl3_accept
s->method = SSLv3_server_method();
s->handshake_func=s->method->ssl_accept;
}

if ((type < 1) || (type > 3))
{
/* bad, very bad */
SSLerr(SSL_F_SSL23_GET_CLIENT_HELLO,SSL_R_UNKNOWN_PROTOCOL);
goto err;
}
s->init_num=0;
if (buf != buf_space) OPENSSL_free(buf);
s->first_packet=1;
// 递归调用SSL_accept(),这时方法是固定的,就是调用ssl2_accept()或ssl3_accept()
return(SSL_accept(s));
err:
if (buf != buf_space) OPENSSL_free(buf);
return(-1);
}
ssl23_get_client_hello()函数最后就是确定了服务器端的方法类型,然后再进行SSL_accept(),实际就是调用ssl2_accept()或ssl3_accept()。

举例ssl3_accept()函数定义如下,ssl2_accept()就不分析了:
/* ssl/s3_srvr.c */
int ssl3_accept(SSL *s)
{
BUF_MEM *buf;
unsigned long l,Time=time(NULL);
void (*cb)(const SSL *ssl,int type,int val)=NULL;
long num1;
int ret= -1;
int new_state,state,skip=0;
// 和前面ssl23_accpet一样进行初始化
RAND_add(&Time,sizeof(Time),0);
ERR_clear_error();
clear_sys_error();
if (s->info_callback != NULL)
cb=s->info_callback;
else if (s->ctx->info_callback != NULL)
cb=s->ctx->info_callback;
/* init things to blank */
s->in_handshake++;
if (!SSL_in_init(s) || SSL_in_before(s)) SSL_clear(s);
if (s->cert == NULL)
{
SSLerr(SSL_F_SSL3_ACCEPT,SSL_R_NO_CERTIFICATE_SET);
return(-1);
}
for (;;)
{
state=s->state;
switch (s->state)
{
case SSL_ST_RENEGOTIATE:
s->new_session=1;
/* s->state=SSL_ST_ACCEPT; */
case SSL_ST_BEFORE:
case SSL_ST_ACCEPT:
case SSL_ST_BEFORE|SSL_ST_ACCEPT:
case SSL_ST_OK|SSL_ST_ACCEPT:
// 这些是客户端服务器固定就用SSL3进行连接时进入的初始状态,如果是从ssl23_accpet
// 过来的是进不到这状态的
// 下面是ssl23_accept时类似的初始化
s->server=1;
if (cb != NULL) cb(s,SSL_CB_HANDSHAKE_START,1);
if ((s->version>>8) != 3)
{
SSLerr(SSL_F_SSL3_ACCEPT, ERR_R_INTERNAL_ERROR);
return -1;
}
s->type=SSL_ST_ACCEPT;
if (s->init_buf == NULL)
{
if ((buf=BUF_MEM_new()) == NULL)
{
ret= -1;
goto end;
}
if (!BUF_MEM_grow(buf,SSL3_RT_MAX_PLAIN_LENGTH))
{
ret= -1;
goto end;
}
s->init_buf=buf;
}
if (!ssl3_setup_buffers(s))
{
ret= -1;
goto end;
}
s->init_num=0;
if (s->state != SSL_ST_RENEGOTIATE)
{
/* Ok, we now need to push on a buffering BIO so that
* the output is sent in a way that TCP likes :-)
*/
if (!ssl_init_wbio_buffer(s,1)) { ret= -1; goto end; }

ssl3_init_finished_mac(s);
s->state=SSL3_ST_SR_CLNT_HELLO_A;
s->ctx->stats.sess_accept++;
}
else
{
/* s->state == SSL_ST_RENEGOTIATE,
* we will just send a HelloRequest */
s->ctx->stats.sess_accept_renegotiate++;
s->state=SSL3_ST_SW_HELLO_REQ_A;
}
break;
case SSL3_ST_SW_HELLO_REQ_A:
case SSL3_ST_SW_HELLO_REQ_B:
// 此状态是是写服务器端的回应的HELLO请求信息
s->shutdown=0;
// 发送服务器端的HELLO
ret=ssl3_send_hello_request(s);
if (ret <= 0) goto end;
// 转入REQ_C状态
s->s3->tmp.next_state=SSL3_ST_SW_HELLO_REQ_C;
s->state=SSL3_ST_SW_FLUSH;
s->init_num=0;
ssl3_init_finished_mac(s);
break;
case SSL3_ST_SW_HELLO_REQ_C:
s->state=SSL_ST_OK;
break;
// 从ssl23_accept过来时的状态是SSL3_ST_SR_CLNT_HELLO_A,属于读数据状态
case SSL3_ST_SR_CLNT_HELLO_A:
case SSL3_ST_SR_CLNT_HELLO_B:
case SSL3_ST_SR_CLNT_HELLO_C:
s->shutdown=0;
// 读取客户端数据,如果是ssl23_accept过来的话数据是由ssl23_get_client_hello()
// 函数自己构造的,而不是实际收到的
ret=ssl3_get_client_hello(s);
if (ret <= 0) goto end;
s->new_session = 2;
// 状态转为服务器准备写HELLO的A状态
s->state=SSL3_ST_SW_SRVR_HELLO_A;
s->init_num=0;
break;
case SSL3_ST_SW_SRVR_HELLO_A:
case SSL3_ST_SW_SRVR_HELLO_B:
// 此状态是是写服务器端的HELLO信息
ret=ssl3_send_server_hello(s);
if (ret <= 0) goto end;
// s->hit用来标志该ssl会话是否是重用(reuse)的,在ssl3_get_client_hello()函数
// 中检查客户端的hello信息后设置
if (s->hit)
// 如果会话是reuse的,状态为CHANGE
s->state=SSL3_ST_SW_CHANGE_A;
else
// 否则为新SSL会话,进入证书处理A状态
s->state=SSL3_ST_SW_CERT_A;
s->init_num=0;
break;
case SSL3_ST_SW_CERT_A:
case SSL3_ST_SW_CERT_B:
// 该状态下进行证书交换,用来计算连接共享密钥
/* Check if it is anon DH */
if (!(s->s3->tmp.new_cipher->algorithms & SSL_aNULL))
{
// 非NULL加密的话发送服务器端的证书
ret=ssl3_send_server_certificate(s);
if (ret <= 0) goto end;
}
else
skip=1;
// 进入密钥交换状态
s->state=SSL3_ST_SW_KEY_EXCH_A;
s->init_num=0;
break;
case SSL3_ST_SW_KEY_EXCH_A:
case SSL3_ST_SW_KEY_EXCH_B:
// 该状态下进行数据加密密钥的交换操作
// 算法类型,由一个常数表示
l=s->s3->tmp.new_cipher->algorithms;
/* clear this, it may get reset by
* send_server_key_exchange */
if ((s->options & SSL_OP_EPHEMERAL_RSA)
#ifndef OPENSSL_NO_KRB5
&amp;& !(l & SSL_KRB5)
#endif /* OPENSSL_NO_KRB5 */
)
// 临时性RSA密钥交换
/* option SSL_OP_EPHEMERAL_RSA sends temporary RSA key
* even when forbidden by protocol specs
* (handshake may fail as clients are not required to
* be able to handle this) */
s->s3->tmp.use_rsa_tmp=1;
else
s->s3->tmp.use_rsa_tmp=0;
/* only send if a DH key exchange, fortezza or
* RSA but we have a sign only certificate */
if (s->s3->tmp.use_rsa_tmp
|| (l & (SSL_DH|SSL_kFZA))
|| ((l & SSL_kRSA)
&& (s->cert->pkeys[SSL_PKEY_RSA_ENC].privatekey == NULL
|| (SSL_C_IS_EXPORT(s->s3->tmp.new_cipher)
&& EVP_PKEY_size(s->cert->pkeys[SSL_PKEY_RSA_ENC].privatekey)*8 > SSL_C_EXPORT_PKEYLENGTH(s->s3->tmp.new_cipher)
)
)
)
)
{
// 进行RSA密钥交换
ret=ssl3_send_server_key_exchange(s);
if (ret <= 0) goto end;
}
else
skip=1;
// 转入证书请求阶段
s->state=SSL3_ST_SW_CERT_REQ_A;
s->init_num=0;
break;
case SSL3_ST_SW_CERT_REQ_A:
case SSL3_ST_SW_CERT_REQ_B:
// 此阶段进入对方证书请求
if (/* don't request cert unless asked for it: */
!(s->verify_mode & SSL_VERIFY_PEER) ||
/* if SSL_VERIFY_CLIENT_ONCE is set,
* don't request cert during re-negotiation: */
((s->session->peer != NULL) &&
(s->verify_mode & SSL_VERIFY_CLIENT_ONCE)) ||
/* never request cert in anonymous ciphersuites
* (see section "Certificate request" in SSL 3 drafts
* and in RFC 2246): */
((s->s3->tmp.new_cipher->algorithms & SSL_aNULL) &amp;&
/* ... except when the application insists on verification
* (against the specs, but s3_clnt.c accepts this for SSL 3) */
!(s->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT)) ||
/* never request cert in Kerberos ciphersuites */
(s->s3->tmp.new_cipher->algorithms & SSL_aKRB5))
{
// 在大多数情况下不需要客户端的证书
// 如果想认证对方,只要以上条件之一不满足就可以认证对方
// CTX的verify_mode则通过SSL_CTX_set_verify()来修改
// s->verify_mode可通过函数SSL_set_verify()来修改,
// s->verify_mode的初始值是ctx->verify_mode赋予的
/* no cert request */
skip=1;
s->s3->tmp.cert_request=0;
// 服务器端协商发送结束
s->state=SSL3_ST_SW_SRVR_DONE_A;
}
else
{
// 发送要获取对方证书的请求
s->s3->tmp.cert_request=1;
ret=ssl3_send_certificate_request(s);
if (ret <= 0) goto end;
#ifndef NETSCAPE_HANG_BUG
// 没预定义HANG_BUG的话服务器端协商写数据应该完成了
s->state=SSL3_ST_SW_SRVR_DONE_A;
#else
// 否则进入清除写缓冲状态
// 下一个状态是准备接收证书A
s->state=SSL3_ST_SW_FLUSH;
s->s3->tmp.next_state=SSL3_ST_SR_CERT_A;
#endif
s->init_num=0;
}
break;
case SSL3_ST_SW_SRVR_DONE_A:
case SSL3_ST_SW_SRVR_DONE_B:
// 发送服务器协商数据完成信息
ret=ssl3_send_server_done(s);
if (ret <= 0) goto end;
// 下一个状态将是接收证书A状态
s->s3->tmp.next_state=SSL3_ST_SR_CERT_A;
// 转入写缓冲清除状态
s->state=SSL3_ST_SW_FLUSH;
s->init_num=0;
break;

case SSL3_ST_SW_FLUSH:
// 清除写缓冲区
/* number of bytes to be flushed */
num1=BIO_ctrl(s->wbio,BIO_CTRL_INFO,0,NULL);
if (num1 > 0)
{
s->rwstate=SSL_WRITING;
num1=BIO_flush(s->wbio);
if (num1 <= 0) { ret= -1; goto end; }
s->rwstate=SSL_NOTHING;
}
// 进入预先保存的下一状态
s->state=s->s3->tmp.next_state;
break;
case SSL3_ST_SR_CERT_A:
case SSL3_ST_SR_CERT_B:
// 此状态下接收对方证书
/* Check for second client hello (MS SGC) */
// 检查对方的HELLO信息
ret = ssl3_check_client_hello(s);
if (ret <= 0)
goto end;
if (ret == 2)
s->state = SSL3_ST_SR_CLNT_HELLO_C;
else {
/* could be sent for a DH cert, even if we
* have not asked for it :-) */
// 获取对方证书
ret=ssl3_get_client_certificate(s);
if (ret <= 0) goto end;
s->init_num=0;
// 准备进入密钥交换状态
s->state=SSL3_ST_SR_KEY_EXCH_A;
}
break;
case SSL3_ST_SR_KEY_EXCH_A:
case SSL3_ST_SR_KEY_EXCH_B:
// 该状态处理密钥交换
ret=ssl3_get_client_key_exchange(s);
if (ret <= 0) goto end;
// 准备进入证书验证状态
s->state=SSL3_ST_SR_CERT_VRFY_A;
s->init_num=0;
/* We need to get hashes here so if there is
* a client cert, it can be verified */
// 验证证书的MAC码
s->method->ssl3_enc->cert_verify_mac(s,
&(s->s3->finish_dgst1),
&(s->s3->tmp.cert_verify_md[0]));
s->method->ssl3_enc->cert_verify_mac(s,
&(s->s3->finish_dgst2),
&(s->s3->tmp.cert_verify_md[MD5_DIGEST_LENGTH]));
break;
case SSL3_ST_SR_CERT_VRFY_A:
case SSL3_ST_SR_CERT_VRFY_B:
// 验证证书
/* we should decide if we expected this one */
ret=ssl3_get_cert_verify(s);
if (ret <= 0) goto end;
// 状态转为接收结束A状态
s->state=SSL3_ST_SR_FINISHED_A;
s->init_num=0;
break;
case SSL3_ST_SR_FINISHED_A:
case SSL3_ST_SR_FINISHED_B:
// 本状态为服务器端接收结束
// 获取结束信息
ret=ssl3_get_finished(s,SSL3_ST_SR_FINISHED_A,
SSL3_ST_SR_FINISHED_B);
if (ret <= 0) goto end;
if (s->hit)
// 如果会话是reuse的, 连接已经建立
s->state=SSL_ST_OK;
else
// 转CHANGE_A
s->state=SSL3_ST_SW_CHANGE_A;
s->init_num=0;
break;
case SSL3_ST_SW_CHANGE_A:
case SSL3_ST_SW_CHANGE_B:
// 本状态为服务器发送修改信息
// SSL加密算法
s->session->cipher=s->s3->tmp.new_cipher;
if (!s->method->ssl3_enc->setup_key_block(s))
{ ret= -1; goto end; }
// 发送修改加密算法信息
ret=ssl3_send_change_cipher_spec(s,
SSL3_ST_SW_CHANGE_A,SSL3_ST_SW_CHANGE_B);
if (ret <= 0) goto end;
// 转发送结束
s->state=SSL3_ST_SW_FINISHED_A;
s->init_num=0;
if (!s->method->ssl3_enc->change_cipher_state(s,
SSL3_CHANGE_CIPHER_SERVER_WRITE))
{
ret= -1;
goto end;
}
break;
case SSL3_ST_SW_FINISHED_A:
case SSL3_ST_SW_FINISHED_B:
// 服务器发送结束,SSL握手完成
ret=ssl3_send_finished(s,
SSL3_ST_SW_FINISHED_A,SSL3_ST_SW_FINISHED_B,
s->method->ssl3_enc->server_finished_label,
s->method->ssl3_enc->server_finished_label_len);
if (ret <= 0) goto end;
// 清除SSL写缓冲
s->state=SSL3_ST_SW_FLUSH;
if (s->hit)
// 如果会话是reuse的,状态转为接收结束
s->s3->tmp.next_state=SSL3_ST_SR_FINISHED_A;
else
// SSL连接成功完成
s->s3->tmp.next_state=SSL_ST_OK;
s->init_num=0;
break;
case SSL_ST_OK:
// 清除连接过程中分配的资源
/* clean a few things up */
ssl3_cleanup_key_block(s);
BUF_MEM_free(s->init_buf);
s->init_buf=NULL;
/* remove buffering on output */
ssl_free_wbio_buffer(s);
s->init_num=0;
if (s->new_session == 2) /* skipped if we just sent a HelloRequest */
{
/* actually not necessarily a 'new' session unless
* SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION is set */

s->new_session=0;

ssl_update_cache(s,SSL_SESS_CACHE_SERVER);

s->ctx->stats.sess_accept_good++;
/* s->server=1; */
s->handshake_func=ssl3_accept;
if (cb != NULL) cb(s,SSL_CB_HANDSHAKE_DONE,1);
}

ret = 1;
goto end;
/* break; */
default:
SSLerr(SSL_F_SSL3_ACCEPT,SSL_R_UNKNOWN_STATE);
ret= -1;
goto end;
/* break; */
}

if (!s->s3->tmp.reuse_message && !skip)
{
if (s->debug)
{
if ((ret=BIO_flush(s->wbio)) <= 0)
goto end;
}

if ((cb != NULL) && (s->state != state))
{
new_state=s->state;
s->state=state;
cb(s,SSL_CB_ACCEPT_LOOP,1);
s->state=new_state;
}
}
skip=0;
}
end:
/* BIO_flush(s->wbio); */
// accept结束, ret=1
s->in_handshake--;
if (cb != NULL)
cb(s,SSL_CB_ACCEPT_EXIT,ret);
return(ret);
}
2.13 SSL_connect
SSL_connect()这个函数完成SSL协商的客户端操作:

/* ssl/ssl_lib.c */
int SSL_connect(SSL *s)
{
if (s->handshake_func == 0)
/* Not properly initialized yet */
SSL_set_connect_state(s);
return(s->method->ssl_connect(s));
}

其中SSL_set_connect_state(s)函数初始化SSL协商处理:

void SSL_set_connect_state(SSL *s)
{
// 客户端
s->server=0;
s->shutdown=0;
// 初始化客户端状态值
s->state=SSL_ST_CONNECT|SSL_ST_BEFORE;
// 握手函数即是ssl_connect函数
s->handshake_func=s->method->ssl_connect;
/* clear the current cipher */
// 清除SSL读写加密算法上下文
ssl_clear_cipher_ctx(s);
}

因此最重要的就是ssl_connect()这两个成员函数,是前面SSLv[2][3]_client_method()函数中定义 的,如对于SSLv23方法,处理函数分别为ssl23_connect()函数,其它SSLv2和SSLv3方法分别对应ssl2_connect() 和ssl3_connect(),后两者就没有协商过程了,ssl23_connect()实际在协商确定协议版本后也是调用ssl2[3] _connect()。掌握了服务器端的accept过程,理解客户端的connect过程就简单了。

/* ssl/s23_clnt.c */
int ssl23_connect(SSL *s)
{
BUF_MEM *buf=NULL;
unsigned long Time=time(NULL);
void (*cb)(const SSL *ssl,int type,int val)=NULL;
int ret= -1;
int new_state,state;
// 和服务器端一样进行基本的初始化
RAND_add(&Time,sizeof(Time),0);
ERR_clear_error();
clear_sys_error();
if (s->info_callback != NULL)
cb=s->info_callback;
else if (s->ctx->info_callback != NULL)
cb=s->ctx->info_callback;

s->in_handshake++;
if (!SSL_in_init(s) || SSL_in_before(s)) SSL_clear(s);
for (;;)
{
state=s->state;
// 在SSL_set_connect_state中s->state被初始化为SSL_ST_CONNECT|SSL_ST_BEFORE
switch(s->state)
{
case SSL_ST_BEFORE:
case SSL_ST_CONNECT:
case SSL_ST_BEFORE|SSL_ST_CONNECT:
case SSL_ST_OK|SSL_ST_CONNECT:
// 进行基本初始化,分配缓冲区
if (s->session != NULL)
{
SSLerr(SSL_F_SSL23_CONNECT,SSL_R_SSL23_DOING_SESSION_ID_REUSE);
ret= -1;
goto end;
}
s->server=0;
if (cb != NULL) cb(s,SSL_CB_HANDSHAKE_START,1);
/* s->version=TLS1_VERSION; */
s->type=SSL_ST_CONNECT;
if (s->init_buf == NULL)
{
if ((buf=BUF_MEM_new()) == NULL)
{
ret= -1;
goto end;
}
if (!BUF_MEM_grow(buf,SSL3_RT_MAX_PLAIN_LENGTH))
{
ret= -1;
goto end;
}
s->init_buf=buf;
buf=NULL;
}
if (!ssl3_setup_buffers(s)) { ret= -1; goto end; }
ssl3_init_finished_mac(s);
// SSL客户端状态转为准备发送HELLO信息
s->state=SSL23_ST_CW_CLNT_HELLO_A;
s->ctx->stats.sess_connect++;
s->init_num=0;
break;
case SSL23_ST_CW_CLNT_HELLO_A:
case SSL23_ST_CW_CLNT_HELLO_B:
// 发送客户端的HELLO信息
s->shutdown=0;
ret=ssl23_client_hello(s);
if (ret <= 0) goto end;
// 转为准备接收服务器端的HELLO状态
s->state=SSL23_ST_CR_SRVR_HELLO_A;
s->init_num=0;
break;
case SSL23_ST_CR_SRVR_HELLO_A:
case SSL23_ST_CR_SRVR_HELLO_B:
// 读取服务器端的HELLO信息,完成SSL握手协商
ret=ssl23_get_server_hello(s);
if (ret >= 0) cb=NULL;
goto end;
/* break; */
default:
SSLerr(SSL_F_SSL23_CONNECT,SSL_R_UNKNOWN_STATE);
ret= -1;
goto end;
/* break; */
}
if (s->debug) { (void)BIO_flush(s->wbio); }
if ((cb != NULL) && (s->state != state))
{
new_state=s->state;
s->state=state;
cb(s,SSL_CB_CONNECT_LOOP,1);
s->state=new_state;
}
}
end:
s->in_handshake--;
if (buf != NULL)
BUF_MEM_free(buf);
if (cb != NULL)
cb(s,SSL_CB_CONNECT_EXIT,ret);
return(ret);
}
ssl23_client_hello()函数发送客户端的HELLO信息:

/* ssl/s23_clnt.c */
static int ssl23_client_hello(SSL *s)
{
unsigned char *buf;
unsigned char *p,*d;
int i,ch_len;
int ret;
buf=(unsigned char *)s->init_buf->data;
if (s->state == SSL23_ST_CW_CLNT_HELLO_A)
{
// 准备发送HELLO, 构造发送缓冲区的数据,数据格式见SSL_accept一节中的说明
p=s->s3->client_random;
RAND_pseudo_bytes(p,SSL3_RANDOM_SIZE);
/* Do the message type and length last */
d= &(buf[2]);
// p指向SSL握手记录头(11字节长)后的第一字节处
p=d+9;
// 消息类型
*(d++)=SSL2_MT_CLIENT_HELLO;
// 填写客户端版本号
if (!(s->options & SSL_OP_NO_TLSv1))
{
*(d++)=TLS1_VERSION_MAJOR;
*(d++)=TLS1_VERSION_MINOR;
s->client_version=TLS1_VERSION;
}
else if (!(s->options & SSL_OP_NO_SSLv3))
{
*(d++)=SSL3_VERSION_MAJOR;
*(d++)=SSL3_VERSION_MINOR;
s->client_version=SSL3_VERSION;
}
else if (!(s->options & SSL_OP_NO_SSLv2))
{
*(d++)=SSL2_VERSION_MAJOR;
*(d++)=SSL2_VERSION_MINOR;
s->client_version=SSL2_VERSION;
}
else
{
SSLerr(SSL_F_SSL23_CLIENT_HELLO,SSL_R_NO_PROTOCOLS_AVAILABLE);
return(-1);
}
// 填写cipher_specs信息
/* Ciphers supported */
i=ssl_cipher_list_to_bytes(s,SSL_get_ciphers(s),p);
if (i == 0)
{
/* no ciphers */
SSLerr(SSL_F_SSL23_CLIENT_HELLO,SSL_R_NO_CIPHERS_AVAILABLE);
return(-1);
}
s2n(i,d);
p+=i;
// 填写会话ID
/* put in the session-id, zero since there is no
* reuse. */
s2n(0,d);
if (s->options & SSL_OP_NETSCAPE_CHALLENGE_BUG)
ch_len=SSL2_CHALLENGE_LENGTH;
else
ch_len=SSL2_MAX_CHALLENGE_LENGTH;
// 填写挑战信息
/* write out sslv2 challenge */
if (SSL3_RANDOM_SIZE < ch_len)
i=SSL3_RANDOM_SIZE;
else
i=ch_len;
s2n(i,d);
memset(&(s->s3->client_random[0]),0,SSL3_RANDOM_SIZE);
RAND_pseudo_bytes(&(s->s3->client_random[SSL3_RANDOM_SIZE-i]),i);
memcpy(p,&(s->s3->client_random[SSL3_RANDOM_SIZE-i]),i);
p+=i;
// 数据长度
i= p- &(buf[2]);
buf[0]=((i>>8)&0xff)|0x80;
buf[1]=(i&0xff);
// 数据完成,状态转为HELLO B准备发送
s->state=SSL23_ST_CW_CLNT_HELLO_B;
/* number of bytes to write */
s->init_num=i+2;
s->init_off=0;
ssl3_finish_mac(s,&(buf[2]),i);
}
/* SSL3_ST_CW_CLNT_HELLO_B */
// 发送HELLO数据
ret = ssl23_write_bytes(s);
if (ret >= 2)
if (s->msg_callback)
s->msg_callback(1, SSL2_VERSION, 0, s->init_buf->data+2, ret-2, s, s->msg_callback_arg); /* CLIENT-HELLO */
return ret;
}

ssl23_get_server_hello()接收服务器端的回应,识别服务器的SSL版本,最后再相应调用ssl2_connect()或ssl3_connect()函数。

/* ssl/s23_clnt.c */
static int ssl23_get_server_hello(SSL *s)
{
char buf[8];
unsigned char *p;
int i;
int n;
// 读7字节的服务器端数据
n=ssl23_read_bytes(s,7);
if (n != 7) return(n);
p=s->packet;
memcpy(buf,p,n);
if ((p[0] & 0x80) && (p[2] == SSL2_MT_SERVER_HELLO) &&
(p[5] == 0x00) && (p[6] == 0x02))
{
// 服务器是SSL2版本
#ifdef OPENSSL_NO_SSL2
SSLerr(SSL_F_SSL23_GET_SERVER_HELLO,SSL_R_UNSUPPORTED_PROTOCOL);
goto err;
#else
/* we are talking sslv2 */
/* we need to clean up the SSLv3 setup and put in the
* sslv2 stuff. */
int ch_len;
if (s->options & SSL_OP_NO_SSLv2)
{
SSLerr(SSL_F_SSL23_GET_SERVER_HELLO,SSL_R_UNSUPPORTED_PROTOCOL);
goto err;
}
// 初始化SSL结构中的SSL2支持,ssl_connect将为ssl2_connect
if (s->s2 == NULL)
{
if (!ssl2_new(s))
goto err;
}
else
ssl2_clear(s);
if (s->options & SSL_OP_NETSCAPE_CHALLENGE_BUG)
ch_len=SSL2_CHALLENGE_LENGTH;
else
ch_len=SSL2_MAX_CHALLENGE_LENGTH;
/* write out sslv2 challenge */
i=(SSL3_RANDOM_SIZE < ch_len)
?SSL3_RANDOM_SIZE:ch_len;
s->s2->challenge_length=i;
memcpy(s->s2->challenge,
&(s->s3->client_random[SSL3_RANDOM_SIZE-i]),i);
if (s->s3 != NULL) ssl3_free(s);
if (!BUF_MEM_grow_clean(s->init_buf,
SSL2_MAX_RECORD_LENGTH_3_BYTE_HEADER))
{
SSLerr(SSL_F_SSL23_GET_SERVER_HELLO,ERR_R_BUF_LIB);
goto err;
}
s->state=SSL2_ST_GET_SERVER_HELLO_A;
if (!(s->client_version == SSL2_VERSION))
/* use special padding (SSL 3.0 draft/RFC 2246, App. E.2) */
s->s2->ssl2_rollback=1;
/* setup the 5 bytes we have read so we get them from
* the sslv2 buffer */
s->rstate=SSL_ST_READ_HEADER;
s->packet_length=n;
s->packet= &(s->s2->rbuf[0]);
memcpy(s->packet,buf,n);
s->s2->rbuf_left=n;
s->s2->rbuf_offs=0;
/* we have already written one */
s->s2->write_sequence=1;
s->method=SSLv2_client_method();
s->handshake_func=s->method->ssl_connect;
#endif
}
else if ((p[0] == SSL3_RT_HANDSHAKE) &&
(p[1] == SSL3_VERSION_MAJOR) &amp;&
((p[2] == SSL3_VERSION_MINOR) ||
(p[2] == TLS1_VERSION_MINOR)) &amp;&
(p[5] == SSL3_MT_SERVER_HELLO))
{
// 服务器版本为SSL3或TLS1,ssl_connect将为ssl3_connect
/* we have sslv3 or tls1 */
if (!ssl_init_wbio_buffer(s,1)) goto err;
/* we are in this state */
s->state=SSL3_ST_CR_SRVR_HELLO_A;
/* put the 5 bytes we have read into the input buffer
* for SSLv3 */
s->rstate=SSL_ST_READ_HEADER;
s->packet_length=n;
s->packet= &(s->s3->rbuf.buf[0]);
memcpy(s->packet,buf,n);
s->s3->rbuf.left=n;
s->s3->rbuf.offset=0;
if ((p[2] == SSL3_VERSION_MINOR) &&
!(s->options & SSL_OP_NO_SSLv3))
{
s->version=SSL3_VERSION;
s->method=SSLv3_client_method();
}
else if ((p[2] == TLS1_VERSION_MINOR) &&
!(s->options & SSL_OP_NO_TLSv1))
{
s->version=TLS1_VERSION;
s->method=TLSv1_client_method();
}
else
{
SSLerr(SSL_F_SSL23_GET_SERVER_HELLO,SSL_R_UNSUPPORTED_PROTOCOL);
goto err;
}

s->handshake_func=s->method->ssl_connect;
}
else if ((p[0] == SSL3_RT_ALERT) &&
(p[1] == SSL3_VERSION_MAJOR) &amp;&
((p[2] == SSL3_VERSION_MINOR) ||
(p[2] == TLS1_VERSION_MINOR)) &amp;&
(p[3] == 0) &&
(p[4] == 2))
{
// SSL告警信息
void (*cb)(const SSL *ssl,int type,int val)=NULL;
int j;
/* An alert */
if (s->info_callback != NULL)
cb=s->info_callback;
else if (s->ctx->info_callback != NULL)
cb=s->ctx->info_callback;

i=p[5];
if (cb != NULL)
{
j=(i<<8)|p[6];
cb(s,SSL_CB_READ_ALERT,j);
}
s->rwstate=SSL_NOTHING;
SSLerr(SSL_F_SSL23_GET_SERVER_HELLO,SSL_AD_REASON_OFFSET+p[6]);
goto err;
}
else
{
SSLerr(SSL_F_SSL23_GET_SERVER_HELLO,SSL_R_UNKNOWN_PROTOCOL);
goto err;
}
s->init_num=0;
/* Since, if we are sending a ssl23 client hello, we are not
* reusing a session-id */
if (!ssl_get_new_session(s,0))
goto err;
s->first_packet=1;
// 递归调用SSL_connect,实际将执行ssl2_connect()或ssl3_connect()
return(SSL_connect(s));
err:
return(-1);
}
举例ssl3_conect()函数定义如下,ssl2_connect()就不分析了:
/* ssl/s3_clnt.c */
int ssl3_connect(SSL *s)
{
BUF_MEM *buf=NULL;
unsigned long Time=time(NULL),l;
long num1;
void (*cb)(const SSL *ssl,int type,int val)=NULL;
int ret= -1;
int new_state,state,skip=0;;
// 对SSL3连接的初始化
RAND_add(&Time,sizeof(Time),0);
ERR_clear_error();
clear_sys_error();
if (s->info_callback != NULL)
cb=s->info_callback;
else if (s->ctx->info_callback != NULL)
cb=s->ctx->info_callback;

s->in_handshake++;
if (!SSL_in_init(s) || SSL_in_before(s)) SSL_clear(s);
for (;;)
{
state=s->state;
switch(s->state)
{
case SSL_ST_RENEGOTIATE:
// 重协商的话从头开始, 注意break是注释掉的
s->new_session=1;
s->state=SSL_ST_CONNECT;
s->ctx->stats.sess_connect_renegotiate++;
/* break */
// 初始状态应该是SSL_ST_BEFORE|SSL_ST_CONNECT
case SSL_ST_BEFORE:
case SSL_ST_CONNECT:
case SSL_ST_BEFORE|SSL_ST_CONNECT:
case SSL_ST_OK|SSL_ST_CONNECT:
// server=0表示是客户端
s->server=0;
if (cb != NULL) cb(s,SSL_CB_HANDSHAKE_START,1);
// 如果本地版本不是SSL3则出错
if ((s->version & 0xff00 ) != 0x0300)
{
SSLerr(SSL_F_SSL3_CONNECT, ERR_R_INTERNAL_ERROR);
ret = -1;
goto end;
}
/* s->version=SSL3_VERSION; */
s->type=SSL_ST_CONNECT;
// 初始化连接缓冲区
if (s->init_buf == NULL)
{
if ((buf=BUF_MEM_new()) == NULL)
{
ret= -1;
goto end;
}
if (!BUF_MEM_grow(buf,SSL3_RT_MAX_PLAIN_LENGTH))
{
ret= -1;
goto end;
}
s->init_buf=buf;
buf=NULL;
}
if (!ssl3_setup_buffers(s)) { ret= -1; goto end; }
/* setup buffing BIO */
if (!ssl_init_wbio_buffer(s,0)) { ret= -1; goto end; }
/* don't push the buffering BIO quite yet */
ssl3_init_finished_mac(s);
// 准备发送HELLO信息
s->state=SSL3_ST_CW_CLNT_HELLO_A;
s->ctx->stats.sess_connect++;
s->init_num=0;
break;
case SSL3_ST_CW_CLNT_HELLO_A:
case SSL3_ST_CW_CLNT_HELLO_B:
// 该状态发送客户端SSL3信息
s->shutdown=0;
ret=ssl3_client_hello(s);
if (ret <= 0) goto end;
// 发送成功,状态转为准备接收服务器的HELLO回应
s->state=SSL3_ST_CR_SRVR_HELLO_A;
s->init_num=0;
/* turn on buffering for the next lot of output */
if (s->bbio != s->wbio)
s->wbio=BIO_push(s->bbio,s->wbio);
break;
case SSL3_ST_CR_SRVR_HELLO_A:
case SSL3_ST_CR_SRVR_HELLO_B:
// 该状态下接收服务器发送的HELLO信息
ret=ssl3_get_server_hello(s);
if (ret <= 0) goto end;
if (s->hit)
// 如果是reuse的连接,状态转为接收结束
s->state=SSL3_ST_CR_FINISHED_A;
else
// 否则状态转为准备接收服务器的证书
s->state=SSL3_ST_CR_CERT_A;
s->init_num=0;
break;
case SSL3_ST_CR_CERT_A:
case SSL3_ST_CR_CERT_B:
// 该状态下接收服务器发送的证书
/* Check if it is anon DH */
if (!(s->s3->tmp.new_cipher->algorithms & SSL_aNULL))
{
ret=ssl3_get_server_certificate(s);
if (ret <= 0) goto end;
}
else
skip=1;
// 状态转为接收密钥交换信息
s->state=SSL3_ST_CR_KEY_EXCH_A;
s->init_num=0;
break;
case SSL3_ST_CR_KEY_EXCH_A:
case SSL3_ST_CR_KEY_EXCH_B:
// 该状态下接收密钥交换信息
ret=ssl3_get_key_exchange(s);
if (ret <= 0) goto end;
// 状态转为接收证书请求
s->state=SSL3_ST_CR_CERT_REQ_A;
s->init_num=0;
/* at this point we check that we have the
* required stuff from the server */
if (!ssl3_check_cert_and_algorithm(s))
{
ret= -1;
goto end;
}
break;
case SSL3_ST_CR_CERT_REQ_A:
case SSL3_ST_CR_CERT_REQ_B:
// 该状态下接收服务器的证书请求
ret=ssl3_get_certificate_request(s);
if (ret <= 0) goto end;
s->state=SSL3_ST_CR_SRVR_DONE_A;
s->init_num=0;
break;
case SSL3_ST_CR_SRVR_DONE_A:
case SSL3_ST_CR_SRVR_DONE_B:
// 该状态下接收服务器信息结束
ret=ssl3_get_server_done(s);
if (ret <= 0) goto end;
if (s->s3->tmp.cert_req)
// 发送客户端证书
s->state=SSL3_ST_CW_CERT_A;
else
// 发送密钥交换信息
s->state=SSL3_ST_CW_KEY_EXCH_A;
s->init_num=0;
break;
case SSL3_ST_CW_CERT_A:
case SSL3_ST_CW_CERT_B:
case SSL3_ST_CW_CERT_C:
case SSL3_ST_CW_CERT_D:
// 该状态下发送客户端证书
ret=ssl3_send_client_certificate(s);
if (ret <= 0) goto end;
// 状态转为发送密钥交换信息
s->state=SSL3_ST_CW_KEY_EXCH_A;
s->init_num=0;
break;
case SSL3_ST_CW_KEY_EXCH_A:
case SSL3_ST_CW_KEY_EXCH_B:
// 该状态下发送密钥交换信息
ret=ssl3_send_client_key_exchange(s);
if (ret <= 0) goto end;
// 加密算法
l=s->s3->tmp.new_cipher->algorithms;
/* EAY EAY EAY need to check for DH fix cert
* sent back */
/* For TLS, cert_req is set to 2, so a cert chain
* of nothing is sent, but no verify packet is sent */
if (s->s3->tmp.cert_req == 1)
{
// 发送证书验证
s->state=SSL3_ST_CW_CERT_VRFY_A;
}
else
{
// TLS, 不用发送证书认证信息,直接转为修改算法状态
s->state=SSL3_ST_CW_CHANGE_A;
s->s3->change_cipher_spec=0;
}
s->init_num=0;
break;
case SSL3_ST_CW_CERT_VRFY_A:
case SSL3_ST_CW_CERT_VRFY_B:
// 该状态发送证书验证信息
ret=ssl3_send_client_verify(s);
if (ret <= 0) goto end;
// 状态转为发送修改算法信息
s->state=SSL3_ST_CW_CHANGE_A;
s->init_num=0;
s->s3->change_cipher_spec=0;
break;
case SSL3_ST_CW_CHANGE_A:
case SSL3_ST_CW_CHANGE_B:
// 该状态下发送修改算法信息
ret=ssl3_send_change_cipher_spec(s,
SSL3_ST_CW_CHANGE_A,SSL3_ST_CW_CHANGE_B);
if (ret <= 0) goto end;
// 状态转为发送结束
s->state=SSL3_ST_CW_FINISHED_A;
s->init_num=0;
// 设置会话加密算法
s->session->cipher=s->s3->tmp.new_cipher;
if (s->s3->tmp.new_compression == NULL)
s->session->compress_meth=0;
else
s->session->compress_meth=
s->s3->tmp.new_compression->id;
if (!s->method->ssl3_enc->setup_key_block(s))
{
ret= -1;
goto end;
}
if (!s->method->ssl3_enc->change_cipher_state(s,
SSL3_CHANGE_CIPHER_CLIENT_WRITE))
{
ret= -1;
goto end;
}
break;
case SSL3_ST_CW_FINISHED_A:
case SSL3_ST_CW_FINISHED_B:
// 该状态下发送协商结束信息
ret=ssl3_send_finished(s,
SSL3_ST_CW_FINISHED_A,SSL3_ST_CW_FINISHED_B,
s->method->ssl3_enc->client_finished_label,
s->method->ssl3_enc->client_finished_label_len);
if (ret <= 0) goto end;
// 转为刷新写缓冲状态
s->state=SSL3_ST_CW_FLUSH;
/* clear flags */
s->s3->flags&= ~SSL3_FLAGS_POP_BUFFER;
if (s->hit)
{
// 如果是reuse的会话, FLUSH后的下一个状态转为协商成功
s->s3->tmp.next_state=SSL_ST_OK;
if (s->s3->flags & SSL3_FLAGS_DELAY_CLIENT_FINISHED)
{
s->state=SSL_ST_OK;
s->s3->flags|=SSL3_FLAGS_POP_BUFFER;
s->s3->delay_buf_pop_ret=0;
}
}
else
{
// 否则状态转为准备接收服务器的协商结束信息
s->s3->tmp.next_state=SSL3_ST_CR_FINISHED_A;
}
s->init_num=0;
break;
case SSL3_ST_CR_FINISHED_A:
case SSL3_ST_CR_FINISHED_B:
// 该状态接收服务器发送的协商结束信息
ret=ssl3_get_finished(s,SSL3_ST_CR_FINISHED_A,
SSL3_ST_CR_FINISHED_B);
if (ret <= 0) goto end;
if (s->hit)
// 如果会话是reuse的,状态转为发送算法
s->state=SSL3_ST_CW_CHANGE_A;
else
// 否则协商成功
s->state=SSL_ST_OK;
s->init_num=0;
break;
case SSL3_ST_CW_FLUSH:
// 该状态下刷新写缓冲区
/* number of bytes to be flushed */
num1=BIO_ctrl(s->wbio,BIO_CTRL_INFO,0,NULL);
if (num1 > 0)
{
s->rwstate=SSL_WRITING;
num1=BIO_flush(s->wbio);
if (num1 <= 0) { ret= -1; goto end; }
s->rwstate=SSL_NOTHING;
}
s->state=s->s3->tmp.next_state;
break;
case SSL_ST_OK:
// 该状态下协商成功结束
// 释放协商时分配的资源
/* clean a few things up */
ssl3_cleanup_key_block(s);
if (s->init_buf != NULL)
{
BUF_MEM_free(s->init_buf);
s->init_buf=NULL;
}
/* If we are not 'joining' the last two packets,
* remove the buffering now */
if (!(s->s3->flags & SSL3_FLAGS_POP_BUFFER))
ssl_free_wbio_buffer(s);
/* else do it later in ssl3_write */
s->init_num=0;
s->new_session=0;
ssl_update_cache(s,SSL_SESS_CACHE_CLIENT);
if (s->hit) s->ctx->stats.sess_hit++;
ret=1;
/* s->server=0; */
s->handshake_func=ssl3_connect;
s->ctx->stats.sess_connect_good++;
if (cb != NULL) cb(s,SSL_CB_HANDSHAKE_DONE,1);
goto end;
/* break; */

default:
SSLerr(SSL_F_SSL3_CONNECT,SSL_R_UNKNOWN_STATE);
ret= -1;
goto end;
/* break; */
}
/* did we do anything */
if (!s->s3->tmp.reuse_message && !skip)
{
if (s->debug)
{
if ((ret=BIO_flush(s->wbio)) <= 0)
goto end;
}
if ((cb != NULL) && (s->state != state))
{
new_state=s->state;
s->state=state;
cb(s,SSL_CB_CONNECT_LOOP,1);
s->state=new_state;
}
}
skip=0;
}
end:
s->in_handshake--;
if (buf != NULL)
BUF_MEM_free(buf);
if (cb != NULL)
cb(s,SSL_CB_CONNECT_EXIT,ret);
return(ret);
}
2.14 SSL_read

SSL结构(struct ssl_st)中的s2,s3指针分别指向SSL2和SSL3的状态结构,这些状态结构中都有用于读的rbuf,其中保存SSL会话接收到的原始数据,另 外还有保存记录的rrec,用来保存解码后的数据。在SSL结构中有个指针packet是指向原始数据的。

SSL_read()实现从SSL通道中读取数据,送到应用程序的数据是已经经过SSL解封装的了。

/* ssl/ssl_lib.c */
int SSL_read(SSL *s,void *buf,int num)
{
if (s->handshake_func == 0)
{
SSLerr(SSL_F_SSL_READ, SSL_R_UNINITIALIZED);
return -1;
}
// 发现接收shutdown标志,接收结束
if (s->shutdown & SSL_RECEIVED_SHUTDOWN)
{
s->rwstate=SSL_NOTHING;
return(0);
}
// 调用具体方法的接收函数, 如ssl3_read(), ssl2_read()等
return(s->method->ssl_read(s,buf,num));
}

下面以ssl3_read()函数进行详细说明,ssl3_read()函数本质是调用ssl3_read_internal()
/* ssl/s3_lib.c */
int ssl3_read(SSL *s, void *buf, int len)
{
// 最后一个参数为0,表示是实实在在的读数据,不是偷看(peek)
// peek的意思是读数据,但不会把已读数据从缓冲区中去掉,因此下一次读还会读到
return ssl3_read_internal(s, buf, len, 0);
}

static int ssl3_read_internal(SSL *s, void *buf, int len, int peek)
{
int ret;
// 清除错误信息
clear_sys_error();
// 检查协商是否成功
if (s->s3->renegotiate) ssl3_renegotiate_check(s);
// 标志现在在读应用层数据
s->s3->in_read_app_data=1;
// ssl3读取数据, 类型是SSL3_RT_APPLICATION_DATA,应用数据
ret=ssl3_read_bytes(s,SSL3_RT_APPLICATION_DATA,buf,len,peek);
if ((ret == -1) && (s->s3->in_read_app_data == 2))
{
// s->s3->in_read_app_data == 2表示要读协商数据
/* ssl3_read_bytes decided to call s->handshake_func, which
* called ssl3_read_bytes to read handshake data.
* However, ssl3_read_bytes actually found application data
* and thinks that application data makes sense here; so disable
* handshake processing and try to read application data again. */
s->in_handshake++;
// 重新读数据
ret=ssl3_read_bytes(s,SSL3_RT_APPLICATION_DATA,buf,len,peek);
s->in_handshake--;
}
else
s->s3->in_read_app_data=0;
return(ret);
}

读取数据的主要函数实际为ssl3_read_bytes():
/* ssl/ssl3_pkt.c */
int ssl3_read_bytes(SSL *s, int type, unsigned char *buf, int len, int peek)
{
int al,i,j,ret;
unsigned int n;
SSL3_RECORD *rr;
void (*cb)(const SSL *ssl,int type2,int val)=NULL;
// 检查是否分配了接收缓冲区
if (s->s3->rbuf.buf == NULL) /* Not initialized yet */
if (!ssl3_setup_buffers(s))
return(-1);
// 只处理两种数据,或是应用层数据或者是SSL握手协商数据,
// peek只处理应用层数据,其他则出错
if ((type && (type != SSL3_RT_APPLICATION_DATA) &amp;&
(type != SSL3_RT_HANDSHAKE) &amp;& type) ||
(peek && (type != SSL3_RT_APPLICATION_DATA)))
{
SSLerr(SSL_F_SSL3_READ_BYTES, ERR_R_INTERNAL_ERROR);
return -1;
}
if ((type == SSL3_RT_HANDSHAKE) && (s->s3->handshake_fragment_len > 0))
/* (partially) satisfy request from storage */
{
// 握手数据碎片, 处理完直到不再是碎片
unsigned char *src = s->s3->handshake_fragment;
unsigned char *dst = buf;
unsigned int k;
/* peek == 0 */
n = 0;
while ((len > 0) && (s->s3->handshake_fragment_len > 0))
{
*dst++ = *src++;
len--; s->s3->handshake_fragment_len--;
n++;
}
/* move any remaining fragment bytes: */
for (k = 0; k < s->s3->handshake_fragment_len; k++)
s->s3->handshake_fragment[k] = *src++;
return n;
}
/* Now s->s3->handshake_fragment_len == 0 if type == SSL3_RT_HANDSHAKE. */
if (!s->in_handshake && SSL_in_init(s))
{
// 没进握手阶段但属于SSL初始化阶段, 调用SSL握手处理
/* type == SSL3_RT_APPLICATION_DATA */
i=s->handshake_func(s);
if (i < 0) return(i);
if (i == 0)
{
SSLerr(SSL_F_SSL3_READ_BYTES,SSL_R_SSL_HANDSHAKE_FAILURE);
return(-1);
}
}
start:
// 正常的接收数据开始, 开始读写状态为NOTHING
s->rwstate=SSL_NOTHING;
/* s->s3->rrec.type - is the type of record
* s->s3->rrec.data, - data
* s->s3->rrec.off, - offset into 'data' for next read
* s->s3->rrec.length, - number of bytes. */
// 记录类型指针
rr = &(s->s3->rrec);
/* get new packet if necessary */
if ((rr->length == 0) || (s->rstate == SSL_ST_READ_BODY))
{
// 当前没记录,获取新的记录信息, 该函数获取SSL记录数据
ret=ssl3_get_record(s);
if (ret <= 0) return(ret);
}
/* we now have a packet which can be read and processed */
if (s->s3->change_cipher_spec /* set when we receive ChangeCipherSpec,
* reset by ssl3_get_finished */
&& (rr->type != SSL3_RT_HANDSHAKE))
{
// 只允许在握手状态下修改当前的算法信息,否则出错
al=SSL_AD_UNEXPECTED_MESSAGE;
SSLerr(SSL_F_SSL3_READ_BYTES,SSL_R_DATA_BETWEEN_CCS_AND_FINISHED);
goto err;
}
/* If the other end has shut down, throw anything we read away
* (even in 'peek' mode) */
if (s->shutdown & SSL_RECEIVED_SHUTDOWN)
{
// 接收到对方的FIN信息, 接收结束
rr->length=0;
s->rwstate=SSL_NOTHING;
return(0);
}

if (type == rr->type) /* SSL3_RT_APPLICATION_DATA or SSL3_RT_HANDSHAKE */
{
// 读到是数据类型和期待读到的数据类型一致
// 这是正常大多数的情况
/* make sure that we are not getting application data when we
* are doing a handshake for the first time */
if (SSL_in_init(s) && (type == SSL3_RT_APPLICATION_DATA) &amp;&
(s->enc_read_ctx == NULL))
{
// 在初始阶段只应该读握手信息而不该读应用信息
al=SSL_AD_UNEXPECTED_MESSAGE;
SSLerr(SSL_F_SSL3_READ_BYTES,SSL_R_APP_DATA_IN_HANDSHAKE);
goto f_err;
}
if (len <= 0) return(len);
// 在期待读取的数据长度len和已经读到的数据记录长度rr->length取小值作为返回的数据长度
if ((unsigned int)len > rr->length)
n = rr->length;
else
n = (unsigned int)len;
memcpy(buf,&(rr->data[rr->off]),n);
if (!peek)
{
// 如果不是peek,移动记录缓冲的数据偏移指针,长度减
rr->length-=n;
rr->off+=n;
if (rr->length == 0)
{
s->rstate=SSL_ST_READ_HEADER;
rr->off=0;
}
}
// 正常返回
return(n);
}

/* If we get here, then type != rr->type; if we have a handshake
* message, then it was unexpected (Hello Request or Client Hello). */
/* In case of record types for which we have 'fragment' storage,
* fill that so that we can process the data at a fixed place.
*/
// 现在情况是读取的数据类型和期待读取的数据类型不一致
// 已经读到的数据不丢弃,而是作为碎片存储起来以后备用
{
unsigned int dest_maxlen = 0;
unsigned char *dest = NULL;
unsigned int *dest_len = NULL;
if (rr->type == SSL3_RT_HANDSHAKE)
{
// 握手类型数据存到握手碎片区
dest_maxlen = sizeof s->s3->handshake_fragment;
dest = s->s3->handshake_fragment;
dest_len = &s->s3->handshake_fragment_len;
}
else if (rr->type == SSL3_RT_ALERT)
{
// 告警信息存到告警碎片区
dest_maxlen = sizeof s->s3->alert_fragment;
dest = s->s3->alert_fragment;
dest_len = &s->s3->alert_fragment_len;
}
if (dest_maxlen > 0)
{
// 有缓冲区, 计算可用的最大缓冲区
n = dest_maxlen - *dest_len; /* available space in 'dest' */
// 如果要写入的长度小于可用空间大小,可全部写入
if (rr->length < n)
n = rr->length; /* available bytes */
// 写入缓冲区
/* now move 'n' bytes: */
while (n-- > 0)
{
dest[(*dest_len)++] = rr->data[rr->off++];
rr->length--;
}
if (*dest_len < dest_maxlen)
goto start; /* fragment was too small */
}
}
/* s->s3->handshake_fragment_len == 4 iff rr->type == SSL3_RT_HANDSHAKE;
* s->s3->alert_fragment_len == 2 iff rr->type == SSL3_RT_ALERT.
* (Possibly rr is 'empty' now, i.e. rr->length may be 0.) */
/* If we are a client, check for an incoming 'Hello Request': */
if ((!s->server) &&
(s->s3->handshake_fragment_len >= 4) &&
(s->s3->handshake_fragment[0] == SSL3_MT_HELLO_REQUEST) &&
(s->session != NULL) && (s->session->cipher != NULL))
{
// 本地是客户端
// 握手碎片长度至少是4字节, 类型是HELLO的握手信息时进行处理
s->s3->handshake_fragment_len = 0;
if ((s->s3->handshake_fragment[1] != 0) ||
(s->s3->handshake_fragment[2] != 0) ||
(s->s3->handshake_fragment[3] != 0))
{
// SSL3_MT_HELLO_REQUEST类型后三个字节都要是0
al=SSL_AD_DECODE_ERROR;
SSLerr(SSL_F_SSL3_READ_BYTES,SSL_R_BAD_HELLO_REQUEST);
goto err;
}
if (s->msg_callback)
s->msg_callback(0, s->version, SSL3_RT_HANDSHAKE, s->s3->handshake_fragment, 4, s, s->msg_callback_arg);
if (SSL_is_init_finished(s) &&
// 协商结束但没设置不需重协商算法标志
!(s->s3->flags & SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS) &amp;&
// 没设置该ssl3会话的重协商标志
!s->s3->renegotiate)
{
// 进行ssl3重协商,实际是将s->s3->renegotiate置1
ssl3_renegotiate(s);
if (ssl3_renegotiate_check(s))
{
// 进行重协商
i=s->handshake_func(s);
if (i < 0) return(i);
if (i == 0)
{
SSLerr(SSL_F_SSL3_READ_BYTES,SSL_R_SSL_HANDSHAKE_FAILURE);
return(-1);
}
if (!(s->mode & SSL_MODE_AUTO_RETRY))
// 该SSL会话不是自动重试模式
{
if (s->s3->rbuf.left == 0) /* no read-ahead left? */
{
BIO *bio;
/* In the case where we try to read application data,
* but we trigger an SSL handshake, we return -1 with
* the retry option set. Otherwise renegotiation may
* cause nasty problems in the blocking world */
s->rwstate=SSL_READING;
bio=SSL_get_rbio(s);
// 清除读BIO重试标志
BIO_clear_retry_flags(bio);
// 设置读BIO重试标志
BIO_set_retry_read(bio);
return(-1);
}
}
}
}
/* we either finished a handshake or ignored the request,
* now try again to obtain the (application) data we were asked for */
// 重新读数据
goto start;
}
if (s->s3->alert_fragment_len >= 2)
{
// 处理告警碎片信息, 长度大于等于2字节时就处理
int alert_level = s->s3->alert_fragment[0];
int alert_descr = s->s3->alert_fragment[1];
s->s3->alert_fragment_len = 0;
// 分别处理msg和info的回调
if (s->msg_callback)
s->msg_callback(0, s->version, SSL3_RT_ALERT, s->s3->alert_fragment, 2, s, s->msg_callback_arg);
if (s->info_callback != NULL)
cb=s->info_callback;
else if (s->ctx->info_callback != NULL)
cb=s->ctx->info_callback;
if (cb != NULL)
{
j = (alert_level << 8) | alert_descr;
cb(s, SSL_CB_READ_ALERT, j);
}
if (alert_level == 1) /* warning */
{
// 普通报警信息
s->s3->warn_alert = alert_descr;
if (alert_descr == SSL_AD_CLOSE_NOTIFY)
{
s->shutdown |= SSL_RECEIVED_SHUTDOWN;
return(0);
}
}
else if (alert_level == 2) /* fatal */
{
// 严重错误信息, 断开SSL会话
char tmp[16];
s->rwstate=SSL_NOTHING;
s->s3->fatal_alert = alert_descr;
SSLerr(SSL_F_SSL3_READ_BYTES, SSL_AD_REASON_OFFSET + alert_descr);
BIO_snprintf(tmp,sizeof tmp,"%d",alert_descr);
ERR_add_error_data(2,"SSL alert number ",tmp);
s->shutdown|=SSL_RECEIVED_SHUTDOWN;
SSL_CTX_remove_session(s->ctx,s->session);
return(0);
}
else
{
al=SSL_AD_ILLEGAL_PARAMETER;
SSLerr(SSL_F_SSL3_READ_BYTES,SSL_R_UNKNOWN_ALERT_TYPE);
goto f_err;
}
goto start;
}
if (s->shutdown & SSL_SENT_SHUTDOWN) /* but we have not received a shutdown */
{
// SSL会话设置发送SHUTDOWN, 表示不再发送数据
s->rwstate=SSL_NOTHING;
rr->length=0;
return(0);
}
if (rr->type == SSL3_RT_CHANGE_CIPHER_SPEC)
{
// 数据记录类型是更改算法参数
/* 'Change Cipher Spec' is just a single byte, so we know
* exactly what the record payload has to look like */
if ( (rr->length != 1) || (rr->off != 0) ||
(rr->data[0] != SSL3_MT_CCS))
{
// 错误检查
i=SSL_AD_ILLEGAL_PARAMETER;
SSLerr(SSL_F_SSL3_READ_BYTES,SSL_R_BAD_CHANGE_CIPHER_SPEC);
goto err;
}
rr->length=0;
if (s->msg_callback)
s->msg_callback(0, s->version, SSL3_RT_CHANGE_CIPHER_SPEC, rr->data, 1, s, s->msg_callback_arg);
// 加密算法更改处理,成功时转到重新接收数据
s->s3->change_cipher_spec=1;
if (!do_change_cipher_spec(s))
goto err;
else
goto start;
}
/* Unexpected handshake message (Client Hello, or protocol violation) */
if ((s->s3->handshake_fragment_len >= 4) && !s->in_handshake)
{
// 异常的握手信息
// SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS标志表示不需要重新协商加密算法
if (((s->state&SSL_ST_MASK) == SSL_ST_OK) &amp;&
!(s->s3->flags & SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS))
{
#if 0 /* worked only because C operator preferences are not as expected (and
* because this is not really needed for clients except for detecting
* protocol violations): */
s->state=SSL_ST_BEFORE|(s->server)
?SSL_ST_ACCEPT
:SSL_ST_CONNECT;
#else
// 如果是服务器端,SSL状态转为接受;如果是客户端, 转为连接
s->state = s->server ? SSL_ST_ACCEPT : SSL_ST_CONNECT;
#endif
// 重新进行新会话
s->new_session=1;
}
// 重新握手协商
i=s->handshake_func(s);
if (i < 0) return(i);
if (i == 0)
{
SSLerr(SSL_F_SSL3_READ_BYTES,SSL_R_SSL_HANDSHAKE_FAILURE);
return(-1);
}
if (!(s->mode & SSL_MODE_AUTO_RETRY))
{
if (s->s3->rbuf.left == 0) /* no read-ahead left? */
{
BIO *bio;
/* In the case where we try to read application data,
* but we trigger an SSL handshake, we return -1 with
* the retry option set. Otherwise renegotiation may
* cause nasty problems in the blocking world */
s->rwstate=SSL_READING;
bio=SSL_get_rbio(s);
BIO_clear_retry_flags(bio);
BIO_set_retry_read(bio);
return(-1);
}
}
goto start;
}
// 最后再根据记录类型进行处理
switch (rr->type)
{
default:
#ifndef OPENSSL_NO_TLS
/* TLS just ignores unknown message types */
// 如果会话是TLS1版本转为重新接收数据
// 其他版本则认为出错
if (s->version == TLS1_VERSION)
{
rr->length = 0;
goto start;
}
#endif
al=SSL_AD_UNEXPECTED_MESSAGE;
SSLerr(SSL_F_SSL3_READ_BYTES,SSL_R_UNEXPECTED_RECORD);
goto f_err;
case SSL3_RT_CHANGE_CIPHER_SPEC:
case SSL3_RT_ALERT:
case SSL3_RT_HANDSHAKE:
// 这几种类型前面已经处理过了, 到这还有的话是哪出错了
/* we already handled all of these, with the possible exception
* of SSL3_RT_HANDSHAKE when s->in_handshake is set, but that
* should not happen when type != rr->type */
al=SSL_AD_UNEXPECTED_MESSAGE;
SSLerr(SSL_F_SSL3_READ_BYTES,ERR_R_INTERNAL_ERROR);
goto f_err;
case SSL3_RT_APPLICATION_DATA:
// 这时情况是收到的是应用数据,而期望接收的是握手数据
/* At this point, we were expecting handshake data,
* but have application data. If the library was
* running inside ssl3_read() (i.e. in_read_app_data
* is set) and it makes sense to read application data
* at this point (session renegotiation not yet started),
* we will indulge it.
*/
// 要接收应用数据
if (s->s3->in_read_app_data &&
// 需要重协商
(s->s3->total_renegotiations != 0) &&
((
(s->state & SSL_ST_CONNECT) &amp;&
(s->state >= SSL3_ST_CW_CLNT_HELLO_A) &&
(s->state <= SSL3_ST_CR_SRVR_HELLO_A)
) || (
(s->state & SSL_ST_ACCEPT) &amp;&
(s->state <= SSL3_ST_SW_HELLO_REQ_A) &&
(s->state >= SSL3_ST_SR_CLNT_HELLO_A)
)
))
{
// in_read_app_data设置为2,会重新执行ssl3_read_bytes()
s->s3->in_read_app_data=2;
return(-1);
}
else
{
al=SSL_AD_UNEXPECTED_MESSAGE;
SSLerr(SSL_F_SSL3_READ_BYTES,SSL_R_UNEXPECTED_RECORD);
goto f_err;
}
}
/* not reached */
f_err:
// 错误时发送告警信息
ssl3_send_alert(s,SSL3_AL_FATAL,al);
err:
return(-1);
}

可以看出接收数据的重点函数是ssl3_get_record():
/* ssl/s3_pkt.c */
static int ssl3_get_record(SSL *s)
{
int ssl_major,ssl_minor,al;
int enc_err,n,i,ret= -1;
SSL3_RECORD *rr;
SSL_SESSION *sess;
unsigned char *p;
unsigned char md[EVP_MAX_MD_SIZE];
short version;
unsigned int mac_size;
int clear=0;
size_t extra;
int decryption_failed_or_bad_record_mac = 0;
unsigned char *mac = NULL;
// 所读数据的记录指针
rr= &(s->s3->rrec);
sess=s->session;
if (s->options & SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER)
// MS的SSL实现可附带较大数据
extra=SSL3_RT_MAX_EXTRA;
else
extra=0;
if (extra != s->s3->rbuf.len - SSL3_RT_MAX_PACKET_SIZE)
// 普通情况下读缓冲大小就是SSL3_RT_MAX_PACKET_SIZE
{
/* actually likely an application error: SLS_OP_MICROSOFT_BIG_SSLV3_BUFFER
* set after ssl3_setup_buffers() was done */
SSLerr(SSL_F_SSL3_GET_RECORD, ERR_R_INTERNAL_ERROR);
return -1;
}
again:
/* check if we have the header */
if ( (s->rstate != SSL_ST_READ_BODY) ||
// 接收的数据长度还不够记录长度
(s->packet_length < SSL3_RT_HEADER_LENGTH))
{
// 读至少SSL3_RT_HEADER_LENGTH长度,最大s->s3->rbuf.len
n=ssl3_read_n(s, SSL3_RT_HEADER_LENGTH, s->s3->rbuf.len, 0);
if (n <= 0) return(n); /* error or non-blocking */
s->rstate=SSL_ST_READ_BODY;
// p现在是SSL收到的原始数据头
p=s->packet;
/* Pull apart the header into the SSL3_RECORD */
// SSL记录类型
rr->type= *(p++);
// 版本信息
ssl_major= *(p++);
ssl_minor= *(p++);
version=(ssl_major<<8)|ssl_minor;
// 数据长度
n2s(p,rr->length);
/* Lets check version */
if (s->first_packet)
{
s->first_packet=0;
}
else
{
// 已经不是第一个包了
// 检查版本是否等于SSL会话版本,这时应该协商好应该是相同的了
// 如果不同就是错的
if (version != s->version)
{
SSLerr(SSL_F_SSL3_GET_RECORD,SSL_R_WRONG_VERSION_NUMBER);
/* Send back error using their
* version number :-) */
s->version=version;
al=SSL_AD_PROTOCOL_VERSION;
goto f_err;
}
}
// 检查版本是否是SSL3
if ((version>>8) != SSL3_VERSION_MAJOR)
{
SSLerr(SSL_F_SSL3_GET_RECORD,SSL_R_WRONG_VERSION_NUMBER);
goto err;
}
// 接收数据过长出错
if (rr->length > SSL3_RT_MAX_ENCRYPTED_LENGTH+extra)
{
al=SSL_AD_RECORD_OVERFLOW;
SSLerr(SSL_F_SSL3_GET_RECORD,SSL_R_PACKET_LENGTH_TOO_LONG);
goto f_err;
}
/* now s->rstate == SSL_ST_READ_BODY */
}
/* s->rstate == SSL_ST_READ_BODY, get and decode the data */
// SSL3_RT_HEADER_LENGTH = 5
if (rr->length > s->packet_length-SSL3_RT_HEADER_LENGTH)
{
// 已收到的数据还不够记录长度, 继续读够记录长度
/* now s->packet_length == SSL3_RT_HEADER_LENGTH */
i=rr->length;
n=ssl3_read_n(s,i,i,1);
if (n <= 0) return(n); /* error or non-blocking io */
/* now n == rr->length,
* and s->packet_length == SSL3_RT_HEADER_LENGTH + rr->length */
}
//
// 状态转为已经接收到SSL头信息
s->rstate=SSL_ST_READ_HEADER; /* set state for later operations */
/* At this point, s->packet_length == SSL3_RT_HEADER_LNGTH + rr->length,
* and we have that many bytes in s->packet
*/
// input指向SSL原始数据头后的数据
rr->input= &(s->packet[SSL3_RT_HEADER_LENGTH]);
/* ok, we can now read from 's->packet' data into 'rr'
* rr->input points at rr->length bytes, which
* need to be copied into rr->data by either
* the decryption or by the decompression
* When the data is 'copied' into the rr->data buffer,
* rr->input will be pointed at the new buffer */
/* We now have - encrypted [ MAC [ compressed [ plain ] ] ]
* rr->length bytes of encrypted compressed stuff. */
/* check is not needed I believe */
if (rr->length > SSL3_RT_MAX_ENCRYPTED_LENGTH+extra)
{
// 数据又读多了
al=SSL_AD_RECORD_OVERFLOW;
SSLerr(SSL_F_SSL3_GET_RECORD,SSL_R_ENCRYPTED_LENGTH_TOO_LONG);
goto f_err;
}
/* decrypt in place in 'rr->input' */
// 记录数据
rr->data=rr->input;
// 对数据进行解密
enc_err = s->method->ssl3_enc->enc(s,0);
if (enc_err <= 0)
{
if (enc_err == 0)
/* SSLerr() and ssl3_send_alert() have been called */
goto err;
/* Otherwise enc_err == -1, which indicates bad padding
* (rec->length has not been changed in this case).
* To minimize information leaked via timing, we will perform
* the MAC computation anyway. */
// 注意 enc_err<0 时并不立即转到错误处理,只是作个标志
decryption_failed_or_bad_record_mac = 1;
}

/* r->length is now the compressed data plus mac */
if ( (sess == NULL) ||
(s->enc_read_ctx == NULL) ||
(s->read_hash == NULL))
// 会话,加密上下文,读认证码操作都为空的情况,设置标志clear
clear=1;
if (!clear)
{
// HASH算法的认证码长度
mac_size=EVP_MD_size(s->read_hash);
if (rr->length > SSL3_RT_MAX_COMPRESSED_LENGTH+extra+mac_size)
{
#if 0 /* OK only for stream ciphers (then rr->length is visible from ciphertext anyway) */
al=SSL_AD_RECORD_OVERFLOW;
SSLerr(SSL_F_SSL3_GET_RECORD,
SSL_R_PRE_MAC_LENGTH_TOO_LONG);
goto f_err;
#else
// 长度过长, 设置出错标志
decryption_failed_or_bad_record_mac = 1;
#endif
}
/* check the MAC for rr->input (it's in mac_size bytes at the tail) */
if (rr->length >= mac_size)
{
// 记录长度中减去认证码长度
rr->length -= mac_size;
// 认证码数据指针
mac = &rr->data[rr->length];
}
else
{
/* record (minus padding) is too short to contain a MAC */
#if 0 /* OK only for stream ciphers */
al=SSL_AD_DECODE_ERROR;
SSLerr(SSL_F_SSL3_GET_RECORD,SSL_R_LENGTH_TOO_SHORT);
goto f_err;
#else
// 记录长度还没认证码长度长, 设置出错标志
decryption_failed_or_bad_record_mac = 1;
rr->length = 0;
#endif
}
// 用认证码对数据进行认证
i=s->method->ssl3_enc->mac(s,md,0);
if (mac == NULL || memcmp(md, mac, mac_size) != 0)
{
decryption_failed_or_bad_record_mac = 1;
}
}
if (decryption_failed_or_bad_record_mac)
{
// 解密错误, 跳出
/* A separate 'decryption_failed' alert was introduced with TLS 1.0,
* SSL 3.0 only has 'bad_record_mac'. But unless a decryption
* failure is directly visible from the ciphertext anyway,
* we should not reveal which kind of error occured -- this
* might become visible to an attacker (e.g. via a logfile) */
al=SSL_AD_BAD_RECORD_MAC;
SSLerr(SSL_F_SSL3_GET_RECORD,
SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC);
goto f_err;
}
/* r->length is now just compressed */
if (s->expand != NULL)
// 现在数据是经过压缩处理的
{
if (rr->length > SSL3_RT_MAX_COMPRESSED_LENGTH+extra)
{
// 压缩数据长度又太长了
al=SSL_AD_RECORD_OVERFLOW;
SSLerr(SSL_F_SSL3_GET_RECORD,
SSL_R_COMPRESSED_LENGTH_TOO_LONG);
goto f_err;
}
// 对数据解压
if (!do_uncompress(s))
{
// 解压失败, 跳出
al=SSL_AD_DECOMPRESSION_FAILURE;
SSLerr(SSL_F_SSL3_GET_RECORD,SSL_R_BAD_DECOMPRESSION);
goto f_err;
}
}
if (rr->length > SSL3_RT_MAX_PLAIN_LENGTH+extra)
{
// 解压缩后的明文数据长度过长, 跳出
al=SSL_AD_RECORD_OVERFLOW;
SSLerr(SSL_F_SSL3_GET_RECORD,SSL_R_DATA_LENGTH_TOO_LONG);
goto f_err;
}
rr->off=0;
/* So at this point the following is true
* ssl->s3->rrec.type is the type of record
* ssl->s3->rrec.length == number of bytes in record
* ssl->s3->rrec.off == offset to first valid byte
* ssl->s3->rrec.data == where to take bytes from, increment
* after use :-).
*/
/* we have pulled in a full packet so zero things */
s->packet_length=0;
/* just read a 0 length packet */
// 如果记录长度为0, 转到继续接收数据
if (rr->length == 0) goto again;
return(1);
f_err:
// 错误发送告警信息
ssl3_send_alert(s,SSL3_AL_FATAL,al);
err:
return(ret);
}

最后看一下ssl3_read_n()函数,接收SSL原始数据, 该函数只被ssl3_get_record()函数调用,是static的:
/* ssl/s3_pkt.c */
static int ssl3_read_n(SSL *s, int n, int max, int extend)
{
// extend为0时表示要读全新数据,非0表示可以用现在缓冲中的数据
/* If extend == 0, obtain new n-byte packet; if extend == 1, increase
* packet by another n bytes.
* The packet will be in the sub-array of s->s3->rbuf.buf specified
* by s->packet and s->packet_length.
* (If s->read_ahead is set, 'max' bytes may be stored in rbuf
* [plus s->packet_length bytes if extend == 1].)
*/
int i,off,newb;
if (!extend)
{
/* start with empty packet ... */
// 确定包缓冲地址, 置当前的包长为0
if (s->s3->rbuf.left == 0)
s->s3->rbuf.offset = 0;
s->packet = s->s3->rbuf.buf + s->s3->rbuf.offset;
s->packet_length = 0;
/* ... now we can act as if 'extend' was set */
}
/* if there is enough in the buffer from a previous read, take some */
if (s->s3->rbuf.left >= (int)n)
{
// 如果已读缓冲区中剩余数据超过要读的数据,直接移动指针即可
s->packet_length+=n;
s->s3->rbuf.left-=n;
s->s3->rbuf.offset+=n;
return(n);
}
/* else we need to read more data */
// 预读数据长度为0
if (!s->read_ahead)
max=n;
{
/* avoid buffer overflow */
// 当前缓冲区最大空闲值
int max_max = s->s3->rbuf.len - s->packet_length;
if (max > max_max)
max = max_max;
}
if (n > max) /* does not happen */
{
// 要读的数据量超过了缓冲区量,出错
SSLerr(SSL_F_SSL3_READ_N,ERR_R_INTERNAL_ERROR);
return -1;
}
off = s->packet_length;
// newb为读缓冲区中剩下的有效数据长度
newb = s->s3->rbuf.left;
/* Move any available bytes to front of buffer:
* 'off' bytes already pointed to by 'packet',
* 'newb' extra ones at the end */
if (s->packet != s->s3->rbuf.buf)
{
/* off > 0 */
memmove(s->s3->rbuf.buf, s->packet, off+newb);
s->packet = s->s3->rbuf.buf;
}
while (newb < n)
{
/* Now we have off+newb bytes at the front of s->s3->rbuf.buf and need
* to read in more until we have off+n (up to off+max if possible) */
clear_sys_error();
if (s->rbio != NULL)
{
s->rwstate=SSL_READING;
// 通过SSL的读BIO读取数据
i=BIO_read(s->rbio, &(s->s3->rbuf.buf[off+newb]), max-newb);
}
else
{
SSLerr(SSL_F_SSL3_READ_N,SSL_R_READ_BIO_NOT_SET);
i = -1;
}
if (i <= 0)
{
// 读完或出错, newb为缓冲区剩余有效数据长度
s->s3->rbuf.left = newb;
return(i);
}
newb+=i;
}
/* done reading, now the book-keeping */
// 读数据完成, 修改偏移量和剩余数据长度
s->s3->rbuf.offset = off + n;
s->s3->rbuf.left = newb - n;
s->packet_length += n;
s->rwstate=SSL_NOTHING;
return(n);
}
2.15 SSL_write
SSL结构(struct ssl_st)中的s2,s3指针分别指向SSL2和SSL3的状态结构,这些状态结构中都有用于写的wbuf,写操作相对读操作要简单一些。

SSL_write()实现向SSL通道中写数据,应用程序只需要向里写入明文数据,SSL通道自动对这些数据进行加密封装。
/* ssl/ssl_lib.c */
int SSL_write(SSL *s,const void *buf,int num)
{
if (s->handshake_func == 0)
{
SSLerr(SSL_F_SSL_WRITE, SSL_R_UNINITIALIZED);
return -1;
}
// 发现发送shutdown标志,发送失败
if (s->shutdown & SSL_SENT_SHUTDOWN)
{
s->rwstate=SSL_NOTHING;
SSLerr(SSL_F_SSL_WRITE,SSL_R_PROTOCOL_IS_SHUTDOWN);
return(-1);
}
// 调用具体方法的发送函数, 如ssl3_write(), ssl2_write()等
return(s->method->ssl_write(s,buf,num));
}
下面以ssl3_write()函数进行详细说明,
/* ssl/s3_lib.c */
int ssl3_write(SSL *s, const void *buf, int len)
{
int ret,n;
#if 0
if (s->shutdown & SSL_SEND_SHUTDOWN)
{
s->rwstate=SSL_NOTHING;
return(0);
}
#endif
// 和read操作类似的一些检查工作
clear_sys_error();
if (s->s3->renegotiate) ssl3_renegotiate_check(s);
/* This is an experimental flag that sends the
* last handshake message in the same packet as the first
* use data - used to see if it helps the TCP protocol during
* session-id reuse */
/* The second test is because the buffer may have been removed */
if ((s->s3->flags & SSL3_FLAGS_POP_BUFFER) &amp;& (s->wbio == s->bbio))
{
// 这个标志导致的操作更多的是实验性功能
/* First time through, we write into the buffer */
if (s->s3->delay_buf_pop_ret == 0)
{
ret=ssl3_write_bytes(s,SSL3_RT_APPLICATION_DATA,
buf,len);
if (ret <= 0) return(ret);
s->s3->delay_buf_pop_ret=ret;
}
s->rwstate=SSL_WRITING;
n=BIO_flush(s->wbio);
if (n <= 0) return(n);
s->rwstate=SSL_NOTHING;
/* We have flushed the buffer, so remove it */
ssl_free_wbio_buffer(s);
s->s3->flags&= ~SSL3_FLAGS_POP_BUFFER;
ret=s->s3->delay_buf_pop_ret;
s->s3->delay_buf_pop_ret=0;
}
else
{
// 正常的SSL3写数据,类型为SSL3_RT_APPLICATION_DATA,应用层数据
ret=ssl3_write_bytes(s,SSL3_RT_APPLICATION_DATA,
buf,len);
if (ret <= 0) return(ret);
}
return(ret);
}

写数据操作主要由ssl3_write_bytes()完成:

/* ssl/s3_pkt.c */
/* Call this to write data in records of type 'type'
* It will return <= 0 if not all data has been sent or non-blocking IO.
*/
int ssl3_write_bytes(SSL *s, int type, const void *buf_, int len)
{
const unsigned char *buf=buf_;
unsigned int tot,n,nw;
int i;
// 状态参数初始化
s->rwstate=SSL_NOTHING;
// s3->wnum是写缓冲区中还没写完的数据长度
tot=s->s3->wnum;
s->s3->wnum=0;
if (SSL_in_init(s) && !s->in_handshake)
{
// 检查是否需要协商
i=s->handshake_func(s);
if (i < 0) return(i);
if (i == 0)
{
SSLerr(SSL_F_SSL3_WRITE_BYTES,SSL_R_SSL_HANDSHAKE_FAILURE);
return -1;
}
}
// 实际要写的数据量
n=(len-tot);
for (;;)
{
// 限制一次写入的最大数据量
if (n > SSL3_RT_MAX_PLAIN_LENGTH)
nw=SSL3_RT_MAX_PLAIN_LENGTH;
else
nw=n;
// 进行具体的写操作
i=do_ssl3_write(s, type, &(buf[tot]), nw, 0);
if (i <= 0)
{
// 写入失败, 恢复未写入数据长度值
s->s3->wnum=tot;
return i;
}
if ((i == (int)n) ||
(type == SSL3_RT_APPLICATION_DATA &&
(s->mode & SSL_MODE_ENABLE_PARTIAL_WRITE)))
{
// 写完或允许只进行部分写时可以成功返回
/* next chunk of data should get another prepended empty fragment
* in ciphersuites with known-IV weakness: */
s->s3->empty_fragment_done = 0;

return tot+i;
}
n-=i;
tot+=i;
}
}
do_ssl3_write()完成对应用层数据的SSL封装,再调用底层发送函数发送数据, 这是一个static的内部函数:
/* ssl/s3_pkt.c */
static int do_ssl3_write(SSL *s, int type, const unsigned char *buf,
unsigned int len, int create_empty_fragment)
{
unsigned char *p,*plen;
int i,mac_size,clear=0;
int prefix_len = 0;
SSL3_RECORD *wr;
SSL3_BUFFER *wb;
SSL_SESSION *sess;
/* first check if there is a SSL3_BUFFER still being written
* out. This will happen with non blocking IO */
// 还有没写完的数据时先写这些数据
if (s->s3->wbuf.left != 0)
return(ssl3_write_pending(s,type,buf,len));
/* If we have an alert to send, lets send it */
if (s->s3->alert_dispatch)
{
// 要发送告警信息
i=ssl3_dispatch_alert(s);
if (i <= 0)
return(i);
/* if it went, fall through and send more stuff */
}
if (len == 0 && !create_empty_fragment)
return 0;
// wr为写的数据记录
wr= &(s->s3->wrec);
// wb指向要写的数据缓冲
wb= &(s->s3->wbuf);
sess=s->session;
if ( (sess == NULL) ||
(s->enc_write_ctx == NULL) ||
(s->write_hash == NULL))
clear=1;
// 实际发送的数据总长要追加的认证码长度
if (clear)
mac_size=0;
else
mac_size=EVP_MD_size(s->write_hash);
/* 'create_empty_fragment' is true only when this function calls itself */
if (!clear && !create_empty_fragment && !s->s3->empty_fragment_done)
{
/* countermeasure against known-IV weakness in CBC ciphersuites
* (see http://www.openssl.org/~bodo/tls-cbc.txt) */
if (s->s3->need_empty_fragments && type == SSL3_RT_APPLICATION_DATA)
{
// 需要空的碎片段的情况
/* recursive function call with 'create_empty_fragment' set;
* this prepares and buffers the data for an empty fragment
* (these 'prefix_len' bytes are sent out later
* together with the actual payload) */
// 以len为0,create_empty_fragment为1递归调用本函数建立空碎片数据,
// 基本就只是IV,供后续的实际数据使用
prefix_len = do_ssl3_write(s, type, buf, 0, 1);
if (prefix_len <= 0)
goto err;
if (s->s3->wbuf.len < (size_t)prefix_len + SSL3_RT_MAX_PACKET_SIZE)
{
// 发送缓冲区大小检查
/* insufficient space */
SSLerr(SSL_F_DO_SSL3_WRITE, ERR_R_INTERNAL_ERROR);
goto err;
}
}
// 设置进行了空碎片操作标志
s->s3->empty_fragment_done = 1;
}
// 具体的要发送的网络数据指针, wb=&(s->s3->wbuf)
p = wb->buf + prefix_len;
/* write the header */
// 类型
*(p++)=type&0xff;
// 写记录的类型
wr->type=type;
// 版本号
*(p++)=(s->version>>8);
*(p++)=s->version&0xff;
/* field where we are to write out packet length */
// 长度,先在保留指针位置,最后数据处理完才写具体长度
plen=p;
p+=2;
/* lets setup the record stuff. */
// 写记录的基本数据
wr->data=p;
wr->length=(int)len;
// 写记录的输入就是原始输入数据
wr->input=(unsigned char *)buf;
/* we now 'read' from wr->input, wr->length bytes into
* wr->data */
/* first we compress */
if (s->compress != NULL)
{
// 需要压缩的话先对数据进行压缩,明文压缩率才比较大,密文的压缩率几乎为0
if (!do_compress(s))
{
SSLerr(SSL_F_DO_SSL3_WRITE,SSL_R_COMPRESSION_FAILURE);
goto err;
}
}
else
{
// 不压缩就直接把输入数据拷贝到输出记录缓冲区
memcpy(wr->data,wr->input,wr->length);
wr->input=wr->data;
}
/* we should still have the output to wr->data and the input
* from wr->input. Length should be wr->length.
* wr->data still points in the wb->buf */
if (mac_size != 0)
{
// 计算认证码
s->method->ssl3_enc->mac(s,&(p[wr->length]),1);
// 将认证码长度添加到数据总长上,注意是对明文或压缩后的明文进行认证
// 而不是对密文进行认证
wr->length+=mac_size;
wr->input=p;
wr->data=p;
}
/* ssl3_enc can only have an error on read */
// 对数据进行加密, 对写数据加密是不会出错的
s->method->ssl3_enc->enc(s,1);
/* record length after mac and block padding */
// 写入实际加密后数据的长度
s2n(wr->length,plen);
/* we should now have
* wr->data pointing to the encrypted data, which is
* wr->length long */
// 写入记录的类型和总长
wr->type=type; /* not needed but helps for debugging */
wr->length+=SSL3_RT_HEADER_LENGTH;
if (create_empty_fragment)
{
/* we are in a recursive call;
* just return the length, don't write out anything here
*/
// 如果是空碎片,直接就返回了,不实际发送
return wr->length;
}
// 实际的要发送的原始数据
/* now let's set up wb */
wb->left = prefix_len + wr->length;
wb->offset = 0;
/* memorize arguments so that ssl3_write_pending can detect bad write retries later */
// 要发送的数据长度
s->s3->wpend_tot=len;
// 要发送明文的缓冲区, 实际是不发送的, 实际发送的是wb指向的缓冲区
s->s3->wpend_buf=buf;
// 数据类型
s->s3->wpend_type=type;
// 如果发送成功返回的数据长度, 这是指明文数据的长度, 供应用层程序判断用的
// 不是实际发送的压缩加密后的数据长度
s->s3->wpend_ret=len;
/* we now just need to write the buffer */
// 调用ssl3_write_pending()发送数据
return ssl3_write_pending(s,type,buf,len);
err:
return -1;
}

ssl3_write_pending()完成实际的数据发送, 这也是个static的函数, 和和普通write()一样, 这个函数可能会阻塞, 而如果套接字是NON_BLOCK的发送不出去会直接返回:

/* if s->s3->wbuf.left != 0, we need to call this */
static int ssl3_write_pending(SSL *s, int type, const unsigned char *buf,
unsigned int len)
{
int i;
/* XXXX */
// 判断数据长度是否出错用的是wpend_buf
if ((s->s3->wpend_tot > (int)len)
|| ((s->s3->wpend_buf != buf) &&
!(s->mode & SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER))
|| (s->s3->wpend_type != type))
{
SSLerr(SSL_F_SSL3_WRITE_PENDING,SSL_R_BAD_WRITE_RETRY);
return(-1);
}
for (;;)
// 循环直到全部数据发送完
{
clear_sys_error();
if (s->wbio != NULL)
{
s->rwstate=SSL_WRITING;
// 实际进行BIO写操作的是s3->wbuf中的数据,这是已经进行了压缩加密了的数据
i=BIO_write(s->wbio,
(char *)&(s->s3->wbuf.buf[s->s3->wbuf.offset]),
(unsigned int)s->s3->wbuf.left);
}
else
{
SSLerr(SSL_F_SSL3_WRITE_PENDING,SSL_R_BIO_NOT_SET);
i= -1;
}
if (i == s->s3->wbuf.left)
{
s->s3->wbuf.left=0;
s->rwstate=SSL_NOTHING;
// 发送完实际数据,返回的是原始明文数据的长度
return(s->s3->wpend_ret);
}
else if (i <= 0)
return(i);
s->s3->wbuf.offset+=i;
s->s3->wbuf.left-=i;
}
}

2.16 SSL_free

SSL_free()函数释放SSL结构:
/* ssl/ssl_lib.c */
void SSL_free(SSL *s)
{
int i;
if(s == NULL)
return;
// 加密锁引用减1
i=CRYPTO_add(&s->references,-1,CRYPTO_LOCK_SSL);
#ifdef REF_PRINT
REF_PRINT("SSL",s);
#endif
if (i > 0) return;
#ifdef REF_CHECK
if (i < 0)
{
fprintf(stderr,"SSL_free, bad reference countn");
abort(); /* ok */
}
#endif
// 释放加密库所需附加数据
CRYPTO_free_ex_data(CRYPTO_EX_INDEX_SSL, s, &s->ex_data);
if (s->bbio != NULL)
{
// 释放BIO缓冲
/* If the buffering BIO is in place, pop it off */
if (s->bbio == s->wbio)
{
s->wbio=BIO_pop(s->wbio);
}
BIO_free(s->bbio);
s->bbio=NULL;
}
// 释放读BIO
if (s->rbio != NULL)
BIO_free_all(s->rbio);
// 释放写BIO
if ((s->wbio != NULL) && (s->wbio != s->rbio))
BIO_free_all(s->wbio);
// 释放初始化缓冲区
if (s->init_buf != NULL) BUF_MEM_free(s->init_buf);

/* add extra stuff */
// 释放加密库链表
if (s->cipher_list != NULL) sk_SSL_CIPHER_free(s->cipher_list);
if (s->cipher_list_by_id != NULL) sk_SSL_CIPHER_free(s->cipher_list_by_id);
/* Make the next call work :-) */
// 清除SSL的会话
if (s->session != NULL)
{
ssl_clear_bad_session(s);
SSL_SESSION_free(s->session);
}
// 释放SSL的读写上下文
ssl_clear_cipher_ctx(s);
// 释放证书
if (s->cert != NULL) ssl_cert_free(s->cert);
/* Free up if allocated */
// 释放加密算法上下文
if (s->ctx) SSL_CTX_free(s->ctx);
if (s->client_CA != NULL)
sk_X509_NAME_pop_free(s->client_CA,X509_NAME_free);
// 释放SSL方法
if (s->method != NULL) s->method->ssl_free(s);
// 释放SSL结构本身
OPENSSL_free(s);
}