技术

IronPython 2.6.1 补上了 SSL 支持

CPython 2.6 里面,把以前的 socket.ssl() 去掉,新增加了一个 ssl module,提供了更完善的API,包括 server-side SSL。IronPython 2.6.0 刚发布的时候,估计把和 CPython 2.6 兼容性的工作重心放在语言核心方面了,漏掉了对这个 ssl 模块的支持,现在终于在 2.6.1 里面补上了。

就我个人测试,IP 的 client-side ssl 是能用的,但是还有点小小的兼容性问题。我在mail-list里指出了这点,开发者为此新开了一个 issue #26778。server-side 用起来明显和 CPython 不同,使用自制的证书(我的翻墙 Proxy)会在握手阶段报一个 "The credentials supplied to the package were not recognized" 错误,现在正在等待列表上的开发者给我回复..

BTW: 我猛然发现我的翻墙工具基本上和 GAppProxy 一样。只不过他是用 gzip 压缩,而我是用 des 来穿过 GFW(这样安全性要好那么一点点);而且我 HTTPS 支持部分的代码比他漂亮,呵呵

Topic: 技术

从U盘安装 Windows XP

假如你的机器没有光驱(常见于上网本,或超便携笔记本)而需要重装操作系统,那就只能从USB安装了。而最方便快捷的方法无疑是生成USB安装盘。

我是因为光驱挂了,所以去往上找相关方案的。搜了一圈,看起来最傻瓜的方法是MSFN 论坛上的一个 "WinSetupFromUSB with GUI",我用的是当前最新的 1.0 beta6

但是它的界面过于 geek 了,经我实战校验,只需要三步(参考下图):
1. 选择一个操作系统类型
2. 选择安装光盘路径,换言之就是那个i386的父目录
3. GO
总之可以忽略界面开始的两个格式化相关按钮。如果为了清理空间而格式化的话,大可以在资源管理器里弄

然后就是从U盘启动,选择First Step,进入 XP 文本安装状态,大约5分钟完成,重起,继续从U盘启动,选择Second Step,进入 XP 图形安装界面,10-15分钟装完,再次重起。接下来就没U盘啥事了。


install xp from usb

Topic: 技术

几个邮件服务商的EHLO SIZE结果

  • gmail.com, 35651584
  • hotmail.com, 29696000
  • sina.cn, 52428800
  • 126/QQ, 不支持在 EHLO 的时候响应 SIZE

考虑到附件大小编码后要增加1/3,上述三个邮箱 SIZE * 0.75 后分别为:26,738,688/gmail,22,272,000/hotmail, 39,321,600/sina

换句话说,向别人发邮件带个20M附件是靠谱的. 再大些的话,就得先看看对方服务器是否支持了

Topic: 技术

翻墙代理的加密部分

就是怎么在 PHP/mcrypt 和 PyCrypto 之间 DES(或其他加密算法,比如3DES/RSA/..) 通信的问题,我这里还额外考察了下 .NET 平台的算法

网上询问相关问题的还挺多,尤其是 PHP 和 .NET 之间的 DES 转换。主要是 PHP/mcrypt 隐藏了 padding 的细节,且只保留了 ZERO_PADDING 模式,所以不明白cyrpto原理的不太容易找到症结所在。我的传输方案统一用 pkcs#7 padding.

首先是给服务器端增加的 PKCS#7 PADDING 函数,来自PHP官方函数手册上某人的注释

function padding_pkcs7($crypto, $mode, $dat)
{
$block = mcrypt_get_block_size($crypto, $mode);
$len = strlen($dat);
$padding = $block - ($len % $block);
$dat .= str_repeat(chr($padding),$padding);
return $dat;

}

function strip_pkcs7($crypto, $mode, $text)
{
$block = mcrypt_get_block_size($crypto, $mode);
$packing = ord($text{strlen($text) - 1});
if($packing and ($packing < $block)){
for($P = strlen($text) - 1; $P >= strlen($text) - $packing; $P--){
if(ord($text{$P}) != $packing){
$packing = 0;
}
}
}
return substr($text,0,strlen($text) - $packing);
}

本地端 .NET 平台的 descrypto 封装

# -*- coding: utf-8 -*-
# ipcrypto.py
from System import Array, Byte
from System.Security.Cryptography import DESCryptoServiceProvider, CryptoStream, CryptoStreamMode, PaddingMode

from System.IO import MemoryStream, StreamWriter, StreamReader

from hashlib import md5

def s2ab(s):

return Array[Byte](tuple(Byte(ord(c)) for c in s))

def ab2s(ab, len=0):
if len == 0: len = ab.Length

return ''.join([chr(ab[i]) for i in range(len)])

class descrypto():
def __init__(self, password):
pwmd5 = md5(password).digest()
self.key = s2ab(pwmd5[:8])
self.iv = s2ab(pwmd5[8:])

self.des = DESCryptoServiceProvider() # 缺省 des.Mode = CipherMode.CBC

def enc(self, input):
ms = MemoryStream()
encStream = CryptoStream(ms, self.des.CreateEncryptor(self.key, self.iv), CryptoStreamMode.Write)
sw = StreamWriter(encStream)
sw.Write(input)
sw.Flush()
encStream.FlushFinalBlock()

return ab2s(ms.GetBuffer(), ms.Length)

def dec(self, input):
ms = MemoryStream(s2ab(input))
length = len(input)
decStream = CryptoStream(ms, self.des.CreateDecryptor(self.key, self.iv), CryptoStreamMode.Read)
byteArray = Array.CreateInstance(Byte, length)
length = decStream.Read(byteArray, 0, length)
return ab2s(byteArray, length)

本地端 PyCrypto 的封装

# -*- coding: utf-8 -*-
# pycrypto.py
from hashlib import md5

from Crypto.Cipher import DES

class descrypto():
def __init__(self, password):
pwmd5 = md5(password).digest()
self.key = pwmd5[:8]

self.iv = pwmd5[8:]

def enc(self, input):
des = DES.new(self.key, DES.MODE_CBC, self.iv)
lastblock = len(input) % 8
if lastblock > 0:
padding = 8 - lastblock

input += padding * chr(padding)

return des.encrypt(input)

def dec(self, input):
des = DES.new(self.key, DES.MODE_CBC, self.iv)
ret = des.decrypt(input)
padding = ord(ret[-1])
for i in range(padding):
if ord(ret[-1 - i]) != padding:
padding = 0
break
if padding > 0:
ret = ret[:-padding]
return ret

Topic: 技术

翻墙代理的远程部分

既然要翻墙,肯定要有一台墙外主机。为了配合加密,以及 HTTP/HTTPS 协议代理,需要编译有 mcrypt 和 curl 的 PHP;在如今我估计这应该都属于web主机标配环境.

如果是文本数据,就加密后返回;如果非文本数据,就不加密了。返回给本地代理以第一个字符是"0" or "1"来指示接下来的数据是否经过加密。

配合其运行的代码见
翻墙代理的本地部分

翻墙代理的加密部分


$PASSWORD = "yourpasswordhere";
$pw_md5 = md5($PASSWORD, true);
$key = substr($pw_md5, 0, 8);

$iv = substr($pw_md5, 8, 8);

$input = file_get_contents("php://input");

$td = mcrypt_module_open('des', '', 'cbc', '');
mcrypt_generic_init($td, $key, $iv);
if (strlen($input) > 0 && $input % 8 == 0) {

$input = strip_pkcs7("des", "cbc", mdecrypt_generic($td, $input));

$req = explode("\r\n\r\n", $input, 3);

$rawreqline = explode(" ", $req[0]);

$url = parse_url($rawreqline[1]);

$_headers = explode("\r\n", trim($req[1]));
//$_headers[count($_headers)] = "X-Forwarded-For: ".$_SERVER['REMOTE_ADDR'];
if ($url["scheme"] == "http" || $url["scheme"] == "https") {
$ch = curl_init($rawreqline[1]);
/* avoid HTTP/1.1 Transfer-Encoding: chunked */
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers);
curl_setopt($ch, CURLOPT_HEADER, 1);
if ($url["scheme"] == "https") {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
if ($rawreqline[0] == "POST" && count($req) == 3) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req[2]);
}

$data = curl_exec($ch);

curl_close($ch);
}

$text_mode = "0";

$res = explode("\r\n\r\n", $data, 2);
$header = explode("\r\n", $res[0], 2); // STATUS HEADER
$headers = explode("\r\n", $header[1]);
foreach ($headers as $hline) {
$h = explode(":", $hline, 2);
$k = strtolower(trim($h[0]));
if ($k == "content-type" && strpos(strtolower(trim($h[1])), "text/") === 0) {
$text_mode = "1";
break;
}
}
if ($text_mode == "1") {
mcrypt_generic_deinit($td);
mcrypt_generic_init($td, $key, $iv);
$data = mcrypt_generic($td, padding_pkcs7("des", "cbc", $data));
}
$data = $text_mode . $data;
}
?>

Topic: 技术

翻墙代理的本地部分

我的这个 proxy 代码参考自 SUZUKI Hisao 的 Tiny HTTP Proxy。主要修改的有两点:

  1. 原版的 do_CONNECT 是两个套接字直接互相转发数据,我改成了 SSL 中间人代理.. 而且依赖到 python 2.6 才支持的 server-side ssl wrap
  2. 另外就是自己封装了 descrypto 类,完成和远程 PHP 的加密
配合其运行的代码见
翻墙代理的远程部分

翻墙代理的加密部分


# -*- coding: utf-8 -*-
# 建议 Python 2.6 环境,以支持 https proxy
# Win32 下需安装
# http://www.python.org/ftp/python/2.6.5/python-2.6.5.msi
# http://www.voidspace.org.uk/downloads/pycrypto-2.0.1.win32-py2.6.exe
# 至于 IronPython 目前还没有 server-side ssl 支持,据说 IP 2.6.1 将会有...
try:
from ipcrypto import descrypto
#IronPython 里的 socket 不支持 bind 到 '0.0.0.0'
import platform
bind_address = (platform.node(), 8000)
except:
from pycrypto import descrypto

bind_address = ('0.0.0.0', 8000)

import urllib2

import BaseHTTPServer, SocketServer

#REMOTEURL/PASSWORD 和国外主机配合
REMOTEURL = 'http://www.dup2.org/blarblarblar.php'

PASSWORD = 'yourpasswordhere'

# KEY/CERT 的生成参考 http://docs.python.org/library/ssl.html
KEYFILE = 'cert.pem'

CERTFILE = 'cert.pem'

#自定义允许的IP列表, 给每个IP起个名字帮助记忆

allow_clients = {'127.0.0.1': 'myself'}

desobj = descrypto(PASSWORD)

skip_headers = ["keep-alive", "proxy-connection", "connection", "accept-encoding"]

class pseudofile():
''' SSL Pseudo File Object'''
def __init__(self, sslobj):
self.sslobj = sslobj

self.closed = 0

def read(self, size):
chunks = []
read = 0
while read < size:
data = self.sslobj.read(size-read)
read += len(data)
chunks.append(data)

return ''.join(chunks)

def readline(self):
line = []
while 1:
char = self.sslobj.read(1)
line.append(char)

if char == "\n": return ''.join(line)

def write(self, data):
bytes = len(data)
while bytes > 0:
sent = self.sslobj.write(data)
if sent == bytes:
break # avoid copy
data = data[sent:]

bytes = bytes - sent

# 下面两个方法是 BaseHTTPServer 里会调用到的
def flush(self):
pass

close = flush

def checkip(f):
def new_f(_self):
(ip, port) = _self.client_address
if ip in allow_clients:
f(_self)
else:
_self.send_error(403)

return new_f

class ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):

@checkip
def do_GET(self):
content_length = 0
if hasattr(self, "sslhost"): self.raw_requestline = "%s https://%s%s %s\r\n" % (self.command, self.sslhost, self.path
, self.request_version)
h = [self.raw_requestline]
for kv in self.headers.items():
if kv[0] == 'content-length':
content_length = int(kv[1])
if kv[0] in skip_headers: continue
h.append("%s: %s" % kv)
h.append("connection: close")

req = "\r\n".join(h) + "\r\n\r\n"

if content_length:

req += self.rfile.read(content_length)

encreq = desobj.enc(req)
req = urllib2.Request(REMOTEURL, encreq)

f = urllib2.urlopen(req)

text_mode = f.read(1)
response = f.read()
if text_mode == "1":
response = desobj.dec(response)
self.wfile.write(response)
print 'REQUEST:', self.raw_requestline.strip()
#有时候一些看起来是 text/* 的请求也是 binary mode,通常是 304 Not Modified

print 'RESPONSE: %s, %d Bytes' % ('crypted mode' if text_mode == "1" else 'raw mode', len(response))

self.close_connection = 1

@checkip
def do_CONNECT(self):
# print self.raw_requestline
# "CONNECT twitter.com:443 HTTP/1.1"
self.sslhost = self.raw_requestline.split()[1]
self.wfile.write(self.protocol_version + " 200 Connection established\r\n")
self.wfile.write("Proxy-agent: QYB\r\n\r\n")
# TODO 浏览器端会看到一个警告,但是没有办法;避免警告是不对的,必须让使用者认识到现在是中间人模式
try:
import ssl
self.rfile = pseudofile(ssl.wrap_socket(self.connection, KEYFILE, CERTFILE, True))
self.wfile = self.rfile
self.handle_one_request()
except:
print 'ssl error:', self.raw_requestline

self.close_connection = 1

do_PUT = do_GET
do_POST = do_GET
do_HEAD = do_GET

do_DELETE = do_GET

class ThreadingHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): pass
httpd = ThreadingHTTPServer(bind_address, ProxyHandler)
httpd.serve_forever()

Topic: 技术

最近为翻墙作的一些技术准备

本来自从换到国外主机后,也就有了一个 ssh 帐号,用来翻墙是通行无阻。但是在某些情况下无法访问外面的 22 端口(比如搜狐的WIFI),那就只能想法通过80端口来翻了.

大概有三种方案:
  1. 弄一个代理页面,输入URL,得到HTML。更强一点的还可以把内容变化一下,在浏览器端重新组装,以避开深度包检测。
  2. 直接在URL上生成一个HTTP Proxy,在浏览器里配置通过它访问。这个可能会被RESET
  3. 我尝试的方向是类似phpproxy,在本地启动Proxy服务。这样的好处是可以同时帮助其他人翻墙

我这个重复造的轮子比 phpproxy 增加的地方有两处,一个是在墙内外传输之间弄了一个DES加密,避开内容检查;另外一个是很凑合的实现了HTTPS代理(就是会有浏览器警告,很像中间人证书欺骗)。本地的 Proxy 在 Python 和 IronPython 下都能运行,如果哪天想用手机翻墙的话再试试 PyS60 能不能跑起来

感觉对付防火墙还是得靠 P2P 的模式,部分人贡献出口通道,静态内容在墙内大量缓存。打算要是有时间的话研究研究 .NET Framework 里的 P2P 支持,做出来的产品能在 Windows 上运行才是王道。

Topic: 技术

闪电邮箱-梦工场,以及第三方应用

其实 Labs 这个东西,本来我最初是想起名为"作坊"的,一方面是俺们行业总是被称呼为作坊,另一方面我确实认为这是项手艺活,该名字非常贴切。但是这个主意肯定会被产品同学痛批,所以还是用"工场"来代替"作坊"了

如果不是来自理工专业的研究生,只能凭空想象 Labs 是什么地方吧。我把它称之为工程师梦开始的地方,希望不会遭到误解。

在我的计划里,梦工场的出产未必是用户所迫切要求,未必是产品/运营所欣赏;而完全是工程师主导的,充满了不确定,以及那么一点点小bug的项目;梦工场的另外一个考虑就是优化框架,让各项应用可以很方便的以 plugins 形式嵌入——但现在还没这么作,需要长时间的磨合。

之所以有上述这样的打算,是因为我认为未来可能有样激动人心的事情就是把WebMail作为一个平台,鼓励第三方加入开发。2月份我在twitter上说,"关于gmail,实验室就是它的appstore...另外,google apps潜在的appstore商业机会才叫一个大"。仿佛为了回应我这段话,上个星期,GoogleCode Blog连续发表两条消息,一条针对企业,"Integrate, Publish, Sell - The Google Apps Marketplace",一条针对个人应用,"Coming soon: Gmail contextual gadgets available for trusted testers"

回到现实,梦工场目前也是产品经理们的实验地,看看产品经理驱动和工程师驱动最后谁会被用户接受,也是让人期待的事情。

Topic: 商业 技术

关于 RPM 的一些事实

不喜欢有人妖魔化 rpm,尤其是如果从来没有用过 fedora/opensuse 也出来乱喷

1. 这只是一个应用的打包规范,本质上和 deb 没有什么差别。
2. 它是 LSB(Linux Standards Base) 的标准格式
3. 它支持 LZMA 压缩,比传统的 zlib 压缩比更高

4. 有一种叫 DeltaRPM 的格式,可以极大缩小每次 Update 的带宽开销。这非常酷

另外和 apt 对应的是 yum/zypper 等高级命令行工具
1. yum 的 fastestmirror 插件自动选择最快的 mirror

2. 我使用 yum 和 zypper 的时候,没有感到什么 rpm 依赖关系的麻烦

我最近两年使用的都是 rpm 的系统,确实不了解最近 deb 的进展。但就我搜索来看,LZMA,Delta Patch 这两个特性 deb 系应该还差一点.

Topic: 技术

IronPython 里某些类型的显式转换


import clr, System
x = clr.Reference[System.UInt64](0)

#创建一个到System.UInt64对象的引用,C的行话是指针

x.Value = 1 * 1

上述代码在最后 x.Value = 1*1 的时候抛了一个类型异常出来..

我就直接在 ipy console 里执行 x.Value = 1 测试

我靠,没有任何错误啊。为啥后面赋值把整数替换成表达式就错呢?我想当然的以为这个是 IronPython 的 bug,差点就去开 Issue 了。后来转念一想,这个 2.0.3 的 ipy 出错,或许 2.6 就 fix 了...

然后发现在 2.6 里更悲剧,x.Value = 1 也直接抛异常..我这才意识到这种赋值操作是有问题的,改成 x.Value = System.UInt64(1*1) 搞定

Topic: 技术
订阅 RSS - 技术 | BT的花