1 | /*************************************************************************** |
2 | * Copyright (C) 2005-2014 by the Quassel Project * |
3 | * devel@quassel-irc.org * |
4 | * * |
5 | * This program is free software; you can redistribute it and/or modify * |
6 | * it under the terms of the GNU General Public License as published by * |
7 | * the Free Software Foundation; either version 2 of the License, or * |
8 | * (at your option) version 3. * |
9 | * * |
10 | * This program is distributed in the hope that it will be useful, * |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
13 | * GNU General Public License for more details. * |
14 | * * |
15 | * You should have received a copy of the GNU General Public License * |
16 | * along with this program; if not, write to the * |
17 | * Free Software Foundation, Inc., * |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * |
19 | ***************************************************************************/ |
20 | |
21 | #include "coreuserinputhandler.h" |
22 | |
23 | #include "util.h" |
24 | |
25 | #include "ctcpparser.h" |
26 | |
27 | #include <QRegExp> |
28 | |
29 | #ifdef HAVE_QCA2 |
30 | # include "cipher.h" |
31 | #endif |
32 | |
33 | CoreUserInputHandler::CoreUserInputHandler(CoreNetwork *parent) |
34 | : CoreBasicHandler(parent) |
35 | { |
36 | } |
37 | |
38 | |
39 | void CoreUserInputHandler::handleUserInput(const BufferInfo &bufferInfo, const QString &msg) |
40 | { |
41 | if (msg.isEmpty()) |
42 | return; |
43 | |
44 | AliasManager::CommandList list = coreSession()->aliasManager().processInput(bufferInfo, msg); |
45 | |
46 | for (int i = 0; i < list.count(); i++) { |
47 | QString cmd = list.at(i).second.section(' ', 0, 0).remove(0, 1).toUpper(); |
48 | QString payload = list.at(i).second.section(' ', 1); |
49 | handle(cmd, Q_ARG(BufferInfo, list.at(i).first), Q_ARG(QString, payload)); |
50 | } |
51 | } |
52 | |
53 | |
54 | // ==================== |
55 | // Public Slots |
56 | // ==================== |
57 | void CoreUserInputHandler::handleAway(const BufferInfo &bufferInfo, const QString &msg) |
58 | { |
59 | Q_UNUSED(bufferInfo) |
60 | if (msg.startsWith("-all" )) { |
61 | if (msg.length() == 4) { |
62 | coreSession()->globalAway(); |
63 | return; |
64 | } |
65 | Q_ASSERT(msg.length() > 4); |
66 | if (msg[4] == ' ') { |
67 | coreSession()->globalAway(msg.mid(5)); |
68 | return; |
69 | } |
70 | } |
71 | issueAway(msg); |
72 | } |
73 | |
74 | |
75 | void CoreUserInputHandler::issueAway(const QString &msg, bool autoCheck) |
76 | { |
77 | QString awayMsg = msg; |
78 | IrcUser *me = network()->me(); |
79 | |
80 | // if there is no message supplied we have to check if we are already away or not |
81 | if (autoCheck && msg.isEmpty()) { |
82 | if (me && !me->isAway()) { |
83 | Identity *identity = network()->identityPtr(); |
84 | if (identity) { |
85 | awayMsg = identity->awayReason(); |
86 | } |
87 | if (awayMsg.isEmpty()) { |
88 | awayMsg = tr("away" ); |
89 | } |
90 | } |
91 | } |
92 | if (me) |
93 | me->setAwayMessage(awayMsg); |
94 | |
95 | putCmd("AWAY" , serverEncode(awayMsg)); |
96 | } |
97 | |
98 | |
99 | void CoreUserInputHandler::handleBan(const BufferInfo &bufferInfo, const QString &msg) |
100 | { |
101 | banOrUnban(bufferInfo, msg, true); |
102 | } |
103 | |
104 | |
105 | void CoreUserInputHandler::handleUnban(const BufferInfo &bufferInfo, const QString &msg) |
106 | { |
107 | banOrUnban(bufferInfo, msg, false); |
108 | } |
109 | |
110 | |
111 | void CoreUserInputHandler::banOrUnban(const BufferInfo &bufferInfo, const QString &msg, bool ban) |
112 | { |
113 | QString banChannel; |
114 | QString banUser; |
115 | |
116 | QStringList params = msg.split(" " ); |
117 | |
118 | if (!params.isEmpty() && isChannelName(params[0])) { |
119 | banChannel = params.takeFirst(); |
120 | } |
121 | else if (bufferInfo.type() == BufferInfo::ChannelBuffer) { |
122 | banChannel = bufferInfo.bufferName(); |
123 | } |
124 | else { |
125 | emit displayMsg(Message::Error, BufferInfo::StatusBuffer, "" , QString("Error: channel unknown in command: /BAN %1" ).arg(msg)); |
126 | return; |
127 | } |
128 | |
129 | if (!params.isEmpty() && !params.contains("!" ) && network()->ircUser(params[0])) { |
130 | IrcUser *ircuser = network()->ircUser(params[0]); |
131 | // generalizedHost changes <nick> to *!ident@*.sld.tld. |
132 | QString generalizedHost = ircuser->host(); |
133 | if (generalizedHost.isEmpty()) { |
134 | emit displayMsg(Message::Error, BufferInfo::StatusBuffer, "" , QString("Error: host unknown in command: /BAN %1" ).arg(msg)); |
135 | return; |
136 | } |
137 | |
138 | static QRegExp ipAddress("\\d+\\.\\d+\\.\\d+\\.\\d+" ); |
139 | if (ipAddress.exactMatch(generalizedHost)) { |
140 | int lastDotPos = generalizedHost.lastIndexOf('.') + 1; |
141 | generalizedHost.replace(lastDotPos, generalizedHost.length() - lastDotPos, '*'); |
142 | } |
143 | else if (generalizedHost.lastIndexOf("." ) != -1 && generalizedHost.lastIndexOf("." , generalizedHost.lastIndexOf("." )-1) != -1) { |
144 | int secondLastPeriodPosition = generalizedHost.lastIndexOf("." , generalizedHost.lastIndexOf("." )-1); |
145 | generalizedHost.replace(0, secondLastPeriodPosition, "*" ); |
146 | } |
147 | banUser = QString("*!%1@%2" ).arg(ircuser->user(), generalizedHost); |
148 | } |
149 | else { |
150 | banUser = params.join(" " ); |
151 | } |
152 | |
153 | QString banMode = ban ? "+b" : "-b" ; |
154 | QString banMsg = QString("MODE %1 %2 %3" ).arg(banChannel, banMode, banUser); |
155 | emit putRawLine(serverEncode(banMsg)); |
156 | } |
157 | |
158 | |
159 | void CoreUserInputHandler::handleCtcp(const BufferInfo &bufferInfo, const QString &msg) |
160 | { |
161 | Q_UNUSED(bufferInfo) |
162 | |
163 | QString nick = msg.section(' ', 0, 0); |
164 | QString ctcpTag = msg.section(' ', 1, 1).toUpper(); |
165 | if (ctcpTag.isEmpty()) |
166 | return; |
167 | |
168 | QString message = msg.section(' ', 2); |
169 | QString verboseMessage = tr("sending CTCP-%1 request to %2" ).arg(ctcpTag).arg(nick); |
170 | |
171 | if (ctcpTag == "PING" ) { |
172 | #if QT_VERSION >= 0x040700 |
173 | message = QString::number(QDateTime::currentMSecsSinceEpoch()); |
174 | #else |
175 | message = QString::number(QDateTime::currentDateTime().toTime_t()); |
176 | #endif |
177 | } |
178 | |
179 | // FIXME make this a proper event |
180 | coreNetwork()->coreSession()->ctcpParser()->query(coreNetwork(), nick, ctcpTag, message); |
181 | emit displayMsg(Message::Action, BufferInfo::StatusBuffer, "" , verboseMessage, network()->myNick()); |
182 | } |
183 | |
184 | |
185 | void CoreUserInputHandler::handleDelkey(const BufferInfo &bufferInfo, const QString &msg) |
186 | { |
187 | QString bufname = bufferInfo.bufferName().isNull() ? "" : bufferInfo.bufferName(); |
188 | #ifdef HAVE_QCA2 |
189 | if (!bufferInfo.isValid()) |
190 | return; |
191 | |
192 | if (!Cipher::neededFeaturesAvailable()) { |
193 | emit displayMsg(Message::Error, typeByTarget(bufname), bufname, tr("Error: QCA provider plugin not found. It is usually provided by the qca-ossl plugin." )); |
194 | return; |
195 | } |
196 | |
197 | QStringList parms = msg.split(' ', QString::SkipEmptyParts); |
198 | |
199 | if (parms.isEmpty() && !bufferInfo.bufferName().isEmpty() && bufferInfo.acceptsRegularMessages()) |
200 | parms.prepend(bufferInfo.bufferName()); |
201 | |
202 | if (parms.isEmpty()) { |
203 | emit displayMsg(Message::Info, typeByTarget(bufname), bufname, |
204 | tr("[usage] /delkey <nick|channel> deletes the encryption key for nick or channel or just /delkey when in a channel or query." )); |
205 | return; |
206 | } |
207 | |
208 | QString target = parms.at(0); |
209 | |
210 | if (network()->cipherKey(target).isEmpty()) { |
211 | emit displayMsg(Message::Info, typeByTarget(bufname), bufname, tr("No key has been set for %1." ).arg(target)); |
212 | return; |
213 | } |
214 | |
215 | network()->setCipherKey(target, QByteArray()); |
216 | emit displayMsg(Message::Info, typeByTarget(bufname), bufname, tr("The key for %1 has been deleted." ).arg(target)); |
217 | |
218 | #else |
219 | Q_UNUSED(msg) |
220 | emit displayMsg(Message::Error, typeByTarget(bufname), bufname, tr("Error: Setting an encryption key requires Quassel to have been built " |
221 | "with support for the Qt Cryptographic Architecture (QCA2) library. " |
222 | "Contact your distributor about a Quassel package with QCA2 " |
223 | "support, or rebuild Quassel with QCA2 present." )); |
224 | #endif |
225 | } |
226 | |
227 | void CoreUserInputHandler::doMode(const BufferInfo &bufferInfo, const QChar& addOrRemove, const QChar& mode, const QString &nicks) |
228 | { |
229 | QString m; |
230 | bool isNumber; |
231 | int maxModes = network()->support("MODES" ).toInt(&isNumber); |
232 | if (!isNumber || maxModes == 0) maxModes = 1; |
233 | |
234 | QStringList nickList; |
235 | if (nicks == "*" ) { // All users in channel |
236 | const QList<IrcUser*> users = network()->ircChannel(bufferInfo.bufferName())->ircUsers(); |
237 | foreach(IrcUser *user, users) { |
238 | if ((addOrRemove == '+' && !network()->ircChannel(bufferInfo.bufferName())->userModes(user).contains(mode)) |
239 | || (addOrRemove == '-' && network()->ircChannel(bufferInfo.bufferName())->userModes(user).contains(mode))) |
240 | nickList.append(user->nick()); |
241 | } |
242 | } else { |
243 | nickList = nicks.split(' ', QString::SkipEmptyParts); |
244 | } |
245 | |
246 | if (nickList.count() == 0) return; |
247 | |
248 | while (!nickList.isEmpty()) { |
249 | int amount = qMin(nickList.count(), maxModes); |
250 | QString m = addOrRemove; for(int i = 0; i < amount; i++) m += mode; |
251 | QStringList params; |
252 | params << bufferInfo.bufferName() << m; |
253 | for(int i = 0; i < amount; i++) params << nickList.takeFirst(); |
254 | emit putCmd("MODE" , serverEncode(params)); |
255 | } |
256 | } |
257 | |
258 | |
259 | void CoreUserInputHandler::handleDeop(const BufferInfo &bufferInfo, const QString &nicks) |
260 | { |
261 | doMode(bufferInfo, '-', 'o', nicks); |
262 | } |
263 | |
264 | |
265 | void CoreUserInputHandler::handleDehalfop(const BufferInfo &bufferInfo, const QString &nicks) |
266 | { |
267 | doMode(bufferInfo, '-', 'h', nicks); |
268 | } |
269 | |
270 | |
271 | void CoreUserInputHandler::handleDevoice(const BufferInfo &bufferInfo, const QString &nicks) |
272 | { |
273 | doMode(bufferInfo, '-', 'v', nicks); |
274 | } |
275 | |
276 | void CoreUserInputHandler::handleHalfop(const BufferInfo &bufferInfo, const QString &nicks) |
277 | { |
278 | doMode(bufferInfo, '+', 'h', nicks); |
279 | } |
280 | |
281 | void CoreUserInputHandler::handleOp(const BufferInfo &bufferInfo, const QString &nicks) { |
282 | doMode(bufferInfo, '+', 'o', nicks); |
283 | } |
284 | |
285 | |
286 | void CoreUserInputHandler::handleInvite(const BufferInfo &bufferInfo, const QString &msg) |
287 | { |
288 | QStringList params; |
289 | params << msg << bufferInfo.bufferName(); |
290 | emit putCmd("INVITE" , serverEncode(params)); |
291 | } |
292 | |
293 | |
294 | void CoreUserInputHandler::handleJoin(const BufferInfo &bufferInfo, const QString &msg) |
295 | { |
296 | Q_UNUSED(bufferInfo); |
297 | |
298 | // trim spaces before chans or keys |
299 | QString sane_msg = msg; |
300 | sane_msg.replace(QRegExp(", +" ), "," ); |
301 | QStringList params = sane_msg.trimmed().split(" " ); |
302 | |
303 | QStringList chans = params[0].split("," , QString::SkipEmptyParts); |
304 | QStringList keys; |
305 | if (params.count() > 1) |
306 | keys = params[1].split("," ); |
307 | |
308 | int i; |
309 | for (i = 0; i < chans.count(); i++) { |
310 | if (!network()->isChannelName(chans[i])) |
311 | chans[i].prepend('#'); |
312 | |
313 | if (i < keys.count()) { |
314 | network()->addChannelKey(chans[i], keys[i]); |
315 | } |
316 | else { |
317 | network()->removeChannelKey(chans[i]); |
318 | } |
319 | } |
320 | |
321 | static const char *cmd = "JOIN" ; |
322 | i = 0; |
323 | QStringList joinChans, joinKeys; |
324 | int slicesize = chans.count(); |
325 | QList<QByteArray> encodedParams; |
326 | |
327 | // go through all to-be-joined channels and (re)build the join list |
328 | while (i < chans.count()) { |
329 | joinChans.append(chans.at(i)); |
330 | if (i < keys.count()) |
331 | joinKeys.append(keys.at(i)); |
332 | |
333 | // if the channel list we built so far either contains all requested channels or exceeds |
334 | // the desired amount of channels in this slice, try to send what we have so far |
335 | if (++i == chans.count() || joinChans.count() >= slicesize) { |
336 | params.clear(); |
337 | params.append(joinChans.join("," )); |
338 | params.append(joinKeys.join("," )); |
339 | encodedParams = serverEncode(params); |
340 | // check if it fits in one command |
341 | if (lastParamOverrun(cmd, encodedParams) == 0) { |
342 | emit putCmd(cmd, encodedParams); |
343 | } |
344 | else if (slicesize > 1) { |
345 | // back to start of slice, try again with half the amount of channels |
346 | i -= slicesize; |
347 | slicesize /= 2; |
348 | } |
349 | joinChans.clear(); |
350 | joinKeys.clear(); |
351 | } |
352 | } |
353 | } |
354 | |
355 | |
356 | void CoreUserInputHandler::handleKeyx(const BufferInfo &bufferInfo, const QString &msg) |
357 | { |
358 | QString bufname = bufferInfo.bufferName().isNull() ? "" : bufferInfo.bufferName(); |
359 | #ifdef HAVE_QCA2 |
360 | if (!bufferInfo.isValid()) |
361 | return; |
362 | |
363 | if (!Cipher::neededFeaturesAvailable()) { |
364 | emit displayMsg(Message::Error, typeByTarget(bufname), bufname, tr("Error: QCA provider plugin not found. It is usually provided by the qca-ossl plugin." )); |
365 | return; |
366 | } |
367 | |
368 | QStringList parms = msg.split(' ', QString::SkipEmptyParts); |
369 | |
370 | if (parms.count() == 0 && !bufferInfo.bufferName().isEmpty() && bufferInfo.acceptsRegularMessages()) |
371 | parms.prepend(bufferInfo.bufferName()); |
372 | else if (parms.count() != 1) { |
373 | emit displayMsg(Message::Info, typeByTarget(bufname), bufname, |
374 | tr("[usage] /keyx [<nick>] Initiates a DH1080 key exchange with the target." )); |
375 | return; |
376 | } |
377 | |
378 | QString target = parms.at(0); |
379 | |
380 | if (network()->isChannelName(target)) { |
381 | emit displayMsg(Message::Info, typeByTarget(bufname), bufname, tr("It is only possible to exchange keys in a query buffer." )); |
382 | return; |
383 | } |
384 | |
385 | Cipher *cipher = network()->cipher(target); |
386 | if (!cipher) // happens when there is no CoreIrcChannel for the target |
387 | return; |
388 | |
389 | QByteArray pubKey = cipher->initKeyExchange(); |
390 | if (pubKey.isEmpty()) |
391 | emit displayMsg(Message::Error, typeByTarget(bufname), bufname, tr("Failed to initiate key exchange with %1." ).arg(target)); |
392 | else { |
393 | QList<QByteArray> params; |
394 | params << serverEncode(target) << serverEncode("DH1080_INIT " ) + pubKey; |
395 | emit putCmd("NOTICE" , params); |
396 | emit displayMsg(Message::Info, typeByTarget(bufname), bufname, tr("Initiated key exchange with %1." ).arg(target)); |
397 | } |
398 | #else |
399 | Q_UNUSED(msg) |
400 | emit displayMsg(Message::Error, typeByTarget(bufname), bufname, tr("Error: Setting an encryption key requires Quassel to have been built " |
401 | "with support for the Qt Cryptographic Architecture (QCA) library. " |
402 | "Contact your distributor about a Quassel package with QCA " |
403 | "support, or rebuild Quassel with QCA present." )); |
404 | #endif |
405 | } |
406 | |
407 | |
408 | void CoreUserInputHandler::handleKick(const BufferInfo &bufferInfo, const QString &msg) |
409 | { |
410 | QString nick = msg.section(' ', 0, 0, QString::SectionSkipEmpty); |
411 | QString reason = msg.section(' ', 1, -1, QString::SectionSkipEmpty).trimmed(); |
412 | if (reason.isEmpty()) |
413 | reason = network()->identityPtr()->kickReason(); |
414 | |
415 | QList<QByteArray> params; |
416 | params << serverEncode(bufferInfo.bufferName()) << serverEncode(nick) << channelEncode(bufferInfo.bufferName(), reason); |
417 | emit putCmd("KICK" , params); |
418 | } |
419 | |
420 | |
421 | void CoreUserInputHandler::handleKill(const BufferInfo &bufferInfo, const QString &msg) |
422 | { |
423 | Q_UNUSED(bufferInfo) |
424 | QString nick = msg.section(' ', 0, 0, QString::SectionSkipEmpty); |
425 | QString pass = msg.section(' ', 1, -1, QString::SectionSkipEmpty); |
426 | QList<QByteArray> params; |
427 | params << serverEncode(nick) << serverEncode(pass); |
428 | emit putCmd("KILL" , params); |
429 | } |
430 | |
431 | |
432 | void CoreUserInputHandler::handleList(const BufferInfo &bufferInfo, const QString &msg) |
433 | { |
434 | Q_UNUSED(bufferInfo) |
435 | emit putCmd("LIST" , serverEncode(msg.split(' ', QString::SkipEmptyParts))); |
436 | } |
437 | |
438 | |
439 | void CoreUserInputHandler::handleMe(const BufferInfo &bufferInfo, const QString &msg) |
440 | { |
441 | if (bufferInfo.bufferName().isEmpty() || !bufferInfo.acceptsRegularMessages()) |
442 | return; // server buffer |
443 | // FIXME make this a proper event |
444 | coreNetwork()->coreSession()->ctcpParser()->query(coreNetwork(), bufferInfo.bufferName(), "ACTION" , msg); |
445 | emit displayMsg(Message::Action, bufferInfo.type(), bufferInfo.bufferName(), msg, network()->myNick(), Message::Self); |
446 | } |
447 | |
448 | |
449 | void CoreUserInputHandler::handleMode(const BufferInfo &bufferInfo, const QString &msg) |
450 | { |
451 | Q_UNUSED(bufferInfo) |
452 | |
453 | QStringList params = msg.split(' ', QString::SkipEmptyParts); |
454 | // if the first argument is neither a channel nor us (user modes are only to oneself) the current buffer is assumed to be the target |
455 | if (!params.isEmpty()) { |
456 | if (!network()->isChannelName(params[0]) && !network()->isMyNick(params[0])) |
457 | params.prepend(bufferInfo.bufferName()); |
458 | if (network()->isMyNick(params[0]) && params.count() == 2) |
459 | network()->updateIssuedModes(params[1]); |
460 | if (params[0] == "-reset" && params.count() == 1) { |
461 | // FIXME: give feedback to the user (I don't want to add new strings right now) |
462 | network()->resetPersistentModes(); |
463 | return; |
464 | } |
465 | } |
466 | |
467 | // TODO handle correct encoding for buffer modes (channelEncode()) |
468 | emit putCmd("MODE" , serverEncode(params)); |
469 | } |
470 | |
471 | |
472 | // TODO: show privmsgs |
473 | void CoreUserInputHandler::handleMsg(const BufferInfo &bufferInfo, const QString &msg) |
474 | { |
475 | Q_UNUSED(bufferInfo); |
476 | if (!msg.contains(' ')) |
477 | return; |
478 | |
479 | QString target = msg.section(' ', 0, 0); |
480 | QByteArray encMsg = userEncode(target, msg.section(' ', 1)); |
481 | |
482 | #ifdef HAVE_QCA2 |
483 | putPrivmsg(serverEncode(target), encMsg, network()->cipher(target)); |
484 | #else |
485 | putPrivmsg(serverEncode(target), encMsg); |
486 | #endif |
487 | } |
488 | |
489 | |
490 | void CoreUserInputHandler::handleNick(const BufferInfo &bufferInfo, const QString &msg) |
491 | { |
492 | Q_UNUSED(bufferInfo) |
493 | QString nick = msg.section(' ', 0, 0); |
494 | emit putCmd("NICK" , serverEncode(nick)); |
495 | } |
496 | |
497 | |
498 | void CoreUserInputHandler::handleNotice(const BufferInfo &bufferInfo, const QString &msg) |
499 | { |
500 | QString bufferName = msg.section(' ', 0, 0); |
501 | QString payload = msg.section(' ', 1); |
502 | QList<QByteArray> params; |
503 | params << serverEncode(bufferName) << channelEncode(bufferInfo.bufferName(), payload); |
504 | emit putCmd("NOTICE" , params); |
505 | emit displayMsg(Message::Notice, typeByTarget(bufferName), bufferName, payload, network()->myNick(), Message::Self); |
506 | } |
507 | |
508 | |
509 | |
510 | void CoreUserInputHandler::handleOper(const BufferInfo &bufferInfo, const QString &msg) |
511 | { |
512 | Q_UNUSED(bufferInfo) |
513 | emit putRawLine(serverEncode(QString("OPER %1" ).arg(msg))); |
514 | } |
515 | |
516 | |
517 | void CoreUserInputHandler::handlePart(const BufferInfo &bufferInfo, const QString &msg) |
518 | { |
519 | QList<QByteArray> params; |
520 | QString partReason; |
521 | |
522 | // msg might contain either a channel name and/or a reaon, so we have to check if the first word is a known channel |
523 | QString channelName = msg.section(' ', 0, 0); |
524 | if (channelName.isEmpty() || !network()->ircChannel(channelName)) { |
525 | channelName = bufferInfo.bufferName(); |
526 | partReason = msg; |
527 | } |
528 | else { |
529 | partReason = msg.mid(channelName.length() + 1); |
530 | } |
531 | |
532 | if (partReason.isEmpty()) |
533 | partReason = network()->identityPtr()->partReason(); |
534 | |
535 | params << serverEncode(channelName) << channelEncode(bufferInfo.bufferName(), partReason); |
536 | emit putCmd("PART" , params); |
537 | } |
538 | |
539 | |
540 | void CoreUserInputHandler::handlePing(const BufferInfo &bufferInfo, const QString &msg) |
541 | { |
542 | Q_UNUSED(bufferInfo) |
543 | |
544 | QString param = msg; |
545 | if (param.isEmpty()) |
546 | param = QTime::currentTime().toString("hh:mm:ss.zzz" ); |
547 | |
548 | putCmd("PING" , serverEncode(param)); |
549 | } |
550 | |
551 | |
552 | // TODO: implement queries |
553 | void CoreUserInputHandler::handleQuery(const BufferInfo &bufferInfo, const QString &msg) |
554 | { |
555 | Q_UNUSED(bufferInfo) |
556 | QString target = msg.section(' ', 0, 0); |
557 | QString message = msg.section(' ', 1); |
558 | if (message.isEmpty()) |
559 | emit displayMsg(Message::Server, BufferInfo::QueryBuffer, target, tr("Starting query with %1" ).arg(target), network()->myNick(), Message::Self); |
560 | else |
561 | emit displayMsg(Message::Plain, BufferInfo::QueryBuffer, target, message, network()->myNick(), Message::Self); |
562 | handleMsg(bufferInfo, msg); |
563 | } |
564 | |
565 | |
566 | void CoreUserInputHandler::handleQuit(const BufferInfo &bufferInfo, const QString &msg) |
567 | { |
568 | Q_UNUSED(bufferInfo) |
569 | network()->disconnectFromIrc(true, msg); |
570 | } |
571 | |
572 | |
573 | void CoreUserInputHandler::issueQuit(const QString &reason) |
574 | { |
575 | emit putCmd("QUIT" , serverEncode(reason)); |
576 | } |
577 | |
578 | |
579 | void CoreUserInputHandler::handleQuote(const BufferInfo &bufferInfo, const QString &msg) |
580 | { |
581 | Q_UNUSED(bufferInfo) |
582 | emit putRawLine(serverEncode(msg)); |
583 | } |
584 | |
585 | |
586 | void CoreUserInputHandler::handleSay(const BufferInfo &bufferInfo, const QString &msg) |
587 | { |
588 | if (bufferInfo.bufferName().isEmpty() || !bufferInfo.acceptsRegularMessages()) |
589 | return; // server buffer |
590 | |
591 | QByteArray encMsg = channelEncode(bufferInfo.bufferName(), msg); |
592 | #ifdef HAVE_QCA2 |
593 | putPrivmsg(serverEncode(bufferInfo.bufferName()), encMsg, network()->cipher(bufferInfo.bufferName())); |
594 | #else |
595 | putPrivmsg(serverEncode(bufferInfo.bufferName()), encMsg); |
596 | #endif |
597 | emit displayMsg(Message::Plain, bufferInfo.type(), bufferInfo.bufferName(), msg, network()->myNick(), Message::Self); |
598 | } |
599 | |
600 | |
601 | void CoreUserInputHandler::handleSetkey(const BufferInfo &bufferInfo, const QString &msg) |
602 | { |
603 | QString bufname = bufferInfo.bufferName().isNull() ? "" : bufferInfo.bufferName(); |
604 | #ifdef HAVE_QCA2 |
605 | if (!bufferInfo.isValid()) |
606 | return; |
607 | |
608 | if (!Cipher::neededFeaturesAvailable()) { |
609 | emit displayMsg(Message::Error, typeByTarget(bufname), bufname, tr("Error: QCA provider plugin not found. It is usually provided by the qca-ossl plugin." )); |
610 | return; |
611 | } |
612 | |
613 | QStringList parms = msg.split(' ', QString::SkipEmptyParts); |
614 | |
615 | if (parms.count() == 1 && !bufferInfo.bufferName().isEmpty() && bufferInfo.acceptsRegularMessages()) |
616 | parms.prepend(bufferInfo.bufferName()); |
617 | else if (parms.count() != 2) { |
618 | emit displayMsg(Message::Info, typeByTarget(bufname), bufname, |
619 | tr("[usage] /setkey <nick|channel> <key> sets the encryption key for nick or channel. " |
620 | "/setkey <key> when in a channel or query buffer sets the key for it." )); |
621 | return; |
622 | } |
623 | |
624 | QString target = parms.at(0); |
625 | QByteArray key = parms.at(1).toLocal8Bit(); |
626 | network()->setCipherKey(target, key); |
627 | |
628 | emit displayMsg(Message::Info, typeByTarget(bufname), bufname, tr("The key for %1 has been set." ).arg(target)); |
629 | #else |
630 | Q_UNUSED(msg) |
631 | emit displayMsg(Message::Error, typeByTarget(bufname), bufname, tr("Error: Setting an encryption key requires Quassel to have been built " |
632 | "with support for the Qt Cryptographic Architecture (QCA) library. " |
633 | "Contact your distributor about a Quassel package with QCA " |
634 | "support, or rebuild Quassel with QCA present." )); |
635 | #endif |
636 | } |
637 | |
638 | |
639 | void CoreUserInputHandler::handleShowkey(const BufferInfo &bufferInfo, const QString &msg) |
640 | { |
641 | QString bufname = bufferInfo.bufferName().isNull() ? "" : bufferInfo.bufferName(); |
642 | #ifdef HAVE_QCA2 |
643 | if (!bufferInfo.isValid()) |
644 | return; |
645 | |
646 | if (!Cipher::neededFeaturesAvailable()) { |
647 | emit displayMsg(Message::Error, typeByTarget(bufname), bufname, tr("Error: QCA provider plugin not found. It is usually provided by the qca-ossl plugin." )); |
648 | return; |
649 | } |
650 | |
651 | QStringList parms = msg.split(' ', QString::SkipEmptyParts); |
652 | |
653 | if (parms.isEmpty() && !bufferInfo.bufferName().isEmpty() && bufferInfo.acceptsRegularMessages()) |
654 | parms.prepend(bufferInfo.bufferName()); |
655 | |
656 | if (parms.isEmpty()) { |
657 | emit displayMsg(Message::Info, typeByTarget(bufname), bufname, tr("[usage] /showkey <nick|channel> shows the encryption key for nick or channel or just /showkey when in a channel or query." )); |
658 | return; |
659 | } |
660 | |
661 | QString target = parms.at(0); |
662 | QByteArray key = network()->cipherKey(target); |
663 | |
664 | if (key.isEmpty()) { |
665 | emit displayMsg(Message::Info, typeByTarget(bufname), bufname, tr("No key has been set for %1." ).arg(target)); |
666 | return; |
667 | } |
668 | |
669 | emit displayMsg(Message::Info, typeByTarget(bufname), bufname, tr("The key for %1 is %2:%3" ).arg(target, network()->cipherUsesCBC(target) ? "CBC" : "ECB" , QString(key))); |
670 | |
671 | #else |
672 | Q_UNUSED(msg) |
673 | emit displayMsg(Message::Error, typeByTarget(bufname), bufname, tr("Error: Setting an encryption key requires Quassel to have been built " |
674 | "with support for the Qt Cryptographic Architecture (QCA2) library. " |
675 | "Contact your distributor about a Quassel package with QCA2 " |
676 | "support, or rebuild Quassel with QCA2 present." )); |
677 | #endif |
678 | } |
679 | |
680 | |
681 | void CoreUserInputHandler::handleTopic(const BufferInfo &bufferInfo, const QString &msg) |
682 | { |
683 | if (bufferInfo.bufferName().isEmpty() || !bufferInfo.acceptsRegularMessages()) |
684 | return; |
685 | |
686 | QList<QByteArray> params; |
687 | params << serverEncode(bufferInfo.bufferName()); |
688 | |
689 | if (!msg.isEmpty()) { |
690 | # ifdef HAVE_QCA2 |
691 | params << encrypt(bufferInfo.bufferName(), channelEncode(bufferInfo.bufferName(), msg)); |
692 | # else |
693 | params << channelEncode(bufferInfo.bufferName(), msg); |
694 | # endif |
695 | } |
696 | |
697 | emit putCmd("TOPIC" , params); |
698 | } |
699 | |
700 | |
701 | void CoreUserInputHandler::handleVoice(const BufferInfo &bufferInfo, const QString &msg) |
702 | { |
703 | QStringList nicks = msg.split(' ', QString::SkipEmptyParts); |
704 | QString m = "+" ; for (int i = 0; i < nicks.count(); i++) m += 'v'; |
705 | QStringList params; |
706 | params << bufferInfo.bufferName() << m << nicks; |
707 | emit putCmd("MODE" , serverEncode(params)); |
708 | } |
709 | |
710 | |
711 | void CoreUserInputHandler::handleWait(const BufferInfo &bufferInfo, const QString &msg) |
712 | { |
713 | int splitPos = msg.indexOf(';'); |
714 | if (splitPos <= 0) |
715 | return; |
716 | |
717 | bool ok; |
718 | int delay = msg.left(splitPos).trimmed().toInt(&ok); |
719 | if (!ok) |
720 | return; |
721 | |
722 | delay *= 1000; |
723 | |
724 | QString command = msg.mid(splitPos + 1).trimmed(); |
725 | if (command.isEmpty()) |
726 | return; |
727 | |
728 | _delayedCommands[startTimer(delay)] = Command(bufferInfo, command); |
729 | } |
730 | |
731 | |
732 | void CoreUserInputHandler::handleWho(const BufferInfo &bufferInfo, const QString &msg) |
733 | { |
734 | Q_UNUSED(bufferInfo) |
735 | emit putCmd("WHO" , serverEncode(msg.split(' '))); |
736 | } |
737 | |
738 | |
739 | void CoreUserInputHandler::handleWhois(const BufferInfo &bufferInfo, const QString &msg) |
740 | { |
741 | Q_UNUSED(bufferInfo) |
742 | emit putCmd("WHOIS" , serverEncode(msg.split(' '))); |
743 | } |
744 | |
745 | |
746 | void CoreUserInputHandler::handleWhowas(const BufferInfo &bufferInfo, const QString &msg) |
747 | { |
748 | Q_UNUSED(bufferInfo) |
749 | emit putCmd("WHOWAS" , serverEncode(msg.split(' '))); |
750 | } |
751 | |
752 | |
753 | void CoreUserInputHandler::defaultHandler(QString cmd, const BufferInfo &bufferInfo, const QString &msg) |
754 | { |
755 | Q_UNUSED(bufferInfo); |
756 | emit putCmd(serverEncode(cmd.toUpper()), serverEncode(msg.split(" " ))); |
757 | } |
758 | |
759 | |
760 | void CoreUserInputHandler::putPrivmsg(const QByteArray &target, const QByteArray &message, Cipher *cipher) |
761 | { |
762 | // Encrypted messages need special care. There's no clear relation between cleartext and encrypted message length, |
763 | // so we can't just compute the maxSplitPos. Instead, we need to loop through the splitpoints until the crypted |
764 | // version is short enough... |
765 | // TODO: check out how the various possible encryption methods behave length-wise and make |
766 | // this clean by predicting the length of the crypted msg. |
767 | // For example, blowfish-ebc seems to create 8-char chunks. |
768 | |
769 | static const char *cmd = "PRIVMSG" ; |
770 | static const char *splitter = " .,-" ; |
771 | |
772 | int maxSplitPos = message.count(); |
773 | int splitPos = maxSplitPos; |
774 | forever { |
775 | QByteArray crypted = message.left(splitPos); |
776 | bool isEncrypted = false; |
777 | #ifdef HAVE_QCA2 |
778 | if (cipher && !cipher->key().isEmpty() && !message.isEmpty()) { |
779 | isEncrypted = cipher->encrypt(crypted); |
780 | } |
781 | #endif |
782 | int overrun = lastParamOverrun(cmd, QList<QByteArray>() << target << crypted); |
783 | if (overrun) { |
784 | // In case this is not an encrypted msg, we can just cut off at the end |
785 | if (!isEncrypted) |
786 | maxSplitPos = message.count() - overrun; |
787 | |
788 | splitPos = -1; |
789 | for (const char *splitChar = splitter; *splitChar != 0; splitChar++) { |
790 | splitPos = qMax(splitPos, message.lastIndexOf(*splitChar, maxSplitPos) + 1); // keep split char on old line |
791 | } |
792 | if (splitPos <= 0 || splitPos > maxSplitPos) |
793 | splitPos = maxSplitPos; |
794 | |
795 | maxSplitPos = splitPos - 1; |
796 | if (maxSplitPos <= 0) { // this should never happen, but who knows... |
797 | qWarning() << tr("[Error] Could not encrypt your message: %1" ).arg(message.data()); |
798 | return; |
799 | } |
800 | continue; // we never come back here for !encrypted! |
801 | } |
802 | |
803 | // now we have found a valid splitpos (or didn't need to split to begin with) |
804 | putCmd(cmd, QList<QByteArray>() << target << crypted); |
805 | if (splitPos < message.count()) |
806 | putPrivmsg(target, message.mid(splitPos), cipher); |
807 | |
808 | return; |
809 | } |
810 | } |
811 | |
812 | |
813 | // returns 0 if the message will not be chopped by the irc server or number of chopped bytes if message is too long |
814 | int CoreUserInputHandler::lastParamOverrun(const QString &cmd, const QList<QByteArray> ¶ms) |
815 | { |
816 | // the server will pass our message truncated to 512 bytes including CRLF with the following format: |
817 | // ":prefix COMMAND param0 param1 :lastparam" |
818 | // where prefix = "nickname!user@host" |
819 | // that means that the last message can be as long as: |
820 | // 512 - nicklen - userlen - hostlen - commandlen - sum(param[0]..param[n-1])) - 2 (for CRLF) - 4 (":!@" + 1space between prefix and command) - max(paramcount - 1, 0) (space for simple params) - 2 (space and colon for last param) |
821 | IrcUser *me = network()->me(); |
822 | int maxLen = 480 - cmd.toLatin1().count(); // educated guess in case we don't know us (yet?) |
823 | |
824 | if (me) |
825 | maxLen = 512 - serverEncode(me->nick()).count() - serverEncode(me->user()).count() - serverEncode(me->host()).count() - cmd.toLatin1().count() - 6; |
826 | |
827 | if (!params.isEmpty()) { |
828 | for (int i = 0; i < params.count() - 1; i++) { |
829 | maxLen -= (params[i].count() + 1); |
830 | } |
831 | maxLen -= 2; // " :" last param separator; |
832 | |
833 | if (params.last().count() > maxLen) { |
834 | return params.last().count() - maxLen; |
835 | } |
836 | else { |
837 | return 0; |
838 | } |
839 | } |
840 | else { |
841 | return 0; |
842 | } |
843 | } |
844 | |
845 | |
846 | #ifdef HAVE_QCA2 |
847 | QByteArray CoreUserInputHandler::encrypt(const QString &target, const QByteArray &message_, bool *didEncrypt) const |
848 | { |
849 | if (didEncrypt) |
850 | *didEncrypt = false; |
851 | |
852 | if (message_.isEmpty()) |
853 | return message_; |
854 | |
855 | if (!Cipher::neededFeaturesAvailable()) |
856 | return message_; |
857 | |
858 | Cipher *cipher = network()->cipher(target); |
859 | if (!cipher || cipher->key().isEmpty()) |
860 | return message_; |
861 | |
862 | QByteArray message = message_; |
863 | bool result = cipher->encrypt(message); |
864 | if (didEncrypt) |
865 | *didEncrypt = result; |
866 | |
867 | return message; |
868 | } |
869 | |
870 | |
871 | #endif |
872 | |
873 | void CoreUserInputHandler::timerEvent(QTimerEvent *event) |
874 | { |
875 | if (!_delayedCommands.contains(event->timerId())) { |
876 | QObject::timerEvent(event); |
877 | return; |
878 | } |
879 | BufferInfo bufferInfo = _delayedCommands[event->timerId()].bufferInfo; |
880 | QString rawCommand = _delayedCommands[event->timerId()].command; |
881 | _delayedCommands.remove(event->timerId()); |
882 | event->accept(); |
883 | |
884 | // the stored command might be the result of an alias expansion, so we need to split it up again |
885 | QStringList commands = rawCommand.split(QRegExp("; ?" )); |
886 | foreach(QString command, commands) { |
887 | handleUserInput(bufferInfo, command); |
888 | } |
889 | } |
890 | |