1 /*************************************************************************
2 * Copyright <2007 - 2013> <Michael Zanetti> <mzanetti@kde.org> *
3 * *
4 * This program is free software; you can redistribute it and/or *
5 * modify it under the terms of the GNU General Public License as *
6 * published by the Free Software Foundation; either version 2 of *
7 * the License or (at your option) version 3 or any later version *
8 * accepted by the membership of KDE e.V. (or its successor approved *
9 * by the membership of KDE e.V.), which shall act as a proxy *
10 * defined in Section 14 of version 3 of the license. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
19 *************************************************************************/
20
21 #include "otrplugin.h"
22 #include "otrguiclient.h"
23 #include "otrlchatinterface.h"
24 #include "kopete_otr.h"
25
26 #include <qtimer.h>
27 #include <qregexp.h>
28 #include <qfile.h>
29 #include <qcolor.h>
30 #include <QDir>
31
32 #include <KLocalizedString>
33
34 #include "plugin_otr_debug.h"
35 #include <qaction.h>
36 #include <kconfig.h>
37 #include <kpluginfactory.h>
38 #include <kselectaction.h>
39 #include <kactioncollection.h>
40
41 #include <kopetemetacontact.h>
42 #include <kopetecontactlist.h>
43 #include <kopetechatsessionmanager.h>
44 #include <kopetesimplemessagehandler.h>
45 #include <kopeteuiglobal.h>
46 #include <kopetecontact.h>
47 #include <kopetemessage.h>
48 #include <kopeteaccount.h>
49 #include <kopeteaccountmanager.h>
50 #include <kopetemessageevent.h>
51 #include <kopeteprotocol.h>
52 #include <ui/kopeteview.h>
53 #include <QStandardPaths>
54
55 /**
56 * @author Michael Zanetti
57 */
58
K_PLUGIN_FACTORY(OTRPluginFactory,registerPlugin<OTRPlugin> ();)59 K_PLUGIN_FACTORY(OTRPluginFactory, registerPlugin<OTRPlugin>();
60 )
61 K_EXPORT_PLUGIN(OTRPluginFactory("kopete_otr"))
62
63 OTRPlugin::OTRPlugin (QObject *parent, const QVariantList & /*args*/)
64 : Kopete::Plugin(parent)
65 {
66 qCDebug(KOPETE_PLUGIN_OTR_LOG) << "OTR Plugin loading...";
67
68 if (!pluginStatic_) {
69 pluginStatic_ = this;
70 }
71
72 m_inboundHandler = new OtrMessageHandlerFactory(this);
73
74 connect(Kopete::ChatSessionManager::self(), SIGNAL(aboutToSend(Kopete::Message&)),
75 SLOT(slotOutgoingMessage(Kopete::Message&)));
76
77 connect(Kopete::ChatSessionManager::self(), SIGNAL(chatSessionCreated(Kopete::ChatSession*)),
78 this, SLOT(slotNewChatSessionWindow(Kopete::ChatSession*)));
79
80 connect(this, SIGNAL(settingsChanged()), this, SLOT(slotSettingsChanged()));
81
82 //initialize the otrlib and create the interface object
83 otrlChatInterface = OtrlChatInterface::self();
84 otrlChatInterface->setPlugin(this);
85
86 // Checking file Permissions
87 const QString otrPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + QStringLiteral("kopete_otr/");
88 QDir().mkdir(otrPath);
89 OtrlChatInterface::self()->checkFilePermissions(otrPath);
90
91 //setting the policy
92 slotSettingsChanged();
93
94 //adding menu to contaclists menubar and contacts popup menu
95 otrPolicyMenu = new KSelectAction(QIcon::fromTheme(QStringLiteral("object-locked")), i18nc("@item:inmenu", "&OTR Policy"), this);
96 actionCollection()->addAction(QStringLiteral("otr_policy"), otrPolicyMenu);
97
98 QAction *separatorAction = new QAction(otrPolicyMenu);
99 separatorAction->setSeparator(true);
100
101 otrPolicyMenu->addAction(i18nc("@item:inmenu Use the default encryption mode specified in settings dialog", "&Default"));
102 otrPolicyMenu->addAction(separatorAction);
103 otrPolicyMenu->addAction(i18nc("@item:inmenu Always encrypt messages", "Al&ways"));
104 otrPolicyMenu->addAction(i18nc("@item:inmenu Use the opportunistic encryption mode", "&Opportunistic"));
105 otrPolicyMenu->addAction(i18nc("@item:inmenu Use the manual encryption mode", "&Manual"));
106 otrPolicyMenu->addAction(i18nc("@item:inmenu Never encrypt messages", "Ne&ver"));
107
108 otrPolicyMenu->setEnabled(false);
109
110 connect(otrPolicyMenu, SIGNAL(triggered(int)), this, SLOT(slotSetPolicy()));
111 connect(Kopete::ContactList::self(), SIGNAL(metaContactSelected(bool)), this, SLOT(slotSelectionChanged(bool)));
112
113 setXMLFile(QStringLiteral("otrui.rc"));
114
115 //Add GUI action to all already existing kmm
116 // (if the plugin is launched when kopete already runing)
117 QList<Kopete::ChatSession *> sessions
118 = Kopete::ChatSessionManager::self()->sessions();
119 QListIterator<Kopete::ChatSession *> it(sessions);
120 while (it.hasNext()) {
121 slotNewChatSessionWindow(it.next());
122 }
123 }
124
~OTRPlugin()125 OTRPlugin::~OTRPlugin()
126 {
127 delete m_inboundHandler;
128 pluginStatic_ = nullptr;
129 qCDebug(KOPETE_PLUGIN_OTR_LOG) << "Exiting OTR plugin";
130 }
131
plugin()132 OTRPlugin *OTRPlugin::plugin()
133 {
134 return pluginStatic_;
135 }
136
137 OTRPlugin *OTRPlugin::pluginStatic_ = nullptr;
138
slotNewChatSessionWindow(Kopete::ChatSession * KMM)139 void OTRPlugin::slotNewChatSessionWindow(Kopete::ChatSession *KMM)
140 {
141 //Check if there is another user in the session.
142 //If not it could be a Jabber-MUC
143 //If there is more than one member it is a MUC
144 // Also don't add the Button on an IRC window!
145 if (KMM->members().count() == 1 && (KMM->protocol()) && (KMM->protocol()->pluginId() != QLatin1String("IRCProtocol"))) {
146 new OtrGUIClient(KMM);
147 }
148 }
149
slotOutgoingMessage(Kopete::Message & msg)150 void OTRPlugin::slotOutgoingMessage(Kopete::Message &msg)
151 {
152 if (msg.direction() == Kopete::Message::Outbound) {
153 QString cacheBody;
154 bool cachePlain;
155 if (msg.format() == Qt::PlainText) {
156 cacheBody = msg.plainBody();
157 cachePlain = true;
158 } else {
159 cacheBody = msg.escapedBody();
160 cachePlain = false;
161 }
162
163 otrlChatInterface->encryptMessage(msg);
164
165 if (!msg.plainBody().isEmpty()) {
166 messageCache.insert(msg.plainBody(), qMakePair(cacheBody, cachePlain));
167 } else {
168 messageCache.insert(QStringLiteral("!OTR:MsgDelByOTR"), qMakePair(cacheBody, cachePlain));
169 }
170
171 qCDebug(KOPETE_PLUGIN_OTR_LOG) << "Outgoing message after processing:" << msg.plainBody() << msg.format();
172 }
173 }
174
slotEnableOtr(Kopete::ChatSession * session,bool enable)175 void OTRPlugin::slotEnableOtr(Kopete::ChatSession *session, bool enable)
176 {
177 if (enable) {
178 QString policy = session->members().first()->metaContact()->pluginData(OTRPlugin::plugin(), QStringLiteral("otr_policy"));
179 bool noerr;
180 KopeteOtrKcfg::self()->load();
181 if (policy.toInt(&noerr, 10) == 4 || (policy.toInt(&noerr, 10) == 0 && KopeteOtrKcfg::self()->rbNever())) {
182 Kopete::Message msg(session->account()->myself(), session->members());
183 msg.setPlainBody(i18nc("@info:status", "Your policy settings do not allow encrypted sessions to this contact."));
184 msg.setDirection(Kopete::Message::Internal);
185 session->appendMessage(msg);
186 } else {
187 QString body = otrlChatInterface->getDefaultQuery(session->account()->accountId());
188 Kopete::Message msg1(session->account()->myself(), session->members().first());
189 msg1.setPlainBody(QString(body));
190 msg1.setDirection(Kopete::Message::Outbound);
191 if (otrlChatInterface->privState(session) > 0) {
192 body = i18nc("@info:status", "Attempting to refresh the OTR session with <b>%1</b>...", otrlChatInterface->formatContact(session->members().first()->contactId()));
193 } else {
194 body = i18nc("@info:status", "Attempting to start a private OTR session with <b>%1</b>...", otrlChatInterface->formatContact(session->members().first()->contactId()));
195 }
196 Kopete::Message msg2(session->account()->myself(), session->members().first());
197 msg2.setHtmlBody(body);
198 msg2.setDirection(Kopete::Message::Internal);
199
200 session->sendMessage(msg1);
201 session->appendMessage(msg2);
202 }
203 } else {
204 otrlChatInterface->disconnectSession(session);
205 }
206 }
207
slotVerifyFingerprint(Kopete::ChatSession * session)208 void OTRPlugin::slotVerifyFingerprint(Kopete::ChatSession *session)
209 {
210 otrlChatInterface->verifyFingerprint(session);
211 }
212
slotSettingsChanged()213 void OTRPlugin::slotSettingsChanged()
214 {
215 KopeteOtrKcfg::self()->load();
216 if (KopeteOtrKcfg::self()->rbAlways()) {
217 otrlChatInterface->setPolicy(OTRL_POLICY_ALWAYS);
218 } else if (KopeteOtrKcfg::self()->rbOpportunistic()) {
219 otrlChatInterface->setPolicy(OTRL_POLICY_OPPORTUNISTIC);
220 } else if (KopeteOtrKcfg::self()->rbManual()) {
221 otrlChatInterface->setPolicy(OTRL_POLICY_MANUAL);
222 } else if (KopeteOtrKcfg::self()->rbNever()) {
223 otrlChatInterface->setPolicy(OTRL_POLICY_NEVER);
224 } else {
225 otrlChatInterface->setPolicy(OTRL_POLICY_DEFAULT);
226 }
227 }
228
emitGoneSecure(Kopete::ChatSession * session,int status)229 void OTRPlugin::emitGoneSecure(Kopete::ChatSession *session, int status)
230 {
231 emit goneSecure(session, status);
232 }
233
getMessageCache()234 QMap<QString, QPair<QString, bool> > OTRPlugin::getMessageCache()
235 {
236 return messageCache;
237 }
238
handleMessage(Kopete::MessageEvent * event)239 void OtrMessageHandler::handleMessage(Kopete::MessageEvent *event)
240 {
241 if (!plugin) {
242 MessageHandler::handleMessage(event);
243 return;
244 }
245
246 Kopete::Message msg = event->message();
247 // Kopete::ChatSession *session = msg.manager();
248 QMap<QString, QPair<QString, bool> > messageCache = plugin->getMessageCache();
249
250 qCDebug(KOPETE_PLUGIN_OTR_LOG) << "OtrMessageHandler::handleMessage:" << msg.plainBody();
251
252 if (msg.direction() == Kopete::Message::Inbound) {
253 if (msg.type() == Kopete::Message::TypeFileTransferRequest) {
254 // file transfers aren't encrypted. Proceed with next plugin
255 MessageHandler::handleMessage(event);
256 return;
257 }
258 int retValue = OtrlChatInterface::self()->decryptMessage(msg);
259 if ((retValue == 2) | OtrlChatInterface::self()->shouldDiscard(msg.plainBody())) {
260 // internal OTR message
261 event->discard();
262 return;
263 } else if (retValue == 1) {
264 // plaintext message. Proceed with next plugin
265 MessageHandler::handleMessage(event);
266 return;
267 }
268 } else if (msg.direction() == Kopete::Message::Outbound) {
269 const QString &plainBody = msg.plainBody();
270 // qCDebug(KOPETE_PLUGIN_OTR_LOG) << "searching cache for" << msg.plainBody();
271 if (messageCache.contains(plainBody)) {
272 if (!messageCache[plainBody].second) {
273 msg.setHtmlBody(messageCache[plainBody].first);
274 } else if (plainBody != messageCache[plainBody].first) {
275 msg.setPlainBody(messageCache[plainBody].first);
276 }
277 messageCache.remove(messageCache[plainBody].first);
278 if (messageCache.count() > 5) {
279 messageCache.clear();
280 }
281 }
282 // Check if Message is an OTR message. Should it be discarded or shown?
283 if (OtrlChatInterface::self()->shouldDiscard(msg.plainBody())) {
284 event->discard();
285 qCDebug(KOPETE_PLUGIN_OTR_LOG) << "OTR: discarding message";
286 return;
287 }
288 // If the message is sent while a Finished state libotr deletes the messagetext.
289 // This prevents the empty message from being shown in our chatwindow
290 if (msg.plainBody().isEmpty()) {
291 event->discard();
292 if (messageCache.contains(QStringLiteral("!OTR:MsgDelByOTR"))) {
293 if (!messageCache[QStringLiteral("!OTR:MsgDelByOTR")].second) {
294 msg.setHtmlBody(messageCache[QStringLiteral("!OTR:MsgDelByOTR")].first);
295 } else {
296 msg.setPlainBody(messageCache[QStringLiteral("!OTR:MsgDelByOTR")].first);
297 }
298 msg.manager()->view()->setCurrentMessage(msg);
299 messageCache.remove(QStringLiteral("!OTR:MsgDelByOTR"));
300 }
301 return;
302 }
303 }
304
305 event->setMessage(msg);
306
307 MessageHandler::handleMessage(event);
308 }
309
slotSelectionChanged(bool single)310 void OTRPlugin::slotSelectionChanged(bool single)
311 {
312 otrPolicyMenu->setEnabled(single);
313
314 if (!single) {
315 return;
316 }
317
318 Kopete::MetaContact *metaContact = Kopete::ContactList::self()->selectedMetaContacts().first();
319
320 QString policy = metaContact->pluginData(this, QStringLiteral("otr_policy"));
321
322 bool noerr;
323 if (!policy.isEmpty() && policy != QLatin1String("null")) {
324 otrPolicyMenu->setCurrentItem(policy.toInt(&noerr, 10) + 1); // +1 because of the Separator
325 } else {
326 otrPolicyMenu->setCurrentItem(0);
327 }
328 }
329
slotSetPolicy()330 void OTRPlugin::slotSetPolicy()
331 {
332 qCDebug(KOPETE_PLUGIN_OTR_LOG) << "Setting contact policy";
333 Kopete::MetaContact *metaContact = Kopete::ContactList::self()->selectedMetaContacts().first();
334 if (metaContact) {
335 metaContact->setPluginData(this, QStringLiteral("otr_policy"), QString::number(otrPolicyMenu->currentItem() - 1)); // -1 because of the Separator
336 }
337 qCDebug(KOPETE_PLUGIN_OTR_LOG) << "Selected policy: " << otrPolicyMenu->currentItem();
338 }
339
slotSecuritySate(Kopete::ChatSession * session,int state)340 void OTRPlugin::slotSecuritySate(Kopete::ChatSession *session, int state)
341 {
342 emitGoneSecure(session, state);
343 }
344
345 #include "otrplugin.moc"
346
347 // vim: set noet ts=4 sts=4 sw=4:
348