基于数据库的新闻组系统,Python实现
netkiller
|
1#
netkiller 发表于 2006-07-05 16:29
基于数据库的新闻组系统,Python实现
usenet.py
#!/usr/bin/python # -*- coding: utf-8 -*- """ Project: Network News Transport Protocol Server Program Description: 基于数据库的新闻组,实现BBS前端使用NNTP协议来访问贴子 Reference: NNTP协议: http://www.mibsoftware.com/userkt/0099.htm 正则表达式: http://wiki.woodpecker.org.cn/mo ... 58cbcde24613d941e9d ------------------------------------------------------------------------- python-chinese Post: send python-chinese@lists.python.cn Subscribe: send subscribe to python-chinese-request@lists.python.cn Unsubscribe: send unsubscribe to python-chinese-request@lists.python.cn Detail Info: http://python.cn/mailman/listinfo/python-chinese ------------------------------------------------------------------------- """ import sys import socket import threading import time import asyncore, asynchat import string, StringIO, re #from netkiller import * import email from messages import Messages #print Messages.banner # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF) CRLF = '\r\n' class nntp_server (asyncore.dispatcher): channel_counter = 0 def __init__ (self, host, port): asyncore.dispatcher.__init__ (self) self.create_socket (socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() self.there = (host, port) self.bind (self.there) self.listen (5) def handle_connect(self): pass def handle_accept (self): conn, addr = self.accept() nntp_receiver (self, (conn, addr)) def handle_error(self): pass def handle_close (self): self.close() class nntp_receiver (asynchat.async_chat): def __init__ (self, server, (conn, addr)): asynchat.async_chat.__init__ (self, conn) self.set_terminator ('\r\n') self.server = server self.server.channel_counter = self.server.channel_counter + 1 self.id = self.server.channel_counter #self.sender = nntp_sender (self, server.there) #self.sender.id = self.id self.buffer = '' self.handle_connect() self.current_group = group_selected(None) def handle_connect (self): if self.connected : self.push(Messages.banner) def collect_incoming_data (self, data): self.buffer = self.buffer + data def found_terminator (self): data = self.buffer self.buffer = '' if self.get_terminator() == '\r\n': parse = self.isCommand(data) if parse: parse[0] = parse[0].lower() self.log('command:'+parse[0]) Usenet(self,parse,self.current_group) if parse[0] == 'post': self.set_terminator ('\r\n.\r\n') if parse[0] == 'quit': self.handle_close() else: Usenet(self,['post',data],self.current_group) self.set_terminator ('\r\n') message = '<== (%d) %s' % (self.server.channel_counter, repr(data)) self.log(message) #self.log('current:'+self.current_group.get_group()) #self.sender.push (data + '\n') def close_when_done(): pass def handle_close (self): self.log('Closing') self.server.channel_counter = self.server.channel_counter - 1 #self.sender.close() self.close() def isCommand(self,data): rcommand = ( r'^mode [reader|stream]', r'^list$',r'^list ',r'^listgroup (.+)',r'^list [active|active.times|newsgroups|subscriptions]', r'^xover [0-9]+-[0-9]+', r'^newgroups [0-9]+ [0-9]+ ', r'^group .+', r'^newgroups [0-9]+ [0-9]+ [a-zA-Z]', r'^head [0-9]+', r'^body [0-9]+', r'^article [0-9]+', r'^post$',r'^next$',r'^last$', r'^ihave$',r'^slave$', r'^help$',r'^quit$' ) parse = [] for command in rcommand: digs = re.compile(command,re.IGNORECASE) if digs.match(data): parse = data.split(' ') return parse self.push("500 command not recognized\r\n") return None """ class nntp_sender (asynchat.async_chat): def __init__ (self, receiver, address): asynchat.async_chat.__init__ (self) self.receiver = receiver self.set_terminator (None) self.create_socket (socket.AF_INET, socket.SOCK_STREAM) self.buffer = '' self.set_terminator ('\n') self.connect (address) def handle_connect (self): print 'Connected' def collect_incoming_data (self, data): self.buffer = self.buffer + data def found_terminator (self): data = self.buffer self.buffer = '' print '==> (%d) %s' % (self.id, repr(data)) self.receiver.push (data + '\n') def handle_close (self): self.receiver.close() self.close() """ class group_selected: #current = None def __init__(self,value): self.set_group(value) def set_group(self,value): self.current = value def get_group(self): return self.current class Usenet: #threading.Thread conn = None addr = None buffer = None msg = None conn = None current_group = None HELP = """\ HELP \r\n 100 Legal commands \r\n authinfo user Name|pass Password \r\n article [MessageID|Number] \r\n body [MessageID|Number] \r\n check MessageID \r\n date \r\n group newsgroup \r\n head [MessageID|Number] \r\n help \r\n ihave \r\n last \r\n list [active|active.times|newsgroups|subscriptions] \r\n listgroup newsgroup \r\n mode stream \r\n mode reader \r\n newgroups yymmdd hhmmss [GMT] [<distributions>] \r\n newnews newsgroups yymmdd hhmmss [GMT] [<distributions>] \r\n next \r\n post \r\n slave \r\n stat [MessageID|Number] \r\n takethis MessageID \r\n xgtitle [group_pattern] \r\n xhdr header [range|MessageID] \r\n xover [range] \r\n xpat header range|MessageID pat [morepat...] \r\n . \r\n""" def __init__(self, conn,command,current_group): self.conn = conn self.buffer = buffer self.msg = Messages() self.current_group = current_group command[0] = command[0].lower() self.commands(command) def welcome(self): self.conn.send(self.msg.banner) def list(self): lists = self.msg.list() #length = str(len(lists)) self.conn.send("215 list of newsgroups follows\r\n") for group in lists: self.conn.push(group+"\r\n") self.conn.push(".\r\n") def listgroup(self,group): group = command[1] numbers = self.msg.listgroup(group) if not numbers: Responses(self.conn,412) else: self.conn.send("211 Article list follows\r\n") for number in numbers: self.conn.push(number+"\r\n") self.conn.push(".\r\n") def xover(self, range): current = self.current_group.get_group() if not current: Responses(self.conn,412) else: first, last = range.split('-') xovers = self.msg.xover(current,first,last) self.conn.push("224 xover information follows\r\n") for xover in xovers: self.conn.push( xover + "\r\n") self.conn.push(".\r\n") def article(self,part,MessageID): current = self.current_group.get_group() if not current: Responses(self.conn,412) else: text = self.msg.article(current,MessageID) if not text: Responses(self.conn,423) else: if part == 'head': message = email.message_from_string(text).__dict__ _headers = message['_headers'] self.conn.push("221 "+MessageID+" <"+current+'.'+MessageID+"@lists.netkiller.org> article retrieved - head follows\r\n") for key, value in _headers: self.conn.push(key+': '+value+CRLF) elif part == 'body': message = email.message_from_string(text).__dict__ _payload = message['_payload'] self.conn.push("222 "+MessageID+" <"+current+'.'+MessageID+"@lists.netkiller.org> article retrieved - body follows\r\n") self.conn.push(_payload) elif part == 'article': self.conn.push("220 "+MessageID+" <"+current+'.'+MessageID+"@lists.netkiller.org> article retrieved - head and body follows\r\n") self.conn.push(text) self.conn.push(CRLF) self.conn.push(".\r\n") def commands(self, command): if not command: return True if command[0] == 'mode': self.conn.push(self.msg.banner) elif command[0] == 'list': self.list() elif command[0] == 'listgroup': group = command[1] self.listgroup(group) elif command[0] == 'group': current = command[1] isNone = self.msg.group(current) if not isNone: Responses(self.conn,411) else: self.current_group.set_group(current) number,first,last,group = isNone self.conn.push("211 " +number+' '+first+' '+last+' '+group+ " selected\r\n") elif command[0] == 'newgroups': #newgroups = self.msg.newgroups(950803,192708,'GMT') #length = len(newgroups) self.conn.push("231 list of new newsgroups follows.\r\n") #for name in newgroups: # self.conn.push(name+"\r\n") self.conn.push(".\r\n") elif command[0] =='article': MessageID = command[1] self.article('article',MessageID) elif command[0] =='head': MessageID = command[1] self.article('head',MessageID) elif command[0] =='body': MessageID = command[1] self.article('body',MessageID) elif command[0] == 'xover': range = command[1] self.xover(range) elif command[0] =='xhdr': xhdr = command[1] subject, range = xhdr.split(' ') first, last = range.split('-') self.msg.xhdr('subject',first,last) #length = len(xover_list) self.conn.push("221 "+subject+" field follows\r\n") for xo in xover_list: self.conn.send( xo + "\r\n") self.conn.send(".\r\n") elif command[0] == 'post': if len(command) == 1: self.conn.push("340 send article\r\n") elif len(command) == 2: data = command[1] post_msg = email.message_from_string(data) group = post_msg['Newsgroups'] MessageID = '<'+group+'@list.netkiller.org>' data = data.replace( '\r\n\r\n', '\r\n'+'Message-ID: '+MessageID+'\r\n\r\n', 1 ) data += '\r\n' self.msg.post(group,MessageID,data) Responses(self.conn,240) elif command[0] == 'rset': self.conn.push("250 OK\r\n") elif command[0] =='help': self.conn.push('100 help text follows\r\n') self.conn.push(self.HELP) elif command[0] =='quit': self.conn.push("205 closing connection - goodbye!\r\n") return True def close(self): self.conn.close() self.buffer = None self.conn = None self.addr = None class Responses: conn = None # Response numbers that are followed by additional text (e.g. article) LONGRESP = ['100', '215', '220', '221', '222', '224', '230', '231', '282'] status_dict = { 211 : '211 list of article numbers follow', 224 : '224 Overview information follows', 240 : '240 article posted ok', 340 : '340 send article to be posted. End with <CR-LF>.<CR-LF>', 411 : '411 no such news group', 412 : '412 No news group current selected', 420 : '420 No article(s) selected', 423 : '423 no such article number in this group', 440 : '440 posting not allowed', 441 : '441 posting failed', 0412 : '412 Not currently in newsgroup', 502 : '502 no permission' } """ 200 server ready - posting allowed 201 server ready - no posting allowed 202 slave status noted 205 closing connection - goodbye! 211 n f l s group selected 215 list of newsgroups follows 220 n <a> article retrieved - head and body follow 221 n <a> article retrieved - head follows 222 n <a> article retrieved - body follows 223 n <a> article retrieved - request text separately 230 list of new articles by message-id follows 231 list of new newsgroups follows 235 article transferred ok 240 article posted ok 335 send article to be transferred. End with <CR-LF>.<CR-LF> 340 send article to be posted. End with <CR-LF>.<CR-LF> 400 service discontinued 411 no such news group 412 no newsgroup has been selected 420 no current article has been selected 421 no next article in this group 422 no previous article in this group 423 no such article number in this group 430 no such article found 435 article not wanted - do not send it 436 transfer failed - try again later 437 article rejected - do not try again. 440 posting not allowed 441 posting failed 500 command not recognized 501 command syntax error 502 access restriction or permission denied 503 program fault - command not performed """ def __init__(self,conn,status): self.conn = conn if status == 205: pass if status in self.status_dict.keys(): result = self.status_dict[status] if result: self.conn.push(result+'\r\n') class Config: config_dict = { 'home' : ' ', 'host' : ' ', 'user' : ' ', 'passwd' : ' ', 'banner' : ' ' } def __init__(self,key): if not key: pass if key in self.config_dict.keys(): result = self.config_dict[key] if result: return result def main(): import os try: if os.name == 'nt': nntpd = nntp_server('127.0.0.1',119) asyncore.loop() elif os.name == 'posix': print 'posix' except KeyboardInterrupt: print "Crtl+C pressed. Shutting down." if __name__ == '__main__': main() |