version 0.3.13
[fms.git] / src / freenet / introductionpuzzleinserter.cpp
1 #include "../../include/freenet/introductionpuzzleinserter.h"\r
2 #include "../../include/freenet/introductionpuzzlexml.h"\r
3 #include "../../include/stringfunctions.h"\r
4 #include "../../include/option.h"\r
5 #include "../../include/freenet/captcha/simplecaptcha.h"\r
6 #include "../../include/base64.h"\r
7 \r
8 #include <Poco/DateTimeFormatter.h>\r
9 #include <Poco/UUIDGenerator.h>\r
10 #include <Poco/UUID.h>\r
11 \r
12 #ifdef XMEM\r
13         #include <xmem.h>\r
14 #endif\r
15 \r
16 IntroductionPuzzleInserter::IntroductionPuzzleInserter():IIndexInserter<long>()\r
17 {\r
18         Initialize();\r
19 }\r
20 \r
21 IntroductionPuzzleInserter::IntroductionPuzzleInserter(FCPv2 *fcp):IIndexInserter<long>(fcp)\r
22 {\r
23         Initialize();\r
24 }\r
25 \r
26 void IntroductionPuzzleInserter::CheckForNeededInsert()\r
27 {\r
28         // only do 1 insert at a time\r
29         if(m_inserting.size()==0)\r
30         {\r
31                 // select all local ids that aren't single use and that aren't currently inserting a puzzle and are publishing a trust list\r
32                 SQLite3DB::Recordset rs=m_db->Query("SELECT LocalIdentityID FROM tblLocalIdentity WHERE PublishTrustList='true' AND SingleUse='false' AND PrivateKey IS NOT NULL AND PrivateKey <> '' ORDER BY LastInsertedPuzzle;");\r
33                 \r
34                 while(!rs.AtEnd())\r
35                 {\r
36                         int localidentityid=0;\r
37                         std::string localidentityidstr="";\r
38                         Poco::DateTime now;\r
39                         float minutesbetweeninserts=0;\r
40                         minutesbetweeninserts=1440.0/(float)m_maxpuzzleinserts;\r
41                         Poco::DateTime lastinsert=now;\r
42                         lastinsert-=Poco::Timespan(0,0,minutesbetweeninserts,0,0);\r
43 \r
44                         if(rs.GetField(0))\r
45                         {\r
46                                 localidentityidstr=rs.GetField(0);\r
47                         }\r
48 \r
49                         // if this identity has any non-solved puzzles for today, we don't need to insert a new puzzle\r
50                         SQLite3DB::Recordset rs2=m_db->Query("SELECT UUID FROM tblIntroductionPuzzleInserts WHERE Day='"+Poco::DateTimeFormatter::format(now,"%Y-%m-%d")+"' AND FoundSolution='false' AND LocalIdentityID="+localidentityidstr+";");\r
51 \r
52                         // identity doesn't have any non-solved puzzles for today - start a new insert\r
53                         if(rs2.Empty()==true)\r
54                         {\r
55                                 // make sure we are on the next day or the appropriate amount of time has elapsed since the last insert\r
56                                 if(m_lastinserted.find(rs.GetInt(0))==m_lastinserted.end() || m_lastinserted[rs.GetInt(0)]<=lastinsert || m_lastinserted[rs.GetInt(0)].day()!=now.day())\r
57                                 {\r
58                                         StartInsert(rs.GetInt(0));\r
59                                         m_lastinserted[rs.GetInt(0)]=now;\r
60                                 }\r
61                                 else\r
62                                 {\r
63                                         m_log->trace("IntroductionPuzzleInserter::CheckForNeededInsert waiting to insert puzzle for "+localidentityidstr);\r
64                                 }\r
65                         }\r
66 \r
67                         rs.Next();\r
68                 }\r
69         }\r
70 }\r
71 \r
72 void IntroductionPuzzleInserter::GenerateCaptcha(std::string &encodeddata, std::string &solution)\r
73 {\r
74         SimpleCaptcha captcha;\r
75         std::vector<unsigned char> puzzle;\r
76         std::vector<unsigned char> puzzlesolution;\r
77 \r
78         captcha.Generate();\r
79         captcha.GetPuzzle(puzzle);\r
80         captcha.GetSolution(puzzlesolution);\r
81 \r
82         encodeddata.clear();\r
83         solution.clear();\r
84 \r
85         Base64::Encode(puzzle,encodeddata);\r
86         solution.insert(solution.begin(),puzzlesolution.begin(),puzzlesolution.end());\r
87 \r
88 }\r
89 \r
90 const bool IntroductionPuzzleInserter::HandlePutFailed(FCPMessage &message)\r
91 {\r
92         SQLite3DB::Statement st;\r
93         std::vector<std::string> idparts;\r
94         long localidentityid;\r
95 \r
96         StringFunctions::Split(message["Identifier"],"|",idparts);\r
97         StringFunctions::Convert(idparts[1],localidentityid);\r
98 \r
99         // non USK\r
100         if(idparts[0]==m_fcpuniquename)\r
101         {\r
102                 // if fatal error or collision - mark index\r
103                 if(message["Fatal"]=="true" || message["Code"]=="9")\r
104                 {\r
105                         m_db->Execute("UPDATE tblIntroductionPuzzleInserts SET Day='"+idparts[5]+"', InsertIndex="+idparts[2]+", FoundSolution='true' WHERE UUID='"+idparts[3]+"';");\r
106                 }\r
107 \r
108                 RemoveFromInsertList(localidentityid);\r
109 \r
110                 m_log->debug("IntroductionPuzzleInserter::HandlePutFailed failed to insert puzzle "+idparts[3]);\r
111         }\r
112 \r
113         return true;\r
114 }\r
115 \r
116 const bool IntroductionPuzzleInserter::HandlePutSuccessful(FCPMessage &message)\r
117 {\r
118         Poco::DateTime now;\r
119         SQLite3DB::Statement st;\r
120         std::vector<std::string> idparts;\r
121         long localidentityid;\r
122         long insertindex;\r
123 \r
124         StringFunctions::Split(message["Identifier"],"|",idparts);\r
125 \r
126         // non USK\r
127         if(idparts[0]==m_fcpuniquename)\r
128         {\r
129                 StringFunctions::Convert(idparts[1],localidentityid);\r
130                 StringFunctions::Convert(idparts[2],insertindex);\r
131 \r
132                 st=m_db->Prepare("UPDATE tblIntroductionPuzzleInserts SET Day=?, InsertIndex=? WHERE UUID=?;");\r
133                 st.Bind(0,idparts[5]);\r
134                 st.Bind(1,insertindex);\r
135                 st.Bind(2,idparts[3]);\r
136                 st.Step();\r
137                 st.Finalize();\r
138 \r
139                 st=m_db->Prepare("UPDATE tblLocalIdentity SET LastInsertedPuzzle=? WHERE LocalIdentityID=?;");\r
140                 st.Bind(0,Poco::DateTimeFormatter::format(now,"%Y-%m-%d %H:%M:%S"));\r
141                 st.Bind(1,localidentityid);\r
142                 st.Step();\r
143                 st.Finalize();\r
144 \r
145                 RemoveFromInsertList(localidentityid);\r
146 \r
147                 m_log->debug("IntroductionPuzzleInserter::HandlePutSuccessful inserted puzzle "+idparts[3]);\r
148         }\r
149 \r
150         return true;\r
151 }\r
152 \r
153 void IntroductionPuzzleInserter::Initialize()\r
154 {\r
155         m_fcpuniquename="IntroductionPuzzleInserter";\r
156         m_maxpuzzleinserts=50;\r
157 }\r
158 \r
159 const bool IntroductionPuzzleInserter::StartInsert(const long &localidentityid)\r
160 {\r
161         Poco::DateTime now;\r
162         std::string idstring;\r
163         long index=0;\r
164         std::string indexstr;\r
165         Poco::UUIDGenerator uuidgen;\r
166         Poco::UUID uuid;\r
167         std::string messagebase;\r
168         IntroductionPuzzleXML xml;\r
169         std::string encodedpuzzle;\r
170         std::string solutionstring;\r
171         FCPMessage message;\r
172         std::string xmldata;\r
173         std::string xmldatasizestr;\r
174         std::string privatekey="";\r
175         std::string publickey="";\r
176         std::string keypart="";\r
177 \r
178         StringFunctions::Convert(localidentityid,idstring);\r
179         SQLite3DB::Recordset rs=m_db->Query("SELECT MAX(InsertIndex) FROM tblIntroductionPuzzleInserts WHERE Day='"+Poco::DateTimeFormatter::format(now,"%Y-%m-%d")+"' AND LocalIdentityID="+idstring+";");\r
180 \r
181         if(rs.Empty() || rs.GetField(0)==NULL)\r
182         {\r
183                 index=0;\r
184         }\r
185         else\r
186         {\r
187                 index=rs.GetInt(0)+1;\r
188         }\r
189         StringFunctions::Convert(index,indexstr);\r
190 \r
191         if(index<m_maxpuzzleinserts)\r
192         {\r
193                 SQLite3DB::Recordset rs2=m_db->Query("SELECT PrivateKey,PublicKey FROM tblLocalIdentity WHERE LocalIdentityID="+idstring+";");\r
194                 if(rs2.Empty()==false && rs2.GetField(0)!=NULL)\r
195                 {\r
196                         privatekey=rs2.GetField(0);\r
197                         if(rs2.GetField(1))\r
198                         {\r
199                                 publickey=rs2.GetField(1);\r
200                         }\r
201                         if(publickey.size()>=50)\r
202                         {\r
203                                 // remove - and ~\r
204                                 keypart=StringFunctions::Replace(StringFunctions::Replace(publickey.substr(4,43),"-",""),"~","");\r
205                         }\r
206                 }\r
207 \r
208                 Option::Instance()->Get("MessageBase",messagebase);\r
209 \r
210                 GenerateCaptcha(encodedpuzzle,solutionstring);\r
211 \r
212                 try\r
213                 {\r
214                         uuid=uuidgen.createRandom();\r
215                 }\r
216                 catch(...)\r
217                 {\r
218                         m_log->fatal("IntroductionPuzzleInserter::StartInsert could not create UUID");\r
219                 }\r
220 \r
221                 xml.SetType("captcha");\r
222                 std::string uuidstr=uuid.toString();\r
223                 StringFunctions::UpperCase(uuidstr,uuidstr);\r
224                 xml.SetUUID(uuidstr+"@"+keypart);\r
225                 xml.SetPuzzleData(encodedpuzzle);\r
226                 xml.SetMimeType("image/bmp");\r
227 \r
228                 xmldata=xml.GetXML();\r
229                 StringFunctions::Convert(xmldata.size(),xmldatasizestr);\r
230 \r
231                 message.SetName("ClientPut");\r
232                 message["URI"]=privatekey+messagebase+"|"+Poco::DateTimeFormatter::format(now,"%Y-%m-%d")+"|IntroductionPuzzle|"+indexstr+".xml";\r
233                 message["Identifier"]=m_fcpuniquename+"|"+idstring+"|"+indexstr+"|"+xml.GetUUID()+"|"+message["URI"];\r
234                 message["UploadFrom"]="direct";\r
235                 message["DataLength"]=xmldatasizestr;\r
236                 m_fcp->SendMessage(message);\r
237                 m_fcp->SendRaw(xmldata.c_str(),xmldata.size());\r
238 \r
239                 // insert to USK\r
240                 message.Reset();\r
241                 message.SetName("ClientPutComplexDir");\r
242                 message["URI"]="USK"+privatekey.substr(3)+messagebase+"|"+Poco::DateTimeFormatter::format(now,"%Y.%m.%d")+"|IntroductionPuzzle/0/";\r
243                 message["Identifier"]=m_fcpuniquename+"USK|"+message["URI"];\r
244                 message["DefaultName"]="IntroductionPuzzle.xml";\r
245                 message["Files.0.Name"]="IntroductionPuzzle.xml";\r
246                 message["Files.0.UplaodFrom"]="direct";\r
247                 message["Files.0.DataLength"]=xmldatasizestr;\r
248                 m_fcp->SendMessage(message);\r
249                 m_fcp->SendRaw(xmldata.c_str(),xmldata.size());\r
250 \r
251                 m_db->Execute("INSERT INTO tblIntroductionPuzzleInserts(UUID,Type,MimeType,LocalIdentityID,PuzzleData,PuzzleSolution) VALUES('"+xml.GetUUID()+"','captcha','image/bmp',"+idstring+",'"+encodedpuzzle+"','"+solutionstring+"');");\r
252 \r
253                 m_inserting.push_back(localidentityid);\r
254 \r
255                 m_log->debug("IntroductionPuzzleInserter::StartInsert started insert for id "+idstring);\r
256         }\r
257         else\r
258         {\r
259                 m_log->warning("IntroductionPuzzleInserter::StartInsert already inserted max puzzles for "+idstring);\r
260         }\r
261 \r
262         return true;\r
263 \r
264 }\r