Support for name lookups in -connect and -addnode
[novacoin.git] / src / irc.cpp
1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Distributed under the MIT/X11 software license, see the accompanying
3 // file license.txt or http://www.opensource.org/licenses/mit-license.php.
4
5 #include "headers.h"
6
7 int nGotIRCAddresses = 0;
8 bool fGotExternalIP = false;
9
10 void ThreadIRCSeed2(void* parg);
11
12
13
14
15 #pragma pack(push, 1)
16 struct ircaddr
17 {
18     int ip;
19     short port;
20 };
21 #pragma pack(pop)
22
23 string EncodeAddress(const CAddress& addr)
24 {
25     struct ircaddr tmp;
26     tmp.ip    = addr.ip;
27     tmp.port  = addr.port;
28
29     vector<unsigned char> vch(UBEGIN(tmp), UEND(tmp));
30     return string("u") + EncodeBase58Check(vch);
31 }
32
33 bool DecodeAddress(string str, CAddress& addr)
34 {
35     vector<unsigned char> vch;
36     if (!DecodeBase58Check(str.substr(1), vch))
37         return false;
38
39     struct ircaddr tmp;
40     if (vch.size() != sizeof(tmp))
41         return false;
42     memcpy(&tmp, &vch[0], sizeof(tmp));
43
44     addr = CAddress(tmp.ip, ntohs(tmp.port), NODE_NETWORK);
45     return true;
46 }
47
48
49
50
51
52
53 static bool Send(SOCKET hSocket, const char* pszSend)
54 {
55     if (strstr(pszSend, "PONG") != pszSend)
56         printf("IRC SENDING: %s\n", pszSend);
57     const char* psz = pszSend;
58     const char* pszEnd = psz + strlen(psz);
59     while (psz < pszEnd)
60     {
61         int ret = send(hSocket, psz, pszEnd - psz, MSG_NOSIGNAL);
62         if (ret < 0)
63             return false;
64         psz += ret;
65     }
66     return true;
67 }
68
69 bool RecvLine(SOCKET hSocket, string& strLine)
70 {
71     strLine = "";
72     loop
73     {
74         char c;
75         int nBytes = recv(hSocket, &c, 1, 0);
76         if (nBytes > 0)
77         {
78             if (c == '\n')
79                 continue;
80             if (c == '\r')
81                 return true;
82             strLine += c;
83             if (strLine.size() >= 9000)
84                 return true;
85         }
86         else if (nBytes <= 0)
87         {
88             if (fShutdown)
89                 return false;
90             if (nBytes < 0)
91             {
92                 int nErr = WSAGetLastError();
93                 if (nErr == WSAEMSGSIZE)
94                     continue;
95                 if (nErr == WSAEWOULDBLOCK || nErr == WSAEINTR || nErr == WSAEINPROGRESS)
96                 {
97                     Sleep(10);
98                     continue;
99                 }
100             }
101             if (!strLine.empty())
102                 return true;
103             if (nBytes == 0)
104             {
105                 // socket closed
106                 printf("IRC socket closed\n");
107                 return false;
108             }
109             else
110             {
111                 // socket error
112                 int nErr = WSAGetLastError();
113                 printf("IRC recv failed: %d\n", nErr);
114                 return false;
115             }
116         }
117     }
118 }
119
120 bool RecvLineIRC(SOCKET hSocket, string& strLine)
121 {
122     loop
123     {
124         bool fRet = RecvLine(hSocket, strLine);
125         if (fRet)
126         {
127             if (fShutdown)
128                 return false;
129             vector<string> vWords;
130             ParseString(strLine, ' ', vWords);
131             if (vWords.size() >= 1 && vWords[0] == "PING")
132             {
133                 strLine[1] = 'O';
134                 strLine += '\r';
135                 Send(hSocket, strLine.c_str());
136                 continue;
137             }
138         }
139         return fRet;
140     }
141 }
142
143 int RecvUntil(SOCKET hSocket, const char* psz1, const char* psz2=NULL, const char* psz3=NULL, const char* psz4=NULL)
144 {
145     loop
146     {
147         string strLine;
148         strLine.reserve(10000);
149         if (!RecvLineIRC(hSocket, strLine))
150             return 0;
151         printf("IRC %s\n", strLine.c_str());
152         if (psz1 && strLine.find(psz1) != -1)
153             return 1;
154         if (psz2 && strLine.find(psz2) != -1)
155             return 2;
156         if (psz3 && strLine.find(psz3) != -1)
157             return 3;
158         if (psz4 && strLine.find(psz4) != -1)
159             return 4;
160     }
161 }
162
163 bool Wait(int nSeconds)
164 {
165     if (fShutdown)
166         return false;
167     printf("IRC waiting %d seconds to reconnect\n", nSeconds);
168     for (int i = 0; i < nSeconds; i++)
169     {
170         if (fShutdown)
171             return false;
172         Sleep(1000);
173     }
174     return true;
175 }
176
177 bool RecvCodeLine(SOCKET hSocket, const char* psz1, string& strRet)
178 {
179     strRet.clear();
180     loop
181     {
182         string strLine;
183         if (!RecvLineIRC(hSocket, strLine))
184             return false;
185
186         vector<string> vWords;
187         ParseString(strLine, ' ', vWords);
188         if (vWords.size() < 2)
189             continue;
190
191         if (vWords[1] == psz1)
192         {
193             printf("IRC %s\n", strLine.c_str());
194             strRet = strLine;
195             return true;
196         }
197     }
198 }
199
200 bool GetIPFromIRC(SOCKET hSocket, string strMyName, unsigned int& ipRet)
201 {
202     Send(hSocket, strprintf("USERHOST %s\r", strMyName.c_str()).c_str());
203
204     string strLine;
205     if (!RecvCodeLine(hSocket, "302", strLine))
206         return false;
207
208     vector<string> vWords;
209     ParseString(strLine, ' ', vWords);
210     if (vWords.size() < 4)
211         return false;
212
213     string str = vWords[3];
214     if (str.rfind("@") == string::npos)
215         return false;
216     string strHost = str.substr(str.rfind("@")+1);
217
218     // Hybrid IRC used by lfnet always returns IP when you userhost yourself,
219     // but in case another IRC is ever used this should work.
220     printf("GetIPFromIRC() got userhost %s\n", strHost.c_str());
221     if (fUseProxy)
222         return false;
223     CAddress addr(strHost, 0, true);
224     if (!addr.IsValid())
225         return false;
226     ipRet = addr.ip;
227
228     return true;
229 }
230
231
232
233 void ThreadIRCSeed(void* parg)
234 {
235     IMPLEMENT_RANDOMIZE_STACK(ThreadIRCSeed(parg));
236     try
237     {
238         ThreadIRCSeed2(parg);
239     }
240     catch (std::exception& e) {
241         PrintExceptionContinue(&e, "ThreadIRCSeed()");
242     } catch (...) {
243         PrintExceptionContinue(NULL, "ThreadIRCSeed()");
244     }
245     printf("ThreadIRCSeed exiting\n");
246 }
247
248 void ThreadIRCSeed2(void* parg)
249 {
250     /* Dont advertise on IRC if we don't allow incoming connections */
251     if (mapArgs.count("-connect") || fNoListen)
252         return;
253
254     if (GetBoolArg("-noirc"))
255         return;
256     printf("ThreadIRCSeed started\n");
257     int nErrorWait = 10;
258     int nRetryWait = 10;
259     bool fNameInUse = false;
260     bool fTOR = (fUseProxy && addrProxy.port == htons(9050));
261
262     while (!fShutdown)
263     {
264         //CAddress addrConnect("216.155.130.130:6667"); // chat.freenode.net
265         CAddress addrConnect("92.243.23.21:6667"); // irc.lfnet.org
266         if (!fTOR)
267         {
268             //struct hostent* phostent = gethostbyname("chat.freenode.net");
269             CAddress addrIRC("irc.lfnet.org:6667", 0, true);
270             if (addrIRC.IsValid())
271                 addrConnect = addrIRC;
272         }
273
274         SOCKET hSocket;
275         if (!ConnectSocket(addrConnect, hSocket))
276         {
277             printf("IRC connect failed\n");
278             nErrorWait = nErrorWait * 11 / 10;
279             if (Wait(nErrorWait += 60))
280                 continue;
281             else
282                 return;
283         }
284
285         if (!RecvUntil(hSocket, "Found your hostname", "using your IP address instead", "Couldn't look up your hostname", "ignoring hostname"))
286         {
287             closesocket(hSocket);
288             hSocket = INVALID_SOCKET;
289             nErrorWait = nErrorWait * 11 / 10;
290             if (Wait(nErrorWait += 60))
291                 continue;
292             else
293                 return;
294         }
295
296         string strMyName;
297         if (addrLocalHost.IsRoutable() && !fUseProxy && !fNameInUse)
298             strMyName = EncodeAddress(addrLocalHost);
299         else
300             strMyName = strprintf("x%u", GetRand(1000000000));
301
302         Send(hSocket, strprintf("NICK %s\r", strMyName.c_str()).c_str());
303         Send(hSocket, strprintf("USER %s 8 * : %s\r", strMyName.c_str(), strMyName.c_str()).c_str());
304
305         int nRet = RecvUntil(hSocket, " 004 ", " 433 ");
306         if (nRet != 1)
307         {
308             closesocket(hSocket);
309             hSocket = INVALID_SOCKET;
310             if (nRet == 2)
311             {
312                 printf("IRC name already in use\n");
313                 fNameInUse = true;
314                 Wait(10);
315                 continue;
316             }
317             nErrorWait = nErrorWait * 11 / 10;
318             if (Wait(nErrorWait += 60))
319                 continue;
320             else
321                 return;
322         }
323         Sleep(500);
324
325         // Get our external IP from the IRC server and re-nick before joining the channel
326         CAddress addrFromIRC;
327         if (GetIPFromIRC(hSocket, strMyName, addrFromIRC.ip))
328         {
329             printf("GetIPFromIRC() returned %s\n", addrFromIRC.ToStringIP().c_str());
330             if (!fUseProxy && addrFromIRC.IsRoutable())
331             {
332                 // IRC lets you to re-nick
333                 fGotExternalIP = true;
334                 addrLocalHost.ip = addrFromIRC.ip;
335                 strMyName = EncodeAddress(addrLocalHost);
336                 Send(hSocket, strprintf("NICK %s\r", strMyName.c_str()).c_str());
337             }
338         }
339
340         Send(hSocket, fTestNet ? "JOIN #bitcoinTEST\r" : "JOIN #bitcoin\r");
341         Send(hSocket, fTestNet ? "WHO #bitcoinTEST\r"  : "WHO #bitcoin\r");
342
343         int64 nStart = GetTime();
344         string strLine;
345         strLine.reserve(10000);
346         while (!fShutdown && RecvLineIRC(hSocket, strLine))
347         {
348             if (strLine.empty() || strLine.size() > 900 || strLine[0] != ':')
349                 continue;
350
351             vector<string> vWords;
352             ParseString(strLine, ' ', vWords);
353             if (vWords.size() < 2)
354                 continue;
355
356             char pszName[10000];
357             pszName[0] = '\0';
358
359             if (vWords[1] == "352" && vWords.size() >= 8)
360             {
361                 // index 7 is limited to 16 characters
362                 // could get full length name at index 10, but would be different from join messages
363                 strlcpy(pszName, vWords[7].c_str(), sizeof(pszName));
364                 printf("IRC got who\n");
365             }
366
367             if (vWords[1] == "JOIN" && vWords[0].size() > 1)
368             {
369                 // :username!username@50000007.F000000B.90000002.IP JOIN :#channelname
370                 strlcpy(pszName, vWords[0].c_str() + 1, sizeof(pszName));
371                 if (strchr(pszName, '!'))
372                     *strchr(pszName, '!') = '\0';
373                 printf("IRC got join\n");
374             }
375
376             if (pszName[0] == 'u')
377             {
378                 CAddress addr;
379                 if (DecodeAddress(pszName, addr))
380                 {
381                     addr.nTime = GetAdjustedTime();
382                     if (AddAddress(addr, 51 * 60))
383                         printf("IRC got new address: %s\n", addr.ToString().c_str());
384                     nGotIRCAddresses++;
385                 }
386                 else
387                 {
388                     printf("IRC decode failed\n");
389                 }
390             }
391         }
392         closesocket(hSocket);
393         hSocket = INVALID_SOCKET;
394
395         // IRC usually blocks TOR, so only try once
396         if (fTOR)
397             return;
398
399         if (GetTime() - nStart > 20 * 60)
400         {
401             nErrorWait /= 3;
402             nRetryWait /= 3;
403         }
404
405         nRetryWait = nRetryWait * 11 / 10;
406         if (!Wait(nRetryWait += 60))
407             return;
408     }
409 }
410
411
412
413
414
415
416
417
418
419
420 #ifdef TEST
421 int main(int argc, char *argv[])
422 {
423     WSADATA wsadata;
424     if (WSAStartup(MAKEWORD(2,2), &wsadata) != NO_ERROR)
425     {
426         printf("Error at WSAStartup()\n");
427         return false;
428     }
429
430     ThreadIRCSeed(NULL);
431
432     WSACleanup();
433     return 0;
434 }
435 #endif