1 #include <uuid/uuid.h>
2 #include <iostream>
3 #include <cassert>
4 #include <cstdlib>
5 #include <stdexcept>
6 #include <string>
7 #include <set>
8 #include <iterator>
9 using namespace std;
10 #include <kingate/exception.h>
11 #include <kingate/plaincgi.h>
12 #include <kingate/cgi_gateway.h>
13 #include <opkele/exception.h>
14 #include <opkele/types.h>
15 #include <opkele/util.h>
16 #include <opkele/uris.h>
17 #include <opkele/discovery.h>
18 #include <opkele/association.h>
19 #include <opkele/sreg.h>
20 using namespace opkele;
21 #include <opkele/prequeue_rp.h>
22 #include <opkele/debug.h>
23 
24 #include "sqlite.h"
25 #include "kingate_openid_message.h"
26 
27 #undef DUMB_RP
28 
29 #ifdef DUMB_RP
30 # define DUMBTHROW throw opkele::dumb_RP(OPKELE_CP_ "This RP is dumb")
31 #else
32 # define DUMBTHROW (void)0
33 #endif
34 
35 class rpdb_t : public sqlite3_t {
36     public:
rpdb_t()37 	rpdb_t()
38 	    : sqlite3_t("/tmp/RP.db") {
39 		assert(_D);
40 		char **resp; int nrow,ncol; char *errm;
41 		if(sqlite3_get_table(
42 			_D,"SELECT a_op FROM assoc LIMIT 0",
43 			&resp,&nrow,&ncol,&errm)!=SQLITE_OK) {
44 		    extern const char *__RP_db_bootstrap;
45 		    DOUT_("Bootstrapping DB");
46 		    if(sqlite3_exec(_D,__RP_db_bootstrap,NULL,NULL,&errm)!=SQLITE_OK)
47 			throw opkele::exception(OPKELE_CP_ string("Failed to bootstrap SQLite database: ")+errm);
48 		}else
49 		    sqlite3_free_table(resp);
50 
51 	    }
52 };
53 
54 class example_rp_t : public opkele::prequeue_RP {
55     public:
56 	mutable rpdb_t db;
57 	kingate::cookie htc;
58 	long as_id;
59 	int ordinal;
60 	kingate::cgi_gateway& gw;
61 
example_rp_t(kingate::cgi_gateway & g)62 	example_rp_t(kingate::cgi_gateway& g)
63 	: as_id(-1), ordinal(0), gw(g), have_eqtop(false)  {
64 	    try {
65 		htc = gw.cookies.get_cookie("ht_session");
66 		as_id = opkele::util::string_to_long(gw.get_param("asid"));
67 	    }catch(kingate::exception_notfound& kenf) {
68 		uuid_t uuid; uuid_generate(uuid);
69 		htc = kingate::cookie("ht_session",util::encode_base64(uuid,sizeof(uuid)));
70 		sqlite3_mem_t<char*> S = sqlite3_mprintf(
71 			"INSERT INTO ht_sessions (hts_id) VALUES (%Q)",
72 			htc.get_value().c_str());
73 		db.exec(S);
74 	    }
75 	}
76 
77 	/* Global persistent store */
78 
store_assoc(const string & OP,const string & handle,const string & type,const secret_t & secret,int expires_in)79 	opkele::assoc_t store_assoc(
80 		const string& OP,const string& handle,
81 		const string& type,const secret_t& secret,
82 		int expires_in) {
83 	    DUMBTHROW;
84 	    DOUT_("Storing '" << handle << "' assoc with '" << OP << "'");
85 	    time_t exp = time(0)+expires_in;
86 	    sqlite3_mem_t<char*>
87 		S = sqlite3_mprintf(
88 			"INSERT INTO assoc"
89 			" (a_op,a_handle,a_type,a_ctime,a_etime,a_secret)"
90 			" VALUES ("
91 			"  %Q,%Q,%Q,"
92 			"  datetime('now'), datetime('now','+%d seconds'),"
93 			"  %Q"
94 			" );", OP.c_str(), handle.c_str(), type.c_str(),
95 			expires_in,
96 			util::encode_base64(&(secret.front()),secret.size()).c_str() );
97 	    db.exec(S);
98 	    return opkele::assoc_t(new opkele::association(
99 			OP, handle, type, secret, exp, false ));
100 	}
101 
find_assoc(const string & OP)102 	opkele::assoc_t find_assoc(
103 		const string& OP) {
104 	    DUMBTHROW;
105 	    DOUT_("Looking for an assoc with '" << OP << '\'');
106 	    sqlite3_mem_t<char*>
107 		S = sqlite3_mprintf(
108 			"SELECT"
109 			"  a_op,a_handle,a_type,a_secret,"
110 			"  strftime('%%s',a_etime) AS a_etime"
111 			" FROM assoc"
112 			" WHERE a_op=%Q AND a_itime IS NULL AND NOT a_stateless"
113 			"  AND ( a_etime > datetime('now','-30 seconds') )"
114 			" LIMIT 1",
115 			OP.c_str());
116 	    sqlite3_table_t T;
117 	    int nr,nc;
118 	    db.get_table(S,T,&nr,&nc);
119 	    if(nr<1)
120 		throw opkele::failed_lookup(OPKELE_CP_ "Couldn't find unexpired handle");
121 	    assert(nr==1);
122 	    assert(nc==5);
123 	    secret_t secret;
124 	    util::decode_base64(T.get(1,3,nc),secret);
125 	    DOUT_(" found '" << T.get(1,1,nc) << '\'');
126 	    return opkele::assoc_t(new opkele::association(
127 			T.get(1,0,nc), T.get(1,1,nc), T.get(1,2,nc),
128 			secret, strtol(T.get(1,4,nc),0,0), false ));
129 	}
130 
retrieve_assoc(const string & OP,const string & handle)131 	opkele::assoc_t retrieve_assoc(
132 		const string& OP,const string& handle) {
133 	    DUMBTHROW;
134 	    DOUT_("Retrieving assoc '" << handle << "' with '" << OP << '\'');
135 	    sqlite3_mem_t<char*>
136 		S = sqlite3_mprintf(
137 			"SELECT"
138 			"  a_op,a_handle,a_type,a_secret,"
139 			"  strftime('%%s',a_etime) AS a_etime"
140 			" FROM assoc"
141 			" WHERE a_op=%Q AND a_handle=%Q"
142 			"  AND a_itime IS NULL AND NOT a_stateless"
143 			" LIMIT 1",
144 			OP.c_str(),handle.c_str());
145 	    sqlite3_table_t T;
146 	    int nr,nc;
147 	    db.get_table(S,T,&nr,&nc);
148 	    if(nr<1)
149 		throw opkele::failed_lookup(OPKELE_CP_ "couldn't retrieve valid association");
150 	    assert(nr==1); assert(nc==5);
151 	    secret_t secret; util::decode_base64(T.get(1,3,nc),secret);
152 	    DOUT_(" found. type=" << T.get(1,2,nc) << '\'');
153 	    return opkele::assoc_t(new opkele::association(
154 			T.get(1,0,nc), T.get(1,1,nc), T.get(1,2,nc),
155 			secret, strtol(T.get(1,4,nc),0,0), false ));
156 	}
157 
invalidate_assoc(const string & OP,const string & handle)158 	void invalidate_assoc(
159 		const string& OP,const string& handle) {
160 	    DUMBTHROW;
161 	    DOUT_("Invalidating assoc '" << handle << "' with '" << OP << '\'');
162 	    sqlite3_mem_t<char*>
163 		S = sqlite3_mprintf(
164 			"UPDATE assoc SET a_itime=datetime('now')"
165 			" WHERE a_op=%Q AND a_handle=%Q",
166 			OP.c_str(), handle.c_str() );
167 	    db.exec(S);
168 	}
169 
check_nonce(const string & OP,const string & nonce)170 	void check_nonce(const string& OP,const string& nonce) {
171 	    DOUT_("Checking nonce '" << nonce << "' from '" << OP << '\'');
172 	    sqlite3_mem_t<char*>
173 		S = sqlite3_mprintf(
174 			"SELECT 1 FROM nonces WHERE n_op=%Q AND n_once=%Q",
175 			OP.c_str(), nonce.c_str());
176 	    sqlite3_table_t T;
177 	    int nr,nc;
178 	    db.get_table(S,T,&nr,&nc);
179 	    if(nr)
180 		throw opkele::id_res_bad_nonce(OPKELE_CP_ "already seen that nonce");
181 	    sqlite3_mem_t<char*>
182 		SS = sqlite3_mprintf(
183 			"INSERT INTO nonces (n_op,n_once) VALUES (%Q,%Q)",
184 			OP.c_str(), nonce.c_str());
185 	    db.exec(SS);
186 	}
187 
188 	/* Session perisistent store */
189 
begin_queueing()190 	void begin_queueing() {
191 	    assert(as_id>=0);
192 	    DOUT_("Resetting queue for session '" << htc.get_value() << "'/" << as_id);
193 	    sqlite3_mem_t<char*> S = sqlite3_mprintf(
194 		    "DELETE FROM endpoints_queue"
195 		    " WHERE as_id=%ld",
196 		    as_id);
197 	    db.exec(S);
198 	}
199 
queue_endpoint(const opkele::openid_endpoint_t & ep)200 	void queue_endpoint(const opkele::openid_endpoint_t& ep) {
201 	    assert(as_id>=0);
202 	    DOUT_("Queueing endpoint " << ep.claimed_id << " : " << ep.local_id << " @ " << ep.uri);
203 	    sqlite3_mem_t<char*> S = sqlite3_mprintf(
204 		    "INSERT INTO endpoints_queue"
205 		    " (as_id,eq_ctime,eq_ordinal,eq_uri,eq_claimed_id,eq_local_id)"
206 		    " VALUES (%ld,strftime('%%s','now'),%d,%Q,%Q,%Q)",
207 		    as_id,ordinal++,
208 		    ep.uri.c_str(),ep.claimed_id.c_str(),ep.local_id.c_str());
209 	    db.exec(S);
210 	}
211 
212 	mutable openid_endpoint_t eqtop;
213 	mutable bool have_eqtop;
214 
get_endpoint() const215 	const openid_endpoint_t& get_endpoint() const {
216 	    assert(as_id>=0);
217 	    if(!have_eqtop) {
218 		sqlite3_mem_t<char*>
219 		    S = sqlite3_mprintf(
220 			    "SELECT"
221 			    "  eq_uri, eq_claimed_id, eq_local_id"
222 			    " FROM endpoints_queue"
223 			    "  JOIN auth_sessions USING(as_id)"
224 			    " WHERE hts_id=%Q AND as_id=%ld"
225 			    " ORDER BY eq_ctime,eq_ordinal"
226 			    " LIMIT 1",htc.get_value().c_str(),as_id);
227 		sqlite3_table_t T; int nr,nc;
228 		db.get_table(S,T,&nr,&nc);
229 		if(nr<1)
230 		    throw opkele::exception(OPKELE_CP_ "No more endpoints queued");
231 		assert(nr==1); assert(nc==3);
232 		eqtop.uri = T.get(1,0,nc);
233 		eqtop.claimed_id = T.get(1,1,nc);
234 		eqtop.local_id = T.get(1,2,nc);
235 		have_eqtop = true;
236 	    }
237 	    return eqtop;
238 	}
239 
next_endpoint()240 	void next_endpoint() {
241 	    assert(as_id>=0);
242 	    get_endpoint();
243 	    have_eqtop = false;
244 	    sqlite3_mem_t<char*> S = sqlite3_mprintf(
245 		    "DELETE FROM endpoints_queue"
246 		    " WHERE as_id=%ld AND eq_uri=%Q AND eq_local_id=%Q",
247 		    htc.get_value().c_str(),as_id,
248 		    eqtop.uri.c_str());
249 	    db.exec(S);
250 	}
251 
252 	mutable string _nid;
253 
set_normalized_id(const string & nid)254 	void set_normalized_id(const string& nid) {
255 	    assert(as_id>=0);
256 	    sqlite3_mem_t<char*> S = sqlite3_mprintf(
257 		    "UPDATE auth_sessions"
258 		    " SET as_normalized_id=%Q"
259 		    " WHERE hts_id=%Q and as_id=%ld",
260 		    nid.c_str(),
261 		    htc.get_value().c_str(),as_id);
262 	    db.exec(S);
263 	    _nid = nid;
264 	}
get_normalized_id() const265 	const string get_normalized_id() const {
266 	    assert(as_id>=0);
267 	    if(_nid.empty()) {
268 		sqlite3_mem_t<char*> S = sqlite3_mprintf(
269 			"SELECT as_normalized_id"
270 			" FROM"
271 			"  auth_sessions"
272 			" WHERE"
273 			"  hts_id=%Q AND as_id=%ld",
274 			htc.get_value().c_str(),as_id);
275 		sqlite3_table_t T; int nr,nc;
276 		db.get_table(S,T,&nr,&nc);
277 		assert(nr==1); assert(nc==1);
278 		_nid = T.get(1,0,nc);
279 	    }
280 	    return _nid;
281 	}
282 
get_this_url() const283 	const string get_this_url() const {
284 	    bool s = gw.has_meta("SSL_PROTOCOL_VERSION");
285 	    string rv = s?"https://":"http://";
286 	    rv += gw.http_request_header("Host");
287 	    const string& port = gw.get_meta("SERVER_PORT");
288 	    if( port!=(s?"443":"80") ) {
289 		rv += ':'; rv += port;
290 	    }
291 	    rv += gw.get_meta("REQUEST_URI");
292 	    return rv;
293 	}
294 
initiate(const string & usi)295 	void initiate(const string& usi) {
296 	    allocate_asid();
297 	    prequeue_RP::initiate(usi);
298 	}
299 
get_self_url() const300 	string get_self_url() const {
301 	    string rv = get_this_url();
302 	    string::size_type q = rv.find('?');
303 	    if(q!=string::npos)
304 		rv.erase(q);
305 	    return rv;
306 	}
307 
allocate_asid()308 	void allocate_asid() {
309 	    sqlite3_mem_t<char*> S = sqlite3_mprintf(
310 		    "INSERT INTO auth_sessions (hts_id)"
311 		    " VALUES (%Q)",
312 		    htc.get_value().c_str());
313 	    db.exec(S);
314 	    as_id = sqlite3_last_insert_rowid(db);
315 	    DOUT_("Allocated authentication session id "<<as_id);
316 	    assert(as_id>=0);
317 	}
318 
319 #ifdef DUMB_RP
associate(const string & OP)320 	virtual assoc_t associate(const string& OP) {
321 	    DUMBTHROW;
322 	}
323 #endif
324 };
325 
main(int,char **)326 int main(int,char **) {
327     try {
328 	kingate::plaincgi_interface ci;
329 	kingate::cgi_gateway gw(ci);
330 	string op;
331 	try { op = gw.get_param("op"); }catch(kingate::exception_notfound&) { }
332 	if(op=="initiate") {
333 	    example_rp_t rp(gw);
334 	    string usi = gw.get_param("openid_identity");
335 	    rp.initiate(usi);
336 	    opkele::sreg_t sreg(opkele::sreg_t::fields_NONE,opkele::sreg_t::fields_ALL);
337 	    opkele::openid_message_t cm;
338 	    string loc;
339 	    cout <<
340 		"Set-Cookie: " << rp.htc.set_cookie_header() << "\n"
341 		"Status: 302 Going to OP\n"
342 		"Location: " << (
343 			loc = rp.checkid_(cm,opkele::mode_checkid_setup,
344 			rp.get_self_url()+
345 			"?op=confirm&asid="+opkele::util::long_to_string(rp.as_id),
346 			rp.get_self_url(),&sreg).append_query(rp.get_endpoint().uri)
347 			)
348 		<< "\n\n";
349 	    DOUT_("Going to " << loc);
350 	}else if(op=="confirm") {
351 	    kingate_openid_message_t om(gw);
352 	    example_rp_t rp(gw);
353 	    opkele::sreg_t sreg(opkele::sreg_t::fields_NONE,opkele::sreg_t::fields_ALL);
354 	    rp.id_res(om,&sreg);
355 	    cout <<
356 		"Content-Type: text/plain\n\n";
357 	    for(opkele::basic_openid_message::fields_iterator i=om.fields_begin();
358 		    i!=om.fields_end();++i) {
359 		cout << *i << '=' << om.get_field(*i) << endl;
360 	    }
361 	    cout << endl
362 		<< "SREG fields: " << sreg.has_fields << endl;
363 	}else{
364 	    cout <<
365 		"Content-type: text/html\n\n"
366 
367 		"<html>"
368 		 "<head><title>test RP</title></head>"
369 		 "<body>"
370 		  "<form action='' method='post'>"
371 		   "<input type='hidden' name='op' value='initiate' />"
372 		   "<input type='text' name='openid_identity'/>"
373 		   "<input type='submit' name='submit' value='submit' />"
374 		  "</form>"
375 		  "<br/><br/>"
376 		  "<a href='?op=initiate&amp;openid_identity=www.myopenid.com&amp;dummy=" << time(0) << "'>login with myopenid.com account</a>"
377 		  "<br/>"
378 		 "</body"
379 		"</html>"
380 		;
381 	}
382 #ifdef OPKELE_HAVE_KONFORKA
383     }catch(konforka::exception& e) {
384 #else
385     }catch(std::exception& e){
386 #endif
387 	DOUT_("Oops: " << e.what());
388 	cout << "Content-Type: text/plain\n\n"
389 	    "Exception:\n"
390 	    " what: " << e.what() << endl;
391 #ifdef OPKELE_HAVE_KONFORKA
392 	cout << " where: " << e.where() << endl;
393 	if(!e._seen.empty()) {
394 	    cout << " seen:" << endl;
395 	    for(list<konforka::code_point>::const_iterator
396 		    i=e._seen.begin();i!=e._seen.end();++i) {
397 		cout << "  " << i->c_str() << endl;
398 	    }
399 	}
400 #endif
401     }
402 }
403