version 0.2.20
[fms.git] / src / freenet / messagerequester.cpp
1 #include "../../include/freenet/messagerequester.h"\r
2 #include "../../include/freenet/messagexml.h"\r
3 \r
4 #include <algorithm>\r
5 \r
6 #ifdef XMEM\r
7         #include <xmem.h>\r
8 #endif\r
9 \r
10 MessageRequester::MessageRequester()\r
11 {\r
12         Initialize();\r
13 }\r
14 \r
15 MessageRequester::MessageRequester(FCPv2 *fcp):IIndexRequester<std::string>(fcp)\r
16 {\r
17         Initialize();\r
18 }\r
19 \r
20 const long MessageRequester::GetBoardID(const std::string &boardname, const std::string &identityname)\r
21 {\r
22         std::string lowerboard=boardname;\r
23         StringFunctions::LowerCase(lowerboard,lowerboard);\r
24         SQLite3DB::Statement st=m_db->Prepare("SELECT BoardID FROM tblBoard WHERE BoardName=?;");\r
25         st.Bind(0,lowerboard);\r
26         st.Step();\r
27 \r
28         if(st.RowReturned())\r
29         {\r
30                 int boardid;\r
31                 st.ResultInt(0,boardid);\r
32                 return boardid;\r
33         }\r
34         else\r
35         {\r
36                 DateTime now;\r
37                 now.SetToGMTime();\r
38                 st=m_db->Prepare("INSERT INTO tblBoard(BoardName,DateAdded,SaveReceivedMessages,AddedMethod) VALUES(?,?,?,?);");\r
39                 st.Bind(0,boardname);\r
40                 st.Bind(1,now.Format("%Y-%m-%d %H:%M:%S"));\r
41                 if(m_savemessagesfromnewboards)\r
42                 {\r
43                         st.Bind(2,"true");\r
44                 }\r
45                 else\r
46                 {\r
47                         st.Bind(2,"false");\r
48                 }\r
49                 st.Bind(3,"Message from "+identityname);\r
50                 st.Step(true);\r
51                 return st.GetLastInsertRowID();\r
52         }       \r
53 }\r
54 \r
55 const std::string MessageRequester::GetIdentityName(const long identityid)\r
56 {\r
57         SQLite3DB::Statement st=m_db->Prepare("SELECT Name,PublicKey FROM tblIdentity WHERE IdentityID=?;");\r
58         st.Bind(0,identityid);\r
59         st.Step();\r
60         if(st.RowReturned())\r
61         {\r
62                 std::vector<std::string> keyparts;\r
63                 std::string key;\r
64                 std::string name;\r
65                 st.ResultText(0,name);\r
66                 st.ResultText(1,key);\r
67                 \r
68                 StringFunctions::SplitMultiple(key,"@,",keyparts);\r
69                 \r
70                 if(keyparts.size()>1)\r
71                 {\r
72                         return name+"@"+keyparts[1];\r
73                 }\r
74                 else\r
75                 {\r
76                         return name+"@invalidpublickey";\r
77                 }\r
78         }\r
79         else\r
80         {\r
81                 return "";\r
82         }\r
83 }\r
84 \r
85 const bool MessageRequester::HandleAllData(FCPMessage &message)\r
86 {\r
87         SQLite3DB::Statement st;\r
88         std::vector<std::string> idparts;\r
89         long datalength;\r
90         std::vector<char> data;\r
91         MessageXML xml;\r
92         long identityid;\r
93         long index;\r
94         bool inserted=false;\r
95         bool validmessage=true;\r
96         long savetoboardcount=0;\r
97 \r
98         StringFunctions::Split(message["Identifier"],"|",idparts);\r
99         StringFunctions::Convert(message["DataLength"],datalength);\r
100         StringFunctions::Convert(idparts[2],identityid);\r
101         StringFunctions::Convert(idparts[4],index);\r
102 \r
103         // wait for all data to be received from connection\r
104         while(m_fcp->Connected() && m_fcp->ReceiveBufferSize()<datalength)\r
105         {\r
106                 m_fcp->Update(1);\r
107         }\r
108 \r
109         // if we got disconnected- return immediately\r
110         if(m_fcp->Connected()==false)\r
111         {\r
112                 return false;\r
113         }\r
114 \r
115         // receive the file\r
116         data.resize(datalength);\r
117         m_fcp->ReceiveRaw(&data[0],datalength);\r
118 \r
119         // mark this index as received\r
120         st=m_db->Prepare("UPDATE tblMessageRequests SET Found='true' WHERE IdentityID=? AND Day=? AND RequestIndex=?;");\r
121         st.Bind(0,identityid);\r
122         st.Bind(1,idparts[3]);\r
123         st.Bind(2,index);\r
124         st.Step();\r
125         st.Finalize();\r
126 \r
127         // parse file into xml and update the database\r
128         if(xml.ParseXML(std::string(data.begin(),data.end()))==true)\r
129         {\r
130                 std::vector<std::string> boards=xml.GetBoards();\r
131                 std::map<long,std::string> replyto=xml.GetInReplyTo();\r
132 \r
133                 if(boards.size()>m_maxboardspermessage)\r
134                 {\r
135                         boards.resize(m_maxboardspermessage);\r
136                 }\r
137 \r
138                 if(boards.size()<=0)\r
139                 {\r
140                         m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"MessageRequester::HandleAllData Message XML did not contain any boards! "+message["Identifier"]);\r
141                         // remove this identityid from request list\r
142                         RemoveFromRequestList(idparts[1]);                      \r
143                         return true;\r
144                 }\r
145                 if(xml.GetReplyBoard()=="")\r
146                 {\r
147                         m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"MessageRequester::HandleAllData Message XML did not contain a reply board! "+message["Identifier"]);\r
148                         // remove this identityid from request list\r
149                         RemoveFromRequestList(idparts[1]);                      \r
150                         return true;\r
151                 }\r
152 \r
153                 // make sure the reply board is on the board list we are saving - if not, replace the last element of boards with the reply board\r
154                 if(xml.GetReplyBoard()!="" && std::find(boards.begin(),boards.end(),xml.GetReplyBoard())==boards.end() && boards.size()>0)\r
155                 {\r
156                         boards[boards.size()-1]=xml.GetReplyBoard();\r
157                 }\r
158 \r
159                 // make sure domain of message id match 43 characters of public key of identity (remove - and ~) - if not, discard message\r
160                 // implement after 0.1.12 is released\r
161                 st=m_db->Prepare("SELECT PublicKey FROM tblIdentity WHERE IdentityID=?;");\r
162                 st.Bind(0,identityid);\r
163                 st.Step();\r
164                 if(st.RowReturned())\r
165                 {\r
166                         std::vector<std::string> uuidparts;\r
167                         std::vector<std::string> keyparts;\r
168                         std::string keypart="";\r
169                         std::string publickey="";\r
170 \r
171                         st.ResultText(0,publickey);\r
172 \r
173                         StringFunctions::SplitMultiple(publickey,"@,",keyparts);\r
174                         StringFunctions::SplitMultiple(xml.GetMessageID(),"@",uuidparts);\r
175 \r
176                         if(uuidparts.size()>1 && keyparts.size()>1)\r
177                         {\r
178                                 keypart=StringFunctions::Replace(StringFunctions::Replace(keyparts[1],"-",""),"~","");\r
179                                 if(keypart!=uuidparts[1])\r
180                                 {\r
181                                         m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"MessageRequester::HandleAllData MessageID in Message doesn't match public key of identity : "+message["Identifier"]);\r
182                                         validmessage=false;\r
183                                 }\r
184                         }\r
185                         else\r
186                         {\r
187                                 m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"MessageRequester::HandleAllData Error with identity's public key or Message ID : "+message["Identifier"]);\r
188                                 validmessage=false;\r
189                         }\r
190                 }\r
191                 else\r
192                 {\r
193                         m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"MessageRequester::HandleAllData Error couldn't find identity : "+message["Identifier"]);\r
194                         validmessage=false;\r
195                 }\r
196 \r
197                 // make sure we will at least save to 1 board before inserting message\r
198                 savetoboardcount=0;\r
199                 for(std::vector<std::string>::iterator bi=boards.begin(); bi!=boards.end(); bi++)\r
200                 {\r
201                         if(SaveToBoard((*bi)))\r
202                         {\r
203                                 savetoboardcount++;\r
204                         }\r
205                 }\r
206 \r
207                 if(validmessage && savetoboardcount>0)\r
208                 {\r
209                         std::string nntpbody="";\r
210                         nntpbody=xml.GetBody();\r
211 \r
212                         //add file keys/sizes to body\r
213                         std::vector<MessageXML::fileattachment> fileattachments=xml.GetFileAttachments();\r
214                         if(fileattachments.size()>0)\r
215                         {\r
216                                 nntpbody+="\r\nAttachments";\r
217                         }\r
218                         for(std::vector<MessageXML::fileattachment>::iterator i=fileattachments.begin(); i!=fileattachments.end(); i++)\r
219                         {\r
220                                 std::string sizestr="0";\r
221                                 StringFunctions::Convert((*i).m_size,sizestr);\r
222 \r
223                                 nntpbody+="\r\n"+(*i).m_key;\r
224                                 nntpbody+="\r\n"+sizestr+" bytes";\r
225                                 nntpbody+="\r\n";\r
226                         }\r
227 \r
228                         st=m_db->Prepare("INSERT INTO tblMessage(IdentityID,FromName,MessageDate,MessageTime,Subject,MessageUUID,ReplyBoardID,Body,MessageIndex) VALUES(?,?,?,?,?,?,?,?,?);");\r
229                         st.Bind(0,identityid);\r
230                         st.Bind(1,GetIdentityName(identityid));\r
231                         st.Bind(2,xml.GetDate());\r
232                         st.Bind(3,xml.GetTime());\r
233                         st.Bind(4,xml.GetSubject());\r
234                         st.Bind(5,xml.GetMessageID());\r
235                         st.Bind(6,GetBoardID(xml.GetReplyBoard(),GetIdentityName(identityid)));\r
236                         st.Bind(7,nntpbody);\r
237                         st.Bind(8,index);\r
238                         inserted=st.Step(true);\r
239                         int messageid=st.GetLastInsertRowID();\r
240 \r
241                         if(inserted==true)\r
242                         {\r
243 \r
244                                 st=m_db->Prepare("INSERT INTO tblMessageBoard(MessageID,BoardID) VALUES(?,?);");\r
245                                 for(std::vector<std::string>::iterator i=boards.begin(); i!=boards.end(); i++)\r
246                                 {\r
247                                         if(SaveToBoard((*i)))\r
248                                         {\r
249                                                 st.Bind(0,messageid);\r
250                                                 st.Bind(1,GetBoardID((*i),GetIdentityName(identityid)));\r
251                                                 st.Step();\r
252                                                 st.Reset();\r
253                                         }\r
254                                 }\r
255                                 st.Finalize();\r
256 \r
257                                 st=m_db->Prepare("INSERT INTO tblMessageReplyTo(MessageID,ReplyToMessageUUID,ReplyOrder) VALUES(?,?,?);");\r
258                                 for(std::map<long,std::string>::iterator j=replyto.begin(); j!=replyto.end(); j++)\r
259                                 {\r
260                                         st.Bind(0,messageid);\r
261                                         st.Bind(1,(*j).second);\r
262                                         st.Bind(2,(*j).first);\r
263                                         st.Step();\r
264                                         st.Reset();\r
265                                 }\r
266                                 st.Finalize();\r
267 \r
268                                 m_log->WriteLog(LogFile::LOGLEVEL_DEBUG,"MessageRequester::HandleAllData parsed Message XML file : "+message["Identifier"]);\r
269 \r
270                         }\r
271                         else    // couldn't insert - was already in database\r
272                         {\r
273                                 //m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"MessageRequester::HandleAddData could not insert message into database.  "+message["Identifier"]);\r
274                         }\r
275 \r
276                 }       // if validmessage\r
277         }\r
278         else\r
279         {\r
280                 m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"MessageRequester::HandleAllData error parsing Message XML file : "+message["Identifier"]);\r
281         }\r
282 \r
283         RemoveFromRequestList(idparts[1]);\r
284 \r
285         return true;\r
286 }\r
287 \r
288 const bool MessageRequester::HandleGetFailed(FCPMessage &message)\r
289 {\r
290         DateTime now;\r
291         SQLite3DB::Statement st;\r
292         std::vector<std::string> idparts;\r
293         std::string requestid;\r
294         long index;\r
295         long identityid;\r
296 \r
297         now.SetToGMTime();\r
298         StringFunctions::Split(message["Identifier"],"|",idparts);\r
299         requestid=idparts[1];\r
300         StringFunctions::Convert(idparts[2],identityid);\r
301         StringFunctions::Convert(idparts[4],index);\r
302 \r
303         // if this is a fatal error - insert index into database so we won't try to download this index again\r
304         if(message["Fatal"]=="true")\r
305         {\r
306                 st=m_db->Prepare("UPDATE tblMessageRequests SET Found='true' WHERE IdentityID=? AND Day=? AND RequestIndex=?;");\r
307                 st.Bind(0,identityid);\r
308                 st.Bind(1,idparts[3]);\r
309                 st.Bind(2,index);\r
310                 st.Step();\r
311                 st.Finalize();\r
312 \r
313                 m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"MessageRequester::HandleGetFailed fatal error requesting "+message["Identifier"]);\r
314         }\r
315 \r
316         // remove this identityid from request list\r
317         RemoveFromRequestList(requestid);\r
318 \r
319         return true;\r
320 }\r
321 \r
322 void MessageRequester::Initialize()\r
323 {\r
324         m_fcpuniquename="MessageRequester";\r
325         std::string tempval;\r
326         Option::Instance()->Get("MaxMessageRequests",tempval);\r
327         StringFunctions::Convert(tempval,m_maxrequests);\r
328         if(m_maxrequests<1)\r
329         {\r
330                 m_maxrequests=1;\r
331                 m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"Option MaxMessageRequests is currently set at "+tempval+".  It must be 1 or greater.");\r
332         }\r
333         if(m_maxrequests>100)\r
334         {\r
335                 m_log->WriteLog(LogFile::LOGLEVEL_WARNING,"Option MaxMessageRequests is currently set at "+tempval+".  This value might be incorrectly configured.");\r
336         }\r
337         Option::Instance()->Get("MessageDownloadMaxDaysBackward",tempval);\r
338         StringFunctions::Convert(tempval,m_maxdaysbackward);\r
339         if(m_maxdaysbackward<0)\r
340         {\r
341                 m_maxdaysbackward=0;\r
342                 m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"Option MessageDownloadMaxDaysBackward is currently set at "+tempval+".  It must be 0 or greater.");\r
343         }\r
344         if(m_maxdaysbackward>30)\r
345         {\r
346                 m_log->WriteLog(LogFile::LOGLEVEL_WARNING,"Option MessageDownloadMaxDaysBackward is currently set at "+tempval+".  This value might be incorrectly configured.");\r
347         }\r
348         Option::Instance()->Get("MaxPeerMessagesPerDay",tempval);\r
349         StringFunctions::Convert(tempval,m_maxpeermessages);\r
350         if(m_maxpeermessages<1)\r
351         {\r
352                 m_maxpeermessages=1;\r
353                 m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"Option MaxPeerMessagesPerDay is currently set at "+tempval+".  It must be 1 or greater.");\r
354         }\r
355         if(m_maxpeermessages<20 || m_maxpeermessages>1000)\r
356         {\r
357                 m_log->WriteLog(LogFile::LOGLEVEL_WARNING,"Option MaxPeerMessagesPerDay is currently set at "+tempval+".  This value might be incorrectly configured.  The suggested value is 200.");\r
358         }\r
359         Option::Instance()->Get("MaxBoardsPerMessage",tempval);\r
360         StringFunctions::Convert(tempval,m_maxboardspermessage);\r
361         if(m_maxboardspermessage<1)\r
362         {\r
363                 m_maxboardspermessage=1;\r
364                 m_log->WriteLog(LogFile::LOGLEVEL_ERROR,"Option MaxBoardsPerMessage is currently set at "+tempval+".  It must be 1 or greater.");\r
365         }\r
366         if(m_maxboardspermessage>20)\r
367         {\r
368                 m_log->WriteLog(LogFile::LOGLEVEL_WARNING,"Option MaxBoardsPerMessage is currently set at "+tempval+".  This value might be incorrectly configured.");\r
369         }\r
370 \r
371         Option::Instance()->Get("SaveMessagesFromNewBoards",tempval);\r
372         if(tempval=="true")\r
373         {\r
374                 m_savemessagesfromnewboards=true;\r
375         }\r
376         else\r
377         {\r
378                 m_savemessagesfromnewboards=false;\r
379         }\r
380 \r
381         Option::Instance()->Get("LocalTrustOverridesPeerTrust",tempval);\r
382         if(tempval=="true")\r
383         {\r
384                 m_localtrustoverrides=true;\r
385         }\r
386         else\r
387         {\r
388                 m_localtrustoverrides=false;\r
389         }\r
390 \r
391 }\r
392 \r
393 void MessageRequester::PopulateIDList()\r
394 {\r
395         DateTime date;\r
396         std::string val1;\r
397         std::string val2;\r
398         std::string val3;\r
399         std::string sql;\r
400         long requestindex;\r
401 \r
402         date.SetToGMTime();\r
403         date.Add(0,0,0,-m_maxdaysbackward);\r
404 \r
405         sql="SELECT tblIdentity.IdentityID,Day,RequestIndex ";\r
406         sql+="FROM tblMessageRequests INNER JOIN tblIdentity ON tblMessageRequests.IdentityID=tblIdentity.IdentityID ";\r
407         sql+="WHERE FromMessageList='true' AND Found='false' AND Day>='"+date.Format("%Y-%m-%d")+"' ";\r
408         if(m_localtrustoverrides==false)\r
409         {\r
410                 sql+="AND (tblIdentity.LocalMessageTrust IS NULL OR tblIdentity.LocalMessageTrust>=(SELECT OptionValue FROM tblOption WHERE Option='MinLocalMessageTrust')) ";\r
411                 sql+="AND (tblIdentity.PeerMessageTrust IS NULL OR tblIdentity.PeerMessageTrust>=(SELECT OptionValue FROM tblOption WHERE Option='MinPeerMessageTrust')) ";\r
412         }\r
413         else\r
414         {\r
415                 sql+="AND (tblIdentity.LocalMessageTrust>=(SELECT OptionValue FROM tblOption WHERE Option='MinLocalMessageTrust') OR (tblIdentity.LocalMessageTrust IS NULL AND (tblIdentity.PeerMessageTrust IS NULL OR tblIdentity.PeerMessageTrust>=(SELECT OptionValue FROM tblOption WHERE Option='MinPeerMessageTrust')))) ";\r
416         }\r
417         sql+="AND tblIdentity.Name <> '' ";\r
418         // sort by day descending - in case there is a bunch of messages on a day that keep timing out, we will eventually get to the next day and hopefully find messages there\r
419         sql+="ORDER BY tblMessageRequests.Day DESC ";\r
420         sql+=";";\r
421 \r
422         SQLite3DB::Statement st=m_db->Prepare(sql);\r
423         st.Step();\r
424 \r
425         m_ids.clear();\r
426 \r
427         while(st.RowReturned())\r
428         {\r
429                 st.ResultText(0,val1);\r
430                 st.ResultText(1,val2);\r
431                 st.ResultText(2,val3);\r
432 \r
433                 requestindex=0;\r
434                 StringFunctions::Convert(val3,requestindex);\r
435 \r
436                 // only continue if index is < max messages we will accept from a peer\r
437                 if(requestindex<m_maxpeermessages)\r
438                 {\r
439                         if(m_ids.find(val1+"*"+val2+"*"+val3)==m_ids.end())\r
440                         {\r
441                                 m_ids[val1+"*"+val2+"*"+val3]=false;\r
442                         }\r
443                 }\r
444                 st.Step();\r
445         }\r
446 \r
447 }\r
448 \r
449 const bool MessageRequester::SaveToBoard(const std::string &boardname)\r
450 {\r
451         bool save=true;\r
452         SQLite3DB::Statement st=m_db->Prepare("SELECT SaveReceivedMessages FROM tblBoard WHERE BoardName=?;");\r
453         st.Bind(0,boardname);\r
454         st.Step();\r
455         if(st.RowReturned())\r
456         {\r
457                 std::string val="";\r
458                 st.ResultText(0,val);\r
459                 if(val=="true")\r
460                 {\r
461                         save=true;\r
462                 }\r
463                 else\r
464                 {\r
465                         save=false;\r
466                 }\r
467         }\r
468         return save;\r
469 }\r
470 \r
471 void MessageRequester::StartRequest(const std::string &requestid)\r
472 {\r
473         FCPMessage message;\r
474         std::vector<std::string> parts;\r
475         std::string tempval;\r
476         long identityid;\r
477         std::string date;\r
478         std::string indexstr;\r
479         std::string publickey;\r
480 \r
481         StringFunctions::Split(requestid,"*",parts);\r
482         StringFunctions::Convert(parts[0],identityid);\r
483         StringFunctions::Convert(parts[1],date);\r
484         indexstr=parts[2];\r
485 \r
486         SQLite3DB::Statement st=m_db->Prepare("SELECT PublicKey FROM tblIdentity WHERE IdentityID=?;");\r
487         st.Bind(0,identityid);\r
488         st.Step();\r
489 \r
490         if(st.RowReturned())\r
491         {\r
492                 st.ResultText(0,publickey);\r
493 \r
494                 message.SetName("ClientGet");\r
495                 message["URI"]=publickey+m_messagebase+"|"+date+"|Message|"+indexstr+".xml";\r
496                 message["Identifier"]=m_fcpuniquename+"|"+requestid+"|"+parts[0]+"|"+parts[1]+"|"+parts[2]+"|"+message["URI"];\r
497                 message["ReturnType"]="direct";\r
498                 message["MaxSize"]="1000000";           // 1 MB\r
499                 message["MaxRetries"]="-1";                     // use ULPR since we are fairly sure message exists since the author says it does\r
500 \r
501                 m_fcp->SendMessage(message);\r
502 \r
503                 m_requesting.push_back(requestid);\r
504 \r
505                 m_log->WriteLog(LogFile::LOGLEVEL_DEBUG,"MessageRequester::StartRequest requesting "+message["Identifier"]);\r
506         }\r
507         \r
508         m_ids[requestid]=true;\r
509 \r
510 }\r