1 /*
2 * Copyright (C) 2004-2020 ZNC, see the NOTICE file for details.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <znc/znc.h>
18 #include <znc/User.h>
19 #include <znc/FileUtils.h>
20
21 using std::set;
22
23 class CDCCMod;
24
25 class CDCCSock : public CSocket {
26 public:
27 CDCCSock(CDCCMod* pMod, const CString& sRemoteNick,
28 const CString& sLocalFile, unsigned long uFileSize = 0,
29 CFile* pFile = nullptr);
30 CDCCSock(CDCCMod* pMod, const CString& sRemoteNick,
31 const CString& sRemoteIP, unsigned short uRemotePort,
32 const CString& sLocalFile, unsigned long uFileSize);
33 ~CDCCSock() override;
34
35 void ReadData(const char* data, size_t len) override;
36 void ConnectionRefused() override;
37 void SockError(int iErrno, const CString& sDescription) override;
38 void Timeout() override;
39 void Connected() override;
40 void Disconnected() override;
41 void SendPacket();
42 Csock* GetSockObj(const CString& sHost, unsigned short uPort) override;
43 CFile* OpenFile(bool bWrite = true);
44 bool Seek(unsigned long int uPos);
45
46 // Setters
SetRemoteIP(const CString & s)47 void SetRemoteIP(const CString& s) { m_sRemoteIP = s; }
SetRemoteNick(const CString & s)48 void SetRemoteNick(const CString& s) { m_sRemoteNick = s; }
SetFileName(const CString & s)49 void SetFileName(const CString& s) { m_sFileName = s; }
SetFileOffset(unsigned long u)50 void SetFileOffset(unsigned long u) { m_uBytesSoFar = u; }
51 // !Setters
52
53 // Getters
GetUserPort() const54 unsigned short GetUserPort() const { return m_uRemotePort; }
GetRemoteNick() const55 const CString& GetRemoteNick() const { return m_sRemoteNick; }
GetFileName() const56 const CString& GetFileName() const { return m_sFileName; }
GetLocalFile() const57 const CString& GetLocalFile() const { return m_sLocalFile; }
GetFile()58 CFile* GetFile() { return m_pFile; }
GetProgress() const59 double GetProgress() const {
60 return ((m_uFileSize) && (m_uBytesSoFar))
61 ? (double)(((double)m_uBytesSoFar / (double)m_uFileSize) *
62 100.0)
63 : 0;
64 }
IsSend() const65 bool IsSend() const { return m_bSend; }
66 // const CString& GetRemoteIP() const { return m_sRemoteIP; }
67 // !Getters
68 private:
69 protected:
70 CString m_sRemoteNick;
71 CString m_sRemoteIP;
72 CString m_sFileName;
73 CString m_sLocalFile;
74 CString m_sSendBuf;
75 unsigned short m_uRemotePort;
76 unsigned long long m_uFileSize;
77 unsigned long long m_uBytesSoFar;
78 bool m_bSend;
79 bool m_bNoDelFile;
80 CFile* m_pFile;
81 CDCCMod* m_pModule;
82 };
83
84 class CDCCMod : public CModule {
85 public:
MODCONSTRUCTOR(CDCCMod)86 MODCONSTRUCTOR(CDCCMod) {
87 AddHelpCommand();
88 AddCommand("Send", t_d("<nick> <file>"),
89 t_d("Send a file from ZNC to someone"),
90 [=](const CString& sLine) { SendCommand(sLine); });
91 AddCommand("Get", t_d("<file>"),
92 t_d("Send a file from ZNC to your client"),
93 [=](const CString& sLine) { GetCommand(sLine); });
94 AddCommand("ListTransfers", "", t_d("List current transfers"),
95 [=](const CString& sLine) { ListTransfersCommand(sLine); });
96 }
97
~CDCCMod()98 ~CDCCMod() override {}
99
100 #ifndef MOD_DCC_ALLOW_EVERYONE
OnLoad(const CString & sArgs,CString & sMessage)101 bool OnLoad(const CString& sArgs, CString& sMessage) override {
102 if (!GetUser()->IsAdmin()) {
103 sMessage = t_s("You must be admin to use the DCC module");
104 return false;
105 }
106
107 return true;
108 }
109 #endif
110
SendFile(const CString & sRemoteNick,const CString & sFileName)111 bool SendFile(const CString& sRemoteNick, const CString& sFileName) {
112 CString sFullPath = CDir::ChangeDir(GetSavePath(), sFileName,
113 CZNC::Get().GetHomePath());
114 CDCCSock* pSock = new CDCCSock(this, sRemoteNick, sFullPath);
115
116 CFile* pFile = pSock->OpenFile(false);
117
118 if (!pFile) {
119 delete pSock;
120 return false;
121 }
122
123 CString sLocalDCCIP = GetUser()->GetLocalDCCIP();
124 unsigned short uPort = CZNC::Get().GetManager().ListenRand(
125 "DCC::LISTEN::" + sRemoteNick, sLocalDCCIP, false, SOMAXCONN, pSock,
126 120);
127
128 if (GetUser()->GetNick().Equals(sRemoteNick)) {
129 PutUser(":*dcc!znc@znc.in PRIVMSG " + sRemoteNick +
130 " :\001DCC SEND " + pFile->GetShortName() + " " +
131 CString(CUtils::GetLongIP(sLocalDCCIP)) + " " +
132 CString(uPort) + " " + CString(pFile->GetSize()) + "\001");
133 } else {
134 PutIRC("PRIVMSG " + sRemoteNick + " :\001DCC SEND " +
135 pFile->GetShortName() + " " +
136 CString(CUtils::GetLongIP(sLocalDCCIP)) + " " +
137 CString(uPort) + " " + CString(pFile->GetSize()) + "\001");
138 }
139
140 PutModule(t_f("Attempting to send [{1}] to [{2}].")(
141 pFile->GetShortName(), sRemoteNick));
142 return true;
143 }
144
GetFile(const CString & sRemoteNick,const CString & sRemoteIP,unsigned short uRemotePort,const CString & sFileName,unsigned long uFileSize)145 bool GetFile(const CString& sRemoteNick, const CString& sRemoteIP,
146 unsigned short uRemotePort, const CString& sFileName,
147 unsigned long uFileSize) {
148 if (CFile::Exists(sFileName)) {
149 PutModule(t_f("Receiving [{1}] from [{2}]: File already exists.")(
150 sFileName, sRemoteNick));
151 return false;
152 }
153
154 CDCCSock* pSock = new CDCCSock(this, sRemoteNick, sRemoteIP,
155 uRemotePort, sFileName, uFileSize);
156
157 if (!pSock->OpenFile()) {
158 delete pSock;
159 return false;
160 }
161
162 CZNC::Get().GetManager().Connect(sRemoteIP, uRemotePort,
163 "DCC::GET::" + sRemoteNick, 60, false,
164 GetUser()->GetLocalDCCIP(), pSock);
165
166 PutModule(
167 t_f("Attempting to connect to [{1} {2}] in order to download [{3}] "
168 "from [{4}].")(sRemoteIP, uRemotePort, sFileName, sRemoteNick));
169 return true;
170 }
171
SendCommand(const CString & sLine)172 void SendCommand(const CString& sLine) {
173 CString sToNick = sLine.Token(1);
174 CString sFile = sLine.Token(2);
175 CString sAllowedPath = GetSavePath();
176 CString sAbsolutePath;
177
178 if ((sToNick.empty()) || (sFile.empty())) {
179 PutModule(t_s("Usage: Send <nick> <file>"));
180 return;
181 }
182
183 sAbsolutePath = CDir::CheckPathPrefix(sAllowedPath, sFile);
184
185 if (sAbsolutePath.empty()) {
186 PutStatus(t_s("Illegal path."));
187 return;
188 }
189
190 SendFile(sToNick, sFile);
191 }
192
GetCommand(const CString & sLine)193 void GetCommand(const CString& sLine) {
194 CString sFile = sLine.Token(1);
195 CString sAllowedPath = GetSavePath();
196 CString sAbsolutePath;
197
198 if (sFile.empty()) {
199 PutModule(t_s("Usage: Get <file>"));
200 return;
201 }
202
203 sAbsolutePath = CDir::CheckPathPrefix(sAllowedPath, sFile);
204
205 if (sAbsolutePath.empty()) {
206 PutModule(t_s("Illegal path."));
207 return;
208 }
209
210 SendFile(GetUser()->GetNick(), sFile);
211 }
212
ListTransfersCommand(const CString & sLine)213 void ListTransfersCommand(const CString& sLine) {
214 CTable Table;
215 Table.AddColumn(t_s("Type", "list"));
216 Table.AddColumn(t_s("State", "list"));
217 Table.AddColumn(t_s("Speed", "list"));
218 Table.AddColumn(t_s("Nick", "list"));
219 Table.AddColumn(t_s("IP", "list"));
220 Table.AddColumn(t_s("File", "list"));
221
222 set<CSocket*>::const_iterator it;
223 for (it = BeginSockets(); it != EndSockets(); ++it) {
224 CDCCSock* pSock = (CDCCSock*)*it;
225
226 Table.AddRow();
227 Table.SetCell(t_s("Nick", "list"), pSock->GetRemoteNick());
228 Table.SetCell(t_s("IP", "list"), pSock->GetRemoteIP());
229 Table.SetCell(t_s("File", "list"), pSock->GetFileName());
230
231 if (pSock->IsSend()) {
232 Table.SetCell(t_s("Type", "list"), t_s("Sending", "list-type"));
233 } else {
234 Table.SetCell(t_s("Type", "list"), t_s("Getting", "list-type"));
235 }
236
237 if (pSock->GetType() == Csock::LISTENER) {
238 Table.SetCell(t_s("State", "list"),
239 t_s("Waiting", "list-state"));
240 } else {
241 Table.SetCell(t_s("State", "list"),
242 CString::ToPercent(pSock->GetProgress()));
243 Table.SetCell(t_s("Speed", "list"),
244 t_f("{1} KiB/s")(static_cast<int>(
245 pSock->GetAvgRead() / 1024.0)));
246 }
247 }
248
249 if (PutModule(Table) == 0) {
250 PutModule(t_s("You have no active DCC transfers."));
251 }
252 }
253
OnModCTCP(const CString & sMessage)254 void OnModCTCP(const CString& sMessage) override {
255 if (sMessage.StartsWith("DCC RESUME ")) {
256 CString sFile = sMessage.Token(2);
257 unsigned short uResumePort = sMessage.Token(3).ToUShort();
258 unsigned long uResumeSize = sMessage.Token(4).ToULong();
259
260 set<CSocket*>::const_iterator it;
261 for (it = BeginSockets(); it != EndSockets(); ++it) {
262 CDCCSock* pSock = (CDCCSock*)*it;
263
264 if (pSock->GetLocalPort() == uResumePort) {
265 if (pSock->Seek(uResumeSize)) {
266 PutModule(
267 t_f("Attempting to resume send from position {1} "
268 "of file [{2}] for [{3}]")(
269 uResumeSize, pSock->GetFileName(),
270 pSock->GetRemoteNick()));
271 PutUser(":*dcc!znc@znc.in PRIVMSG " +
272 GetUser()->GetNick() + " :\001DCC ACCEPT " +
273 sFile + " " + CString(uResumePort) + " " +
274 CString(uResumeSize) + "\001");
275 } else {
276 PutModule(t_f(
277 "Couldn't resume file [{1}] for [{2}]: not sending "
278 "anything.")(sFile, GetUser()->GetNick()));
279 }
280 }
281 }
282 } else if (sMessage.StartsWith("DCC SEND ")) {
283 CString sLocalFile =
284 CDir::CheckPathPrefix(GetSavePath(), sMessage.Token(2));
285 if (sLocalFile.empty()) {
286 PutModule(t_f("Bad DCC file: {1}")(sMessage.Token(2)));
287 }
288 unsigned long uLongIP = sMessage.Token(3).ToULong();
289 unsigned short uPort = sMessage.Token(4).ToUShort();
290 unsigned long uFileSize = sMessage.Token(5).ToULong();
291 GetFile(GetClient()->GetNick(), CUtils::GetIP(uLongIP), uPort,
292 sLocalFile, uFileSize);
293 }
294 }
295 };
296
CDCCSock(CDCCMod * pMod,const CString & sRemoteNick,const CString & sLocalFile,unsigned long uFileSize,CFile * pFile)297 CDCCSock::CDCCSock(CDCCMod* pMod, const CString& sRemoteNick,
298 const CString& sLocalFile, unsigned long uFileSize,
299 CFile* pFile)
300 : CSocket(pMod) {
301 m_sRemoteNick = sRemoteNick;
302 m_uFileSize = uFileSize;
303 m_uRemotePort = 0;
304 m_uBytesSoFar = 0;
305 m_pModule = pMod;
306 m_pFile = pFile;
307 m_sLocalFile = sLocalFile;
308 m_bSend = true;
309 m_bNoDelFile = false;
310 SetMaxBufferThreshold(0);
311 }
312
CDCCSock(CDCCMod * pMod,const CString & sRemoteNick,const CString & sRemoteIP,unsigned short uRemotePort,const CString & sLocalFile,unsigned long uFileSize)313 CDCCSock::CDCCSock(CDCCMod* pMod, const CString& sRemoteNick,
314 const CString& sRemoteIP, unsigned short uRemotePort,
315 const CString& sLocalFile, unsigned long uFileSize)
316 : CSocket(pMod) {
317 m_sRemoteNick = sRemoteNick;
318 m_sRemoteIP = sRemoteIP;
319 m_uRemotePort = uRemotePort;
320 m_uFileSize = uFileSize;
321 m_uBytesSoFar = 0;
322 m_pModule = pMod;
323 m_pFile = nullptr;
324 m_sLocalFile = sLocalFile;
325 m_bSend = false;
326 m_bNoDelFile = false;
327 SetMaxBufferThreshold(0);
328 }
329
~CDCCSock()330 CDCCSock::~CDCCSock() {
331 if ((m_pFile) && (!m_bNoDelFile)) {
332 m_pFile->Close();
333 delete m_pFile;
334 }
335 }
336
ReadData(const char * data,size_t len)337 void CDCCSock::ReadData(const char* data, size_t len) {
338 if (!m_pFile) {
339 DEBUG("File not open! closing get.");
340 if (m_bSend) {
341 m_pModule->PutModule(t_f("Sending [{1}] to [{2}]: File not open!")(
342 m_sFileName, m_sRemoteNick));
343 } else {
344 m_pModule->PutModule(
345 t_f("Receiving [{1}] from [{2}]: File not open!")(
346 m_sFileName, m_sRemoteNick));
347 }
348 Close();
349 return;
350 }
351
352 // DCC specs says the receiving end sends the number of bytes it
353 // received so far as a 4 byte integer in network byte order, so we need
354 // uint32_t to do the job portably. This also means that the maximum
355 // file that we can transfer is 4 GiB big (see OpenFile()).
356 if (m_bSend) {
357 m_sSendBuf.append(data, len);
358
359 while (m_sSendBuf.size() >= 4) {
360 uint32_t iRemoteSoFar;
361 memcpy(&iRemoteSoFar, m_sSendBuf.data(), sizeof(iRemoteSoFar));
362 iRemoteSoFar = ntohl(iRemoteSoFar);
363
364 if ((iRemoteSoFar + 65536) >= m_uBytesSoFar) {
365 SendPacket();
366 }
367
368 m_sSendBuf.erase(0, 4);
369 }
370 } else {
371 m_pFile->Write(data, len);
372 m_uBytesSoFar += len;
373 uint32_t uSoFar = htonl((uint32_t)m_uBytesSoFar);
374 Write((char*)&uSoFar, sizeof(uSoFar));
375
376 if (m_uBytesSoFar >= m_uFileSize) {
377 Close();
378 }
379 }
380 }
381
ConnectionRefused()382 void CDCCSock::ConnectionRefused() {
383 DEBUG(GetSockName() << " == ConnectionRefused()");
384 if (m_bSend) {
385 m_pModule->PutModule(t_f("Sending [{1}] to [{2}]: Connection refused.")(
386 m_sFileName, m_sRemoteNick));
387 } else {
388 m_pModule->PutModule(
389 t_f("Receiving [{1}] from [{2}]: Connection refused.")(
390 m_sFileName, m_sRemoteNick));
391 }
392 }
393
Timeout()394 void CDCCSock::Timeout() {
395 DEBUG(GetSockName() << " == Timeout()");
396 if (m_bSend) {
397 m_pModule->PutModule(t_f("Sending [{1}] to [{2}]: Timeout.")(
398 m_sFileName, m_sRemoteNick));
399 } else {
400 m_pModule->PutModule(
401 t_f("Receiving [{1}] from [{2}]: Timeout.")(
402 m_sFileName, m_sRemoteNick));
403 }
404 }
405
SockError(int iErrno,const CString & sDescription)406 void CDCCSock::SockError(int iErrno, const CString& sDescription) {
407 DEBUG(GetSockName() << " == SockError(" << iErrno << ", " << sDescription
408 << ")");
409 if (m_bSend) {
410 m_pModule->PutModule(
411 t_f("Sending [{1}] to [{2}]: Socket error {3}: {4}")(
412 m_sFileName, m_sRemoteNick, iErrno, sDescription));
413 } else {
414 m_pModule->PutModule(
415 t_f("Receiving [{1}] from [{2}]: Socket error {3}: {4}")(
416 m_sFileName, m_sRemoteNick, iErrno, sDescription));
417 }
418 }
419
Connected()420 void CDCCSock::Connected() {
421 DEBUG(GetSockName() << " == Connected(" << GetRemoteIP() << ")");
422 if (m_bSend) {
423 m_pModule->PutModule(t_f("Sending [{1}] to [{2}]: Transfer started.")(
424 m_sFileName, m_sRemoteNick));
425 } else {
426 m_pModule->PutModule(
427 t_f("Receiving [{1}] from [{2}]: Transfer started.")(
428 m_sFileName, m_sRemoteNick));
429 }
430
431 if (m_bSend) {
432 SendPacket();
433 }
434
435 SetTimeout(120);
436 }
437
Disconnected()438 void CDCCSock::Disconnected() {
439 const CString sStart = ((m_bSend) ? "DCC -> [" : "DCC <- [") +
440 m_sRemoteNick + "][" + m_sFileName + "] - ";
441
442 DEBUG(GetSockName() << " == Disconnected()");
443
444 if (m_uBytesSoFar > m_uFileSize) {
445 if (m_bSend) {
446 m_pModule->PutModule(t_f("Sending [{1}] to [{2}]: Too much data!")(
447 m_sFileName, m_sRemoteNick));
448 } else {
449 m_pModule->PutModule(
450 t_f("Receiving [{1}] from [{2}]: Too much data!")(
451 m_sFileName, m_sRemoteNick));
452 }
453 } else if (m_uBytesSoFar == m_uFileSize) {
454 if (m_bSend) {
455 m_pModule->PutModule(
456 t_f("Sending [{1}] to [{2}] completed at {3} KiB/s")(
457 m_sFileName, m_sRemoteNick,
458 static_cast<int>(GetAvgWrite() / 1024.0)));
459 } else {
460 m_pModule->PutModule(
461 t_f("Receiving [{1}] from [{2}] completed at {3} KiB/s")(
462 m_sFileName, m_sRemoteNick,
463 static_cast<int>(GetAvgRead() / 1024.0)));
464 }
465 } else {
466 m_pModule->PutModule(sStart + "Incomplete!");
467 }
468 }
469
SendPacket()470 void CDCCSock::SendPacket() {
471 if (!m_pFile) {
472 if (m_bSend) {
473 m_pModule->PutModule(
474 t_f("Sending [{1}] to [{2}]: File closed prematurely.")(
475 m_sFileName, m_sRemoteNick));
476 } else {
477 m_pModule->PutModule(
478 t_f("Receiving [{1}] from [{2}]: File closed prematurely.")(
479 m_sFileName, m_sRemoteNick));
480 }
481
482 Close();
483 return;
484 }
485
486 if (GetInternalWriteBuffer().size() > 1024 * 1024) {
487 // There is still enough data to be written, don't add more
488 // stuff to that buffer.
489 DEBUG("SendPacket(): Skipping send, buffer still full enough ["
490 << GetInternalWriteBuffer().size() << "][" << m_sRemoteNick
491 << "][" << m_sFileName << "]");
492 return;
493 }
494
495 char szBuf[4096];
496 ssize_t iLen = m_pFile->Read(szBuf, 4096);
497
498 if (iLen < 0) {
499 if (m_bSend) {
500 m_pModule->PutModule(
501 t_f("Sending [{1}] to [{2}]: Error reading from file.")(
502 m_sFileName, m_sRemoteNick));
503 } else {
504 m_pModule->PutModule(
505 t_f("Receiving [{1}] from [{2}]: Error reading from file.")(
506 m_sFileName, m_sRemoteNick));
507 }
508
509 Close();
510 return;
511 }
512
513 if (iLen > 0) {
514 Write(szBuf, iLen);
515 m_uBytesSoFar += iLen;
516 }
517 }
518
GetSockObj(const CString & sHost,unsigned short uPort)519 Csock* CDCCSock::GetSockObj(const CString& sHost, unsigned short uPort) {
520 Close();
521
522 CDCCSock* pSock = new CDCCSock(m_pModule, m_sRemoteNick, m_sLocalFile,
523 m_uFileSize, m_pFile);
524 pSock->SetSockName("DCC::SEND::" + m_sRemoteNick);
525 pSock->SetTimeout(120);
526 pSock->SetFileName(m_sFileName);
527 pSock->SetFileOffset(m_uBytesSoFar);
528 m_bNoDelFile = true;
529
530 return pSock;
531 }
532
OpenFile(bool bWrite)533 CFile* CDCCSock::OpenFile(bool bWrite) {
534 if ((m_pFile) || (m_sLocalFile.empty())) {
535 if (m_bSend) {
536 m_pModule->PutModule(
537 t_f("Sending [{1}] to [{2}]: Unable to open file.")(
538 m_sFileName, m_sRemoteNick));
539 } else {
540 m_pModule->PutModule(
541 t_f("Receiving [{1}] from [{2}]: Unable to open file.")(
542 m_sFileName, m_sRemoteNick));
543 }
544 return nullptr;
545 }
546
547 m_pFile = new CFile(m_sLocalFile);
548
549 if (bWrite) {
550 if (m_pFile->Exists()) {
551 delete m_pFile;
552 m_pFile = nullptr;
553 m_pModule->PutModule(
554 t_f("Receiving [{1}] from [{2}]: File already exists.")(
555 m_sFileName, m_sRemoteNick));
556 return nullptr;
557 }
558
559 if (!m_pFile->Open(O_WRONLY | O_TRUNC | O_CREAT)) {
560 delete m_pFile;
561 m_pFile = nullptr;
562 m_pModule->PutModule(
563 t_f("Receiving [{1}] from [{2}]: Could not open file.")(
564 m_sFileName, m_sRemoteNick));
565 return nullptr;
566 }
567 } else {
568 if (!m_pFile->IsReg()) {
569 delete m_pFile;
570 m_pFile = nullptr;
571 m_pModule->PutModule(
572 t_f("Sending [{1}] to [{2}]: Not a file.")(
573 m_sFileName, m_sRemoteNick));
574 return nullptr;
575 }
576
577 if (!m_pFile->Open()) {
578 delete m_pFile;
579 m_pFile = nullptr;
580 m_pModule->PutModule(
581 t_f("Sending [{1}] to [{2}]: Could not open file.")(
582 m_sFileName, m_sRemoteNick));
583 return nullptr;
584 }
585
586 // The DCC specs only allow file transfers with files smaller
587 // than 4GiB (see ReadData()).
588 unsigned long long uFileSize = m_pFile->GetSize();
589 if (uFileSize > (unsigned long long)0xffffffffULL) {
590 delete m_pFile;
591 m_pFile = nullptr;
592 m_pModule->PutModule(
593 t_f("Sending [{1}] to [{2}]: File too large (>4 GiB).")(
594 m_sFileName, m_sRemoteNick));
595 return nullptr;
596 }
597
598 m_uFileSize = uFileSize;
599 }
600
601 m_sFileName = m_pFile->GetShortName();
602
603 return m_pFile;
604 }
605
Seek(unsigned long int uPos)606 bool CDCCSock::Seek(unsigned long int uPos) {
607 if (m_pFile) {
608 if (m_pFile->Seek(uPos)) {
609 m_uBytesSoFar = uPos;
610 return true;
611 }
612 }
613
614 return false;
615 }
616
617 template <>
TModInfo(CModInfo & Info)618 void TModInfo<CDCCMod>(CModInfo& Info) {
619 Info.SetWikiPage("dcc");
620 }
621
622 USERMODULEDEFS(CDCCMod,
623 t_s("This module allows you to transfer files to and from ZNC"))
624