/sagator-1.2.3/srv/webq.py
Python | 307 lines | 285 code | 8 blank | 14 comment | 5 complexity | e2b7d7b4f143e7e0232ef6d2ea147063 MD5 | raw file
Possible License(s): GPL-2.0
- '''
- webq.py - An service for sagator's quarantine over HTTP.
- (c) 2005-2009 Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- '''
- import re, db, gettext, avlib
- from aglib import *
- from avir.basic import const
- from avlib import trans_class, _
- from BaseHTTPServer import HTTPServer
- from SimpleHTTPServer import SimpleHTTPRequestHandler
- from cgi import parse_qs
- from crypt import crypt
- import base64, binascii
- __all__ = ['webq']
- # load neccessary libraries for chroot
- crypt("sagator", '$1$sagator$')
- class condition:
- def __init__(self, WA):
- self.WA=WA
- def like(self, key, value):
- if not value:
- return None
- if value[:1]=='!':
- self.WA.append(value[1:])
- return key+" NOT LIKE %s"
- else:
- self.WA.append(value)
- return key+" LIKE %s"
- def date(self, key, value, dir):
- if not value:
- return None
- self.WA.append(value)
- return "%s %s %%s" % (key, dir)
- def eq(self, key, user):
- if not user:
- return None
- if user[:1]=='!':
- oper="!="
- user=user[1:]
- else:
- oper="="
- self.WA.append(user)
- return key+oper+'%s'
- class webq(ServiceTCPServer,HTTPServer):
- '''
- Web service for sagator's quaratine access.
-
- This service can be used to access email collected by sagator via
- web interface.
-
- Requirements: python-genshi-0.4 or higher
- Usage: webq(host='0.0.0.0', port=8008, db, log='/var/log/sagator/webq.log',
- scanner, userconv)
- Where: host is an string, which defines IP address to bind,
- default: 0.0.0.0
- port is an integer, which defines tcp port to listen, default: 8008
- db is a database connection. For description see Databases.txt.
- log is defining a log file name, by default /var/log/sagator/webq.log
- scanner is a scanner to use for checking (only one scanner
- can be used here and it must be a buffer scanner!)
- userconv is an array, which defines regular expression
- and substitution strings. Usernames from login prompt
- are marched against this regular expression and substitued
- by substitution string.
-
- It is recommended to use apache mod_proxy module to redirect standard
- web traffic from port 80 to webq()'s 8008. For example:
- ProxyPass /webq http://localhost:8008
- ProxyPassReverse /webq http://localhost:8008
- Example: See default config file for example.
- New in version 0.9.0.
- '''
- name='webq()'
- def __init__(self, host='0.0.0.0', port=8008, db=None,
- log='/var/log/sagator/webq.log',
- scanner=const(0),
- userconv=['^(.*)$','\\1']):
- self.BINDTO=(host, port)
- self.LOGFILE=log
- self.SCANNERS=[scanner]
- self.WORK_DIR=os.path.dirname(os.path.abspath(avlib.__file__))
- self.WEB_ROOT=os.path.join(self.WORK_DIR, 'srv/web')
- self.REQUEST_HANDLER=webq_request_handler
- self.REQUEST_HANDLER.ENV={
- 'gettext': _,
- 'DB': db,
- 'SCANNER': scanner,
- 'REQUESTS': {},
- 'REQ': {},
- 'LANGS': ['en_US', 'sk_SK'],
- 'condition': condition,
- 'check_passwd': self.check_passwd,
- 'change_passwd': self.change_passwd
- }
- from genshi.template import TemplateLoader
- # email parser is required to run in chroot
- import email
- email.message_from_string('') # ignore output, just load module
- self.REQUEST_HANDLER.load_template = TemplateLoader().load
- self.REQUEST_HANDLER.USERCONV_REG = re.compile(userconv[0])
- self.REQUEST_HANDLER.USERCONV_REPL = userconv[1]
- try:
- import cracklib
- except ImportError, err:
- try:
- # debian hack to load renamed cracklib
- import crack as cracklib
- except ImportError:
- cracklib = None
- debug.echo(1, "webq(): Cracklib import failed (%s), "
- "disabling cracklib functionality." % err)
- self.cracklib = cracklib
- if cracklib:
- self.cracklib_trans = trans_class('cracklib')
- def test_scanners(self, scanner):
- ServiceTCPServer.test_scanners(self, self.SCANNERS)
- # copy all templates
- import shutil
- try:
- import srv.web
- except ImportError:
- debug.echo(1, 'webq(): ERROR: webq not installed, '
- 'please install sagator-webq package')
- raise
- webqsource=os.path.abspath(os.path.dirname(srv.web.__file__))
- webqtarget=safe.fn(self.WEB_ROOT)
- if webqsource!=webqtarget:
- try:
- if not os.path.isdir(webqtarget):
- debug.echo(9, "Making webq chroot directory: ", webqtarget)
- os.makedirs(webqtarget)
- for f in os.listdir(webqsource):
- if os.path.isfile(os.path.join(webqsource, f)):
- debug.echo(9, "Copying webq file: ", f)
- shutil.copy(os.path.join(webqsource, f),
- os.path.join(webqtarget, f))
- except OSError, e:
- debug.echo(9, "OSError: %s" % e)
- def serve_forever(self):
- if safe.fn('/usr')=='/usr':
- os.chdir(self.WORK_DIR)
- sys.path.insert(0, 'srv/web')
- import srv.web # reimport for chroot
- self.sighup(0,0) # redirect logs
- HTTPServer.serve_forever(self)
- def sighup(self,sn,stack):
- if debug.logfile=="-":
- return
- # reopen logs
- os.close(1)
- os.open(self.LOGFILE, os.O_CREAT|os.O_WRONLY|os.O_APPEND, 0640)
- os.dup2(1,2) # copy to stderr fd
- def check_passwd(self, DB, login, old):
- passwd, perms, lang, showrows = DB.query(
- "SELECT pass,perms,lang,showrows "
- "FROM webaccess WHERE email=%s",
- [login]
- )[0]
- if passwd!=crypt(old, passwd):
- return dict()
- return dict(
- REMOTE_LOGIN = login,
- PERMS = perms,
- LANG = lang or "en_US",
- SHOW_ROWS = showrows or 50
- )
- def change_passwd(self, DB, login, old, new, retyped, lang='C'):
- if not new:
- return ""
- if not self.check_passwd(DB, login, old):
- return "Wrong password!"
- if self.cracklib:
- try:
- e = self.cracklib.FascistCheck(new)
- except ValueError, e:
- pass
- if e and e!=new:
- self.cracklib_trans.set_lang(lang)
- return _('Password %s.') % self.cracklib_trans.gettext(str(e))
- if new != retyped:
- return "New and retyped passwords does not match!"
- DB.execute("UPDATE webaccess SET pass=%s WHERE email=%s",
- [crypt(new, '$1$'+randomchars(8)), login])
- return "Password changed successfully."
- class webq_request_handler(SimpleHTTPRequestHandler):
- def do_POST(self):
- return self.send_head()
- def send_head(self):
- path = self.path
- i = path.rfind('?')
- if i>=0:
- path, query = path[:i], path[i+1:]
- else:
- query = ''
- if not path.strip('/'):
- path = '/index.html'
- scriptfile = self.translate_path("srv/web/"+path)
- if not os.path.exists(scriptfile):
- self.send_error(404, "No such template (%r)" % scriptfile)
- return
- if not os.path.isfile(scriptfile):
- self.send_error(403, "Template is not a plain file (%r)" % path)
- return
- ns=self.ENV.copy()
- if query:
- ns['QUERY_STRING'] = query
- ns['REQUESTS'].update(parse_qs(query, True))
- length = int(self.headers.getheader('content-length') or '0')
- if length > 0:
- data=self.rfile.read(length)
- ns['REQUESTS'].update(parse_qs(data, True))
- for key,value in ns['REQUESTS'].items():
- ns['REQ'][key]=value[0].decode('UTF-8')
- ns['CONTENT_LENGTH'] = length
- ns['REMOTE_ADDR'] = self.client_address[0]
- ns['REMOTE_USER'] = ''
- ns['REMOTE_LOGIN'] = ''
- ns['PERMS'] = ''
- ns['LANG'] = 'C'
- ns.update(self.check_auth())
- ns['gettext'].set_lang(ns['LANG'])
- if not ns.get('REMOTE_USER'):
- self.send_response(401, "Authorization Required")
- self.send_header("WWW-Authenticate", 'Basic realm="Restricted access"')
- self.end_headers()
- self.wfile.write("Authorization Required")
- return
- try:
- template = self.load_template(scriptfile)
- s = template.generate(**ns).render('xhtml')
- self.send_response(200, "Script output follows")
- self.send_header("Content-type", "text/html; charset=UTF-8")
- self.send_header("Content-Length", str(len(s)))
- self.end_headers()
- self.wfile.write(s)
- except Exception, e:
- import traceback
- s=traceback.format_exc()
- self.send_response(500, "Internal server error!")
- self.send_header("Content-type", "text/plain; charset=UTF-8")
- self.send_header("Content-Length", str(len(s)))
- self.end_headers()
- self.wfile.write(s)
- def check_auth(self):
- '''
- Check authorization request.
- Return value is an empty dictionary, if authorization fails or an
- namespace update on success.
- '''
- env={}
- authorization = self.headers.getheader("authorization")
- if authorization:
- authorization = authorization.split()
- if len(authorization) == 2:
- env['AUTH_TYPE'] = authorization[0]
- if authorization[0].lower() == "basic":
- try:
- authorization = base64.decodestring(authorization[1])
- except binascii.Error:
- pass
- else:
- authorization = authorization.split(':')
- if len(authorization) == 2:
- env.update(self.check_pass(authorization))
- return env
- def check_pass(self, auth):
- '''
- Check user password. Parameter auth is an array of:
- ['login','plain text password']
- Return value is an empty dictionary, if authorization fails,
- or an namespace update on success.
- '''
- try:
- passwd,perms,lang,showrows = self.ENV['DB'].query(
- "SELECT pass,perms,lang,showrows FROM webaccess WHERE email=%s",
- auth[:1]
- )[0]
- except IndexError:
- return {} # no record found for this login
- cryptpass = crypt(auth[1], passwd)
- if passwd!=cryptpass:
- return {}
- return {
- 'REMOTE_USER': self.USERCONV_REG.sub(self.USERCONV_REPL, auth[0]),
- 'REMOTE_LOGIN': auth[0],
- 'PERMS': perms,
- 'LANG': lang or "en_US",
- 'SHOW_ROWS': showrows or 50
- }