Skip to content

Instantly share code, notes, and snippets.

@thwarted
Created March 25, 2013 22:59
Show Gist options
  • Save thwarted/5241639 to your computer and use it in GitHub Desktop.
Save thwarted/5241639 to your computer and use it in GitHub Desktop.
Stupid and simple supybot plugin that does LDAP/AD lookups and returns phone and pager contact information. From Yelp! Hack-a-thon X, going for the "Useful" award.
###
# Copyright (c) 2013, Andy Bakun and Yelp!
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
# This will be called by supybot to configure this module. advanced is
# a bool that specifies whether the user identified himself as an advanced
# user or not. You should effect your configuration by manipulating the
# registry as appropriate.
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Scout', True)
Scout = conf.registerPlugin('Scout')
# This is where your configuration variables (if any) should go. For example:
# conf.registerGlobalValue(Random, 'someConfigVariableName',
# registry.Boolean(False, """Help for someConfigVariableName."""))
conf.registerGlobalValue(Scout, 'ForceMailTo', registry.String('', """For debugging, send all email to a single address"""))
conf.registerGlobalValue(Scout, 'LDAP_URL', registry.String('', """LDAP Server URL"""))
conf.registerGlobalValue(Scout, 'LDAP_basedn_users', registry.String('', """LDAP base dn when searching for users"""))
conf.registerGlobalValue(Scout, 'LDAP_basedn_groups', registry.String('', """LDAP base dn when searching for groups"""))
conf.registerGlobalValue(Scout, 'LDAP_username', registry.String('', """Bind to LDAP using this username"""))
conf.registerGlobalValue(Scout, 'LDAP_password', registry.String('', """Bind to LDAP using this password"""))
conf.registerGlobalValue(Scout, 'LDAP_user_filter', registry.String('', """LDAP search filter to use when searching for users"""))
conf.registerGlobalValue(Scout, 'LDAP_grouplist_filter', registry.String('', """LDAP search filter to use when discoverying the groups"""))
conf.registerGlobalValue(Scout, 'LDAP_groupmember_filter', registry.String('', """LDAP search filter to use when listing the members of a group"""))
conf.registerGlobalValue(Scout, 'mailfrom', registry.String('', """email address that email from the bot comes from"""))
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
###
# Copyright (c) 2013, Andy Bakun and Yelp!
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import random
import re
import string
import ldap
import os
import sys
import smtplib
from email.mime.text import MIMEText
class Scout(callbacks.Plugin):
"""This plugin provides a few random number commands and some
commands for getting random samples. Use the "seed" command to seed
the plugin's random number generator if you like, though it is
unnecessary as it gets seeded upon loading of the plugin. The
"random" command is most likely what you're looking for, though
there are a number of other useful commands in this plugin. Use
'list random' to check them out. """
threaded = False
def __init__(self, irc):
self.__parent = super(Scout, self)
self.__parent.__init__(irc)
self.rng = random.Random()
self.rng.seed()
def random(self, irc, msg, args):
"""takes no arguments
Returns the next random number from the random number generator.
"""
irc.reply(str(self.rng.random()))
random = wrap(random)
def ldap_open(self):
con = ldap.initialize(self.registryValue('LDAP_URL'), trace_level=0, trace_file=sys.stderr)
con.set_option(ldap.OPT_DEBUG_LEVEL, 255)
con.set_option(ldap.OPT_NETWORK_TIMEOUT, 3)
con.set_option(ldap.OPT_REFERRALS, 0)
con.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)
con.start_tls_s()
con.simple_bind_s(self.registryValue('LDAP_username'), self.registryValue('LDAP_password'))
return con
def ldap_close(self, con):
con.unbind_s()
def find_user(self, who):
if not re.match(r'^(\w+)$', who):
return None
con = self.ldap_open()
try:
filterstr = self.registryValue('LDAP_user_filter') % (who,)
results = con.search_s(self.registryValue('LDAP_basedn_users'), ldap.SCOPE_SUBTREE, filterstr=filterstr, attrlist=['pager', 'mobile', 'cn'])
except:
results = ()
finally:
self.ldap_close(con)
return results
def send_message(self, mto, subject, message):
mfrom = self.registryValue('mailfrom')
debugmto = self.registryValue('ForceMailTo')
if debugmto:
mto = debugmto
msg = MIMEText(message)
msg['Subject'] = subject
msg['From'] = mfrom
msg['To'] = mto
s = smtplib.SMTP('localhost')
s.sendmail(mfrom, [mto], msg.as_string())
s.quit()
def page(self, irc, msg, args, username, message):
"""<username> [<message>]
Sends a message to <username>
"""
try:
if msg.args[0][0] != "#":
irc.reply("I refuse to summon someone to a /private/ conversation.")
return
except:
return
results = self.find_user(username)
if results is None:
irc.reply('is confused by the username "%s".' % username, action=True)
return
if results:
userattr = results[0][1]
if 'pager' in userattr:
if message:
subject = "%s in %s" % (msg.nick, msg.args[0])
message = message.strip()
irc.reply("sends \"%s\" to %s's pager." % (message, username), action=True)
else:
subject = "%s requests your presence in %s" % (msg.nick, msg.args[0])
message = ""
irc.reply("tries to summon %s at %s's request." % (username, msg.nick), action=True)
self.send_message(userattr['pager'][0], subject, message)
else:
irc.reply("%s (%s) has no pager listed in the directory." % (userattr['cn'][0], username))
else:
irc.reply('could not find "%s"' % username, action=True)
page = wrap(page, ['something', optional('text')])
def lookup(self, irc, msg, args, username):
"""<username>
Shows contact information for <username>
"""
results = self.find_user(username)
if results is None:
irc.reply('is confused by the username "%s".' % username, action=True)
return
if results:
userattr = results[0][1]
info = "%s (%s)" % (userattr['cn'][0], username)
if "mobile" in userattr:
info += " can be reached at %s." % userattr['mobile'][0]
elif "pager" in userattr:
info += " can be paged at %s (no phone number listed)." % userattr['pager'][0]
else:
info += " does not have contact information recorded in the directory."
irc.reply(info)
else:
irc.reply('can\'t find a user named "%s".' % username, action=True)
lookup = wrap(lookup, ['something'])
def find_groups(self):
con = self.ldap_open()
try:
filterstr = self.registryValue('LDAP_grouplist_filter')
results = con.search_s(self.registryValue('LDAP_basedn_groups'), ldap.SCOPE_SUBTREE, filterstr=filterstr, attrlist=['cn'])
finally:
self.ldap_close(con)
return results
def groups(self, irc, msg, args):
"""
List the groups
"""
results = self.find_groups()
groups = [x[1]['cn'][0].lower() for x in results]
groups = filter(lambda x: x, groups)
irc.reply('I found %d groups: %s' % (len(groups), utils.str.commaAndify(sorted(groups))))
groups = wrap(groups, [])
def find_members(self, groupname):
if not re.match(r'^(\w+)$', groupname):
return None
con = self.ldap_open();
try:
filterstr = self.registryValue('LDAP_groupmember_filter') % groupname
results = con.search_s(self.registryValue('LDAP_basedn_users'), ldap.SCOPE_SUBTREE, filterstr=filterstr, attrlist=['sAMAccountName'])
finally:
self.ldap_close(con)
return results
def members(self, irc, msg, args, groupname):
"""<groupname>
List the members of <groupname>
"""
results = self.find_members(groupname)
if results is None:
irc.reply('is confused by the groupname "%s".' % groupname, action=True)
return
if results:
members = [x[1]['sAMAccountName'][0].lower() for x in results]
members = filter(lambda x: x, members)
irc.reply('I found %d members of %s: %s' % (len(members), groupname, utils.str.commaAndify(sorted(members))))
else:
irc.reply('Group %s appears to have no members or doesn\'t exist.' % groupname)
members = wrap(members, ['something'])
def help(self, irc, msg, args):
irc.reply("Known commands: groups; members <groupname>; lookup <username>; page <username> [<message>]")
help = wrap(help, [])
#def doPrivmsg(self, irc, msg):
# print repr(msg)
# (tochannel, textmsg) = msg.args
# return
#def inFilter(self, irc, msg):
# print repr(msg)
# return msg
#def doTopic(self, irc, msg):
# if len(msg.args) == 1:
# return # It's an empty TOPIC just to get the current topic.
# channel = msg.args[0]
# self.doLog(irc, channel,
# '*** %s changes topic to "%s"\n', msg.nick, msg.args[1])
Class = Scout
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
# These are just samples, supybot should write a list of these to
# the config file for you to edit.
# note that any SSL options that are necessary to connect to your
# LDAP/AD server will need to be specified in the environment before
# starting supybot:
# LDAPTLS_REQCERT=demand
# LDAPTLS_CACERT=/path/to/ca.cert
# since the python-ldap library uses the ldap library which reads
# these and this code doesn't handle setting them.
supybot.plugins.Scout: True
supybot.plugins.Scout.public: True
supybot.plugins.Scout.ForceMailTo:
supybot.plugins.Scout.LDAP_URL: ldap://your-ldap-server:389
supybot.plugins.Scout.LDAP_basedn_users: ou=Users...
supybot.plugins.Scout.LDAP_basedn_groups: ou=Groups...
supybot.plugins.Scout.LDAP_username: bind-username
supybot.plugins.Scout.LDAP_password: bind-password
supybot.plugins.Scout.LDAP_user_filter: (&(objectclass=user)(sAMAccountName=%s)(memberOf=CN=...))
supybot.plugins.Scout.LDAP_grouplist_filter: (&(objectclass=group)(|(cn=...)(cn=...)))
supybot.plugins.Scout.LDAP_groupmember_filter: (&(objectclass=user)(memberOf=CN=%s,OU=Groups...))
supybot.plugins.Scout.mailfrom: some-name
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment