]>
jfr.im git - irc/quakenet/iauthd.git/blob - quakenet-iauthd
04f14a8282e67264144d0b8712d17472226ff3ce
2 # Copyright (C) 2013 Gunnar Beutner
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 TRUST_HOST
= '127.0.0.1'
20 TRUST_NAME
= 'gnb.netsplit.net'
21 TRUST_PASS
= 'test123'
24 # Do not modify beyond this point.
26 VERSION
= 333333333333333333333333333333333
38 class LineDispatcher(asyncore
.dispatcher
):
39 def __init__(self
, fd
=None, map=None):
40 asyncore
.dispatcher
.__init
__(self
, None, map)
45 self
.send('VERSION %s\n' % (VERSION
))
53 except AttributeError:
56 # set it to non-blocking mode
57 flags
= fcntl
.fcntl(fd
, fcntl
.F_GETFL
, 0)
58 flags
= flags | os
.O_NONBLOCK
59 fcntl
.fcntl(fd
, fcntl
.F_SETFL
, flags
)
61 def set_file(self
, fd
):
62 self
.socket
= asyncore
.file_wrapper(fd
)
63 self
._fileno
= self
.socket
.fileno()
66 def handle_read(self
):
67 data
= self
.recv(8192)
72 self
.in_buffer
+= data
75 pos
= self
.in_buffer
.find('\n')
80 line
= self
.in_buffer
[0:pos
]
81 self
.in_buffer
= self
.in_buffer
[pos
+ 1:]
83 # remove new-line characters
84 line
= line
.rstrip('\r\n')
86 self
.handle_line(line
)
89 return self
.out_buffer
!= ''
92 self
.out_buffer
+= data
94 def handle_write(self
):
96 sent
= asyncore
.dispatcher
.send(self
, self
.out_buffer
)
97 self
.out_buffer
= self
.out_buffer
[sent
:]
99 def log_info(self
, message
, type='info'):
100 if __debug__
or type != 'info':
101 sys
.stderr
.write('%s: %s\n' % (type, message
))
103 class IAuthHandler(LineDispatcher
):
104 def __init__(self
, fin
):
105 LineDispatcher
.__init
__(self
, fin
)
108 self
.next_unique_id
= 0
110 self
.send("V :quakenet-iauthd\n")
113 def handle_line(self
, line
):
116 # tokenize the line according to RFC 2812
117 if line
.find(' :') != -1:
118 line
, trailing
= line
.split(' :', 1)
119 tokens
= line
.split()
120 tokens
.append(trailing
)
122 tokens
= line
.split()
124 if len(tokens
) < 3: # too few tokens
131 if id != '-1' and not id in self
.clients
:
132 self
.clients
[id] = {}
134 if command
== 'C': # new client
135 if len(params
) < 2: # too few parameters
138 self
.clients
[id]['remoteip'] = params
[0]
139 self
.clients
[id]['remoteport'] = params
[1]
141 self
.clients
[id]['unique_id'] = str(self
.next_unique_id
)
142 self
.next_unique_id
+= 1
143 elif command
== 'H': # hurry state (ircd has finished DNS/ident check)
144 if trust_handler
and trust_handler
.connected
and trust_handler
.authed
:
145 trust_handler
.send('CHECK %s-%s %s %s\n' %
146 (id, self
.clients
[id]['unique_id'], self
.clients
[id]['username'], self
.clients
[id]['remoteip']))
148 self
.send_verdict('%s-%s' % (id, self
.clients
[id]['unique_id']), 'PASS')
149 elif command
== 'u' or command
== 'U': # trusted/untrusted username
152 # untrusted username (i.e. non-working identd)
154 username
= '~' + username
156 if not 'username' in self
.clients
[id] or username
[0] != '~':
157 self
.clients
[id]['username'] = username
158 elif command
== 'D': # client disconnected
159 if id in self
.clients
:
162 def send_verdict(self
, combined_id
, verdict
, message
=None):
163 global stats_passed
, stats_killed
165 tokens
= combined_id
.split('-', 1)
170 id, unique_id
= tokens
172 if not id in self
.clients
or self
.clients
[id]['unique_id'] != unique_id
:
175 if verdict
== 'PASS':
176 # Every 10000th accepted connection gets a free cow.
177 if stats_passed
% 10000 == 0:
178 cow
= [ '(__)', ' oo\\\\\\~', ' !!!!' ]
181 self
.send('C %s %s %s :%s\n' %
182 (id, self
.clients
[id]['remoteip'], self
.clients
[id]['remoteport'], line
))
185 self
.send('C %s %s %s :%s\n' %
186 (id, self
.clients
[id]['remoteip'], self
.clients
[id]['remoteport'], message
))
188 self
.send('D %s %s %s\n' %
189 (id, self
.clients
[id]['remoteip'], self
.clients
[id]['remoteport']))
194 message
= 'Connections from your host cannot be accepted at this time.'
196 self
.send('k %s %s %s :%s\n' %
197 (id, self
.clients
[id]['remoteip'], self
.clients
[id]['remoteport'], message
))
203 def send_snotice(self
, message
):
204 self
.send('> :iauthd: %s\n' % (message
))
206 def send_throttle(self
, op
, addr
):
207 self
.send('T %s %s\n' % (op
, addr
))
209 def clear_stats(self
):
212 def add_stats(self
, message
):
213 self
.send('S quakenet-iauthd :%s\n' % message
)
215 class TrustHandler(LineDispatcher
):
216 def __init__(self
, host
, port
):
217 LineDispatcher
.__init
__(self
)
220 self
.connected_since
= False
222 self
.create_socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
223 if BIND_HOST
!= '0.0.0.0': # maybe unnecessary?
224 self
.bind( (BIND_HOST
, 0) )
225 self
.connect( (TRUST_HOST
, TRUST_PORT
) )
227 def handle_connect_event(self
):
229 LineDispatcher
.handle_connect_event(self
)
231 self
.connected_since
= time()
233 iauth_handler
.send_snotice('Could not connect to trusts backend: ' + str(sys
.exc_info()[1]))
235 def handle_line(self
, line
):
238 tokens
= line
.split(' ', 1)
240 if tokens
[0] == 'KILL' or tokens
[0] == 'PASS':
244 arguments
= tokens
[1].split(' ', 1)
250 if len(arguments
) > 1:
251 message
= arguments
[1]
253 iauth_handler
.send_verdict(id, verdict
, message
)
254 elif tokens
[0] == 'THROTTLE' or tokens
[0] == 'UNTHROTTLE':
258 if tokens
[0] == 'THROTTLE':
265 iauth_handler
.send_throttle(op
, addr
)
266 elif tokens
[0] == 'AUTH':
270 iauth_handler
.send_snotice('Received authentication request from trusts backend.')
274 self
.send('AUTH %s %s\n' %
275 (TRUST_NAME
, hmac
.HMAC(TRUST_PASS
, nonce
).hexdigest()))
276 elif tokens
[0] == 'AUTHOK':
277 iauth_handler
.send_snotice('Successfully authenticated with trusts backend.')
279 elif tokens
[0] == 'QUIT':
281 message
= 'No reason specified.'
285 iauth_handler
.send_snotice('Trusts backend closed connection: ' + message
)
288 def handle_connect(self
):
290 iauth_handler
.send_snotice('Reconnected to trusts backend.')
292 def handle_close(self
):
294 iauth_handler
.send_snotice('Connection to trusts backend failed.')
298 iauth_handler
= IAuthHandler(sys
.stdin
)
301 last_restart
= time()
308 # Try to (re-)connect to trusts backend if necessary
309 if (not trust_handler
or not trust_handler
.connected
) and last_reconnect
+ 10 < time():
311 trust_handler
.close()
313 iauth_handler
.send_snotice('Attempting to reconnect to trusts backend.')
314 trust_handler
= TrustHandler(TRUST_HOST
, TRUST_PORT
)
315 last_reconnect
= time()
317 if last_stats
+ 15 < time():
319 iauth_handler
.clear_stats()
321 iauth_handler
.add_stats('Version: %s' % (VERSION
))
322 iauth_handler
.add_stats('Started: %s seconds ago' % (int(time() - last_restart
)))
324 if trust_handler
and trust_handler
.connected
and trust_handler
.authed
:
325 iauth_handler
.add_stats('Connected to trusts backend for %s seconds.' % (int(time() - trust_handler
.connected_since
)))
327 iauth_handler
.add_stats('Not connected to trusts backend.')
329 iauth_handler
.add_stats('Accepted connections: %d' % (stats_passed
))
330 iauth_handler
.add_stats('Rejected connections: %d' % (stats_killed
))
331 iauth_handler
.add_stats('Pending connections: %d' % (len(iauth_handler
.clients
)))
333 ru
= resource
.getrusage(resource
.RUSAGE_SELF
)
335 iauth_handler
.add_stats('--')
336 iauth_handler
.add_stats('Memory usage: %s kB' % (ru
[2] / 1024))
337 iauth_handler
.add_stats('CPU usage: %.2f%%' % ((ru
[0] + ru
[1]) / (time() - last_restart
)))
341 asyncore
.loop(timeout
=10, count
=1)