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>'
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'])
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')):
42 xchat
.command('CAP LS')
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')
50 def cap_cb(word
, word_eol
, userdata
):
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')):
63 # Actually set capabilities
64 xchat
.command('CAP REQ :%s' % ' '.join(toSet
))
66 # Sorry, nothing useful found, or we don't support these
67 xchat
.command('CAP END')
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
75 xchat
.command('CAP END')
77 xchat
.command('CAP END')
78 elif subcmd
== 'LIST':
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
)))
89 xchat
.command('AUTHENTICATE +')
91 while len(authStr
) >= 400:
92 toSend
= authStr
[:400]
93 authStr
= authStr
[400:]
94 xchat
.command('AUTHENTICATE %s' % toSend
)
97 xchat
.command('AUTHENTICATE %s' % authStr
)
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
):
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
):
111 print('Usage: /SASL <-set|-unset> <network> [<nick> <password>]')
116 # -set needs also a nick and its password
118 print('Usage: /SASL -set <network> <nick> <password>')
122 if not conf
.has_section(network
):
123 conf
.add_section(network
)
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')
134 conffile
= open(conffileName
, 'w')
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')
145 print('Successfully removed SASL settings for network ' + network
)
147 print('SASL authentication is not configured for network ' + network
)
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')