1. freebsd

1.1. qmail下使用python编写脚本来过滤邮件和发送短信

实现qmail下邮件过滤和邮件到达短信通知

作者:梅劲松

时间:2004年9月10日

感谢:HD、刘鑫

短信这么火热,如果自己的邮件服务器也能和短信结合起来,那多么好啊?在qmail下,我用qmail_queue来解决邮件过滤和短信到达通知。

本邮件系统在freebsd 4.10上实施成功。

1. 对于邮件系统的安装,没有太多说的。建议大家看HD的 http://bsd.huangdong.com/server/mail/qmail.html 来安装。如果已经安装了有qmail+vpopmail+qmail_queue补丁的,请直接跳到4继续安装。在新装邮件服务器时有几点需要注意的是,安装qmail的时候应该将步骤改为:

cd /usr/ports/mail/qmail
make WITH_QMAILQUEUE_PATCH=yes WITH_BIG_TODO_PATCH=yes WITH_OUTGOINGIP_PATCH=yes WITH_PRESERVE_CONFIG_FILES=yes extract
cd /usr/ports/mail/qmail/work/qmail-1.03
fetch http://www.nimh.org/dl/qmail-smtpd.c
cd /usr/ports/mail/qmail
make WITH_QMAILQUEUE_PATCH=yes WITH_BIG_TODO_PATCH=yes WITH_OUTGOINGIP_PATCH=yes WITH_PRESERVE_CONFIG_FILES=yes install
make disable-sendmail
make enable-qmail
make clean

在安装vpopmail的时候可能因为port更新的原因让使用vpopmail的时候出现奇怪问题,最好是安装webmin将建立的vpopmail数据库删除后,重新建立,并指定数据库的用户权限。由于webmin的安装不是本文重点,这里将忽略。

2、关于文章里面的杀毒部分因为系统效率问题,我没有安装。各位请自行决定是否安装。

3、反垃圾邮件当然需要,我用的是 http://anti-spam.org.cn 的cbl+服务。使用的时候只需要将smtpd的脚本改为:

#!/bin/sh
QMAILDUID=`/usr/bin/id -u qmaild`
NOFILESGID=`/usr/bin/id -g qmaild`
exec /usr/local/bin/tcpserver -H -R -l 0 -p -x \
        /usr/local/vpopmail/etc/tcp.smtp.cdb -u"$QMAILDUID" \
        -g"$NOFILESGID" -v -c100 0 smtp rblsmtpd \
        -r cblplus.anti-spam.org.cn \
        -r relays.ordb.org \
        /var/qmail/bin/qmail-smtpd /usr/local/vpopmail/bin/vchkpw-smtpd /usr/bin/true 2>&1

http://anti-spam.org.cn/cgi-bin/rblclient/(你的邮件服务器的dns服务器IP地址) 看到你的邮件服务器的流量和使用cbl+服务的情况。

4、这里是重点了。过滤和短信到达是需要qmail_queue来完成的,一定要打这个包。

使用python来实现这个功能,当然需要安装python啦。

cd /usr/ports/lang/python
make;make install;make clean

一般来讲这个安装是非常顺利的。 安装结束后。

cd /var/qmail/bin

编辑qmfilt.py,内容如下:

   1 #!/usr/local/bin/python --
   2 
   3 import os, sys, string, time, traceback, re, socket
   4 
   5 Version  = '1.1'
   6 PyVersion  = '1.0'
   7 Logging  = 1
   8 Debug    = 0
   9 QmailD   = 82#这里需要和你/etc/password里面qmaild用户的一样
  10 LogFile  = None
  11 TestMode = None
  12 Filters  = []
  13 MailEnvelope = ''
  14 MailData     = ''
  15 ValidActions = { 'trap': '', 'drop' : '', 'block' :'' }
  16 
  17 #这里是你通过ucp协议将消息发送到哪个服务器的哪个端口
  18 def mail_sms(msg):
  19     host = "xxx.xxx.114.2"
  20     port = 9999
  21     buf = 500
  22     addr = (host,port)
  23 
  24     # Create socket
  25     UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
  26 
  27     def_msg = msg;
  28     UDPSock.sendto(def_msg,addr)
  29 
  30     # Close socket
  31     UDPSock.close()
  32 
  33 def openlog():
  34   global LogFile
  35 
  36   if not Logging:
  37      return
  38   try:
  39     LogFile = os.open('/var/log/qmfilt', os.O_APPEND|os.O_WRONLY|os.O_CREAT, 0744)
  40   except:
  41     if TestMode:
  42       print "Can't create /var/log/qmfilt"
  43     else:
  44       # Indicate a write error
  45       sys.exit(53)
  46 
  47 def log(message):
  48   message = time.asctime(time.localtime(time.time())) + " - " + message + "\n"
  49   if Logging:
  50     os.write(LogFile, message)
  51     # Indicate a write error
  52     #sys.exit(53)
  53   if TestMode:
  54     print message
  55 
  56 
  57 def showFilters():
  58 
  59   if len(Filters) == 0:
  60     print "No filters"
  61   for f in Filters:
  62     print "Filter -  %s - Action: %s  Regex: %s" % (f[0], f[1], f[2])
  63 
  64 def readFilters():
  65   global Filters
  66 
  67   try:
  68     file = open('/var/qmail/control/qmfilt', 'r')
  69     configLines = file.readlines()
  70     file.close()
  71   except:
  72     log("Can't read /var/qmail/control/qmfilt")
  73     if not TestMode:
  74       # Indicate a 'cannot read config file error'
  75       sys.exit(53)
  76 
  77   reg = re.compile('(.*)::(.+)::(.+)::')
  78   for line in configLines:
  79     if line[0] == '#':
  80       continue
  81     m = reg.match(line)
  82     if m != None:
  83       action = string.lower(m.group(2))
  84       if not ValidActions.has_key(action):
  85          log("Invalid action in config file [%s] - SKIPPED" %(action))
  86          continue
  87       Filters.append( [m.group(1), string.lower(m.group(2)), m.group(3)] )
  88 
  89 def readDescriptor(desc):
  90   result = ''
  91   while 1:
  92     data = os.read(desc, 16384)
  93     if data == '':
  94       break
  95     result = result + data
  96 
  97   return result
  98 
  99 def writeDescriptor(desc, data):
 100   while data:
 101     num  = os.write(desc, data)
 102     data = data[num:]
 103 
 104   os.close(desc)
 105 
 106 
 107 def filterHits():
 108 
 109   for regEx in Filters:
 110     reg = re.compile(regEx[2], re.M|re.I)
 111     match = reg.search(MailData)
 112     if match:
 113       log("Matched [%s]" % (regEx[0]))
 114       return regEx[1], regEx[0]
 115 
 116   return None,None
 117 
 118 
 119 def storeInTrap(hit):
 120   try:
 121     pid = os.getpid()
 122     mailFile = os.open('/var/qmail/qmfilt/qmfilt.%s' %(pid), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)
 123   except:
 124     log("Can't create /var/qmail/qmfilt/qmfilt.%s" %(pid))
 125     # Indicate a write error
 126     sys.exit(53)
 127 
 128   try:
 129     #如果不屏蔽会出现很多问题,至于为什么我还没弄明白,如果你找到问题所在,请告诉我。
 130     """(None, inode, None, None, None, None, size, None, None, None) = os.fstat(mailFile)"""
 131     os.rename('/var/qmail/qmfilt/qmfilt.%s' %(pid), '/var/qmail/qmfilt/%s.mail' %(inode))
 132   except:
 133     log("Can't rename /var/qmail/qmfilt/qmfilt.%s -> /var/qmail/qmfilt/%s.mail" %(pid, inode))
 134     # Indicate a write error
 135     sys.exit(53)
 136 
 137   try:
 138     envFile = os.open('/var/qmail/qmfilt/%s.envelope' %(inode), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)
 139     mesgFile = os.open('/var/qmail/qmfilt/%s.qmfilt' %(inode), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744)
 140     writeDescriptor(mailFile, MailData)
 141     writeDescriptor(envFile,  MailEnvelope)
 142     writeDescriptor(mesgFile, "Matched filter [ %s] to file %s" %(hit, inode))
 143     log("Matched filter [ %s] to file %s" %(hit, inode))
 144   except:
 145     log("Can't create/write files into trap")
 146     # Indicate a write error
 147     sys.exit(53)
 148 
 149   return 0
 150 
 151 
 152 def sendToQmailQueue():
 153 
 154   # Open a pipe to qmail queue
 155   fd0 = os.pipe()
 156   fd1 = os.pipe()
 157   pid = os.fork()
 158   if pid < 0:
 159     log("Error couldn't fork")
 160     sys.exit(81)
 161   if pid == 0:
 162     # This is the child
 163     os.dup2(fd0[0], 0)
 164     os.dup2(fd1[0], 1)
 165     i = 2
 166     while (i < 64):
 167       try:
 168         os.close(i)
 169       except:
 170         pass
 171       i = i + 1
 172     os.chdir('/var/qmail')
 173     #time.sleep(10)
 174     os.execv("bin/qmail-queue", ('bin/qmail-queue',))
 175     log("Something went wrong")
 176     sys.exit(127)  # This is only reached on error
 177   else:
 178     # This is the parent
 179     # close the readable descriptors
 180     os.close(fd0[0])
 181     os.close(fd1[0])
 182 
 183 
 184   # Send the data
 185   recvHdr = "Received: (QMFILT: %s); " %(Version)
 186   recvHdr = recvHdr + time.strftime("%d %b %Y %H:%M:%S", time.gmtime(time.time()))
 187   recvHdr = recvHdr + " -0000\n"
 188   os.write(fd0[1], recvHdr)
 189   writeDescriptor(fd0[1], MailData)
 190   writeDescriptor(fd1[1], MailEnvelope)
 191 
 192   # Catch the exit code to return
 193   result = os.waitpid(pid, 0)[1]
 194   if PyVersion > '1.5.1':
 195     if os.WIFEXITED(result):
 196       return os.WEXITSTATUS(result)
 197     else:
 198       log("Didn't exit normally")
 199       sys.exit(81)
 200   else:
 201     return result
 202 
 203 def conver(msg):
 204   msg = msg.replace(chr(00),' ')
 205   msg = msg.replace('F','From:',1)
 206   msg = msg.replace(' T',' To:')
 207   return msg
 208 
 209 
 210 def main(argv, stdout, environ):
 211 
 212   global TestMode
 213   global MailData
 214   global MailEnvelope
 215   global PyVersion
 216 
 217   PyVersion = string.split(sys.version)[0]
 218 
 219   if len(argv) > 1 and argv[1] == '--test':
 220     TestMode = 1
 221 
 222   os.setuid(QmailD)
 223 
 224   # Insure Environment is OK
 225 
 226   # Try to open log
 227   openlog()
 228 
 229   if not os.path.exists('/var/qmail/qmfilt'):
 230     # Indicate a problem with the queue directory
 231     log("Directory /var/qmail/qmfilt doesn't exist")
 232     if not TestMode:
 233       sys.exit(61)
 234 
 235   if os.path.exists('/var/qmail/control/qmfilt'):
 236     readFilters()
 237   else:
 238     if TestMode:
 239       print "No filter file /var/qmail/control/qmfilt - no filters applied"
 240 
 241   if TestMode:
 242     showFilters()
 243 
 244   # Go no further if in test mode
 245   if TestMode:
 246     sys.exit(0)
 247 
 248 
 249 
 250   # Get the data
 251 
 252   # Read the data
 253   MailData     = readDescriptor(0)
 254 
 255   # Read the envelope
 256   MailEnvelope = readDescriptor(1)
 257   if Debug:
 258     log(MailData)
 259     log(MailEnvelope)
 260 
 261   action,hit = filterHits()
 262 
 263   if action == 'trap':
 264     storeInTrap(hit)
 265   if action == 'block':
 266     log("Matched filter [ %s] and email was BLOCKED/Refused delivery" %(hit))
 267     sys.exit(31)
 268   if action == 'drop':
 269     log("Matched filter [ %s] and email was DROPPED" %(hit))
 270   if action == None:
 271     sendToQmailQueue()
 272   #Log
 273   log(conver(MailEnvelope))
 274   #send sms
 275   mail_sms(conver(MailEnvelope))
 276 
 277   if Debug:
 278     log("qmailqueue returned [%d]" %(result))
 279   sys.exit(0)
 280 
 281 if __name__ == "__main__":
 282   try:
 283     main(sys.argv, sys.stdout, os.environ)
 284 
 285   # Catch the sys.exit() errors
 286   except SystemExit, val:
 287     sys.exit(val)
 288   except:
 289     # return a fatal error for the unknown error
 290     if TestMode:
 291       traceback.print_exc()
 292     sys.exit(81)

然后在cd /var/qmail/control 编辑qmfilt内容如下:

#
# This is the qmfilt control file
# If any email comes in that matches this
# filter, the mail will be redirected
# to the filter directory
#
# A filter regular expression must be on a single
# line and in between a pair of '::'
#
# Valid actions:
# trap - store in the trap directory
# block - tell the smtp sender that we won't take the mail
# drop - accept the mail, but don't queue it
#

# This will match any executable Visual Basic Scripts
VisualBasic::block::^Content-Type: application/octet-stream;\s+name="(.*\.vbs)"::
SHS::block::^Content-Type: application/octet-stream;\s+name="(.*\.shs)"::
SHB::block::^Content-Type: application/octet-stream;\s+name="(.*\.shb)"::
COM::block::^Content-Type: application/octet-stream;\s+name="(.*\.com)"::
EXE::block::^Content-Type: application/octet-stream;\s+name="(.*\.exe)"::
SCR::block::^Content-Type: application/octet-stream;\s+name="(.*\.scr)"::
PIF::block::^Content-Type: application/octet-stream;\s+name="(.*\.pif)"::
BAT::block::^Content-Type: application/octet-stream;\s+name="(.*\.bat)"::

# This is the Outlook date overflow bug
DATE::block::^Date:.{160,}::

在/var/log下建立qmfilt文件,内容为空

chown qmaild qmfilt

在/var/qmail下建立qmfilt目录。

chown -R qmaild qmfilt

好了,来让我们的程序启用吧 先测试一把

/var/qmail/bin/qmfilt.py --test

如果返回了先定义的qmfilt里面的内容,恭喜成功了一半了。内容应该和下面的相似:

Filter - VisualBasic - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.vbs)"
Filter - SHS - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.shs)"
Filter - SHB - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.shb)"
Filter - COM - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.com)"
Filter - EXE - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.exe)"
Filter - SCR - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.scr)"
Filter - PIF - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.pif)"
Filter - BAT - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.bat)"
Filter - DATE - Action: block Regex: ^Date:.{160,}

好了,让我们的过滤器用起来吧

cd /usr/local/vpopmail/etc

编译tcp.smtp内容如下

127.:allow,RELAYCLIENT="",QMAILQUEUE="bin/qmfilt.py"
:allow,QMAILQUEUE="bin/qmfilt.py"

然后生成tcp.smtp.cdb文件

tcprules tcp.smtp.cdb tcp.smtp.tmp < tcp.smtp

发个带exe为附件的邮件看看。如果成功过滤,应该在/var/log/qmfilt里面看到如下信息:

Fri Sep 10 14:37:01 2004 - Matched filter [ EXE] and email was BLOCKED/Refused delivery
Fri Sep 10 14:38:09 2004 - Matched [SCR]

如果你是用foxmail等客户端发送带exe为结尾的邮件的话,服务器会提示你拒绝接收的。到这里,你的邮件过滤已经成功了。

你可以在/var/qmail/control的qmfilt文件里面定义你需要过滤的指定代码,然后测试规则是否生效了。

关于短信到达通知,因为安全和商业上的问题。不能将服务器端的代码贴出来。我将机制给大家讲一下。

qmfilt.py这个程序里面mail_sms用udp将类似 From:[email protected] To:[email protected] 发送到指定的服务器上。服务器接收到这个数据包后,将这个数据包的内容用短信发送出去。

当然可以将邮件主题等信息一起发送到手机上,大家根据自己的情况更改吧。

这个程序是根据 http://sourceforge.net/projects/qmfilt/ 这个项目更改的,但是直接用他的代码会造成不能收信,大家可以在这个项目的cvs关注他的代码修改情况。

结束,谢谢!

1.2. FreeBSD 5.3的使用技巧

  • FreeBSD5.3直接安装后不像4x样默认启动了sshd服务,你需要在安装的时候选择使用sshd服务。或者在/etc/rc.conf中加入sshd_enable="YES" 如果你使用SecureCRT用ssh2连接时提示unable to authenticate using any of the configured authentication methods !错误,请在连接属性中的Authentication的Primary选项中,选择Keyboard Interactive。

1.3. FreeBSD 5.3下安装Jail

感谢:黄冬

  • 按照黄冬的文章在FB5.3下安装Jail未果,他提醒我FB5.3的Jail已经发生了变更,在他的帮助下成功在FB5.3下安装了Jail,整理成资料大家分享一下。

    首先安装系统时,最好划分一个单独的分区来存放你的vhost,如果不想变更你的分区设置。也可以将vhost安装在你的/usr或者/home中。如果你有了cvsup,请到/usr/share/examples/cvsup中修改你的stable-supfile文件,设置*default host=cvsup.FreeBSD.org。然后将他丢到后台开始下载最新的源码吧。cvsup -g -L 2 stable-supfile & 由于FB5.3已经不能直接make world ,更新完代码后,到/usr/src下按步骤编译。如果你初装系统,建议你将整个系统都编译和优化一下。如果只想安装jail,就没有必要完整编译整个系统了。

    • 在/usr/src下make buildworld&在后台编译,这个过程可能需要2小时或者4小时。你可以做点别的事情去。结束后,我们来安装我们的jail吧。我做了个新建jail的脚本new_jail.sh,贴到这里

#!/bin/sh 
if [ -z $1 ]; then 
echo "specify dest dir such as $0 /some/dir" 
exit 
fi 

D=$1 
echo $D
mkdir -p $D
cd /usr/src 
make installworld DESTDIR=$D 
cd etc 
make distribution DESTDIR=$D 
cd $D 
ln -sf dev/null kernel 

先给它加上执行权限chmod +x new_jail.sh

然后

mkdir -p /vhost/jail/179

./new_jail.sh /vhost/jail/179

来创建你的新vhost,这里/vhost/jail/179替换为你要安装的虚机路径。最好是绝对地址,防止出错。

当一切结束后,用单用户来启动我们的vhost吧

ifconfig_eth0_alias0="inet 10.0.0.179 netmask 255.255.255.255"

替换eh0为你的实际网卡设备名,如果你不知道可以用ifconfig命令查看

jail /vhost/jail/179 dns 10.0.0.79 /bin/sh

现在设置你的root密码吧passwd 输入新密码

vhost没有tty供你操作,你需要运行sysinstall,在用户管理中建立一个属于wheel组的用户,用来ssh登陆上去。然后设置一下时区是非常有必要的。

然后我们还需要为ssh准备一个key

/etc/rc.d/sshd start

然后回车,或者输入点什么东西再回车。

我们还需要将这个vhost中设置它的文件

/etc/hosts设置主机名

/etc/rc.conf 中加入

sshd_enable="YES"

sendmail_enable="NONE"

/etc/crontab中删除和adjkerntz 相关的内容。

/etc/resolv.conf 中设置你的dns服务器地址

格式为:nameserver IP地址

好了,输入exit退出单用户模式。回到主系统后修改主系统的rc.conf,加上以下信息

ifconfig_eth0_alias0="inet 10.0.0.179 netmask 255.255.255.255"

jail_enable="YES" 

jail_list="dns" 

jail_dns_hostname="dns.test.com" 

jail_dns_ip="10.0.0.179" 

jail_dns_rootdir="/vhost/jail/179" 

jail_dns_exec="/bin/sh /etc/rc" 

jail_dns_devfs_enable="YES" 

jail_dns_devfs_ruleset="devfsrules_jail"

重新启动你的机器吧,当然如果你想继续你的uptime时间,你可以先输入init 1后,在提示行下输入exit来重新回到多用户模式。

dmesg -a | more来看看你的启动信息,如果你的jail和下面的信息相似,恭喜你。可以用ssh连接10.0.0.179来登陆你的jail了。

Starting jails:

  • dns.test.com

.

Local package initialization:

还有点小技巧

1、 /etc/rc.d/jail这个命令可以用来开始,结束,重新启动你的jail,输入这个命令看看帮助吧

jls这个命令可以看现在正在运行的 jail的列表。试一下?

vhost1# jls

  • JID IP Address Hostname Path
    • 3 10.0.0.179 dns.test.com /vhost/jail/179

2、 删除jail

/etc/rc.d/jail stop dns

chflags -R noschg 179

rm -R 179就可以删除了

3、 在jail中使用ports

先在jail中建立ports目录,比如mkdir /usr/ports

在再主系统中执行mount_nullfs /usr/ports /vhost/jail/179/usr/ports

4、 如果需要同时运行多个jail,你的rc.conf应该这样配置

ifconfig_eth0_alias0="inet 10.0.0.179 netmask 255.255.255.255"

ifconfig_eth0_alias0="inet 10.0.0.180 netmask 255.255.255.255"

jail_enable="YES"

jail_list="dns mail" 

jail_dns_hostname="dns.test.com" 

jail_dns_ip="10.0.0.179" 

jail_dns_rootdir="/vhost/jail/179" 

jail_dns_exec="/bin/sh /etc/rc" 

jail_dns_devfs_enable="YES" 

jail_dns_devfs_ruleset="devfsrules_jail"


jail_mail_hostname="mail.test.com"

jail_mail_ip="10.0.0.180"       
     
jail_mail_rootdir="/vhost/jail/180"

jail_mail_exec="/bin/sh /etc/rc"         
      
jail_mail_devfs_enable="YES"   
 
jail_mail_devfs_ruleset="devfsrules_jail"

启动或者停止其中一个jail可以/etc/rc.d/jail start mail或者/etc/rc.d/jail stop dns来操作。

2. python

2.1. python调用com以及com事件

python调用有事件发生的com时,需要一些技巧。

我们以ie这个com为例来讲解一下我的经验。

首先我们需要pywin32这个模块的支持,它提供了我们调用com便利直接方法。你可以www.sf.net搜索并下载它。

先运行如下代码:

   1 import win32gui
   2 import win32com
   3 import win32com.client
   4 import pythoncom
   5 import time
   6 
   7 
   8 class EventHandler:
   9 
  10     def OnVisible(self, visible):
  11         global bVisibleEventFired
  12         bVisibleEventFired = 1
  13     def OnDownloadBegin(self):
  14         print "DownloadBegin"
  15     def OnDownloadComplete(self):
  16         print "DownloadComplete"
  17     def OnDocumentComplete(self, pDisp = pythoncom.Missing , URL = pythoncom.Missing):
  18         print "documentComplete of %s" % URL
  19 
  20 #这里用EventHandler类来处理ie中发生的事件,这里的函数名必须和事件名称一致。
  21 ie = win32com.client.DispatchWithEvents("InternetExplorer.Application", EventHandler)
  22 ie.Visible = 1
  23 ie.Navigate("www.aawns.com")
  24 #这里是等待事件的发生
  25 pythoncom.PumpMessages()
  26 ie.Quit()

我们看到,程序运行正常,能打开我们指定的站点,并各事件被触发后都能作出正确的反映。

但是假如我们希望在事件发生后,能调用我们继承下来的一些方法和属性。你会发现无法使用。

如下代码将展示这个例子

   1 # -*- coding: cp936 -*-
   2 import win32gui
   3 import win32com
   4 import win32com.client
   5 import pythoncom
   6 import time
   7 
   8 class test:
   9     def runtest(self):
  10         print 'tuntest'
  11 
  12 class EventHandler:
  13 
  14     def OnVisible(self, visible):
  15         global bVisibleEventFired
  16         bVisibleEventFired = 1
  17     def OnDownloadBegin(self):
  18         print "DownloadBegin"
  19     def OnDownloadComplete(self):
  20         print "DownloadComplete"
  21     def OnDocumentComplete(self, pDisp = pythoncom.Missing , URL = pythoncom.Missing):
  22         print "documentComplete of %s" % URL
  23         #在这里我们再调用test的runtest方法,看是否继承成功。
  24         self.runtest()
  25 
  26 class runcom(test):
  27     def __init__(self):
  28         ie = win32com.client.DispatchWithEvents("InternetExplorer.Application", EventHandler)
  29         ie.Visible = 1
  30         ie.Navigate("www.aawns.com")
  31         #这里调用test的runtest方法,看是否继承成功。
  32         self.runtest()
  33         pythoncom.PumpMessages()
  34         ie.Quit()
  35 a=runcom()

运行结果是错误的。

tuntest
DownloadBegin
DownloadComplete
DownloadBegin
DownloadComplete
documentComplete of http://www.aawns.com/
pythoncom error: Python error invoking COM method.
Traceback (most recent call last):
  File "C:\Python23\Lib\site-packages\win32com\server\policy.py", line 283, in _
Invoke_
    return self._invoke_(dispid, lcid, wFlags, args)
  File "C:\Python23\Lib\site-packages\win32com\server\policy.py", line 288, in _
invoke_
    return S_OK, -1, self._invokeex_(dispid, lcid, wFlags, args, None, None)
  File "C:\Python23\Lib\site-packages\win32com\server\policy.py", line 550, in _
invokeex_
    return func(*args)
  File "D:\python\test.py", line 24, in OnDocumentComplete
    self.runtest()
  File "C:\Python23\Lib\site-packages\win32com\client\__init__.py", line 454, in
 __getattr__
    raise AttributeError, "'%s' object has no attribute '%s'" % (repr(self), att
r)
exceptions.AttributeError: '<win32com.client.COMEventClass instance at 0x1554295
2>' object has no attribute 'runtest'

我们看到test类的runtest方法并没有被继承进去,为什么呢?我的理解是因为com的事件模式让你无法继承python中的self,因为在调用ie的时候并不是用EventHandler的实例而是将这个类作为了事件处理的方法(不知道这里理解是否正确,如果有更好的理解。我们交流)

经过查找了很多资料和试探了很多方法,只有采用全局变量的方式才能在事件和各个类之间传递数据。代码变更成了这样

   1 # -*- coding: cp936 -*-
   2 import win32gui
   3 import win32com
   4 import win32com.client
   5 import pythoncom
   6 import time
   7 
   8 
   9 class EventHandler:
  10 
  11     def OnVisible(self, visible):
  12         global bVisibleEventFired
  13         bVisibleEventFired = 1
  14     def OnDownloadBegin(self):
  15         print "DownloadBegin"
  16         #先继承全局变量增加一个字符串
  17         global testlist
  18         testlist.append("DownloadBegin")
  19     def OnDownloadComplete(self):
  20         print "DownloadComplete"
  21         #先继承全局变量增加一个字符串
  22         global testlist
  23         testlist.append("DownloadComplete")
  24     def OnDocumentComplete(self, pDisp = pythoncom.Missing , URL = pythoncom.Missing):
  25         print "documentComplete of %s" % URL
  26         #先继承全局变量再打印
  27         global testlist
  28         print testlist
  29 
  30 class runcom:
  31     def __init__(self):
  32         global testlist
  33         ie = win32com.client.DispatchWithEvents("InternetExplorer.Application", EventHandler)
  34         ie.Visible = 1
  35         ie.Navigate("www.aawns.com")
  36         #打印全局变量
  37         print testlist
  38         pythoncom.PumpMessages()
  39         ie.Quit()
  40 testlist=[]
  41 a=runcom()

可以看到,用全局变量的方式解决了于事件内传输数据的问题。

没有解决的问题:使用Twisted的时候也有相应的事件,如何保证Twisted 和com中的事件都被触发?

2.2. 使用python写nt服务

如果我们想让系统启动的时候就执行某个程序,windows系统和unix系统是不一样的,对于unix只需要将要执行的命令放到rc.local中,系统重新启动的时候就可以加载了。windows就麻烦多了,如果你将程序放到启动组中,只有输入了密码后,程序才被执行,如果想在系统一启动的时候就执行程序,必须使用nt服务。 python下如何使用nt服务,其实很简单。 下载python的win32支持。我使用的是:pywin32-202.win32-py2.3.exe安装好后就可以来写我们的服务了。 我们先来建立一个空的服务,建立test1.py这个文件,并写入如下代码:

   1 # -*- coding: cp936 -*-
   2 import win32serviceutil
   3 import win32service
   4 import win32event
   5 
   6 class test1(win32serviceutil.ServiceFramework):
   7     _svc_name_ = "test_python"
   8     _svc_display_name_ = "test_python"
   9     def __init__(self, args):
  10         win32serviceutil.ServiceFramework.__init__(self, args)
  11         self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
  12 
  13     def SvcStop(self):
  14         # 先告诉SCM停止这个过程
  15         self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
  16         # 设置事件
  17         win32event.SetEvent(self.hWaitStop)
  18 
  19     def SvcDoRun(self):
  20         # 等待服务被停止
  21         win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
  22 
  23 if __name__=='__main__':
  24     win32serviceutil.HandleCommandLine(test1)

这里注意,如果你需要更改文件名,比如将win32serviceutil.HandleCommandLine(test1)中的test1更改为你的文件名,同时class也需要和你的文件名一致,否则会出现服务不能启动的问题。 在命令窗口执行,test1.py可以看到帮助提示

C:\>test1.py
Usage: 'test1.py [options] install|update|remove|start [...]|stop|restart [...]|
debug [...]'
Options for 'install' and 'update' commands only:
 --username domain\username : The Username the service is to run under
 --password password : The password for the username
 --startup [manual|auto|disabled] : How the service starts, default = manual
 --interactive : Allow the service to interact with the desktop.

C:\>

安装我们的服务

C:\>test1.py install
Installing service test_python to Python class C:\test1.test1
Service installed

C:\>

我们就可以用命令或者在控制面板-》管理工具-》服务中管理我们的服务了。在服务里面可以看到test_python这个服务,虽然这个服务什么都不做,但能启动和停止他。 下面我们来写个复杂点的服务。 改天写,要下班了。

2.3. 使用python来发扑克牌

在QQ群里面出了个题目,发52张扑克牌到4个人,要随机,速度足够快。我写了一个,请大家指正。

   1 #!/usr/local/bin/python --
   2 # -*- coding: cp936 -*-
   3 import random,time
   4 
   5 class timer:
   6     def __init__(self):
   7         self.start= time.time()
   8     def stop(self):
   9         self.end= time.time()
  10         return  "\n 本次运行已用时 %s秒"% (self.end-self.start)
  11 
  12 class people:
  13         #定义四个玩牌的人
  14         jack = []
  15         python = []
  16         java = []
  17         stephen = []
  18         def print_jack(self):
  19             i=0
  20             tmp = ''
  21             while i < 13:
  22                 tmp += self.jack[i]+' '
  23                 i += 1
  24             return tmp
  25         def print_python(self):
  26             i=0
  27             tmp = ''
  28             while i < 13:
  29                 tmp += self.python[i]+' '
  30                 i += 1
  31             return tmp
  32         def print_java(self):
  33             i=0
  34             tmp = ''
  35             while i < 13:
  36                 tmp += self.java[i]+' '
  37                 i += 1
  38             return tmp
  39         def print_stephen(self):
  40             i=0
  41             tmp = ''
  42             while i < 13:
  43                 tmp += self.stephen[i]+' '
  44                 i += 1
  45             return tmp
  46 
  47 class agent(people):
  48     def __init__(self):
  49         #定义一个空的列表存放牌
  50         self.card = []
  51         #定义洗牌的次数,并不是越大牌就越乱
  52         self.count = 52
  53         #定义一个基本的牌序
  54         self.base = ['A','2','3','4','5','6','7','8','9','10','J','Q','K']
  55 
  56     def arrangement(self):
  57         #重新排列纸牌
  58         i = 0
  59         while i < 13:
  60             self.card.insert(0,'黑桃'+self.base[i])
  61             i += 1
  62         i = 0
  63         while i < 13:
  64             self.card.insert(0,'红桃'+self.base[i])
  65             i += 1
  66         i = 0
  67         while i < 13:
  68             self.card.insert(0,'草花'+self.base[i])
  69             i += 1
  70         i = 0
  71         while i < 13:
  72             self.card.insert(0,'方块'+self.base[i])
  73             i += 1
  74 
  75     def shuffle(self):
  76         #洗牌
  77         i = 1
  78         while i < self.count:
  79             #产生一个随机数来确定要交换牌的位置
  80             position = random.randint(0,51)
  81             #临时取出一张牌准备用来交换位置
  82             tmp = self.card[position]
  83             #交换随机位置的牌和第一张牌
  84             self.card[position] = self.card[0]
  85             self.card[0] = tmp
  86             i += 1
  87 
  88     def outcards(self):
  89         #发牌
  90         i = 0
  91         while i < 52:
  92             if i <= 12:
  93                 self.jack.insert(0,self.card[i])
  94             elif i >= 12 and i <= 25 :
  95                 self.python.insert(0,self.card[i])
  96             elif i >= 25 and i <= 38 :
  97                 self.java.insert(0,self.card[i])
  98             elif i >= 38 and i <= 52 :
  99                 self.stephen.insert(0,self.card[i])
 100             i += 1
 101 
 102 start=timer()
 103 a=agent()
 104 a.arrangement()
 105 a.shuffle()
 106 a.outcards()
 107 print a.print_jack()
 108 print a.print_python()
 109 print a.print_java()
 110 print a.print_stephen()
 111 print start.stop()

另外,0.00999999046326好象是最小计数了,再小就计算成0秒了。

2.4. 如何打印对象的方法和属性

有时候我们需要让对象可以打印来方便调试,可以重载对象的str方法来实现。

class header:
    """dns消息头抽象类"""
    def __init__(self, qid = 1):
        #初始化消息头
        self.qid       = qid #DNS 查询封包编号,作为确认依据。长度为16 byte
        self.qr        = 0   #查询封包为 0 ﹔回应为 1 。长度为 1 byte
        self.opcode    = 0   #封包类别(QUERY, IQUERY, STATUS, Reserved)。长度为 4 bytes。
        self.aa        = 0   #Flags共 4 bytes ,各表示:AA(Authoritative Answer)、TC(Truncation)、RD(Recursion Desired)、RA(Recursion Avalable)。
        self.tc        = 0
        self.rd        = 1
        self.ra        = 0
        self.reserved  = 0    #保留未用。
        self.rcode     = 0    #回应讯息,长 4 bytes ,除 0 及 6-15 保留未用外,1-5 分别为:Format Error、Server Failure、Name
        self.qsection  = 1    #问题部分,只支持1。
        self.ansection = 0    #答案部分。
        self.ausection = 0    #权力部分。
        self.arsection = 0    #另外的部分。

    def __str__(self):
        """字符串化"""
        if len(self.__dict__) > 0:
            plist = []
            for field in self.__dict__:
                plist.append(str(field) + ":"  + str(self.__dict__[field]))
            return reduce(lambda x,y: x + "\n" + y, plist)
        else:
            return ""

test=header()

print test

2.5. 使用Twisted实现一个简单Web服务器

作者:梅劲松 版权:本文档为MIT授权 运行环境:Python 2.3+Twisted的py-23安装版本

自己实现Web服务器的优点就不用说太多了,主要是能控制具体的实现。也能按照自己的习惯实现互动方式。

而Twisted在tcp以下是C写的,ip和udp部分应该是C和Python的混合产物,而http smtp等则是Python的,自己能很好的扩充。

下面来看个具体的例子:

首先你需要编辑一个html为结尾的文件名放到你的htm目录下。

然后在htm的上一级目录建立一个文件,文件名为web.py,内容如下:

代码:

PORT = 80#这个是80,如果你的端口被占用了,换成其他的                                                                     
                                                            
                                                                                
from twisted.web.resource import Resource                                       
from twisted.web import server                                                   
from twisted.web import static                                                   
from twisted.internet import reactor 
                                                                                                              
                                                      
class ReStructured( Resource ):                                                                                                                                 
  def __init__( self, filename, *a ):                                         
      self.rst = open( filename ).read( )                                                                                                               
                                                                                
  def render( self, request ): 
      return self.rst               
                                                                                

resource = static.File('./htm/')                                                   
resource.processors = { '.html'  : ReStructured }                               
resource.indexNames = [ 'index.html']                                   
                                                                                
reactor.listenTCP(                                                               
      PORT,                                                                   
      server.Site( resource )                                                 
      )                                                                       
reactor.run( ) 

 

在控制台下进入目录输入 python web.py,然后打开浏览器,输入http://127.0.0.1,看到你的站点了吗?

3. sip

3.1. 用sip服务器实现msn企业内部交流

winxp里面的windows messager可以登陆企业自己的sip服务器,实现企业内部交流。

你可以在这里下载berkeke OnDO SIP Server 1.2

http://www.brekeke-sip.com/download/oss/dl/oss1_2_7_4.exe

如果不能下载,请自己想办法。这个软件确实可下,只不过被国际出口上锁住了。

配置berkeke server的方法在QQ群6390838里的朋友苏醒的帮助下,终于成功了。

在服务里启动server后,进入http://localhost:18080/oss/ 页面,用sa,sa登录后,选择Authentication栏目,在右边的edit框里填写用户的名称

User

Password

(Confirm)

Name

Email Address

Description

接着按"add"按钮,有Update succeeded. 字样。然后在windows messenger里的服务器ip写你的服务器ip,比如192.168.3.135,可以不加端口号5060。还要把连接方式改为udp(你连接不上,可能是你没改协议的原因)。可以不写用户名。然后登录,在登录的时候写你的User名。

这段时间准备用python写一个sip服务器提供给windows messager用,有兴趣的朋友可以和我联系。

3.2. 反馈

  • 看起来很不错,不过有没有对于sip服务更详细的东西?还有你的nt服务的下一章还没开始啊。 -- Dreamingk