No more passwords

This commit is contained in:
Dennis Kaarsemaker 2007-02-15 23:16:44 +01:00
parent a3481af25c
commit 30a8dfeda6
8 changed files with 248 additions and 161 deletions

View File

@ -16,144 +16,33 @@
import sys
sys.path.append('/var/www/bots.ubuntulinux.nl')
from commoncgi import *
import lp_auth
import sha
### Variables
db = '/home/dennis/ubugtu/data/bans.db'
lp_group = 'ubuntu-irc'
num_per_page = 100
con = sqlite.connect(db)
cur = con.cursor()
# Login check
person = None
error = ''
anonymous = form.has_key('anonymous')
anonlink = ''
if anonymous:
anonlink = '&anonymous=1';
user = None
# Delete old sessions
cur.execute("""DELETE FROM sessions WHERE time < %d""", int(time.time()) - 2592000 * 3)
# Registration?
if form.has_key('lpuser') and form.has_key('lpmail'):
cur.execute("""SELECT * FROM USERS WHERE username = %s""", form['lpuser'].value)
if len(cur.fetchall()):
error = """User is already registered"""
else:
import sha, commands, random
try:
newperson = lp_auth.LaunchpadPerson(nick=form['lpuser'].value, email=form['lpmail'].value)
except:
error = """Username incorrect. Your username is the $someone in
http://launchpad.net/people/$someone that is your
launchpad homepage"""
else:
mailsha = sha.new('mailto:%s' % form['lpmail'].value).hexdigest().lower()
if mailsha in newperson.mail_shasums:
if not newperson.key:
error = """Your launchpad account does not have a GPG key. Please
set a GPG key on launchpad"""
else:
chars = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"
password = ""
salt = ""
for i in xrange(8):
password += chars[random.randint(0,len(chars)-1)]
salt += chars[random.randint(0,len(chars)-1)]
try:
os.system('gpg --homedir /tmp --keyserver hkp://subkeys.pgp.net --recv-keys %s 2>/dev/null' % newperson.key)
(infd, outfd) = os.popen2('gpg --homedir /tmp --encrypt --armor --trust-model always --recipient %s 2>/dev/null'
% newperson.key)
infd.write(password)
infd.close()
gpg = outfd.read()
outfd.close()
except:
error = "A gpg error occured. Please check your key on launchpad"
else:
fd = os.popen('mail -a "From: Ubugtu <ubugtu@ubuntu-nl.org>" -s "Your bantracker account" %s'
% form['lpmail'].value.replace('ubuntu@sourceguru.net','mezzle@gmail.com'), 'w')
fd.write(gpg)
fd.close()
error = "Your password has been sent (encrypted) to your e-mail address"
cur.execute("""INSERT INTO users (username, salt, password) VALUES (%s, %s, %s)""",
(form['lpuser'].value, salt,
sha.new(salt + sha.new(password + salt).hexdigest().lower()).hexdigest().lower()))
con.commit()
else:
error = """Username and mailaddress don't match. Username is the $someone
in http://launchpad.net/people/$someone that is your
launchpad homepage"""
# Session handling
if form.has_key('sess'):
cookie['sess'] = form['sess'].value
if cookie.has_key('sess'):
try:
sess = cookie['sess'].value
cur.execute("""SELECT user FROM sessions WHERE session_id=%s""",sess)
user = cur.fetchall()[0][0]
person = pickle.loads(user)
except:
con.commit()
pass
# Login
if not person and form.has_key('user') and form.has_key('pw'):
import sha
cur.execute("SELECT salt, password FROM users WHERE username = %s", form['user'].value)
data = cur.fetchall()
if data:
salt, password = data[0]
if password != sha.new(salt + sha.new(form['pw'].value + salt).hexdigest().lower()).hexdigest().lower():
error = "Username or password incorrect"
else:
try:
person = lp_auth.LaunchpadPerson(nick = form['user'].value)
except lp_auth.LaunchpadException:
person = None
error = 'An error occured while talking to launchpad'
person.authenticated = True
if person.check_group_membership(lp_group):
# Create a session
sessid = md5.new('%s%s%d' % (os.environ['REMOTE_ADDR'], time.time(), random.randint(1,100000))).hexdigest()
cookie['sess'] = sessid
try:
cur.execute("""INSERT INTO sessions (session_id, user, time) VALUES
(%s, %s, %d);""", (sessid, pickle.dumps(person), int(time.time())))
except:
con.commit()
raise
con.commit()
else:
person.authenticated = False
error = "You are not in the '%s' group on launchpad" % lp_group
# Not authenticated.
if not (person and person.authenticated) and not anonymous:
if error:
print """<span style="color:red">%s</span>""" % error
print """<form action="/bans.cgi" method="post">
Login:<br />
<input class="input" type="text" name="user" /><br />
Password:<br />
<input class="input" type="password" name="pw" /><br />
<input class="submit" type="submit" value="Log in" />
</form>
<form>
No account yet? Enter your launchpad name and mailaddress
here.<br /><br />
Name:<br />
<input class="input" type="text" name="lpuser" /><br />
Mail address:<br />
<input class="input" type="text" name="lpmail" /><br /><br />
<input class="submit" type="submit" value="Request password" />
</form>
<a href="/bans.cgi?anonymous=1">Browse the bantracker anonymously</a>
"""
send_page('bans.tmpl')
# Log
if form.has_key('log'):
cur.execute("""SELECT log FROM bans WHERE id=%s""", form['log'].value)
@ -164,23 +53,22 @@ if form.has_key('log'):
# Main page
# Process comments
if form.has_key('comment') and form.has_key('comment_id') and not anonymous:
if form.has_key('comment') and form.has_key('comment_id') and user:
cur.execute("""SELECT ban_id FROM comments WHERE ban_id=%s and comment=%s""", (form['comment_id'].value, form['comment'].value))
comm = cur.fetchall()
if not len(comm):
cur.execute("""INSERT INTO comments (ban_id, who, comment, time) VALUES (%s, %s, %s, %s)""",
(form['comment_id'].value,person.name,form['comment'].value,pickle.dumps(datetime.datetime.now(pytz.UTC))))
(form['comment_id'].value,user,form['comment'].value,pickle.dumps(datetime.datetime.now(pytz.UTC))))
con.commit()
# Write the page
print '<form action="bans.cgi" method="POST">'
if anonymous:
print '<input type="hidden" name="anonymous" value="1" />'
# Personal data
print '<div class="pdata">'
if not anonymous:
print 'Logged in as: %s <br /> ' % person.name
if user:
print 'Logged in as: %s <br /> ' % user
print 'Timezone: '
if form.has_key('tz') and form['tz'].value in pytz.common_timezones:
tz = form['tz'].value
@ -196,23 +84,6 @@ for zone in pytz.common_timezones:
print ' selected="selected"'
print ">%s</option>" % zone
print '</select><input class="submit" type="submit" value="change" /></form><br />'
if not anonymous:
if form.has_key('pw1') and form.has_key('pw2'):
pw1 = form['pw1'].value; pw2 = form['pw2'].value
if pw1 and pw2:
if pw1 != pw2:
print "Passwords don't match!<br />"
else:
cur.execute("SELECT salt FROM users WHERE username = %s", person.nick)
salt = cur.fetchall()[0][0]
cur.execute("UPDATE USERS SET password = %s WHERE username = %s",
(sha.new(salt + sha.new(pw1 + salt).hexdigest().lower()).hexdigest().lower(), person.nick))
con.commit()
print '<form action="bans.cgi" method="POST">'
print 'Password: '
print '<input class="input" type="password" name="pw1" size="10"/>'
print '<input class="input" type="password" name="pw2" size="10"/>'
print '<input class="submit" type="submit" value="change" /></form>'
print '</div>'
tz = pytz.timezone(tz)
@ -220,8 +91,6 @@ tz = pytz.timezone(tz)
# Search form
print '<div class="search">'
print '<form action="/bans.cgi" method="GET">'
if anonymous:
print '<input type="hidden" name="anonymous" value="1" />'
print '<input class="input" type="text" name="query"'
if form.has_key('query'):
print 'value="%s" ' % form['query'].value
@ -266,7 +135,7 @@ if not form.has_key('query'):
cur.execute('SELECT COUNT(id) FROM bans')
nump = math.ceil(int(cur.fetchall()[0][0]) / float(num_per_page))
for i in range(nump):
print '<a href="bans.cgi?page=%d%s%s">%d</a> &middot;' % (i, sort, anonlink, i+1)
print '<a href="bans.cgi?page=%d%s">%d</a> &middot;' % (i, sort, i+1)
print '</div>'
# Empty log div, will be filled with AJAX
@ -282,7 +151,7 @@ for h in [['Channel',0], ['Nick/Mask',1], ['Operator',2], ['Time',6]]:
if v < 10: h[1] += 10
except:
pass
print '<th><a href="bans.cgi?sort=%s%s">%s</a></th>' % (h[1],anonlink,h[0])
print '<th><a href="bans.cgi?sort=%s">%s</a></th>' % (h[1],h[0])
print '<th>Log</th></tr>'
# Select and filter bans
@ -385,7 +254,7 @@ for b in bans[start:end]:
print q(c[1])
print u' <span class="removal"><br />%s, %s</span><br />' % \
(c[0],pickle.loads(c[2]).astimezone(tz).strftime("%b %d %Y %H:%M:%S"))
if not anonymous:
if user:
print """<span class="pseudolink" onclick="toggle('%s','comment')">Add comment</span>""" % b[6]
print """<div class="invisible" id="comment_%s"><br />""" % b[6]
print """<form action="bans.cgi" method="POST"><textarea cols="50" rows="5" class="input" name="comment"></textarea><br />"""

View File

@ -47,7 +47,8 @@ import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.ircmsgs as ircmsgs
import supybot.conf as conf
import sqlite, pytz, cPickle, datetime, time
import supybot.ircdb as ircdb
import sqlite, pytz, cPickle, datetime, time, random, md5
tz = 'Europe/Amsterdam'
@ -64,8 +65,8 @@ class Bantracker(callbacks.Plugin):
self.lastMsgs = {}
self.lastStates = {}
self.logs = {}
for channel in irc.state.channels:
self.doStatsLog(irc, channel, "START")
# for channel in irc.state.channels:
# self.doStatsLog(irc, channel, "START")
db = self.registryValue('database')
if db:
@ -114,14 +115,14 @@ class Bantracker(callbacks.Plugin):
s = time.strftime(format) + " " + ircutils.stripFormatting(s)
self.logs[channel] = self.logs[channel][-199:] + [s.strip()]
def doStatsLog(self, irc, chan, type):
if not self.registryValue('stats', chan):
return
num = len(irc.state.channels[chan].users)
format = conf.supybot.log.timestampFormat()
statslog = open('/home/dennis/ubugtu/data/statslog','a')
statslog.write("%-20s %f %s %-6s %d\n" % (chan, time.time(), time.strftime(format), type, num))
statslog.close()
# def doStatsLog(self, irc, chan, type):
# if not self.registryValue('stats', chan):
# return
# num = len(irc.state.channels[chan].users)
# format = conf.supybot.log.timestampFormat()
# statslog = open('/home/dennis/ubugtu/data/statslog','a')
# statslog.write("%-20s %f %s %-6s %d\n" % (chan, time.time(), time.strftime(format), type, num))
# statslog.close()
def doKickban(self, irc, channel, nick, target, kickmsg = None):
if not self.registryValue('enabled', channel):
@ -167,8 +168,8 @@ class Bantracker(callbacks.Plugin):
for channel in msg.args[0].split(','):
self.doLog(irc, channel,
'*** %s has joined %s\n' % (msg.nick or msg.prefix, channel))
if irc.prefix != msg.prefix:
self.doStatsLog(irc, msg.args[0], "JOIN")
#if irc.prefix != msg.prefix:
# self.doStatsLog(irc, msg.args[0], "JOIN")
def doKick(self, irc, msg):
if len(msg.args) == 3:
@ -183,7 +184,7 @@ class Bantracker(callbacks.Plugin):
self.doLog(irc, channel,
'*** %s was kicked by %s\n' % (target, msg.nick))
self.doKickban(irc, channel, msg.nick, target, kickmsg)
self.doStatsLog(irc, msg.args[0], "KICK")
#self.doStatsLog(irc, msg.args[0], "KICK")
def doPart(self, irc, msg):
for channel in msg.args[0].split(','):
@ -191,8 +192,8 @@ class Bantracker(callbacks.Plugin):
if msg.args[1].startswith('requested by'):
args = msg.args[1].split()
self.doKickban(irc, channel, args[2].replace(':',''), msg.nick, ' '.join(args[3:])[1:-1].strip())
if irc.prefix != msg.prefix:
self.doStatsLog(irc, msg.args[0], "PART")
#if irc.prefix != msg.prefix:
# self.doStatsLog(irc, msg.args[0], "PART")
def doMode(self, irc, msg):
channel = msg.args[0]
@ -228,7 +229,7 @@ class Bantracker(callbacks.Plugin):
for (channel, chan) in self.lastStates[irc].channels.iteritems():
if msg.nick in chan.users:
self.doLog(irc, channel, '*** %s has quit IRC (%s)\n' % (msg.nick, msg.args[0]))
self.doStatsLog(irc, channel, "QUIT")
#self.doStatsLog(irc, channel, "QUIT")
def outFilter(self, irc, msg):
# Gotta catch my own messages *somehow* :)
@ -239,7 +240,25 @@ class Bantracker(callbacks.Plugin):
self(irc, m)
return msg
def do366(self, irc, msg):
self.doStatsLog(irc, msg.args[1], "START")
#def do366(self, irc, msg):
# self.doStatsLog(irc, msg.args[1], "START")
def btlogin(self, irc, msg, args):
print "hi!"
if msg.tagged('identified'):
try:
user = ircdb.users.getUser(msg.prefix[:msg.prefix.find('!')])
except:
irc.error(conf.supybot.replies.incorrectAuthentication())
return
user.addAuth(msg.prefix)
ircdb.users.setUser(user, flush=False)
sessid = md5.new('%s%s%d' % (msg.prefix, time.time(), random.randint(1,100000))).hexdigest()
self.db_run("""INSERT INTO sessions (session_id, user, time) VALUES (%s, %s, %d);""",
(sessid, msg.prefix[:msg.prefix.find('!')], int(time.time())))
irc.reply('Log in at https://bots.ubuntulinux.nl/bans.cgi?sess=%s' % sessid, private=True)
btlogin = wrap(btlogin)
Class = Bantracker

1
LpLogin/README.txt Normal file
View File

@ -0,0 +1 @@
Insert a description of your plugin here, with any notes, etc. about using it.

38
LpLogin/__init__.py Normal file
View File

@ -0,0 +1,38 @@
###
# Copyright (c) 2007, Dennis Kaarsemaker
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
"""
Add a description of the plugin (to be presented to the user inside the wizard)
here. This should describe *what* the plugin does.
"""
import supybot
import supybot.world as world
__version__ = "0.1"
__author__ = supybot.Author("Dennis Kaarsemaker","Seveas","dennis@kaarsemaker.net")
__contributors__ = {}
__url__ = 'http://bots.ubuntulinux.nl/'
import config
reload(config)
import plugin
reload(plugin)
if world.testing:
import test
Class = plugin.Class
configure = config.configure

20
LpLogin/config.py Normal file
View File

@ -0,0 +1,20 @@
###
# Copyright (c) 2007, Dennis Kaarsemaker
# All rights reserved.
#
#
###
import supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('LpLogin', True)
LpLogin = conf.registerPlugin('LpLogin')
conf.registerGlobalValue(LpLogin, 'UserList',
registry.String('', """Filename of file with list of users"""))
conf.registerGlobalValue(LpLogin, 'DeleteUnknowns',
registry.Boolean(True, """Unregister people not in the list"""))

43
LpLogin/get_ircteam.py Executable file
View File

@ -0,0 +1,43 @@
#!/usr/bin/python
import urllib, urllib2
import xml.dom.minidom as dom
import re, sys, optparse
usage = "Usage: %prog [options] launchpad_group"
parser = optparse.OptionParser(usage=usage)
parser.add_option("-o", "--output", dest='outfile', help="Output to FILE",
metavar="FILE")
(options, args) = parser.parse_args()
if len(args) < 1:
parser.error('No group specified')
lp_group = args[0]
if options.outfile:
outfd = open(options.outfile,'w')
else:
outfd = sys.stdout
people_re = re.compile(r"""(?:href=".*?~(?P<person>[^/]*?)".*?)
#relationship.*?(href=".*?~(?P<group>.*?)".*?)*""",
re.VERBOSE | re.I | re.DOTALL)
nickname_re = re.compile(r'<code.*?120.*?>(.*?)</code>.*\n.*freenode',re.I)
def get_group_members(group):
nicks = []
u = urllib2.urlopen('http://launchpad.net/~%s' % urllib.quote(group))
html = u.read().lower()
# Split into people and groups
p1 = html.find('team members')
p2 = html.find('relationship to other teams')
p3 = html.find('a member of')
people = people_re.findall(html[p1:p2])
teams = people_re.findall(html[p2:p3])
for p in people:
u = urllib2.urlopen('http://launchpad.net/~%s' % urllib.quote(p))
html = u.read()
n = nickname_re.findall(html)
nicks += n
for t in teams:
nicks += get_group_members(t)
return [x.lower() for x in nicks]
outfd.write("\n".join(get_group_members(lp_group)))

83
LpLogin/plugin.py Normal file
View File

@ -0,0 +1,83 @@
###
# Copyright (c) 2007, Dennis Kaarsemaker
# All rights reserved.
#
#
###
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.ircmsgs as ircmsgs
import supybot.callbacks as callbacks
import supybot.ircdb as ircdb
import supybot.conf as conf
import random, os
class LpLogin(callbacks.Plugin):
"""Add the help for "@plugin help LpLogin" here
This should describe *how* to use this plugin."""
threaded = True
def __init__(self, irc):
callbacks.Plugin.__init__(self, irc)
uf = self.registryValue('UserList')
if not uf or not os.path.exists(uf):
self.log.info('Not loading non-existant userlist')
return
fd = open(uf)
users = fd.read()
fd.close()
knownusers = [x.lower() for x in users.split() if x]
self.knownusers = knownusers
allusers = [u.name.lower() for u in ircdb.users.itervalues()]
print knownusers, allusers
if self.registryValue('DeleteUnknowns'):
to_delete = [x for x in allusers if x not in knownusers and not
ircdb.users.getUser(x)._checkCapability('owner')]
for u in to_delete:
self.log.info("Would delete %s" % u)
to_add = [x for x in knownusers if x not in allusers]
for a in to_add:
self.log.info("Adding %s" % a)
user = ircdb.users.newUser()
user.name = a
rp = ''
chars = '''1234567890-=~!@#$%^&*()_qwertyuiop[]QWERTYUIOP{}}|asdfghjkl;ASDFGHJKL:zxcvbnm,./ZXCVBNM<>?'''
for i in range(16):
rp += chars[random.randint(1,len(chars))-1]
user.setPassword(rp)
irc.queueMsg(ircmsgs.IrcMsg('CAPAB IDENTIFY-MSG'))
def do290(self, irc, msg):
assert 'IDENTIFY-MSG' in msg.args[1]
irc.getRealIrc()._Freenode_capabed = True
def do376(self, irc, msg):
irc.queueMsg(ircmsgs.IrcMsg('CAPAB IDENTIFY-MSG'))
def inFilter(self, irc, msg):
if getattr(irc,'_Freenode_capabed',None) and msg.command == 'PRIVMSG':
first = msg.args[1][0]
rest = msg.args[1][1:]
msg.tag('identified', first == '+')
msg = ircmsgs.privmsg(msg.args[0], rest, msg=msg)
assert msg.receivedAt and msg.receivedOn and msg.receivedBy
return msg
def login(self, irc, msg, args):
if msg.tagged('identified'):
try:
user = ircdb.users.getUser(msg.prefix[:msg.prefix.find('!')])
except:
irc.error(conf.supybot.replies.incorrectAuthentication())
return
user.addAuth(msg.prefix)
ircdb.users.setUser(user, flush=False)
login = wrap(login)
Class = LpLogin
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

14
LpLogin/test.py Normal file
View File

@ -0,0 +1,14 @@
###
# Copyright (c) 2007, Dennis Kaarsemaker
# All rights reserved.
#
#
###
from supybot.test import *
class LpLoginTestCase(PluginTestCase):
plugins = ('LpLogin',)
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: