163 lines
6.2 KiB
Python
163 lines
6.2 KiB
Python
|
#
|
||
|
# Copyright (C) 2010 Roberto Leandrini <anaconda@ditalinux.it>
|
||
|
#
|
||
|
# 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.
|
||
|
#
|
||
|
# 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.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with this program; if not, write to the Free Software
|
||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
#
|
||
|
|
||
|
__module_name__ = 'cap_sasl'
|
||
|
__module_version__ = '0.5'
|
||
|
__module_description__ = 'SASL authentication plugin'
|
||
|
__module_author__ = 'Roberto Leandrini <anaconda@ditalinux.it>'
|
||
|
|
||
|
import base64
|
||
|
import ConfigParser
|
||
|
import os
|
||
|
|
||
|
import xchat
|
||
|
|
||
|
conf = ConfigParser.SafeConfigParser()
|
||
|
conffileName = xchat.get_info('xchatdir') + os.sep + 'sasl.conf'
|
||
|
# cwd is different for cb functions :/
|
||
|
# conf.read([conffileName, 'sasl.conf'])
|
||
|
saslTimers = {}
|
||
|
|
||
|
def connected_cb(word, word_eol, userdata):
|
||
|
# Ask the server for the list of supported capabilities
|
||
|
conf.read(conffileName)
|
||
|
if not conf.has_section(xchat.get_info('network')):
|
||
|
return
|
||
|
|
||
|
xchat.command('CAP LS')
|
||
|
return xchat.EAT_NONE
|
||
|
|
||
|
def sasl_timeout_cb(userdata):
|
||
|
# Tell the server we've finished playing with capabilities if SASL times out
|
||
|
xchat.command('CAP END')
|
||
|
return xchat.EAT_NONE
|
||
|
|
||
|
def cap_cb(word, word_eol, userdata):
|
||
|
subcmd = word[3]
|
||
|
caps = word[4:]
|
||
|
caps[0] = caps[0][1:]
|
||
|
if subcmd == 'LS':
|
||
|
toSet = []
|
||
|
# Parse the list of capabilities received from the server
|
||
|
if 'multi-prefix' in caps:
|
||
|
toSet.append('multi-prefix')
|
||
|
# Ask for the SASL capability only if there is a configuration for this network
|
||
|
if 'sasl' in caps and conf.has_section(xchat.get_info('network')):
|
||
|
toSet.append('sasl')
|
||
|
if toSet:
|
||
|
# Actually set capabilities
|
||
|
xchat.command('CAP REQ :%s' % ' '.join(toSet))
|
||
|
else:
|
||
|
# Sorry, nothing useful found, or we don't support these
|
||
|
xchat.command('CAP END')
|
||
|
elif subcmd == 'ACK':
|
||
|
if 'sasl' in caps:
|
||
|
xchat.command('AUTHENTICATE PLAIN')
|
||
|
print("SASL authenticating")
|
||
|
saslTimers[xchat.get_info('network')] = xchat.hook_timer(5000, sasl_timeout_cb) # Timeout after 5 seconds
|
||
|
# In this case CAP END is delayed until authentication ends
|
||
|
else:
|
||
|
xchat.command('CAP END')
|
||
|
elif subcmd == 'NAK':
|
||
|
xchat.command('CAP END')
|
||
|
elif subcmd == 'LIST':
|
||
|
if not caps:
|
||
|
caps = 'none'
|
||
|
print('CAP(s) currently enabled: %s') % ', '.join(caps)
|
||
|
return xchat.EAT_XCHAT
|
||
|
|
||
|
def authenticate_cb(word, word_eol, userdata):
|
||
|
nick = conf.get(xchat.get_info('network'), 'nick')
|
||
|
passwd = conf.get(xchat.get_info('network'), 'password')
|
||
|
authStr = base64.b64encode('\0'.join((nick, nick, passwd)))
|
||
|
if not len(authStr):
|
||
|
xchat.command('AUTHENTICATE +')
|
||
|
else:
|
||
|
while len(authStr) >= 400:
|
||
|
toSend = authStr[:400]
|
||
|
authStr = authStr[400:]
|
||
|
xchat.command('AUTHENTICATE %s' % toSend)
|
||
|
if len(authStr):
|
||
|
# Send last part
|
||
|
xchat.command('AUTHENTICATE %s' % authStr)
|
||
|
else:
|
||
|
# Last part was exactly 400 bytes, inform the server that there's nothing more
|
||
|
xchat.command('AUTHENTICATE +')
|
||
|
return xchat.EAT_XCHAT
|
||
|
|
||
|
def sasl_90x_cb(word, word_eol, userdata):
|
||
|
# Remove timer
|
||
|
xchat.unhook(saslTimers[xchat.get_info('network')])
|
||
|
xchat.command('CAP END')
|
||
|
return xchat.EAT_NONE
|
||
|
|
||
|
def sasl_cb(word, word_eol, userdata):
|
||
|
if len(word) < 3:
|
||
|
print('Usage: /SASL <-set|-unset> <network> [<nick> <password>]')
|
||
|
else:
|
||
|
subcmd = word[1]
|
||
|
network = word[2]
|
||
|
if subcmd == '-set':
|
||
|
# -set needs also a nick and its password
|
||
|
if len(word) < 5:
|
||
|
print('Usage: /SASL -set <network> <nick> <password>')
|
||
|
else:
|
||
|
nick = word[3]
|
||
|
passwd = word[4]
|
||
|
if not conf.has_section(network):
|
||
|
conf.add_section(network)
|
||
|
what = 'Added'
|
||
|
else:
|
||
|
what = 'Updated'
|
||
|
conf.set(network, 'nick', nick)
|
||
|
conf.set(network, 'password', passwd)
|
||
|
# This parameter is currently unused, but reserved for a future version.
|
||
|
# Currently, PLAIN is the only supported mechanism.
|
||
|
conf.set(network, 'mechanism', 'PLAIN')
|
||
|
# Save settings
|
||
|
conffile = open(conffileName, 'w')
|
||
|
print(os.getcwd())
|
||
|
conf.write(conffile)
|
||
|
conffile.close()
|
||
|
print('%s SASL settings for network %s') % (what, network)
|
||
|
elif subcmd == '-unset':
|
||
|
if conf.remove_section(network): # Returns True if section existed
|
||
|
# Write on disk only if configuration is actually changed
|
||
|
conffile = open(conffileName, 'w')
|
||
|
conf.write(conffile)
|
||
|
conffile.close()
|
||
|
print('Successfully removed SASL settings for network ' + network)
|
||
|
else:
|
||
|
print('SASL authentication is not configured for network ' + network)
|
||
|
else:
|
||
|
print('Usage: /SASL <-set|-unset> <network> [<nick> <password>]')
|
||
|
return xchat.EAT_NONE
|
||
|
|
||
|
xchat.hook_print('Connected', connected_cb)
|
||
|
|
||
|
xchat.hook_server('AUTHENTICATE', authenticate_cb)
|
||
|
xchat.hook_server('CAP', cap_cb)
|
||
|
xchat.hook_server('903', sasl_90x_cb) # RPL_SASLSUCCESS
|
||
|
xchat.hook_server('904', sasl_90x_cb) # ERR_SASLFAIL
|
||
|
xchat.hook_server('905', sasl_90x_cb) # ERR_SASLTOOLONG
|
||
|
xchat.hook_server('906', sasl_90x_cb) # ERR_SASLABORTED
|
||
|
xchat.hook_server('907', sasl_90x_cb) # ERR_SASLALREADY
|
||
|
|
||
|
xchat.hook_command('SASL', sasl_cb, help = 'Usage: /SASL <-set|-unset> <network> [<nick> <password>], ' +
|
||
|
'set or unset SASL authentication for an IRC network. Arguments <nick> and <password> are optional for -unset')
|