monotone

monotone Mtn Source Tree

Root/depot.cc

1// copyright (C) 2003 graydon hoare <graydon@pobox.com>
2// all rights reserved.
3// licensed to the public under the terms of the GNU GPL (>= 2)
4// see the file COPYING for details
5
6// this file contains the entire "depot" CGI functionality,
7// which is a very lightweight device for posting packets
8// and letting other people retrieve them. it is not meant
9// to be a general purpose patch/version/file server, just
10// a packet exchanger (a friendlier surrogate for NNTP).
11
12#include <iostream>
13#include <string>
14#include <map>
15#include <stdexcept>
16
17#include <stdlib.h>
18
19#include <boost/tokenizer.hpp>
20#include <boost/lexical_cast.hpp>
21
22#include <boost/spirit.hpp>
23
24#include "adler32.hh"
25#include "cleanup.hh"
26#include "constants.hh"
27#include "schema_migration.hh"
28
29#include "cryptopp/base64.h"
30#include "cryptopp/hex.h"
31#include "cryptopp/cryptlib.h"
32#include "cryptopp/sha.h"
33#include "cryptopp/rsa.h"
34
35#include "sqlite/sqlite.h"
36
37using namespace std;
38using namespace boost;
39
40// defined in depot_schema.sql, defines symbol depot_schema_constant,
41// converted to header:
42#include "depot_schema.h"
43
44void check_schema(sqlite *sql)
45{
46 // nb. update this whenever the schema changes
47 string id;
48 string want("b0f3041a8ded95006584340ef76bd70ae81bb376");
49 calculate_schema_id(sql, id);
50 if (id != want)
51 throw runtime_error("database schemas do not match: wanted " + want
52+ ", got " + id
53+ ". try migrating database");
54}
55
56template<typename T>
57int read_value_cb (void * vp,
58 int ncols,
59 char ** values,
60 char ** colnames)
61{
62 // nb. this function is designed to *not* kill query processing when
63 // there's an error, but to return 0 and let the query just live with
64 // default values.
65
66 if (ncols != 1)
67 return 0;
68
69 if (vp == NULL)
70 return 0;
71
72 T *p = reinterpret_cast<T *>(vp);
73
74 if (values == NULL ||
75 values[0] == NULL)
76 return 0;
77
78 try
79 {
80 *p = lexical_cast<T>(string(values[0]));
81 }
82 catch (...)
83 {
84 // it is better for us to just not modify the value.
85 // sqlite gets a bit snickery if you throw exceptions
86 // up its call stack.
87 return 0;
88 }
89 return 0;
90}
91
92template int read_value_cb<unsigned long>(void *,int,char **, char**);
93template int read_value_cb<string>(void *,int,char **, char**);
94template int read_value_cb<bool>(void *,int,char **, char**);
95
96
97// ----- PROCESSING 'STATUS' QUERIES -----
98
99void execute_status_query (sqlite *sql)
100{
101 char *errmsg = NULL;
102 unsigned long count = 0;
103
104 int res = sqlite_exec_printf(sql,
105 "SELECT COUNT(*) FROM packets ",
106 &read_value_cb<unsigned long>, &count, &errmsg);
107 if (res == SQLITE_OK)
108 {
109 cout << "Status: 200 OK\n"
110 << "Content-type: text/plain\n"
111 << "\n"
112 << "depot operational with " << count << " packets.\n";
113 }
114 else
115 {
116 cout << "Status: 204 No Content\n"
117 << "Content-type: text/plain\n"
118 << "\n"
119 << "depot error: " << (errmsg != NULL ? errmsg : "unknown") << "\n";
120 }
121}
122
123// ----- PROCESSING 'SINCE' QUERIES -----
124
125int write_since_results(void * dummy,
126int ncols,
127char ** values,
128char ** colnames)
129{
130 if (ncols != 3)
131 return 0;
132
133 if (values == NULL ||
134 values[0] == NULL ||
135 values[1] == NULL ||
136 values[2] == NULL)
137 return 0;
138
139 cout << values[2] << "\n";
140 cout << "[seq " << values[0] << " " << values[1] << "]\n";
141
142 return 0;
143}
144
145void execute_since_query (unsigned long maj,
146 unsigned long min,
147 string const & group,
148 sqlite *sql)
149{
150 cout << "Status: 200 OK\n"
151 << "Content-type: application/x-monotone-packets\n"
152 << "\n";
153
154 int res = sqlite_exec_printf(sql,
155 "SELECT major, minor, contents "
156 "FROM packets "
157 "WHERE (major > %lu OR "
158 "(major = %lu AND minor > %lu)) "
159 "AND groupname = '%q' "
160 "ORDER BY major, minor",
161 &write_since_results, NULL, NULL,
162 maj, maj, min, group.c_str());
163
164 if (res != SQLITE_OK)
165 throw runtime_error("sqlite returned error");
166}
167
168
169// ----- PROCESSING 'POST' QUERIES -----
170
171
172static string err2msg (char *em)
173{
174 if (em != NULL)
175 return string(em);
176 else
177 return string("(null)");
178}
179
180void execute_post_query (string const & user,
181 string const & sig,
182 string const & group,
183 unsigned long nbytes,
184 sqlite *sql)
185{
186
187 char *errmsg = NULL;
188
189 if (nbytes >= constants::maxbytes)
190 throw runtime_error("uploading too much data");
191
192 // step 1: get incoming data
193 string tmp;
194 tmp.clear();
195 tmp.reserve(nbytes);
196
197 {
198 char buf[constants::bufsz];
199 while(tmp.size() < constants::maxbytes &&
200 tmp.size() < nbytes &&
201 cin.good())
202 {
203 unsigned long count = constants::bufsz;
204 if (nbytes - tmp.size() < count)
205 count = nbytes - tmp.size();
206
207 cin.read(buf, count);
208 tmp.append(buf, cin.gcount());
209 }
210
211 size_t p;
212 if ((p = tmp.find_first_not_of("abcdefghijklmnopqrstuvwxyz"
213 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
214 "0123456789"
215 "-+/=_.@[] \n\t"))
216!= string::npos)
217 throw runtime_error(string("illegal character in uploaded data: ")
218 + lexical_cast<string>(static_cast<int>(tmp.at(p))));
219
220 if (tmp.size() >= constants::maxbytes)
221 throw runtime_error("overflowed safety limit for maximum upload");
222 }
223
224 // step 2: pick up pubkey
225 string pubkey;
226 {
227 int res = sqlite_exec_printf(sql,
228 "SELECT pubkey "
229 "FROM users "
230 "WHERE name = '%q' "
231 "LIMIT 1",
232 &read_value_cb<string>,
233 &pubkey, &errmsg,
234 user.c_str());
235 if (res != SQLITE_OK)
236 throw runtime_error("sqlite returned error for pubkey (user '" + user + "'): "
237 + err2msg(errmsg));
238 if (pubkey == "")
239 throw runtime_error("no pubkey found for user '" + user + "'");
240 }
241
242
243 // step 3: confirm sig on incoming data
244 // (yes yes, this repeats code in keys.cc,
245 // but I don't want to link with it)
246
247 {
248 using namespace CryptoPP;
249 StringSource keysource(pubkey, true, new Base64Decoder);
250 RSASSA_PKCS1v15_SHA_Verifier verifier(keysource);
251
252 string decodedsig;
253 StringSource sigsource(sig, true, new HexDecoder (new StringSink (decodedsig)));
254 VerifierFilter *vf = NULL;
255
256 try
257 {
258vf = new VerifierFilter(verifier);
259vf->Put(reinterpret_cast<byte const *>(decodedsig.data()), decodedsig.size());
260 }
261 catch (...)
262 {
263if (vf)
264 delete vf;
265throw runtime_error("signature verifier threw exception");
266 }
267
268 if (vf == NULL)
269 throw runtime_error("signature verifier became NULL");
270
271 StringSource ss(tmp, true, vf);
272 if (! vf->GetLastResult())
273 throw runtime_error("bad signature value");
274 }
275
276
277 // step 4: begin transaction
278 {
279 int res = sqlite_exec_printf(sql, "BEGIN", NULL, NULL, &errmsg);
280 if (res != SQLITE_OK)
281 throw runtime_error("sqlite returned error on BEGIN: " + err2msg(errmsg));
282 }
283
284
285 // step 5: chop up data and insert it
286 {
287 string::size_type lo = string::npos, hi = string::npos;
288 string tok("[end]\n");
289 for(lo = 0; (hi = tmp.find(tok, lo)) != string::npos; lo = hi+tok.size())
290 {
291string content = tmp.substr(lo, (hi+tok.size()) - lo);
292bool exists = false;
293adler32 ad(content.data(), content.size());
294int res = sqlite_exec_printf(sql,
295 "SELECT COUNT(*) > 0 FROM packets "
296 "WHERE groupname = '%q' AND adler32 = %lu AND contents = '%q'",
297 &read_value_cb<bool>, &exists, &errmsg,
298 group.c_str(),
299 ad.sum(),
300 content.c_str());
301
302if (res != SQLITE_OK)
303 throw runtime_error("sqlite returned error on adler32 COUNT: " + err2msg(errmsg));
304
305if (! exists)
306 {
307 unsigned long maj = 0, min = 0;
308 res = sqlite_exec_printf(sql,
309 "SELECT MAX(major) FROM packets "
310 "WHERE groupname = '%q'",
311 &read_value_cb<unsigned long>, &maj, &errmsg,
312 group.c_str());
313
314 if (res != SQLITE_OK)
315 throw runtime_error("sqlite returned error on MAX(major): " + err2msg(errmsg));
316
317 res = sqlite_exec_printf(sql,
318 "SELECT MAX(minor) FROM packets "
319 "WHERE groupname = '%q'",
320 &read_value_cb<unsigned long>, &min, &errmsg,
321 group.c_str());
322
323 if (res != SQLITE_OK)
324 throw runtime_error("sqlite returned error on MAX(minor): " + err2msg(errmsg));
325
326 res = sqlite_exec_printf(sql,
327 "INSERT INTO packets "
328 "VALUES (%lu, %lu, '%q', %lu, '%q')",
329 NULL, NULL, &errmsg,
330 maj, min+1, group.c_str(),
331 ad.sum(), content.c_str());
332
333 if (res != SQLITE_OK)
334 throw runtime_error("sqlite returned error on INSERT : " + err2msg(errmsg));
335 }
336 }
337 }
338
339 // step 6: end transaction
340 {
341 int res = sqlite_exec_printf(sql, "COMMIT", NULL, NULL, &errmsg);
342 if (res != SQLITE_OK)
343 throw runtime_error("sqlite returned error on COMMIT: " + err2msg(errmsg));
344 }
345
346 cout << "Status: 202 OK\n"
347 << "Content-type: text/plain\n"
348 << "\n"
349 << "packets accepted, thank you.\n";
350}
351
352
353
354
355// ----- GENERIC CODE FOR ALL QUERY TYPES -----
356
357
358void decode_query(map<string,string> & q)
359{
360 char *qs = NULL;
361 qs = getenv("QUERY_STRING");
362 if (qs == NULL)
363 throw runtime_error("no QUERY_STRING");
364
365 string query(qs);
366
367 // query string can only contain all alphanumerics, some URL "safe" chars
368 // ( '@', '-', '_', '.' ) and the normal query-string separators & and
369 // =. this is a restriction on our part, but I don't care much about URL
370 // encoding. sorry.
371
372 if (query.find_first_not_of("0123456789@-_."
373 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
374 "abcdefghijklmnopqrstuvwxyz"
375 "=&") != string::npos)
376 throw runtime_error("invalid chars in input");
377
378 typedef tokenizer<char_separator<char> > pairtok;
379 char_separator<char> sep("&");
380 pairtok pairs(query, sep);
381 for (pairtok::iterator pair = pairs.begin(); pair != pairs.end(); ++pair)
382 {
383 string p(*pair);
384 if (count(p.begin(), p.end(), '=') != 1)
385throw runtime_error("bad number of '=' symbols in query pair");
386
387 string::size_type eq = p.find('=');
388
389 if (eq == string::npos)
390throw runtime_error("missing '=' in query pair");
391
392 if (eq == 0)
393throw runtime_error("empty key for query pair");
394
395 if (eq >= (p.size() - 1))
396throw runtime_error("empty value for query pair");
397
398 q.insert(make_pair(p.substr(0, eq), p.substr(eq+1)));
399
400 }
401}
402
403template <typename T> T
404param(string const & key, map<string,string> params)
405{
406 map<string,string>::const_iterator k = params.find(key);
407 if (k == params.end())
408 throw runtime_error("cannot find query key " + key);
409 return lexical_cast<T>(k->second);
410}
411
412
413void read_and_insert_user(string const & in, sqlite *sql)
414{
415 // FIXME: yes yes, this grammar should be shared with packet.cc and also
416 // packet.cc should use spirit and also I should have had the foresight
417 // to know this would happen and also I'm a very bad person etc. etc.
418
419 using namespace boost::spirit;
420
421 string username;
422 string pubkey;
423 rule<> sp = chset<>("\t\r\n ");
424 rule<> head = str_p("[pubkey") >> (+sp)
425 >> (+chset<>("a-zA-Z0-9_.@-"))[assign(username)]
426 >> ch_p(']');
427 rule<> body = (+chset<>("a-zA-Z0-9+/=\t\r\n "))[assign(pubkey)];
428 rule<> end = str_p("[end]") >> (*sp);
429 rule<> packet = head >> body >> end;
430
431 if (parse (in.c_str(), packet).full)
432 {
433 char *errmsg = NULL;
434 int res = sqlite_exec_printf(sql,
435 "INSERT INTO users VALUES ('%q','%q') ",
436 NULL, NULL, &errmsg,
437 username.c_str(), pubkey.c_str());
438 if (res != SQLITE_OK)
439throw runtime_error ("error inserting pubkey: " + err2msg (errmsg));
440 }
441 else
442 throw runtime_error ("failed to parse pubkey packet");
443}
444
445void run_cmdline(vector<string> const & args)
446{
447 if (args.size() == 0)
448 throw runtime_error("no command-line args");
449
450 if (args[0] == "initdb")
451 {
452 cleanup_ptr<sqlite *, void>
453sql(sqlite_open("depot.db", 0755, NULL), &sqlite_close);
454
455 if (sql() == NULL)
456throw runtime_error("cannot open depot.db");
457
458 char *errmsg = NULL;
459 if (sqlite_exec(sql(), depot_schema_constant, NULL, NULL, &errmsg) != SQLITE_OK)
460throw runtime_error("database initialization failed: " + string(errmsg));
461 return;
462 }
463
464 else if (args[0] == "migratedb")
465 {
466 if (args.size() != 1)
467throw runtime_error("bad extra args to migratedb");
468
469 cleanup_ptr<sqlite *, void>
470sql(sqlite_open("depot.db", 0755, NULL), &sqlite_close);
471
472 if (sql() == NULL)
473throw runtime_error("cannot open depot.db");
474
475 migrate_depot_schema (sql());
476
477 return;
478 }
479
480 else if (args[0] == "dbversion")
481 {
482 if (args.size() != 1)
483throw runtime_error("bad extra args to dbversion");
484
485 cleanup_ptr<sqlite *, void>
486sql(sqlite_open("depot.db", 0755, NULL), &sqlite_close);
487
488 if (sql() == NULL)
489throw runtime_error("cannot open depot.db");
490
491 string id;
492 calculate_schema_id(sql(), id);
493 cerr << "database schema version: " << id << endl;
494 return;
495 }
496
497
498 else if (args[0] == "adduser")
499 {
500 if (args.size() != 1)
501throw runtime_error("wrong number of args to adduser, need just packet input");
502
503 cleanup_ptr<sqlite *, void>
504sql(sqlite_open("depot.db", 0755, NULL), &sqlite_close);
505 check_schema(sql());
506
507 if (sql() == NULL)
508throw runtime_error("cannot open depot.db");
509
510 string packet;
511 char c;
512 while (cin.get(c))
513packet += c;
514
515 read_and_insert_user (packet, sql());
516 return;
517 }
518
519 else if (args[0] == "deluser")
520 {
521 if (args.size() != 2)
522throw runtime_error("wrong number of args to deluser, need <userid>");
523
524 cleanup_ptr<sqlite *, void>
525sql(sqlite_open("depot.db", 0755, NULL), &sqlite_close);
526 check_schema(sql());
527
528 if (sql() == NULL)
529throw runtime_error("cannot open depot.db");
530
531 char *errmsg = NULL;
532 if (sqlite_exec_printf(sql(), "DELETE FROM users WHERE name = '%q'",
533 NULL, NULL, NULL,
534 args[1].c_str()) != SQLITE_OK)
535throw runtime_error("user deletion failed: " + string(errmsg));
536 return;
537 }
538
539 throw runtime_error("unrecognized command");
540}
541
542int main(int argc, char ** argv)
543{
544
545
546 if (argc > 1 && getenv("GATEWAY_INTERFACE") == NULL)
547 {
548 ++argv; --argc;
549 try
550{
551 run_cmdline(vector<string>(argv, argv+argc));
552}
553 catch (runtime_error & e)
554{
555 cerr << "runtime error: " << e.what() << endl;
556 exit(1);
557}
558 exit(0);
559 }
560
561 try
562 {
563
564 map<string,string> keys;
565 decode_query(keys);
566
567 string q = param<string>("q", keys);
568
569 if (q == "status")
570{
571 cleanup_ptr<sqlite *, void>
572 sql(sqlite_open("depot.db", 0755, NULL), &sqlite_close);
573 check_schema(sql());
574
575 if (sql() == NULL)
576 throw runtime_error("cannot open depot.db");
577 execute_status_query (sql());
578 exit(0);
579}
580
581 else if (q == "since")
582{
583 cleanup_ptr<sqlite *, void>
584 sql(sqlite_open("depot.db", 0755, NULL), &sqlite_close);
585 check_schema(sql());
586
587 if (sql() == NULL)
588 throw runtime_error("cannot open depot.db");
589
590 unsigned long maj = param<unsigned long>("maj", keys);
591 unsigned long min = param<unsigned long>("min", keys);
592 string group = param<string>("group", keys);
593 execute_since_query (maj, min, group, sql());
594 exit(0);
595}
596
597 else if (q == "post")
598{
599 cleanup_ptr<sqlite *, void>
600 sql(sqlite_open("depot.db", 0755, NULL), &sqlite_close);
601 check_schema(sql());
602
603 if (sql() == NULL)
604 throw runtime_error("cannot open depot.db");
605
606 string user = param<string>("user", keys);
607 string sig = param<string>("sig", keys);
608 string group = param<string>("group", keys);
609
610 unsigned long nbytes = 0;
611 {
612 char * clen = NULL;
613 clen = getenv("CONTENT_LENGTH");
614 if (clen == NULL)
615 throw runtime_error("null content length");
616 nbytes = lexical_cast<unsigned long>(string(clen));
617 }
618
619 execute_post_query (user, sig, group, nbytes, sql());
620 exit(0);
621}
622
623 }
624 catch (runtime_error & e)
625 {
626 cerr << e.what() << endl;
627 cout << "Status: 500 Error\n"
628 << "Content-type: text/plain\n"
629 << "\n"
630 << "depot error: " << e.what() << "\n";
631 exit(1);
632 }
633
634
635}

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status