version 0.3.10
[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                                 if(m_lastinserted.find(rs.GetInt(0))==m_lastinserted.end() || m_lastinserted[rs.GetInt(0)]<=lastinsert)\r
56                                 {\r
57                                         StartInsert(rs.GetInt(0));\r
58                                         m_lastinserted[rs.GetInt(0)]=now;\r
59                                 }\r
60                                 else\r
61                                 {\r
62                                         m_log->trace("IntroductionPuzzleInserter::CheckForNeededInsert waiting to insert puzzle for "+localidentityidstr);\r
63                                 }\r
64                         }\r
65 \r
66                         rs.Next();\r
67                 }\r
68         }\r
69 }\r
70 \r
71 void IntroductionPuzzleInserter::GenerateCaptcha(std::string &encodeddata, std::string &solution)\r
72 {\r
73         SimpleCaptcha captcha;\r
74         std::vector<unsigned char> puzzle;\r
75         std::vector<unsigned char> puzzlesolution;\r
76 \r
77         captcha.Generate();\r
78         captcha.GetPuzzle(puzzle);\r
79         captcha.GetSolution(puzzlesolution);\r
80 \r
81         encodeddata.clear();\r
82         solution.clear();\r
83 \r
84         Base64::Encode(puzzle,encodeddata);\r
85         solution.insert(solution.begin(),puzzlesolution.begin(),puzzlesolution.end());\r
86 \r
87 }\r
88 \r
89 const bool IntroductionPuzzleInserter::HandlePutFailed(FCPMessage &message)\r
90 {\r
91         SQLite3DB::Statement st;\r
92         std::vector<std::string> idparts;\r
93         long localidentityid;\r
94 \r
95         StringFunctions::Split(message["Identifier"],"|",idparts);\r
96         StringFunctions::Convert(idparts[1],localidentityid);\r
97 \r
98         // non USK\r
99         if(idparts[0]==m_fcpuniquename)\r
100         {\r
101                 // if fatal error or collision - mark index\r
102                 if(message["Fatal"]=="true" || message["Code"]=="9")\r
103                 {\r
104                         m_db->Execute("UPDATE tblIntroductionPuzzleInserts SET Day='"+idparts[5]+"', InsertIndex="+idparts[2]+", FoundSolution='true' WHERE UUID='"+idparts[3]+"';");\r
105                 }\r
106 \r
107                 RemoveFromInsertList(localidentityid);\r
108 \r
109                 m_log->debug("IntroductionPuzzleInserter::HandlePutFailed failed to insert puzzle "+idparts[3]);\r
110         }\r
111 \r
112         return true;\r
113 }\r
114 \r
115 const bool IntroductionPuzzleInserter::HandlePutSuccessful(FCPMessage &message)\r
116 {\r
117         Poco::DateTime now;\r
118         SQLite3DB::Statement st;\r
119         std::vector<std::string> idparts;\r
120         long localidentityid;\r
121         long insertindex;\r
122 \r
123         StringFunctions::Split(message["Identifier"],"|",idparts);\r
124 \r
125         // non USK\r
126         if(idparts[0]==m_fcpuniquename)\r
127         {\r
128                 StringFunctions::Convert(idparts[1],localidentityid);\r
129                 StringFunctions::Convert(idparts[2],insertindex);\r
130 \r
131                 st=m_db->Prepare("UPDATE tblIntroductionPuzzleInserts SET Day=?, InsertIndex=? WHERE UUID=?;");\r
132                 st.Bind(0,idparts[5]);\r
133                 st.Bind(1,insertindex);\r
134                 st.Bind(2,idparts[3]);\r
135                 st.Step();\r
136                 st.Finalize();\r
137 \r
138                 st=m_db->Prepare("UPDATE tblLocalIdentity SET LastInsertedPuzzle=? WHERE LocalIdentityID=?;");\r
139                 st.Bind(0,Poco::DateTimeFormatter::format(now,"%Y-%m-%d %H:%M:%S"));\r
140                 st.Bind(1,localidentityid);\r
141                 st.Step();\r
142                 st.Finalize();\r
143 \r
144                 RemoveFromInsertList(localidentityid);\r
145 \r
146                 m_log->debug("IntroductionPuzzleInserter::HandlePutSuccessful inserted puzzle "+idparts[3]);\r
147         }\r
148 \r
149         return true;\r
150 }\r
151 \r
152 void IntroductionPuzzleInserter::Initialize()\r
153 {\r
154         m_fcpuniquename="IntroductionPuzzleInserter";\r
155         m_maxpuzzleinserts=50;\r
156 }\r
157 \r
158 const bool IntroductionPuzzleInserter::StartInsert(const long &localidentityid)\r
159 {\r
160         Poco::DateTime now;\r
161         std::string idstring;\r
162         long index=0;\r
163         std::string indexstr;\r
164         Poco::UUIDGenerator uuidgen;\r
165         Poco::UUID uuid;\r
166         std::string messagebase;\r
167         IntroductionPuzzleXML xml;\r
168         std::string encodedpuzzle;\r
169         std::string solutionstring;\r
170         FCPMessage message;\r
171         std::string xmldata;\r
172         std::string xmldatasizestr;\r
173         std::string privatekey="";\r
174         std::string publickey="";\r
175         std::string keypart="";\r
176 \r
177         StringFunctions::Convert(localidentityid,idstring);\r
178         SQLite3DB::Recordset rs=m_db->Query("SELECT MAX(InsertIndex) FROM tblIntroductionPuzzleInserts WHERE Day='"+Poco::DateTimeFormatter::format(now,"%Y-%m-%d")+"' AND LocalIdentityID="+idstring+";");\r
179 \r
180         if(rs.Empty() || rs.GetField(0)==NULL)\r
181         {\r
182                 index=0;\r
183         }\r
184         else\r
185         {\r
186                 index=rs.GetInt(0)+1;\r
187         }\r
188         StringFunctions::Convert(index,indexstr);\r
189 \r
190         if(index<m_maxpuzzleinserts)\r
191         {\r
192                 SQLite3DB::Recordset rs2=m_db->Query("SELECT PrivateKey,PublicKey FROM tblLocalIdentity WHERE LocalIdentityID="+idstring+";");\r
193                 if(rs2.Empty()==false && rs2.GetField(0)!=NULL)\r
194                 {\r
195                         privatekey=rs2.GetField(0);\r
196                         if(rs2.GetField(1))\r
197                         {\r
198                                 publickey=rs2.GetField(1);\r
199                         }\r
200                         if(publickey.size()>=50)\r
201                         {\r
202                                 // remove - and ~\r
203                                 keypart=StringFunctions::Replace(StringFunctions::Replace(publickey.substr(4,43),"-",""),"~","");\r
204                         }\r
205                 }\r
206 \r
207                 Option::Instance()->Get("MessageBase",messagebase);\r
208 \r
209                 GenerateCaptcha(encodedpuzzle,solutionstring);\r
210 \r
211                 try\r
212                 {\r
213                         uuid=uuidgen.createRandom();\r
214                 }\r
215                 catch(...)\r
216                 {\r
217                         m_log->fatal("IntroductionPuzzleInserter::StartInsert could not create UUID");\r
218                 }\r
219 \r
220                 xml.SetType("captcha");\r
221                 std::string uuidstr=uuid.toString();\r
222                 StringFunctions::UpperCase(uuidstr,uuidstr);\r
223                 xml.SetUUID(uuidstr+"@"+keypart);\r
224                 xml.SetPuzzleData(encodedpuzzle);\r
225                 xml.SetMimeType("image/bmp");\r
226 \r
227                 xmldata=xml.GetXML();\r
228                 StringFunctions::Convert(xmldata.size(),xmldatasizestr);\r
229 \r
230                 message.SetName("ClientPut");\r
231                 message["URI"]=privatekey+messagebase+"|"+Poco::DateTimeFormatter::format(now,"%Y-%m-%d")+"|IntroductionPuzzle|"+indexstr+".xml";\r
232                 message["Identifier"]=m_fcpuniquename+"|"+idstring+"|"+indexstr+"|"+xml.GetUUID()+"|"+message["URI"];\r
233                 message["UploadFrom"]="direct";\r
234                 message["DataLength"]=xmldatasizestr;\r
235                 m_fcp->SendMessage(message);\r
236                 m_fcp->SendRaw(xmldata.c_str(),xmldata.size());\r
237 \r
238                 // insert to USK\r
239                 message.Reset();\r
240                 message.SetName("ClientPutComplexDir");\r
241                 message["URI"]="USK"+privatekey.substr(3)+messagebase+"|"+Poco::DateTimeFormatter::format(now,"%Y.%m.%d")+"|IntroductionPuzzle/0/";\r
242                 message["Identifier"]=m_fcpuniquename+"USK|"+message["URI"];\r
243                 message["DefaultName"]="IntroductionPuzzle.xml";\r
244                 message["Files.0.Name"]="IntroductionPuzzle.xml";\r
245                 message["Files.0.UplaodFrom"]="direct";\r
246                 message["Files.0.DataLength"]=xmldatasizestr;\r
247                 m_fcp->SendMessage(message);\r
248                 m_fcp->SendRaw(xmldata.c_str(),xmldata.size());\r
249 \r
250                 m_db->Execute("INSERT INTO tblIntroductionPuzzleInserts(UUID,Type,MimeType,LocalIdentityID,PuzzleData,PuzzleSolution) VALUES('"+xml.GetUUID()+"','captcha','image/bmp',"+idstring+",'"+encodedpuzzle+"','"+solutionstring+"');");\r
251 \r
252                 m_inserting.push_back(localidentityid);\r
253 \r
254                 m_log->debug("IntroductionPuzzleInserter::StartInsert started insert for id "+idstring);\r
255         }\r
256         else\r
257         {\r
258                 m_log->warning("IntroductionPuzzleInserter::StartInsert already inserted max puzzles for "+idstring);\r
259         }\r
260 \r
261         return true;\r
262 \r
263 }\r