磨刀不误砍柴工

切记: 数据要比编程逻辑更容易驾驭(The Art of Unix Programming )

Wednesday, March 10, 2010

自己的域名与Blogger

通过自己的域名(如本人使用blog.jessinio.info)去访问自己的Blogger内容, 早就是这样使用的了。
但是没有理会其中的具体细节。

今天在看DNS方面的知识时, 突然就对这个问题感兴趣了。提出这一个问题:为什么使用blog.jessinio.info可以访问blogger的内容?

下面做一下测试, 随便玩一下。
本人的blog.jessinio.info通过browser是可以访问的。这也是Blogger的内容。看一下是什么:
jessinio@jessinio-laptop:~$ ping blog.jessinio.info
PING ghs.you8g.com (69.164.192.240) 56(84) bytes of data.
64 bytes from li107-240.members.linode.com (69.164.192.240): icmp_seq=1 ttl=49 time=303 ms
64 bytes from li107-240.members.linode.com (69.164.192.240): icmp_seq=2 ttl=49 time=304 ms

网络是通的, 也很正常, 就是对应着一个IP地址。但是一个很神奇的事情:在browser的地址栏中直接访问上面的IP地址是得不到blogger的内容的:

Not Found

The requested URL / was not found on this server.

* 可以亲自试一试: http://69.164.192.240/

先看一下ping命令的結果是什么意思:

0. blog.jessinio.info,本人创建的一个CNAME记录
1. ghs.you8g.com, 也是一个CNAME记录,指向的ANAME记录
2. li107-240.members.linode.com,所谓的正规主机名,也为ANAME
* http://tools.ietf.org/html/rfc1034并没有明文规定禁止CNAME指向CNAME。只是建议避免这种情况

这是为什么直指访问IP地址得不到blogger的内容呢? 难道是因为blog.jessinio.info是一个CNAME的问题?其实这一想法是被pass掉的。browser发出的請求怎么可能包括CNAME之类的信息的嘛

browser的请求明显还包括一个信息: Host

为了求证这一点, 自己写了段请求代码, 因为为了看清請求的具体情况, 代码是直接使用socket写的:
import socket
import re
import os
import sys 


if __name__ == "__main__":
  blog_ip = socket.gethostbyname("blog.jessinio.info")
  s = socket.socket()
  s.connect((blog_ip,80))
  s.sendall("""GET / HTTP/1.1
Host: blog.jessinio.info
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.8) Gecko/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7



""")
  data = ''
  while True:
    retval = s.recv(1024)
    if not retval: break
    data += retval
  s.close()
  # 第一个空行开始的内容都是browser需要的内容
  d = re.search(r'(\r\n|\n)\1(.*)', data, re.S).group(2)
  f = open("/tmp/blog.gz", "w")
  f.write(d)
  f.close()
* 得到的"/tmp/blog.gz"是一个gzip文件, 可以使用gunzip解压一看就明白

从代码可以看到, 访问IP之所以不行, 是因为ghs.google.com需要得到請求中Host参数

注意: 要想使用自己的域名去访问blogger是需要在blogger后台里配置的, 这一配置才能使ghs.google.com根据不同的Host值得到不出的blogger帐号

Friday, February 26, 2010

数字签名和数据加密

北京这两天的雾特别大, 去逛有些不爽, 加上这几天都有出去逛, 干脆休息一天, 坐着看看文档
好久没有怎么看文档了。

一沉下去,就被"数字签名"这一概念搞得了点乱:
1. 首先是android手机上的不同程序要想为同一个user ID, 一定需要程序的数字签名相同
2. 然后是ubuntu upgrade时出现PGP error。

都是与“签名”这一概念扯上关系的。

沉就沉下去吧。 花点时间搞明白。

自己所知道


初见PGP时, 大约还是学生時期,那时只知道有PGP这个“东西“, 大概了解PGP的用途: 保护个人隐私, 数据安全。但是中国的一般计算机用户基本就不会理会这种事。
再说, 那时还算是个小小白. 戴着windows的链锁在跳舞。

工作后碰到ssh的, 会碰到“公钥“与”私钥“这两个概念。关于这方面的理论就不说了, 搜一下就一堆信息出来。

PGP与RSA,DSA的关系

Pretty Good Privacy (PGP) is a computer program that provides cryptographic privacy and authentication. PGP is often used for signing, encrypting and decrypting e-mails to increase the security of e-mail communications. It was created by Philip Zimmermann in 1991.
可以看到, PGP就是一种软件。它利用了“公钥”体系理论。
RSA, DSA为“公钥”体系的一种具体方式。

PGP和GPG

从名字看上去很纠结, 它们都是类似的东西
PGP: http://en.wikipedia.org/wiki/Pretty_Good_Privacy

“公钥”体系的加密与解密

RSA(Rivest-Shamir-Adleman)算法是一种基于大数不可能质因数分解假设的公匙体系。简单地说就是找两个很大的质数,一个公开即公钥,另一个不告诉任何人,即私钥。这两个密匙是互补的,就是说用公匙加密的密文可以用私匙解密,反过来也一样

证书与公钥的关系

http://en.wikipedia.org/wiki/X.509 页面可以看到:
In cryptographyX.509 is an ITU-T standard for a public key infrastructure (PKI) for single sign-on (SSO) and Privilege Management Infrastructure (PMI). X.509 specifies, amongst other things, standard formats for public key certificatescertificate revocation listsattribute certificates, and a certification path validation algorithm.
X.509是证书统一的格式标准

http://en.wikipedia.org/wiki/Public_key_certificate 看到:
In cryptography, a public key certificate (also known as a digital certificate or identity certificate) is an electronic document which uses a digital signature to bind together a public key with an identity — information such as the name of a person or an organization, their address, and so forth. The certificate can be used to verify that a public key belongs to an individual.
我们平时说的"数字证书"就是这样的一种东西: public key再加上一堆信息按照X.509的格式组成的文件。 

申请证书的过程

用户首先产生自己的密钥对,并将公共密钥及部分个人身份信息传送给认证中心。认证中心在核实身份后,将执行一些必要的步骤,以确信请求确实由用户发送而来,然后,认证中心将发给用户一个数字证书,该证书内包含用户的个人信息和他的公钥信息,同时还附有认证中心的签名信息。数字证书由独立的证书发行机构发布。数字证书各不相同,每种证书可提供不同级别的可信度。可以从证书发行机构获得您自己的数字证书

所谓的签名

从wikipedia里找到一幅很好的图片:

图中说明了签名和认证的过程. 但是有一点没有说明的: 认证签名时怎么确认使用certificate(也就是public key)就是发出者的呢?, 需要实现这种认证, 要有第三方认证机构的存在
* certificate可能会和数据文件一起发出, 也有可能不一起发出.

自签名与第三方认证机构

为了做到证明某一个certificate就是发出者的证书, 出现了第三方认证机构. 

说到这里, 已经不是技术问题了. 认证机构比的就是信用. 使用者使用前需要认同的一点: 认证机构上的证书都是可信的.

认证机构有两种性质的:
1. 商业
2. 非商业

注: 不同的认证机构不一定形网络. 有些认证机构是独立的. 

利用“公钥”体系对数据进行加密

上面一节: “公钥”体系的加密与解密 已经说到了“公钥”体系是可以把数据进行加密的。 但是存在一个问题: “公钥“体系的加密与解密速度是很慢的。
在这点上, “公钥”体系远远比不上对称加密方法快。 

PGP类型软件则结合了“公钥”体系和对称加密方法。它除了签名功能外, 还提供了一种功能:对数据进行加密, 实现数据在传输过程中安全性。 

实现方法: 每次使用PGP软件时,PGP都随机产生一个128位的IDEA会话密钥,用IDEA密钥来加密需要传输的数据(对称加密)。然后使用公钥对IDEA密钥加密,这两次加密的数据被一并传输。 通过这种方法间接地保护需要传输的数据内容



Thursday, January 21, 2010

**作为C函数参数(2)

之前写了一篇<<**作为C函数参数>>的文章。 今天又看到这个样的代码。 不过, 比之前的多一点需要注意的地方
在glibc/login/login.c代码里:
static int
tty_name (int fd, char **tty, size_t buf_len)
{
.....((被切掉).....

  if (rv == 0)
    /*
      这里 *tty = buf已经修改了tty这个变量指向的空间.
      为什么不在改变前把传入的空间free掉呢?如:
      如果不free掉, 使用这个函数前需要有另外一个变量引用这块空间
      不用调用free, 是因为login里, _tty是一个局部变量, 会在函数退出后自己释放
    */
    *tty = buf;   /* Return buffer to the user.  */
  else if (buf != *tty)
    free (buf);   /* Free what we malloced when returning an error.  */

  return rv;
}

作者还是清楚的:

#ifdef PATH_MAX
  char _tty[PATH_MAX + UT_LINESIZE];
#else
  char _tty[512 + UT_LINESIZE];
#endif
  char *tty = _tty;
.....
found_tty = tty_name (STDIN_FILENO, &tty, sizeof (_tty));

看出, 有两个变量指向那块空间. 还是数组, 安全得很呢.

因此, 如果**作为参数时:
1. 如果在函数内, 改变了**指向的内存, 这时, **不是指向通过malloc得到的内存是安全的
2. 否则, 请确保还有其它变量引用之

如:

int len = 100;
char *name = (char *)malloc( len );
char * other = name;
tty_name (1, &name, len)

或者是:

char name[100];
char *new = name;
tty_name (1, &new, sizeof( name ));

这种做法:
char name[100];

char *new = name;

可以像malloc一样得到内存, 但是它比较安全: 会自动根据变量的作用范围释放使用的内存.

这种细节, 处理又麻烦, 不处理又不对. 纠结.... 需要长期的code能力.

Wednesday, January 20, 2010

man手册里的section 2和section 3

一直对section 2 和 section 3两个段的API分得不是很开.

section 2和3的说明:
       2   System calls (functions provided by the kernel)
       3   Library calls (functions within program libraries)

当然, 本人是分得到system calls和library calls的.

只是这两者的API有部分是一样的.

可以man 2 setgid和man 3 setgid. 可以发现, 差不多是一个屁样了.

3的存在, 是因为glibc需要有posix标准的一套API. 如glibc的setgid函数其实就是调用了system call的setgid API.

往往对section 2和3的API出现混淆. 举个例子吧:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>


int main()
{
  int fd = open("/tmp", O_DIRECTORY|O_RDONLY);
  if(fd >=0)
    printf("sucess\n");
  else
    printf("no sucess\n");
}
编译时使用: gcc  /tmp/c2.c  -D _GNU_SOURCE -o /tmp/a.out
这时的open是使用system call的open, 可以打开一个文件夹的, 手册是man 2 open里说的内容.

如果不用_GNU_SOURCE这个宏的话, 就是man 3 open里说的内容

对特同名这种函数, 使用了宏去控制


取大文件最后N行数据

取大文件最后N行数据, 这个问题, 如果是我想的话, 就直接往'\n'字符里去想了:
1. 使用seek, 从后面读一块数据出.
2. 数'\n'的个数.

看到人家这样做:

import os

def tail (name, n_line):
    lines = []
    f = file (name)
    n = 1
    while len(lines) <= n_line:
        try:
            // -n_line * 80 是一个大概猜测的一块数据长度

            f.seek (-n_line * 80 * n, os.SEEK_END)

            // 当 len( lines ) > n_line 才跳出循环, 其中, lines[0]行的数据不一定是完整的
            // 这代码的好处就是当最小条件跳出循环时, len( lines ) ==4 . 就算lines[0]是不完整的数据也没有关系. 因为只想取len(lines) - 1 行的数据.

            lines = f.readlines()


            n = n + 1
        except:
            lines = f.seek (0, os.SEEK_SET)
            lines = f.readlines ()
            break
    return lines[-n_line:]

[] 操作符用于函数参数

glibc/locale/setlocale.c文件里

113 /* Construct a new composite name.  */
114 static char *
115 new_composite_name (int category, const char *newnames[__LC_LAST])

这种代码奶奶的还真没有看过. 一般都是直接使用指针做参数的. 这里使用了 数组的形式 做参数. 有什么好处呢?

朋友给出了一个简单的例子说明了道理:
#include <stdio.h>
void test(char *p[2])
{
   puts(p[1]);
}

int main()
{
   char *p[2] = {"hello\0","world\0"};
   test(p);
}
输出为:
world
 
之所以使用数组的形式传参, 主要是为了方便地址偏移方便. 如果不是这样的话, 偏移时需要计算一下, 自己的例子说明:


void test(char **p)
{
   puts(*(p + 1));
}


int main()
{
   char *p[2] = {"hello\0","world\0"};
   test(p);
}

重点被颜色的文字标出


顺便来一个整数偏移:

void test(char **p)
{
   // convert pointer to int
   int number = (int )p;
   // convert int to pointer
   // 可以看出, 指针变量加1是偏移4bytes, 如果使用整数的话, 还需要自己计算
   p = (char **) (number + 4);
   puts(*(p));
}


int main()
{
   char *p[2] = {"hello\0","world\0"};
   test(p);
}


**作为C函数参数

C的指针一旦多层. 读代码是相当难理解.

glibc/string/argz-ctsep.c里:

 26 error_t
 27 __argz_create_sep (const char *string, int delim, char **argz, size_t *len)

这个参数有一个char **argz, 很难理解是什么意思.

后面想通了这种做法意义.

1. 指针:  期待函数可以对指向的内存进行修改, 从而得到返回值. (除指针使用const修饰)
2. 指针的指针: 期待函数返回指向一块内存的地址.

举个例子
指针的情况:
extern int change( char *buffer);
char *buffer = (char *)malloc(100);
change(buffer);
* change函数会修改buffer内存

指针的指针的情况:
extern int change( char **buffer);

char *buffer = NULL;

change( &buffer );
* change函数会返回一块已经被修改的内存.

再看一组函数:
1.  char *ttyname(int fd);
2.  int ttyname_r(int fd, char *buf, size_t buflen);

参数是char *buf。 可以猜测。 ttyname_r会对一块已经申请的内存进行修改。因为内存在函数体里无法知道长度, 所以就有size_t buflen参数。因此使用时是这样的:

size_t buflen = 100;
char *buffer = (char *) malloc( buflen ); #这是一定事先申请的
if ( ttyname_r ( 1, buffer, buflen ) ){
    ...do_you_want....
}

如果 glibc是提供了一个这样的函数: int ttyname_r(int fd, char **buf);

那么使用时就可以:
char  *buffer=NULL;
if ( ttyname_r ( 1,  &buffer ) ){

    ...do_you_want....

}

这样感觉传少了参数, 还少打代码.

但是用法没有绝对的! 这样总结只是为了看代码是容易有方法