1
2
3
4
5 """ an Irc object handles the connection to the irc server .. receiving,
6 sending, connect and reconnect code """
7
8 __copyright__ = 'this file is in the public domain'
9
10 from gozerbot.generic import rlog, handle_exception, getrandomnick, toenc, \
11 fix_format, splittxt, waitforqueue, strippedtxt, fromenc, uniqlist, lockdec
12 from gozerbot.wait import Wait
13 from gozerbot.config import config
14 from gozerbot.ircevent import Ircevent
15 from gozerbot.monitor import saymonitor
16 from gozerbot.less import Less
17 from gozerbot.ignore import shouldignore
18 from gozerbot.pdod import Pdod
19 from gozerbot.datadir import datadir
20 from gozerbot.eventhandler import Outputhandler
21 from gozerbot.fleet import fleet
22 from gozerbot.botbase import BotBase
23 from gozerbot.plugins import plugins
24 from gozerbot.thr import start_new_thread, threaded
25 import time, thread, socket, threading, os, Queue, random
26
27 outlock = thread.allocate_lock()
28 outlocked = lockdec(outlock)
29 reconnectlock = thread.allocate_lock()
30 reconnectlocked = lockdec(outlock)
33
34 """ already connected exception """
35
36 pass
37
39
40 """ bot is already connecting exception """
41
42 pass
43
45
46 """ the irc class, provides interface to irc related stuff """
47
48 - def __init__(self, name='main', owner=[]):
49 BotBase.__init__(self, name, owner)
50 self.type = 'irc'
51 self.wait = Wait()
52 self.outputlock = thread.allocate_lock()
53 self.fsock = None
54 self.oldsock = None
55 self.sock = None
56 self.nolimiter = config['nolimiter']
57 self.reconnectcount = 0
58 self.pongcheck = 0
59 self.nickchanged = 0
60 self.noauto433 = 0
61 if not self.state.has_key('alternick'):
62 self.state['alternick'] = config['alternick']
63 if not self.state.has_key('no-op'):
64 self.state['no-op'] = []
65 if not self.state.has_key('nick'):
66 self.state['nick'] = config['nick'] or 'gb1'
67 self.nick = self.state['nick']
68 self.nrevents = 0
69 self.gcevents = 0
70 self.outqueues = [Queue.Queue() for i in range(10)]
71 self.whenidle = Queue.Queue()
72 self.nicks401 = []
73 self.stopreadloop = False
74 self.stopoutloop = False
75
76 - def _raw(self, txt):
77 """ send raw text to the server """
78 if not txt:
79 return
80 rlog(2, self.name + '.sending', txt)
81 try:
82 if self.ssl:
83 self.sock.write(txt + '\n')
84 else:
85 self.sock.send(txt[:500] + '\n')
86 except Exception, ex:
87
88
89 try:
90 (errno, errstr) = ex
91 if errno != 32:
92 raise
93 else:
94 rlog(10, self.name, 'broken pipe error .. ignoring')
95 except:
96 rlog(10, self.name, "ERROR: can't send %s" % str(ex))
97
98 - def _connect(self, nick, server, port, password, ipv6, ssl=0):
99 """ connect to server/port using nick """
100 if self.connecting:
101 rlog(10, self.name, 'already connecting')
102 raise AlreadyConnecting()
103 if self.connectok.isSet():
104 rlog(10, self.name, 'already connected')
105 raise AlreadyConnected()
106 self.connecting = True
107 self.connectok.clear()
108 self.nick = str(nick)
109 self.orignick = self.nick
110 self.server = str(server)
111 self.port = int(port)
112 self.password = str(password)
113 self.ipv6 = int(ipv6)
114 self.ssl = int(ssl)
115
116 if self.ipv6:
117 rlog(10, self.name, 'creating ipv6 socket')
118 self.oldsock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
119 self.ipv6 = 1
120 else:
121 rlog(10, self.name, 'creating ipv4 socket')
122 self.oldsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
123
124 elite = config['bindhost']
125 if elite:
126 try:
127 self.oldsock.bind((elite, 0))
128 except socket.gaierror:
129 rlog(10, self.name, "can't bind to %s" % elite)
130
131 server = None
132 if not server:
133
134 try: socket.inet_pton(socket.AF_INET6, self.server)
135 except socket.error: pass
136 else: server = self.server
137 if not server:
138
139 try: socket.inet_pton(socket.AF_INET, self.server)
140 except socket.error: pass
141 else: server = self.server
142 if not server:
143
144 ips = []
145 try:
146 for item in socket.getaddrinfo(self.server, None):
147 if item[0] in [socket.AF_INET, socket.AF_INET6] and item[1] == socket.SOCK_STREAM:
148 ip = item[4][0]
149 if ip not in ips: ips.append(ip)
150 except socket.error: pass
151 else: server = random.choice(ips)
152
153 rlog(10, self.name, 'connecting to %s (%s)' % (server, self.server))
154 self.oldsock.settimeout(30)
155 self.oldsock.connect((server, int(self.port)))
156
157 rlog(10, self.name, 'connection ok')
158 self.stopped = 0
159
160 self.fsock = self.oldsock.makefile("r")
161
162 self.oldsock.setblocking(self.blocking)
163 self.fsock._sock.setblocking(self.blocking)
164
165 if self.blocking:
166 socktimeout = config['socktimeout']
167 if not socktimeout:
168 socktimeout = 301.0
169 else:
170 socktimeout = float(socktimeout)
171 self.oldsock.settimeout(socktimeout)
172 self.fsock._sock.settimeout(socktimeout)
173
174 if self.ssl:
175 rlog(10, self.name, 'ssl enabled')
176 self.sock = socket.ssl(self.oldsock)
177 else:
178 self.sock = self.oldsock
179 try:
180 self.outputlock.release()
181 except thread.error:
182 pass
183 start_new_thread(self._readloop, ())
184 start_new_thread(self._outloop, ())
185
186 self._logon()
187 self._onconnect()
188
189 self.nickchanged = 0
190 self.connecting = False
191 self.reconnectcount = 0
192 saymonitor.start()
193 return 1
194
196 """ loop on the socketfile """
197 self.stopreadloop = 0
198 self.stopped = 0
199 doreconnect = 0
200 timeout = 1
201 rlog(5, self.name, 'starting readloop')
202 prevtxt = ""
203 while not self.stopped and not self.stopreadloop:
204 try:
205 time.sleep(0.01)
206 if self.ssl:
207 intxt = self.sock.read().split('\n')
208 else:
209 intxt = self.fsock.readline().split('\n')
210
211 if self.stopreadloop:
212 doreconnect = 0
213 return
214 if not intxt and not self.stopped:
215 doreconnect = 1
216 break
217 if self.stopped:
218 break
219 if prevtxt:
220 intxt[0] = prevtxt + intxt[0]
221 prevtxt = ""
222 if intxt[-1] != '':
223 prevtxt = intxt[-1]
224 intxt = intxt[:-1]
225 for r in intxt:
226 r = r.rstrip()
227 rr = fromenc(r)
228 if not rr:
229 continue
230 res = strippedtxt(rr)
231 res = rr
232 rlog(2, self.name, res)
233
234 try:
235 ievent = Ircevent().parse(self, res)
236 except Exception, ex:
237 handle_exception()
238 continue
239
240 if ievent:
241 self.handle_ievent(ievent)
242 timeout = 1
243 except UnicodeError:
244 handle_exception()
245 continue
246 except socket.timeout:
247
248
249 if self.stopped:
250 break
251 timeout += 1
252 if timeout > 2:
253 doreconnect = 1
254 rlog(10, self.name, 'no pong received')
255 break
256 rlog(1, self.name, "socket timeout")
257 pingsend = self.ping()
258 if not pingsend:
259 doreconnect = 1
260 break
261 continue
262 except socket.sslerror, ex:
263
264
265 if self.stopped or self.stopreadloop:
266 break
267 if not 'timed out' in str(ex):
268 handle_exception()
269 doreconnect = 1
270 break
271 timeout += 1
272 if timeout > 2:
273 doreconnect = 1
274 rlog(10, self.name, 'no pong received')
275 break
276 rlog(1, self.name, "socket timeout")
277 pingsend = self.ping()
278 if not pingsend:
279 doreconnect = 1
280 break
281 continue
282 except IOError, ex:
283 if 'temporarily' in str(ex):
284 continue
285 except Exception, ex:
286 if self.stopped or self.stopreadloop:
287 break
288 err = ex
289 try:
290 (errno, msg) = ex
291 except:
292 errno = -1
293 msg = err
294
295
296 if errno == 35 or errno == 11:
297 time.sleep(0.5)
298 continue
299 rlog(10, self.name, "error in readloop: %s" % msg)
300 doreconnect = 1
301 break
302 rlog(5, self.name, 'readloop stopped')
303
304 self.connectok.clear()
305 self.connecting = False
306 self.connected = False
307 if doreconnect:
308 self.reconnect()
309
311 for index in range(len(self.outqueues)):
312 if not self.outqueues[index].empty():
313 return self.outqueues[index]
314
316 self.outqueues[nr].put_nowait(*args)
317
319 rlog(5, self.name, 'starting output loop')
320 self.stopoutloop = 0
321 while not self.stopped and not self.stopoutloop:
322 time.sleep(0.01)
323 queue = self._getqueue()
324 if queue:
325 rlog(5, self.name, "outputsizes: %s" % self.outputsizes())
326 try:
327 res = queue.get_nowait()
328 except Queue.Empty:
329 continue
330 if not res:
331 continue
332 try:
333 (printto, what, who, how, fromm, speed) = res
334 except ValueError:
335 self.send(res)
336 continue
337 if not self.stopped and not self.stopoutloop and printto \
338 not in self.nicks401:
339 self.out(printto, what, who, how, fromm, speed)
340 rlog