2007年6月29日星期五

syslog的编程和配置


syslog的编程和配置
 
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
 
参考文献:man syslog syslogd syslog.conf openlog, RFC3164
 
1. 前言
 
syslog是UNIX系统中提供的一种日志记录方法(RFC3164),syslog本身是一个服务器,程序中凡是使用syslog记录的信 息都会发送到该服务器,服务器根据配置决定此信息是否记录,是记录到磁盘文件还是其他地方,这样使系统内所有应用程序都能以统一的方式记录日志,为系统日 志的统一审计提供了方便。
 
2. 日志格式
 
syslog记录的日志格式为:
月 日 时:分:秒 主机名 标志 日志内容
 
3. syslog编程
 
为记录日志,通常用到3个函数,openlog(3),syslog(3)和closelog(3),openlog(3)和closelog(3)不是必须的,没有openlog(3)的话将用系统缺省的方式记录日志。
 
      #include <syslog.h>
      void openlog( char *ident, int option, int  facility)
      void syslog( int priority, char *format, ...)
      void closelog( void )
 
openlog(3)有三个参数,第一个参数是标志字符串,也就是日志中的第5个字段,不设的话缺省取程序名称;
第二个参数是选项,是下面一些标志位的组合:
       LOG_CONS:日志信息在写给日志服务器的同时打印到终端
       LOG_NDELAY:立即记录日志
       LOG_PERROR:把日志信息也输出到标准错误流
       LOG_PID:在标志字段中记录进程的PID值
第三个参数是说明日志类型的,定义了以下类型(各类型啥意思就自己看或猜吧,俺就不多说了):
       LOG_AUTH
       LOG_AUTHPRIV
       LOG_CRON
       LOG_DAEMON
       LOG_KERN
       LOG_LOCAL0 through LOG_LOCAL7
       LOG_LPR
       LOG_MAIL
       LOG_NEWS
       LOG_SYSLOG
       LOG_USER(default)
       LOG_UUCP
 
syslog(3)函数主要的是第一个参数priority,后面那些参数就是和printf(3)函数用法一样了,priority值表示该条日志的级别,日志级别分8级,由高到低的顺序为:
       LOG_EMERG
       LOG_ALERT
       LOG_CRIT
       LOG_ERR
       LOG_WARNING
       LOG_NOTICE
       LOG_INFO
       LOG_DEBUG
如果openlog(3)时没有指定facility,是可以把facility的值或到priority中的,如(LOG_AUTH | LOG_INFO),已经设置了就可以不用或了。
 
closelog(3)这个没啥好说的了,关闭日志记录。

4. syslog服务器配置
 
syslog服务器的配置文件为/etc/syslog.conf,syslog(3)函数把想记录的日志信息都发送给日志服务器,但此日志最 终是否记录到文件或发送给远程服务器,则是由此配置文件来决定的,该配置文件就是告诉日志服务器要记录那些类型和级别的日志,如何记录等信息。
 
配置文件是文本文件,每行配置分两个字段,第一字段是说明要记录哪类日志,第二字段是说明日志存放位置,可以是本地文件,也可以是远程服务器。
 
第一字段:
第一字段基本格式是"facility.priority",可以同时定义多个,中间用逗号","或分号";"分隔。
facility名称就是上面说的facility值的后半部的小写,如news, mail,kern, cron等,也可以用"*"表示所有facility类型;
priority名称就是上面说的priority值的后半部的小写,如emerg, alert,err, info等,也可以用"*"表示所有priority类型,比此级别高的日志都会自动记录,用none表示不记录;
举例:
kern.* : 所有级别的内核类型日志
mail.err: 错误及错误级别以上的mail类型日志
如果不记录某级别的日志,在级别前加"!",如:
auth.info;auth.!err :info及info级别以上但不包括err级别的auth类型日志

第二字段:
第二字段分两类,本地文件和远程服务器
本地文件:直接就是写本地文件的文件名,如 /var/log/messages。一般来说日志信息会立即写到文件中,但会降低系统效率,可以在文件名前加减号"-"表示先将信息缓存,到一定量后再一次性写入文件,这样可以提高效率;
远程服务器:格式是"@address","@"表示进行远程记录,将日志发送到远程的日志服务器,日志服务器的端口是UDP514,address可以是IP地址,也可以是域名
 
举例:
# 将所有级别的内核日志发送到终端
kern.*  /dev/console 
 
# 将所有类型所有级别的日志记录到/var/log/messages文件
*.* /var/log/messages
 
# 所有info级别以上的信息,不包括mail类型所有级别和authpriv类型的err级别信息,
# 记录到/var/log/messages文件,不立即写入
*.info;mail.none;authpriv.!err       -/var/log/messages
 
#将所有级别的内核日志发送到远程syslog服务器
kern.*  @1.1.1.1
 
5. syslog服务器
 
在linux下提供了sysklogd的syslog服务器的实现,可以记录本机日志也可以接收(syslogd的-r选项)和转发(syslogd的-h选项)来自外部的日志。
 
sysklogd包括两个程序,klogd和syslogd,klogd用于接收内核日志,再发送到syslogd,syslogd则可以直接 接收应用程序和远程的日志,syslogd是通过一个域socket(AF_UNIX)来接收数据的,syslog()函数记录的日志都发送到此域 socket,socket文件是/dev/log。
 
syslog(3)函数发送给syslogd服务器的日志信息前都加上了类型和级别信息,具体格式是"<x>","x"是一个 0~255的数,8位,低3位表示日志级别,所以共8级,高五位表示日志类型,最多32种,不过目前没用到那么多,可以看看 /usr/include/sys/syslog.h中的定义就知道了。
 
要生成日志信息时,syslogd是先生成日志前部信息:月 日 时:分:秒 主机名 标志,再和日志内容信息拼接起来的,日期用ctime(3)函数获取,隐去了前4个表示星期的字节和后面年的信息,最终生成你所看到的日期格式,老实说那段代码及其丑陋。
 
6. 结论
 
syslog方便了程序信息的记录,由于使用了统一的格式记录使得审计也可以比较方便。要记录日志,除了在应用程序中用syslog(3)函数记录外,还要正确配置/etc/syslog.conf文件,使服务器能正确记录那些想记录的日志。

--
一步一步教你从互联网赚钱 http://www.zqzn.com/index.asp?rid=key480769
投资理财 http://li-cai.blogspot.com/

SSL连接建立过程分析(6)

SSL连接建立过程分析(6)
 
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

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);
  }

SSL连接建立过程分析(5)

SSL连接建立过程分析(5)

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

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) &&
     (type != SSL3_RT_HANDSHAKE) && 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) &&
   (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) &&
// 没设置该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) &&
   !(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;

SSL连接建立过程分析(4)

SSL连接建立过程分析(4)
 
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

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;

SSL连接建立过程分析(3)

SSL连接建立过程分析(3)
 
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

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;

SSL连接建立过程分析(2)


SSL连接建立过程分析(2)
 
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

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;

SSL连接建立过程分析(1)

SSL连接建立过程分析(1)
 
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
 
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=%d\n", status);
        goto bad;
    }
// 加载私钥文件
    if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) <= 0) {
        fprintf(stderr, "Use private key fail\n");
        goto bad;
    }
// 检查证书和私钥是否匹配
    if (!SSL_CTX_check_private_key(ctx)) {
        fprintf("Private key does not match the certificate public key\n");
        goto bad;
    }
    fprintf("Cert and key OK\n");
    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();

OpenSSL中对称加密算法的统一接口

OpenSSL中对称加密算法的统一接口
 
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

1. 前言
OpenSSL是一个开源的SSL实现,其中集成了多种加密算法。OpenSSL将各算法的各自独特地方封装在内部,对外则使用了统一的算法接 口,因此外部应用只需指定使用何种算法,就可以用相同的方法调用加解密函数而不用考虑其差异。这种算法统一封装的方式在其他很多软件中都采用,也给算法扩 充提供了方便。
以下代码来自OpenSSL-0.9.7b。

2. EVP接口
2.1 数据结构
Openssl/crypto/evp目录下定义各种算法的接口源文件,这些文件要作的事就是要填写描述算法的EVP_CIPHER结构,每个算法都有一个EVP_CIPHER结构进行描述:
Openssl/crypto/evp/evp.h
struct evp_cipher_st
 {
 int nid;
 int block_size;
 int key_len;  /* Default value for variable length ciphers */
 int iv_len;
 unsigned long flags; /* Various flags */
 int (*init)(EVP_CIPHER_CTX *ctx, const unsigned char *key,
      const unsigned char *iv, int enc); /* init key */
 int (*do_cipher)(EVP_CIPHER_CTX *ctx, unsigned char *out,
    const unsigned char *in, unsigned int inl);/* encrypt/decrypt data */
 int (*cleanup)(EVP_CIPHER_CTX *); /* cleanup ctx */
 int ctx_size;  /* how big ctx->cipher_data needs to be */
 int (*set_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); /* Populate a ASN1_TYPE with parameters */
 int (*get_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); /* Get parameters from a ASN1_TYPE */
 int (*ctrl)(EVP_CIPHER_CTX *, int type, int arg, void *ptr); /* Miscellaneous operations */
 void *app_data;  /* Application data */
 } /* EVP_CIPHER */;
typedef struct evp_cipher_st EVP_CIPHER;
nid:算法的ID号,在include/openssl/object.h中定义;
block_size:加解密的分组长度
key_len:密钥长度
iv_len:初始向量长度
flags:标志
(* init):初始化函数,提供密钥,IV向量,算法上下文CTX,加密还是解密
(* do_cipher):加解密函数,提供算法上下文CTX,输出数据,输入数据和输入数据长度
(* clean_up):资源释放
ctx_size:各算法相关数据大小,实际就是各算法的密钥数据
(*set_asn1_parameters):设置asn1参数
(*get_asn1_parameters):获取asn1参数
(*ctrl):其他控制操作
app_data:算法相关数据
 
另一个重要的数据结构是描述加密算法的上下文结构EVP_CIPHER_CTX,这个结构是进入算法前由系统根据指定的算法提供的:
struct evp_cipher_ctx_st
 {
 const EVP_CIPHER *cipher;
 ENGINE *engine; /* functional reference if 'cipher' is ENGINE-provided */
 int encrypt;  /* encrypt or decrypt */
 int buf_len;  /* number we have left */
 unsigned char  oiv[EVP_MAX_IV_LENGTH]; /* original iv */
 unsigned char  iv[EVP_MAX_IV_LENGTH]; /* working iv */
 unsigned char buf[EVP_MAX_BLOCK_LENGTH];/* saved partial block */
 int num;    /* used by cfb/ofb mode */
 void *app_data;  /* application stuff */
 int key_len;  /* May change for variable length cipher */
 unsigned long flags; /* Various flags */
 void *cipher_data; /* per EVP data */
 int final_used;
 int block_mask;
 unsigned char final[EVP_MAX_BLOCK_LENGTH];/* possible final block */
 } /* EVP_CIPHER_CTX */;
typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX;
参数为:
cipher:算法指针
engine:加解密引擎
encrypt:加密或解密
buf_len:剩余空间
oiv:原始的初始向量
iv:当前的初始向量
buf:保存的部分块数据
num:cfb/ofb方式时的数据数量
app_data:应用相关数据
key_len:密钥长度
flags:标志
cipher_data:各算法相关部分,主要是各算法的key等
final_used:
block_mask:块的掩码
final:最后的分组块
1.2.2 算法接口
每种算法就是要填写各自的EVP_CIPHER结构,以RC4为例,定义了两个RC4的EVP_CIPHER结构,只是密钥长度不同,一个是128位(16字节密钥),一个是40位(5字节密钥),而算法都一样:
#ifndef OPENSSL_NO_RC4
#include <stdio.h>
#include "cryptlib.h"
#include <openssl/evp.h>
#include <openssl/objects.h>
#include <openssl/rc4.h>
/* FIXME: surely this is available elsewhere? */
#define EVP_RC4_KEY_SIZE  16

//这个结构是各加密算法独有的,各算法的各自不同
//也就是EVP_CIPHER_CTX结构中cipher_data
typedef struct
    {
    RC4_KEY ks; /* working key */
    } EVP_RC4_KEY;
#define data(ctx) ((EVP_RC4_KEY *)(ctx)->cipher_data)
static int rc4_init_key(EVP_CIPHER_CTX *ctx, const unsigned char *key,
   const unsigned char *iv,int enc);
static int rc4_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out,
        const unsigned char *in, unsigned int inl);
static const EVP_CIPHER r4_cipher=
 {
 NID_rc4,
 1,EVP_RC4_KEY_SIZE,0,
 EVP_CIPH_VARIABLE_LENGTH,
 rc4_init_key,
 rc4_cipher,
 NULL,
 sizeof(EVP_RC4_KEY),
 NULL,
 NULL,
 NULL
 };
static const EVP_CIPHER r4_40_cipher=
 {
 NID_rc4_40,
 1,5 /* 40 bit */,0,
 EVP_CIPH_VARIABLE_LENGTH,
 rc4_init_key,
 rc4_cipher,
 NULL,
 sizeof(EVP_RC4_KEY),
 NULL,
 NULL,
 NULL
 };
// 返回算法结构指针
const EVP_CIPHER *EVP_rc4(void)
 {
 return(&r4_cipher);
 }
const EVP_CIPHER *EVP_rc4_40(void)
 {
 return(&r4_40_cipher);
 }
// 密钥初始化函数
// ctx:加解密上下文;key:密钥字符串;iv:初始化向量;enc:加密还是解密
static int rc4_init_key(EVP_CIPHER_CTX *ctx, const unsigned char *key,
   const unsigned char *iv, int enc)
 {
// RC4算法设置密钥函数
// 一般来说,加解密时算法里用的密钥并不是用户输入的密码字符串本身,因为算法
// 使用的密钥长度要求是固定的,通常为64位或128位,而用户自己定义的密码长度
// 则不确定,所以一般都都要对用户输入的密码进行变换,映射到一个固定长度密钥
// 上,然后算法再使用该密钥加密,所以算法中用的密钥和用户的密码一般是不同的
 RC4_set_key(&data(ctx)->ks,EVP_CIPHER_CTX_key_length(ctx),
      key);
 return 1;
 }
// 加解密处理函数
// ctx:加解密上下文;out:输出数据;in:输入数据;inl:输入数据长度
static int rc4_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out,
        const unsigned char *in, unsigned int inl)
 {
 RC4(&data(ctx)->ks,inl,in,out);
 return 1;
 }

2007年6月27日星期三

在visual studio中使用vim编辑程序

 

hehe,是我2002年写的。只支持vc6

以前都是在linux下编程序,现在改到写vc程序,便觉得visual studio内置的编辑器非常的不好用,呵呵 :)

下载了一个vim for windows 6.1之后,看了一下文档发现早就有人有同感了,写了一个VisVim( vim add-in )来解决,现在把安装心得和大家共享一下

1.首先安装一个vim

可以到ftp://ftp.vim.org/pub/vim/pc/gvim61.exe下载一个完全的安装版本 这个东东比较大, 但是比较方便。呵呵。当然你也可以去下载相应的OLE GUI executable 和runtime files 但是注意一定要是OLE 的GUI版本才行。

2.到你的vim安装目录下,假设是e:\tools\gvim
>cd e:\tools\gvim
>cd vim61\visvim
>regsvr32 VisVim.dll #win95/98可以跳过这步

3.要是你还没有注你的OLE gvim
>cd e:\tools\gvim\vim61
>gvim -register

4.打开visual studio 运行
Tools
Customize...
Add-Ins and Macro Files

5.点Browse 找到gvim\vim61\visvim\目录下面VisVim.dll

6.选中新加入的这个Add-in,关闭对话框

7.这时你就可以看到工具条上的Vim按钮了。第一个按钮会弹出一个设置对话框 。。。。这之后我就不讲了。

到现在为止,你只需要配置好你的gvim 比如字体呀、颜色呀、自动缩进呀、语法呀什么的就行了。是不是很好用? :P



--
一步一步教你从互联网赚钱 http://www.zqzn.com/index.asp?rid=key480769
投资理财 http://li-cai.blogspot.com/

用socks代理 fetchmail from gmail

在学校里,不能直接连接国外网站,当然我们有办法找到代理来浏览网页 :P . 不过对于fetchmail + mutt的邮件解决方案,fetchmail怎么利用代理收gmail 的信呢?

首先你得有能用的socks代理。

其次,你需要有runsocks或者tsocks这一类socks客户端。这种客户端当你配置好配置文件以后,可以用runsocks command的方式运行你的程序"command",它就会用socks代理出去了.关于这么配置,我就不赘述了,请参照文档。

这时,你可能已经想到,那我直接用runsocks运行fetchmail不就行了么? 没错, 当然可行,不过前提是你的fetchmail是支持SSL的。而且有些小问题:

1。有可能你的fetchmail不止收取一个gmail的信件,还有其他服务器的,比如(eyou.com? :P),他就是国内的了,用了代理反而慢了。(当然这个问题也能解决,比如你自己在本地建一个socks代理,配置成访问指定目标地址时使用另一个站外 socks服务)

2。还有可能,你的整个Linux系统就只运行了一个fetchmail daemon他是全局的,读取所有用户的配置文件,你没法让它以runsocks运行,或者你不想修改配置好的/etc/init.d/fetchmail启动文件。 那就可以用我下面说的方法:

答案是:stunnel . stunnel可以建立一个SSL的tunnel. 看下面这个命令

runsocks stunnel -d 10000 -c -r pop.gmail.com:995

-d 10000 表示在本地监听10000这个TCP端口。-c 表示使用client模式。 -r pop.gmail.com:995 表示连接远程的服务器以及端口。

运行完这条命令之后,你在试一下telnet localhost 10000 ?然后再输入 user yourid, pass yourpass , list, exit等POP3会话命令试试,可以了吧?没错,我们在本地10000端口和远程的pop.gmail.com:995建立了一个tunnel,而且是脱掉了SSL的,就是明文,(没关系,这是本地的连接,不怕sniffer的).

利用这种方法,你只需要在你的fetchmail配置文件中配置主机为localhost,端口为10000就可以了。


BTW:值得提到的是,fetmail是支持plugin的,在命令行里写 --plugin "command %h %p" 或者在配置文件里写 poll xx.xx.xxx.xx plugin "command %h %p", command就是你提供的程序,%h是host,%p是port,fetchmail从command的stdout读东西,把要写数据写给 command的stdin,
所以我们又有了一个方案,前提是fetchmail支持ssl, plugin里面写

plugin "runsocks nc %h %p"

其中nc就是有着TCP/IP瑞士军刀之称的 netcat


--
一步一步教你从互联网赚钱 http://www.zqzn.com/index.asp?rid=key480769
投资理财 http://li-cai.blogspot.com/

2007年6月20日星期三

Asymptote学习系列(12)

作图命令:
 
所有的Asymptote图形都是基于4个基本的作图命令:其中的3个PostScript命令drawfillclip以它们出现的顺序处理,最后出现的会放在最上面,而label命令可以用于增加文本标签或外部EPS图形,则会放于所有上面的3个PostScript命令上面。
若你想在一个label上作图的话,可以使用layer函数:
void layer(picture pic=currentpicture);
它全重新创建一个新的PostScript/Latex图层,所有的图层顺序处理,最后出现的放在最上面。当然在图形内部,label命令还是会在其它3个PostScript命令的上面。
 
  • draw
 void draw(picture pic=currentpicture, Label L="", path g,align align=NoAlign, pen p=currentpen,arrowbar arrow=None, arrowbar bar=None, margin margin=NoMargin,Label legend="", marker marker=nomarker);
用于在pic上画path g,用画笔p。还有些其它的参数,其中意义为:
bar : 用来在g两端画条竖线,可取值为 None, BeginBar, EndBar (= Bar), Bars(=BeginBar & EndBar) ,而且它们都可以接受一个real型参数,用来指定竖线的长度(PostScript单位)。
arrowbar : 用来画箭头,可取为: None, Blank, BeginArrow, MidArrow, EndArrow(=Arrow), Arrows ,而且它们可以接受的参数包括:一个real型参数,用来指定大小(PostScript单位);一个real型参数angle用来指定角度; FillDraw, Fill, NoFill, Draw ;一个real型参数position指定位置。

void dot(picture pic=currentpicture, pair z, pen p=currentpen);
void dot(picture pic=currentpicture, pair[] z, pen p=currentpen);
void dot(picture pic=currentpicture, pair[] x, pair[] y, pen p=currentpen);
void dot(picture pic=currentpicture, Label L, pair z, align align=NoAlign,string format=defaultformat, pen p=currentpen)
void dot(picture pic=currentpicture, Label L, pen p=currentpen);
void dot(picture pic=currentpicture, path g, pen p=currentpen);
用于画点
 
示例:
 
path line=(0,0)--(5cm,0);

draw(line, Arrow(20bp,position=.75));
draw(shift(0,-2cm)* line,Arrow(20bp,40,.75,filltype=NoFill));

position pos=BeginPoint ;
pos.position=.75;
draw(shift(0,-4cm)*line, BeginArrow(20bp,pos));
draw(shift(0,-6cm)*line, BeginArrow(20bp,40,pos,filltype=NoFill));

path line=(0,0)--(5cm,0);
transform T= shift(0,-cm);

draw(line,linewidth(1mm),Bars);
draw(T^2*line,Bars(5mm));
draw(T^3*line ,linewidth(1mm),Bars(5mm));
draw(T^4* line,dotted+red,Bars);

pair O=0;
draw(scale(2)*Label( "N",.8red),O,10*N,linewidth(3mm));
draw(scale(2)*Label("S",.8red),O,10* S);
draw(scale(2)*Label("E",.8red), O,10*E);
draw(scale(2)*Label("W",.8 red),O,10*W);

draw(rotate(45)*Label( "NE"),O,5NE);
draw(rotate(-45)*Label ("SE"),O,5SE);
draw(rotate(-45)* Label("NW"),O,5NW);
draw(rotate(45)* Label("SW"),O,5SW);
 

--
一步一步教你从互联网赚钱 http://www.zqzn.com/index.asp?rid=key480769
投资理财 http://li-cai.blogspot.com/

2007年6月19日星期二

Asymptote学习系列(11)

数据类型(续):
 
framepicture
 
frame 是用PostScript坐标系的画布。通常不会直接用到它,而会使用相同功能的picture。一些常用函数包括:
bool empty(frame f)
      f是否为空
pair min(frame f)
      返回f的(left,bottom)
pair max(frame f)
      返回f的(right,top)
void add(frame dest, frame src);
void prepend(frame dest, frame src);
frame align(frame f, pair align);
picture 是在模块'plain'中定义的高层结构,用来提供以用户坐标下的画布。有个缺省的picture是currentpicture ,所有命令的缺省picture参数都是这个currentpicture。前面介绍的 sizeunitsize函数的原型是:
void size(picture pic=currentpicture, real x, real y=x,bool keepAspect=Aspect);
void unitsize(picture pic=currentpicture, real x, real y=x);
其中,若x与y均为0,则会把它当作PostScript坐标系,此时,把pic变换到最后的输出frame时用的是单位变换。若x或y中有一个为0,则不会在这个方向上作限制,它会用与另一个方向相同的大小作变换。若 keepAspect 为 Aspect 或 true 的话,则在x和y方向作协同变换,这样,最后的picture在x方向不会超过x,以及在y方向上不会超过y;若 keepAspect 为 IgnoreAspect 或 false 的话,则picture会在x和y方向上同时放缩,从而最后的picture宽为x,高为y。
还有一些常用的函数包括:
 
void size(picture pic=currentpicture, real xsize, real ysize,pair min, pair max);
        这个函数的作用是重新计算pic中的放缩,使得它满足把box(min,max)中的区域变为xsize宽,ysize长。
transform fixedscaling(picture pic=currentpicture, pair min,pair max, pen p=nullpen);
        这个函数
void shipout(string prefix=defaultfilename, picture pic,frame preamble=patterns,orientation orientation=orientation,string format="", bool wait=NoWait, bool view=true);
void shipout(string prefix=defaultfilename,orientation orientation=orientation,string format="", bool wait=NoWait, bool view=true);
        这两个函数可以把pic放入preamble中,并且写入文件prefix中,其中的orientation的取值包括:PortraitLandscapeSeascape UpsideDown
frame pic.fit(real xsize=pic.xsize, real ysize=pic.ysize,bool keepAspect=pic.keepAspect);
frame pic.scale(real xsize=this.xsize, real ysize=this.ysize,bool keepAspect=this.keepAspect);
        这两个函数用于显式地把pic映射到frame中去。
frame bbox(picture pic=currentpicture, real xmargin=0,real ymargin=xmargin, pen p=currentpen,filltype filltype=NoFill);
        这个函数与前两个函数的不同之处在于,它同时还画边框。其中的参数filltype可以设为:
FillDraw Fill with the pen used to draw the boundary.
FillDraw(real xmargin=0, real ymargin=xmargin, pen p=nullpen)
        If p is nullpen, fill with the pen used to draw the boundary; otherwise fill with pen p. An optional margin of xmargin and ymargin can be specified.
Fill Fill with the drawing pen.
Fill(real xmargin=0, real ymargin=xmargin, pen p=nullpen)
        If p is nullpen, fill with the drawing pen; otherwise fill with pen p.An optional margin of xmargin and ymargin can be specified.NoFill Do not fill.
Draw Draw only the boundary.
Draw(real xmargin=0, real ymargin=xmargin, pen p=nullpen)
        If p is nullpen, draw the boundary with the drawing pen; otherwise draw with pen p. An optional margin of xmargin and ymargin can be specified.
UnFill Clip the region.
UnFill(real xmargin=0, real ymargin=xmargin
        Clip the region and surrounding margins xmargin and ymargin.
RadialShade(pen penc, pen penr)
        Fill varying radially from penc at the center of the bounding box to penr at the edge.
 
pair min(picture pic);
pair max(picture pic);
        用于给出pic和边界
pair point(picture pic=currentpicture, pair dir);
        用来计算以pic的中心为起点,方向在dir的线交pic的边界的坐标。
pair truepoint(picture pic=currentpicture, pair dir);
pair framepoint(picture pic=currentpicture, pair dir);
        这两个函数与前面的point类似,framepoint返回的是最后的PostScript坐标。
void add(picture src, bool group=true,filltype filltype=NoFill, bool put=Above);
void add(picture dest, picture src, bool group=true,filltype filltype=NoFill, bool put=Above);
void add(picture dest, picture src, pair position, bool group=true,filltype filltype=NoFill, bool put=Above);
void add(picture src, pair position, bool group=true,filltype filltype=NoFill, bool put=Above);
void add(picture dest=currentpicture, frame src, pair position=0,bool group=true, filltype filltype=NoFill,bool put=Above);
void add(picture dest=currentpicture, frame src, pair position,pair align, bool group=true, filltype filltype=NoFill,bool put=Above);
        这些函数把src放入dest或currentpicture中。
void attach(picture dest=currentpicture, frame src,pair position=0, bool group=true,filltype filltype=NoFill, bool put=Above);
void attach(picture dest=currentpicture, frame src,pair position=0, pair align, bool group=true,filltype filltype=NoFill, bool put=Above);
        把src放入dest,同时调整dest的大小
path box(frame f, Label L="", real xmargin=0,real ymargin=xmargin, pen p=currentpen,filltype filltype=NoFill, bool put=Above);
path ellipse(frame f, Label L="", real xmargin=0,real ymargin=xmargin, pen p=currentpen,filltype filltype=NoFill, bool put=Above);
void box(picture pic=currentpicture, Label L,real xmargin=0, real ymargin=xmargin, pen p=currentpen,filltype filltype=NoFill, bool put=Above);
        用来在Label,frame或pic周围画一个矩形或椭圆。
void erase(picture pic=currentpicture);
save().
restore().
        用来消除,保存,恢复pic的环境
还有更多的函数,都定义在模块plain中。
 
在pic中,你还可以直接插入PostScript命令和tex命令:
void postscript(picture pic=currentpicture, string s);
void postscript(picture pic=currentpicture, string s, pair min,pair max)
void tex(picture pic=currentpicture, string s);
void tex(picture pic=currentpicture, string s, pair min, pair max)
 
 
--
一步一步教你从互联网赚钱 http://www.zqzn.com/index.asp?rid=key480769
投资理财 http://li-cai.blogspot.com/

2007年6月15日星期五

Asymptote学习系列(10)

数据类型(续):

transform
transform t=(t.x,t.y,t.xx,t.xy,t.yx,t.yy) 把一个点 pair (x,y) 变为点 (x',y') ,其中
x' = t.x + t.xx * x + t.xy * y
y' = t.y + t.yx * x + t.yy * y
它和 PostScript 中的 transformation [t.xx t.yx t.xy t.yy t.x t.y] 是一样的。
另外,它可以通过[ * ]号从左边作用在pair、path、guide、pen、transform、frame 和 picture 上。transform 之间可以互相组合,还有个求逆的操作:
transform inverse(transform t);
还支持整数的指数操作 [ ^ ] ,如:
transform t=shift((0,1));
transform t2=t^2;   //表示t操作两次,
常用的函数包括:
transform identity();
      单位变换
transform shift(pair z);
      移动距离坐标 z;
transform shift(real x, real y);
      移动距离坐标 (x,y);
transform xscale(real x);
      x方向放大x倍
transform yscale(real y);
      y方向放大y倍
transform scale(real s);
      在x和y方向同时放大s倍
transform slant(real s);
      把点 (x,y) 变为 (x+s*y,y);
transform rotate(real angle, pair z=(0,0));
      以z为中心旋转 angle 度角
transform reflect(pair a, pair b);
      以线 line a--b 为轴,求镜像
shift(transform t) 返回 transforms (t.x,t.y,0,0,0,0)
shiftless(transform t) 返回 transforms (0,0,t.xx,t.xy,t.yx,t.yy)

--
一步一步教你从互联网赚钱 http://www.zqzn.com/index.asp?rid=key480769
投资理财 http://li-cai.blogspot.com/

2007年6月13日星期三

Asymptote学习系列(9)

数据类型(续):
 
pen
pen是个作图环境,它包括颜色、线型、粗细等,还有line join, fill rule, text alignment, font, font size,pattern, overwrite mode, 及 calligraphic transforms on the pen nib。pen之间可以用"+"号来融合,这会混合两种pen的颜色,对于其它的特性,则会用后面的替代前面的。如一支黄色的画点线的pen,可以用下面的任何一种方法得到:
dotted+red+green
red+green+dotted
red+dotted+green
符号"*"作用到pen上表示对pen的颜色*相应的数,如:
red*0.5
 
颜色
定义pen的颜色的函数包括:
pen gray(real g);
      灰度笔,其中 g 在 [0,1] 上,0.0 表示黑色,1.0 表示白色。
pen rgb(real r, real g, real b);
      用 RGB 颜色构造 pen ,每个变量都在 [0,1] 间。
pen cmyk(real c, real m, real y, real k);
      由 CMYK(cyan, magenta, yellow, black) 构造 pen ,同样每个变量在 [0,1] 间。
pen colorless(pen);
      得到一个没有颜色的pen(可以用来避免颜色混合)。
real[] colors(pen)
      得到pen的颜色的各个分量
 
一些常量:
pen invisible;
      不可见的pen,它会调节整个的边界。
还有如下预定义好的颜色可以用:
palered,lightred,mediumred,red,heavyred,brown,darkbrown,
palegreen,lightgreen,mediumgreen,green,heavygreen,deepgreen,darkgreen
paleblue,lightblue,mediumblue,blue,heavyblue,deepblue,darkblue
palecyan,lightcyan,mediumcyan,cyan,heavycyan,deepcyan,darkcyan
pink,lightmagenta,mediummagenta,magenta,heavymagenta,deepmagenta,darkmagenta
paleyellow,lightyellow,mediumyellow,yellow,lightolive,olive,darkolive
palegray,lightgray,mediumgray,gray,heavygray,deepgray,darkgray
black
white
orange
fuchsia
chartreuse
springgreen
purple
royalblue
Cyan
Magenta
Yellow
Black
cmyk+red
cmyk+blue
cmyk+green
 
线型
线型由函数定义:
pen linetype(string s, real offset=0,bool scale=true, bool adjust=true)
其中 s 是由空格分开的整数或实数,如下是些预定义的量:
pen solid=linetype("");
pen dotted=linetype("0 4");
pen dashed=linetype("8 8");
pen longdashed=linetype("24 8");
pen dashdotted=linetype("8 8 0 8");
pen longdashdotted=linetype("24 8 0 8");
pen Dotted=dotted+1.0;
pen Dotted(pen p=currentpen) {return dotted+2*linewidth(p);}
s 中的数中,第1个表示用pen画多长,第2个表示不用这个pen画多长,依次下去。
 
粗细 
精细由函数定义:
pen linewidth(real).
一支pen与实数的加法"+"定义为:
static pen operator +(pen p, real w) {return p+linewidth(w);}
static pen operator +(real w, pen p) {return linewidth(w)+p;}
如:
pen p=red+1.0;
 
还有一些其它的特性,请参考帮助文件。
 
--
一步一步教你从互联网赚钱 http://www.zqzn.com/index.asp?rid=key480769
投资理财 http://li-cai.blogspot.com/

Asymptote学习系列(8)

数据类型(续):

guide path
guide 和 path 都表示分段的三次曲线,参数t从0变化到n(也就是节点数)。不同的是,guide 是在最后画这前才计算出这条曲线,而path则是在定义的时候就计算好的。这样,guide在做两条曲线连接的时候,就可以得到光滑连接的曲线。
如下所示的例子:
size(200);
real mexican(real x) {return (1-8x^2)*exp(-(4x^2));}

int n=30;
real a=1.5;
real width=2a/n;
guide hat;
path solved;
for(int i=0; i < n; ++i) {
    real t=-a+i*width;
    pair z=(t,mexican(t));
    hat=hat..z;
    solved=solved..z;
}
draw(hat);
dot(hat,red);
draw(solved,dashed);

一个点也可以看作是一个长度为0的path,如:
path p=(0,2);


构造一个path的最简单方法就是连接两个path或点,常用的算子有:
p--q   表示用直线连接p的最后一个点和q的第一个点
p..q    表示用一条Bezier三次样条曲线来连接p和q,这样连接是光滑的
p^^q    它并不真正连接p和q,而是把它们从新参数化,从而使它们成为一条曲线,也就是说画笔从p的最后一个点移到q的第一个点然后再画

在构造path的时候,还可以显式指定控制点,曲率,方向,张力等,如:
draw((0,0)..controls (0,100) and (100,100)..(100,0));
draw((100,0)..tension 2 ..(100,100)..(0,100));           // 张力
draw((100,0)..tension 2 and 1 ..(100,100)..(0,100));
draw((100,0)..tension atleast 1 ..(100,100)..(0,100));
draw((100,0){curl 0}..(100,100)..{curl 0}(0,100));   // 指定曲率,0表示直线,1表示圆弧
draw((0,0){up}::(100,25){right}::(200,0){down});   // ::是..tension at least 1..
draw((0,0){up}---(100,25){right}---(200,0){down}; //---表示..tension atleast infinite..

常量:
path unitcircle ;

常用的函数:
path circle(pair c, real r);
path Circle(pair c, real r, int n=400);  //比circle更精确的圆
path arc(pair c, real r, real angle1, real angle2);
path arc(pair c, explicit pair z1, explicit pair z2,bool direction=CCW) //CCW表示逆时针,CW表示顺时针
path Arc(pair c, real r, real angle1, real angle2,int n=400); //比arc更精确的圆弧
path ellipse(pair c, real a, real b); //椭圆

int length(path p);
         p由几个段组成,若p是个封闭的图形,则与p中的节点个数是一样的。
int size(path p);
         p的节点个数,若p是个封闭的图形,则与length(p)是相等的。
pair point(path p, int t);
         若p是个封闭的图形,则返回节点 t mod length(p) 的坐标。否则,返回节点 t 的坐标,t<0时,返回节点0的坐标,t>length(p)时,则返回length(p)处的坐标。
pair point(path p, real t);
         与前面类似,不同的是返回的是节点 floor(t) 和节点 floor(t)+1 之间的三次样条曲线在参数
t-floor(t)  处的坐标。若 t 在 [0,length(p)] 之外,且p是个封闭的图形,则先取模 length(p)  ,否则,就取相应端点处的坐标。
pair dir(path p, int t, int sign=0);
         若 sign < 0 ,返回 p 中节点 t 处的入流切线方向,若 sign > 0 ,则为出流切线方向,若 sign=0 ,则两个方向的平均。若 p 只含一个点,返回 (0,0) 。
pair dir(path p, real t);
         返回 p 在节点 floor(t)  和 floor(t)+1 之间的样条曲线在参数 t-floor(t) 处的切线方向。若 p 只有一个点,则返回 (0,0)  。
pair precontrol(path p, int t);
         返回 p 在节点 t 处的precontrol控制点。
pair precontrol(path p, real t);
         返回 p 在 参数 t 处的有效precontrol控制点
pair postcontrol(path p, int t);
         returns the postcontrol point of p at node t.
pair postcontrol(path p, real t);
         returns the effective postcontrol point of p at parameter t.
real arclength(path p);
         返回 p 的长度
real arctime(path p, real L);
         返回从第一个节点开始达到 arclength 为 L 的参数值。
real dirtime(path p, pair z);
         返回切线方向为 z 的参数值。若不存在的话,则返回-1。
real reltime(path p, real l);
         returns the time on path p at the relative fraction l of its arclength.
pair relpoint(path p, real l);
         returns the point on path p at the relative fraction l of its arclength.
pair midpoint(path p);
         returns the point on path p at half of its arclength.
path reverse(path p);
         返回一条沿 p 反向运动的path。
path subpath(path p, int a, int b);
         返回沿 p 的第 a 个节点到第 b 个节点的子路径。若 a < b ,则为反向运动。
path subpath(path p, real a, real b);
         与前面类似。
real[] intersect(path p, path q, real fuzz=0);
        若 p 和 q 有至少一个交点,则返回一个长度为2的real型数组,其中是相应的路径参数(time)。若没有相交,则返回长度为0的数组。
pair intersectionpoint(path p, path q, real fuzz=0);

         返回 p 和 q 的交点坐标: point(p,intersect(p,q,fuzz)[0]) 。
pair[] intersectionpoints(path p, path q);
         返回 p 和 q 的所有交点。
slice firstcut(path p, path q);
         返回  p 中 在 p 和 q 第一个交点的"前"和"后"两部分。若没有交点,则整个 path 看作是"前"部
         struct slice {
                  path before,after;
         }
slice lastcut(path p, path q);
          返回  p 中 在 p 和 q 最后一个交点的"前"和"后"两部分。若没有交点,则整个 path 看作是"后"部
path buildcycle(... path[] p);
         This returns the path surrounding a region bounded by a list of consecutively intersecting paths, following the behaviour of the MetaPost buildcycle command.
pair min(path p);
         returns the pair (left,bottom) for the path bounding box of path p.
pair max(path p);
         returns the pair (right,top) for the path bounding box of path p.
bool cyclic(path p);
         若 p 是封闭的,则返回 true。
bool straight(path p, int i);
         若 p 中节点 i 和 i+1 之间是直线的话,则返回 true。
int windingnumber(path p, pair z);
         returns the winding number of the cyclic path g relative to the point z. The winding number is positive if the path encircles z in the counterclockwise direction.
bool inside(path g, pair z, pen p=currentpen);
         若 z 点在由封闭的 g 所包含的区域内的话,返回 true。

___
海阔天空,我所感兴趣的一切,从股票到C++,从笑话到Linux,从subversion到Latex
http://hai-kuo.blogspot.com/

2007年6月12日星期二

Asymptote学习系列(7)

数据类型(续):

string
string字符串类,由STL string 类实现。与C/C++中的字符串是一致。
内置函数包括:
int length(string s)
int find(string s, string t, int pos=0)
int rfind(string s, string t, int pos=-1)
string insert(string s, int pos, string t)
string erase(string s, int pos, int n)
string substr(string s, int pos, int n=-1)
string reverse(string s)
string replace(string s, string before, string after)
string replace(string s, string[][] table)
string format(string s, int n)
string format(string s, real x)
string time(string format="%a %b %d %T %Z %Y")
            time();
            time("%a %b %d %H:%M:%S %Z %Y");
int seconds(string t="", string format="")
            seconds("Mar 02 11:12:36 AM PST 2007","%b %d %r PST %Y");
            seconds(time("%b %d %r %z %Y"),"%b %d %r %z %Y");
            seconds(time("%b %d %r %Z %Y"),"%b %d %r "+time("%Z")+" %Y");
            1+(seconds()-seconds("Jan 1","%b %d"))/(24*60*60);
string time(int seconds, string format="%a %b %d %T %Z %Y")
string string(real x, int digits=realDigits)
abort(string s);

--
海阔天空,我所感兴趣的一切,从股票到C++,从笑话到Linux,从subversion到Latex
http://hai-kuo.blogspot.com/