default to utf-8 encoding
[rofl0r-ixchat.git] / scripts / cap_sasl_xchat.py
blob4ba60a78fee4169eec557a3147b3162fb3f78dfc
2 # Copyright (C) 2010 Roberto Leandrini <anaconda@ditalinux.it>
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 __module_name__ = 'cap_sasl'
20 __module_version__ = '0.5'
21 __module_description__ = 'SASL authentication plugin'
22 __module_author__ = 'Roberto Leandrini <anaconda@ditalinux.it>'
24 import base64
25 import ConfigParser
26 import os
28 import xchat
30 conf = ConfigParser.SafeConfigParser()
31 conffileName = xchat.get_info('xchatdir') + os.sep + 'sasl.conf'
32 # cwd is different for cb functions :/
33 # conf.read([conffileName, 'sasl.conf'])
34 saslTimers = {}
36 def connected_cb(word, word_eol, userdata):
37 # Ask the server for the list of supported capabilities
38 conf.read(conffileName)
39 if not conf.has_section(xchat.get_info('network')):
40 return
42 xchat.command('CAP LS')
43 return xchat.EAT_NONE
45 def sasl_timeout_cb(userdata):
46 # Tell the server we've finished playing with capabilities if SASL times out
47 xchat.command('CAP END')
48 return xchat.EAT_NONE
50 def cap_cb(word, word_eol, userdata):
51 subcmd = word[3]
52 caps = word[4:]
53 caps[0] = caps[0][1:]
54 if subcmd == 'LS':
55 toSet = []
56 # Parse the list of capabilities received from the server
57 if 'multi-prefix' in caps:
58 toSet.append('multi-prefix')
59 # Ask for the SASL capability only if there is a configuration for this network
60 if 'sasl' in caps and conf.has_section(xchat.get_info('network')):
61 toSet.append('sasl')
62 if toSet:
63 # Actually set capabilities
64 xchat.command('CAP REQ :%s' % ' '.join(toSet))
65 else:
66 # Sorry, nothing useful found, or we don't support these
67 xchat.command('CAP END')
68 elif subcmd == 'ACK':
69 if 'sasl' in caps:
70 xchat.command('AUTHENTICATE PLAIN')
71 print("SASL authenticating")
72 saslTimers[xchat.get_info('network')] = xchat.hook_timer(15000, sasl_timeout_cb) # Timeout after 15 seconds
73 # In this case CAP END is delayed until authentication ends
74 else:
75 xchat.command('CAP END')
76 elif subcmd == 'NAK':
77 xchat.command('CAP END')
78 elif subcmd == 'LIST':
79 if not caps:
80 caps = 'none'
81 print('CAP(s) currently enabled: %s') % ', '.join(caps)
82 return xchat.EAT_XCHAT
84 def authenticate_cb(word, word_eol, userdata):
85 nick = conf.get(xchat.get_info('network'), 'nick')
86 passwd = conf.get(xchat.get_info('network'), 'password')
87 authStr = base64.b64encode('\0'.join((nick, nick, passwd)))
88 if not len(authStr):
89 xchat.command('AUTHENTICATE +')
90 else:
91 while len(authStr) >= 400:
92 toSend = authStr[:400]
93 authStr = authStr[400:]
94 xchat.command('AUTHENTICATE %s' % toSend)
95 if len(authStr):
96 # Send last part
97 xchat.command('AUTHENTICATE %s' % authStr)
98 else:
99 # Last part was exactly 400 bytes, inform the server that there's nothing more
100 xchat.command('AUTHENTICATE +')
101 return xchat.EAT_XCHAT
103 def sasl_90x_cb(word, word_eol, userdata):
104 # Remove timer
105 xchat.unhook(saslTimers[xchat.get_info('network')])
106 xchat.command('CAP END')
107 return xchat.EAT_NONE
109 def sasl_cb(word, word_eol, userdata):
110 if len(word) < 3:
111 print('Usage: /SASL <-set|-unset> <network> [<nick> <password>]')
112 else:
113 subcmd = word[1]
114 network = word[2]
115 if subcmd == '-set':
116 # -set needs also a nick and its password
117 if len(word) < 5:
118 print('Usage: /SASL -set <network> <nick> <password>')
119 else:
120 nick = word[3]
121 passwd = word[4]
122 if not conf.has_section(network):
123 conf.add_section(network)
124 what = 'Added'
125 else:
126 what = 'Updated'
127 conf.set(network, 'nick', nick)
128 conf.set(network, 'password', passwd)
129 # This parameter is currently unused, but reserved for a future version.
130 # Currently, PLAIN is the only supported mechanism, but there are
131 # more or less serious plans to implement DH-BLOWFISH.
132 conf.set(network, 'mechanism', 'PLAIN')
133 # Save settings
134 conffile = open(conffileName, 'w')
135 print(os.getcwd())
136 conf.write(conffile)
137 conffile.close()
138 print('%s SASL settings for network %s') % (what, network)
139 elif subcmd == '-unset':
140 if conf.remove_section(network): # Returns True if section existed
141 # Write on disk only if configuration is actually changed
142 conffile = open(conffileName, 'w')
143 conf.write(conffile)
144 conffile.close()
145 print('Successfully removed SASL settings for network ' + network)
146 else:
147 print('SASL authentication is not configured for network ' + network)
148 else:
149 print('Usage: /SASL <-set|-unset> <network> [<nick> <password>]')
150 return xchat.EAT_NONE
152 xchat.hook_print('Connected', connected_cb)
154 xchat.hook_server('AUTHENTICATE', authenticate_cb)
155 xchat.hook_server('CAP', cap_cb)
156 xchat.hook_server('903', sasl_90x_cb) # RPL_SASLSUCCESS
157 xchat.hook_server('904', sasl_90x_cb) # ERR_SASLFAIL
158 xchat.hook_server('905', sasl_90x_cb) # ERR_SASLTOOLONG
159 xchat.hook_server('906', sasl_90x_cb) # ERR_SASLABORTED
160 xchat.hook_server('907', sasl_90x_cb) # ERR_SASLALREADY
162 xchat.hook_command('SASL', sasl_cb, help = 'Usage: /SASL <-set|-unset> <network> [<nick> <password>], ' +
163 'set or unset SASL authentication for an IRC network. Arguments <nick> and <password> are optional for -unset')