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