1 //
2 // Copyright (C) 2004 Grzegorz Jaskiewicz <gj at pointblue.com.pl>
3 //
4 // gadudcctransaction.cpp
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 // 02110-1301, USA.
20 
21 #include "gadudcctransaction.h"
22 
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <sys/socket.h>
26 #include <sys/un.h>
27 
28 #include <netinet/in.h>
29 
30 #include <kdebug.h>
31 #include <klocale.h>
32 #include <kmessagebox.h>
33 
34 #include "kopetetransfermanager.h"
35 #include "kopetemetacontact.h"
36 #include "kopeteuiglobal.h"
37 
38 #include <qsocketnotifier.h>
39 #include <qfile.h>
40 
41 #include "gaducontact.h"
42 #include "gaduaccount.h"
43 #include "gadudcc.h"
44 
45 #include "libgadu.h"
46 
GaduDCCTransaction(GaduDCC * parent)47 GaduDCCTransaction::GaduDCCTransaction(GaduDCC *parent)
48     : QObject(parent)
49     , gaduDCC_(parent)
50 {
51     read_ = NULL;
52     write_ = NULL;
53     contact = NULL;
54     transfer_ = NULL;
55     dccSock_ = NULL;
56     peer = 0;
57 }
58 
~GaduDCCTransaction()59 GaduDCCTransaction::~GaduDCCTransaction()
60 {
61     closeDCC();
62 }
63 
64 unsigned int
recvUIN()65 GaduDCCTransaction::recvUIN()
66 {
67     if (dccSock_) {
68         return dccSock_->uin;
69     }
70     return 0;
71 }
72 
73 unsigned int
peerUIN()74 GaduDCCTransaction::peerUIN()
75 {
76     if (dccSock_) {
77         return dccSock_->peer_uin;
78     }
79     return 0;
80 }
81 
82 bool
setupOutgoing(GaduContact * peerContact,QString & filePath)83 GaduDCCTransaction::setupOutgoing(GaduContact *peerContact, QString &filePath)
84 {
85     GaduContact *me;
86     GaduAccount *metoo;
87 
88     if (!peerContact) {
89         return false;
90     }
91 
92     me = static_cast<GaduContact *>(peerContact->account()->myself());
93 
94     QString aaa = peerContact->contactIp().toString();
95     kDebug(14100) << "slotOutgoin for UIN: " << peerContact->uin() << " port "
96                   << peerContact->contactPort() << " ip " <<aaa;
97     kDebug(14100) << "File path is " << filePath;
98 
99     if (peerContact->contactPort() >= 10) {
100         dccSock_ = gg_dcc_send_file(htonl(
101                                         peerContact->contactIp().toIPv4Address()),
102                                     peerContact->contactPort(),
103                                     me->uin(), peerContact->uin());
104         gg_dcc_fill_file_info(dccSock_, filePath.toAscii());
105         transfer_ = Kopete::TransferManager::transferManager()->addTransfer(peerContact,
106                                                                             filePath,
107                                                                             dccSock_->file_info.size,
108                                                                             peerContact->metaContact()->displayName(),
109                                                                             Kopete::FileTransferInfo::Outgoing);
110         createNotifiers(true);
111         enableNotifiers(dccSock_->check);
112     } else {
113         kDebug(14100) << "Peer " << peerContact->uin()
114                       << " is passive, requesting reverse connection";
115         metoo = static_cast<GaduAccount *>(me->account());
116         gaduDCC_->requests[peerContact->uin()] = filePath;
117         metoo->dccRequest(peerContact);
118     }
119 
120     return false;
121 }
122 
123 bool
setupIncoming(const unsigned int uin,GaduContact * peerContact)124 GaduDCCTransaction::setupIncoming(const unsigned int uin, GaduContact *peerContact)
125 {
126     if (!peerContact) {
127         kDebug(14100) << "setupIncoming called with peerContact == NULL ";
128         return false;
129         return false;
130     }
131 
132     QString aaa = peerContact->contactIp().toString();
133     kDebug(14100) << "setupIncoming for UIN: " << uin << " port " << peerContact->contactPort()
134                   << " ip " <<aaa;
135 
136     peer = peerContact->uin();
137     dccSock_ = gg_dcc_get_file(htonl(
138                                    peerContact->contactIp().toIPv4Address()),
139                                peerContact->contactPort(), uin, peer);
140 
141     contact = peerContact;
142     return setupIncoming(dccSock_);
143 }
144 
145 bool
setupIncoming(gg_dcc * dccS)146 GaduDCCTransaction::setupIncoming(gg_dcc *dccS)
147 {
148     if (!dccS) {
149         kDebug(14100) << "gg_dcc_get_file failed in GaduDCCTransaction::setupIncoming";
150         return false;
151     }
152 
153     dccSock_ = dccS;
154 
155     peer = dccS->uin;
156 
157     connect(Kopete::TransferManager::transferManager(), SIGNAL(accepted(Kopete::Transfer *,
158                                                                         QString)),
159             this, SLOT(slotIncomingTransferAccepted(Kopete::Transfer*,QString)));
160     connect(Kopete::TransferManager::transferManager(), SIGNAL(refused(Kopete::FileTransferInfo)),
161             this, SLOT(slotTransferRefused(Kopete::FileTransferInfo)));
162 
163     incoming = true;
164     createNotifiers(true);
165     enableNotifiers(dccSock_->check);
166 
167     return true;
168 }
169 
170 void
closeDCC()171 GaduDCCTransaction::closeDCC()
172 {
173     kDebug(14100) << "closeDCC()";
174 
175     disableNotifiers();
176     destroyNotifiers();
177     gg_dcc_free(dccSock_);
178     dccSock_ = NULL;
179 }
180 
181 void
destroyNotifiers()182 GaduDCCTransaction::destroyNotifiers()
183 {
184     disableNotifiers();
185     delete read_;
186     read_ = NULL;
187     delete write_;
188     write_ = NULL;
189 }
190 
191 void
createNotifiers(bool connect)192 GaduDCCTransaction::createNotifiers(bool connect)
193 {
194     if (!dccSock_) {
195         return;
196     }
197 
198     read_ = new QSocketNotifier(dccSock_->fd, QSocketNotifier::Read, this);
199     read_->setEnabled(false);
200 
201     write_ = new QSocketNotifier(dccSock_->fd, QSocketNotifier::Write, this);
202     write_->setEnabled(false);
203 
204     if (connect) {
205         QObject::connect(read_, SIGNAL(activated(int)), SLOT(watcher()));
206         QObject::connect(write_, SIGNAL(activated(int)), SLOT(watcher()));
207     }
208 }
209 
210 void
enableNotifiers(int checkWhat)211 GaduDCCTransaction::enableNotifiers(int checkWhat)
212 {
213     if ((checkWhat & GG_CHECK_READ) && read_) {
214         read_->setEnabled(true);
215     }
216     if ((checkWhat & GG_CHECK_WRITE) && write_) {
217         write_->setEnabled(true);
218     }
219 }
220 
221 void
disableNotifiers()222 GaduDCCTransaction::disableNotifiers()
223 {
224     if (read_) {
225         read_->setEnabled(false);
226     }
227     if (write_) {
228         write_->setEnabled(false);
229     }
230 }
231 
232 void
slotIncomingTransferAccepted(Kopete::Transfer * transfer,const QString & fileName)233 GaduDCCTransaction::slotIncomingTransferAccepted(Kopete::Transfer *transfer, const QString &fileName)
234 {
235     if ((long)transfer->info().transferId() != transferId_) {
236         return;
237     }
238 
239     transfer_ = transfer;
240     localFile_.setFileName(fileName);
241 
242     if (localFile_.exists()) {
243         KGuiItem resumeButton(i18n("&Resume"));
244         KGuiItem overwriteButton(i18n("Over&write"));
245         switch (KMessageBox::questionYesNoCancel(Kopete::UI::Global::mainWidget(),
246                                                  i18n(
247                                                      "The file %1 already exists, do you want to resume or overwrite it?",
248                                                      fileName),
249                                                  i18n("File Exists: %1", fileName), resumeButton,
250                                                  overwriteButton)) {
251         // resume
252         case KMessageBox::Yes:
253             if (localFile_.open(QIODevice::WriteOnly | QIODevice::Append)) {
254                 dccSock_->offset = localFile_.size();
255                 dccSock_->file_fd = localFile_.handle();
256             }
257             break;
258         // overwrite
259         case KMessageBox::No:
260             if (localFile_.open(QIODevice::ReadWrite)) {
261                 dccSock_->offset = 0;
262                 dccSock_->file_fd = localFile_.handle();
263             }
264             break;
265 
266         // cancel
267         default:
268             closeDCC();
269             deleteLater();
270             return;
271             break;
272         }
273         if (localFile_.handle() < 1) {
274             closeDCC();
275             deleteLater();
276             return;
277         }
278     } else {
279         // overwrite by default
280         if (localFile_.open(QIODevice::ReadWrite) == false) {
281             transfer->slotError(KIO::ERR_COULD_NOT_WRITE, fileName);
282             closeDCC();
283             deleteLater();
284             return;
285         }
286         dccSock_->offset = 0;
287         dccSock_->file_fd = localFile_.handle();
288     }
289 
290     connect(transfer, SIGNAL(result(KJob*)), this, SLOT(slotTransferResult()));
291 
292     // reenable notifiers
293     enableNotifiers(dccSock_->check);
294 }
295 
296 void
slotTransferResult()297 GaduDCCTransaction::slotTransferResult()
298 {
299     if (transfer_->error() == KIO::ERR_USER_CANCELED) {
300         closeDCC();
301         deleteLater();
302     }
303 }
304 
305 void
slotTransferRefused(const Kopete::FileTransferInfo & transfer)306 GaduDCCTransaction::slotTransferRefused(const Kopete::FileTransferInfo &transfer)
307 {
308     if ((long)transfer.transferId() != transferId_) {
309         return;
310     }
311     closeDCC();
312     deleteLater();
313 }
314 
315 void
askIncommingTransfer()316 GaduDCCTransaction::askIncommingTransfer()
317 {
318     transferId_ = Kopete::TransferManager::transferManager()->askIncomingTransfer(contact,
319                                                                                   QString((const
320                                                                                            char *)
321                                                                                           dccSock_->
322                                                                                           file_info.
323                                                                                           filename), dccSock_->file_info.size);
324 }
325 
326 void
watcher()327 GaduDCCTransaction::watcher()
328 {
329     gg_event *dccEvent;
330     GaduAccount *account;
331 
332     disableNotifiers();
333 
334     dccEvent = gg_dcc_watch_fd(dccSock_);
335     if (!dccEvent) {
336         // connection is fucked
337         closeDCC();
338         return;
339     }
340     switch (dccEvent->type) {
341     case GG_EVENT_DCC_CLIENT_ACCEPT:
342         kDebug(14100) << " GG_EVENT_DCC_CLIENT_ACCEPT ";
343         // check dccsock->peer_uin, if unknown, fuck it;
344 
345         // is it for us ?
346         account = gaduDCC_->account(dccSock_->uin);
347         if (!account) {
348             kDebug(14100) << " this dcc transaction is for uin " << dccSock_->uin
349                           << ", which is not quite for me... closing";
350             // unknown 'to' ?, we're off
351             gg_free_event(dccEvent);
352             closeDCC();
353             deleteLater();
354             return;
355         }
356 
357         if (!peer) {
358             contact
359                 = static_cast<GaduContact *>(account->contacts().value(QString::number(dccSock_->
360                                                                                        peer_uin)));
361         } else {
362             contact = static_cast<GaduContact *>(account->contacts().value(QString::number(peer)));
363         }
364 
365         if (contact == NULL) {
366             // refusing, contact on the list
367             kDebug(14100) << " dcc connection from " << dccSock_->peer_uin
368                           << " refused, UIN not on the list ";
369             gg_free_event(dccEvent);
370             closeDCC();
371             // emit error
372             deleteLater();
373             return;
374         } else {
375             // ask user to accept that transfer
376             kDebug(14100) <<  " dcc accepted from " << dccSock_->peer_uin;
377         }
378 
379         break;
380     case GG_EVENT_DCC_CALLBACK:
381         kDebug(14100) << "GG_DCC_EVENT_CALLBACK";
382         break;
383     case GG_EVENT_NONE:
384         kDebug(14100) << " GG_EVENT_NONE";
385         // update gui with progress
386         if (transfer_) {
387             transfer_->slotProcessed(dccSock_->offset);
388         }
389         break;
390 
391     case GG_EVENT_DCC_NEED_FILE_ACK:
392         kDebug(14100) << " GG_EVENT_DCC_NEED_FILE_ACK ";
393         gg_free_event(dccEvent);
394         askIncommingTransfer();
395         return;
396         break;
397     case GG_EVENT_DCC_NEED_FILE_INFO:
398         if (gaduDCC_->requests.contains(dccSock_->peer_uin)) {
399             QString filePath = gaduDCC_->requests[dccSock_->peer_uin];
400             kDebug() << "Callback request found. Sending " << filePath;
401             gaduDCC_->requests.remove(dccSock_->peer_uin);
402             gg_dcc_fill_file_info(dccSock_, filePath.toAscii());
403             transfer_ = Kopete::TransferManager::transferManager()->addTransfer(contact,
404                                                                                 filePath,
405                                                                                 dccSock_->file_info.size,
406                                                                                 contact->metaContact()->displayName(),
407                                                                                 Kopete::FileTransferInfo::Outgoing);
408         } else {
409             gg_free_event(dccEvent);
410             closeDCC();
411             deleteLater();
412             return;
413         }
414         break;
415 
416     case GG_EVENT_DCC_ERROR:
417         kDebug(14100) << " GG_EVENT_DCC_ERROR :" << dccEvent->event.dcc_error;
418         if (transfer_) {
419             switch (dccEvent->event.dcc_error) {
420             case GG_ERROR_DCC_REFUSED:
421                 transfer_->slotError(KIO::ERR_SLAVE_DEFINED,
422                                      i18n(
423                                          "Connection to peer was refused; it possibly does not listen for incoming connections."));
424                 break;
425 
426             case GG_ERROR_DCC_EOF:
427                 transfer_->slotError(KIO::ERR_SLAVE_DEFINED,
428                                      i18n("File transfer transaction was not agreed by peer."));
429                 break;
430 
431             case GG_ERROR_DCC_HANDSHAKE:
432                 transfer_->slotError(KIO::ERR_SLAVE_DEFINED, i18n(
433                                          "File-transfer handshake failure."));
434                 break;
435             case GG_ERROR_DCC_FILE:
436                 transfer_->slotError(KIO::ERR_SLAVE_DEFINED,
437                                      i18n("File transfer had problems with the file."));
438                 break;
439             case GG_ERROR_DCC_NET:
440                 transfer_->slotError(KIO::ERR_SLAVE_DEFINED,
441                                      i18n("There was network error during file transfer."));
442                 break;
443             default:
444                 transfer_->slotError(KIO::ERR_SLAVE_DEFINED, i18n("Unknown File-Transfer error."));
445                 break;
446             }
447         }
448         gg_free_event(dccEvent);
449         closeDCC();
450         deleteLater();
451         return;
452 
453     case GG_EVENT_DCC_DONE:
454         if (transfer_) {
455             transfer_->slotComplete();
456         }
457         closeDCC();
458         deleteLater();
459         if (dccEvent) {
460             gg_free_event(dccEvent);
461         }
462         return;
463 
464     default:
465         kDebug(14100) << "unknown/unhandled DCC EVENT: " << dccEvent->type;
466         break;
467     }
468 
469     if (dccEvent) {
470         gg_free_event(dccEvent);
471     }
472 
473     enableNotifiers(dccSock_->check);
474 }
475