1 #include <errno.h>
2 #include <cassert>
3 #include <cctype>
4 #include <cstring>
5 #include <vector>
6 #include <string>
7 #include <stack>
8 #include <algorithm>
9 #include <openssl/bio.h>
10 #include <openssl/evp.h>
11 #include <openssl/sha.h>
12 #include <openssl/hmac.h>
13 #include <opkele/util.h>
14 #include <opkele/exception.h>
15 #include <opkele/data.h>
16 #include <opkele/debug.h>
17 
18 #include <config.h>
19 #ifdef HAVE_DEMANGLE
20 # include <cxxabi.h>
21 #endif
22 
23 namespace opkele {
24     using namespace std;
25 
26     namespace util {
27 
28 	/*
29 	 * base64
30 	 */
encode_base64(const void * data,size_t length)31 	string encode_base64(const void *data,size_t length) {
32 	    BIO *b64 = 0, *bmem = 0;
33 	    try {
34 		b64 = BIO_new(BIO_f_base64());
35 		if(!b64)
36 		    throw exception_openssl(OPKELE_CP_ "failed to BIO_new() base64 encoder");
37 		BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);
38 		bmem = BIO_new(BIO_s_mem());
39 		BIO_set_flags(b64,BIO_CLOSE);
40 		if(!bmem)
41 		    throw exception_openssl(OPKELE_CP_ "failed to BIO_new() memory buffer");
42 		BIO_push(b64,bmem);
43 		if(((size_t)BIO_write(b64,data,length))!=length)
44 		    throw exception_openssl(OPKELE_CP_ "failed to BIO_write()");
45 		if(BIO_flush(b64)!=1)
46 		    throw exception_openssl(OPKELE_CP_ "failed to BIO_flush()");
47 		char *rvd;
48 		long rvl = BIO_get_mem_data(bmem,&rvd);
49 		string rv(rvd,rvl);
50 		BIO_free_all(b64);
51 		return rv;
52 	    }catch(...) {
53 		if(b64) BIO_free_all(b64);
54 		throw;
55 	    }
56 	}
57 
decode_base64(const string & data,vector<unsigned char> & rv)58 	void decode_base64(const string& data,vector<unsigned char>& rv) {
59 	    BIO *b64 = 0, *bmem = 0;
60 	    rv.clear();
61 	    try {
62 		bmem = BIO_new_mem_buf((void*)data.data(),data.size());
63 		if(!bmem)
64 		    throw exception_openssl(OPKELE_CP_ "failed to BIO_new_mem_buf()");
65 		b64 = BIO_new(BIO_f_base64());
66 		if(!b64)
67 		    throw exception_openssl(OPKELE_CP_ "failed to BIO_new() base64 decoder");
68 		BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);
69 		BIO_push(b64,bmem);
70 		unsigned char tmp[512];
71 		size_t rb = 0;
72 		while((rb=BIO_read(b64,tmp,sizeof(tmp)))>0)
73 		    rv.insert(rv.end(),tmp,&tmp[rb]);
74 		BIO_free_all(b64);
75 	    }catch(...) {
76 		if(b64) BIO_free_all(b64);
77 		throw;
78 	    }
79 	}
80 
81 	/*
82 	 * big numerics
83 	 */
84 
base64_to_bignum(const string & b64)85 	BIGNUM *base64_to_bignum(const string& b64) {
86 	    vector<unsigned char> bin;
87 	    decode_base64(b64,bin);
88 	    BIGNUM *rv = BN_bin2bn(&(bin.front()),bin.size(),0);
89 	    if(!rv)
90 		throw failed_conversion(OPKELE_CP_ "failed to BN_bin2bn()");
91 	    return rv;
92 	}
93 
dec_to_bignum(const string & dec)94 	BIGNUM *dec_to_bignum(const string& dec) {
95 	    BIGNUM *rv = 0;
96 	    if(!BN_dec2bn(&rv,dec.c_str()))
97 		throw failed_conversion(OPKELE_CP_ "failed to BN_dec2bn()");
98 	    return rv;
99 	}
100 
bignum_to_base64(const BIGNUM * bn)101 	string bignum_to_base64(const BIGNUM *bn) {
102 	    vector<unsigned char> bin(BN_num_bytes(bn)+1);
103 	    unsigned char *binptr = &(bin.front())+1;
104 	    int l = BN_bn2bin(bn,binptr);
105 	    if(l && (*binptr)&0x80){
106 		(*(--binptr)) = 0; ++l;
107 	    }
108 	    return encode_base64(binptr,l);
109 	}
110 
111 	/*
112 	 * w3c times
113 	 */
114 
time_to_w3c(time_t t)115 	string time_to_w3c(time_t t) {
116 	    struct tm tm_t;
117 	    if(!gmtime_r(&t,&tm_t))
118 		throw failed_conversion(OPKELE_CP_ "failed to BN_dec2bn()");
119 	    char rv[25];
120 	    if(!strftime(rv,sizeof(rv)-1,"%Y-%m-%dT%H:%M:%SZ",&tm_t))
121 		throw failed_conversion(OPKELE_CP_ "failed to strftime()");
122 	    return rv;
123 	}
124 
125 #ifndef HAVE_TIMEGM
timegm(struct tm * t)126 	static time_t timegm(struct tm *t) {
127 	    char *tz = getenv("TZ");
128 	    setenv("TZ","",1); tzset();
129 	    time_t rv = mktime(t);
130 	    if(tz)
131 		setenv("TZ",tz,1);
132 	    else
133 		unsetenv("TZ");
134 	    tzset();
135 	    return rv;
136 	}
137 #	define timegm opkele::util::timegm
138 #endif /* HAVE_TIMEGM */
139 
w3c_to_time(const string & w)140 	time_t w3c_to_time(const string& w) {
141 	    int fraction;
142 	    struct tm tm_t;
143 	    memset(&tm_t,0,sizeof(tm_t));
144 	    if( (
145 			sscanf(
146 			    w.c_str(),
147 			    "%04d-%02d-%02dT%02d:%02d:%02dZ",
148 			    &tm_t.tm_year,&tm_t.tm_mon,&tm_t.tm_mday,
149 			    &tm_t.tm_hour,&tm_t.tm_min,&tm_t.tm_sec
150 			    ) != 6
151 		) && (
152 		    sscanf(
153 			w.c_str(),
154 			"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
155 			&tm_t.tm_year,&tm_t.tm_mon,&tm_t.tm_mday,
156 			&tm_t.tm_hour,&tm_t.tm_min,&tm_t.tm_sec,
157 			&fraction
158 			) != 7
159 		    ) )
160 		throw failed_conversion(OPKELE_CP_ "failed to sscanf()");
161 	    tm_t.tm_mon--;
162 	    tm_t.tm_year-=1900;
163 	    time_t rv = timegm(&tm_t);
164 	    if(rv==(time_t)-1)
165 		throw failed_conversion(OPKELE_CP_ "failed to gmtime()");
166 	    return rv;
167 	}
168 
169 	/*
170 	 *
171 	 */
172 
isrfc3986unreserved(int c)173 	static inline bool isrfc3986unreserved(int c) {
174 	    if(c<'-') return false;
175 	    if(c<='.') return true;
176 	    if(c<'0') return false; if(c<='9') return true;
177 	    if(c<'A') return false; if(c<='Z') return true;
178 	    if(c<'_') return false;
179 	    if(c=='_') return true;
180 	    if(c<'a') return false; if(c<='z') return true;
181 	    if(c=='~') return true;
182 	    return false;
183 	}
184 
185 	struct __url_encoder : public unary_function<char,void> {
186 	    public:
187 		string& rv;
188 
__url_encoderopkele::util::__url_encoder189 		__url_encoder(string& r) : rv(r) { }
190 
operator ()opkele::util::__url_encoder191 		result_type operator()(argument_type c) {
192 		    if(isrfc3986unreserved(c))
193 			rv += c;
194 		    else{
195 			char tmp[4];
196 			snprintf(tmp,sizeof(tmp),"%%%02X",
197 				(c&0xff));
198 			rv += tmp;
199 		    }
200 		}
201 	};
202 
url_encode(const string & str)203 	string url_encode(const string& str) {
204 	    string rv;
205 	    for_each(str.begin(),str.end(),
206 		    __url_encoder(rv));
207 	    return rv;
208 	}
209 
url_decode(const string & str)210 	string url_decode(const string& str) {
211 	    string rv;
212 	    back_insert_iterator<string> ii(rv);
213 	    char tmp[3]; tmp[2] = 0;
214 	    for(string::const_iterator i=str.begin(),ie=str.end();
215 		    i!=ie;++i) {
216 		switch(*i) {
217 		    case '+':
218 			*(ii++) = ' '; break;
219 		    case '%':
220 			if((++i)==ie)
221 			    throw failed_conversion(OPKELE_CP_ "trailing percent in the url-encoded string");
222 			tmp[0] = *i;
223 			if((++i)==ie)
224 			    throw failed_conversion(OPKELE_CP_ "not enough hexadecimals after the percent sign in url-encoded string");
225 			tmp[1] = *i;
226 			if(!(isxdigit(tmp[0]) && isxdigit(tmp[1])))
227 			    throw failed_conversion(OPKELE_CP_ "non-hex follows percent in url-encoded string");
228 			*(ii++) = (char)strtol(tmp,0,16);
229 			break;
230 		    default:
231 			*(ii++) = *i; break;
232 		}
233 	    }
234 	    return rv;
235 	}
236 
attr_escape(const string & str)237 	string attr_escape(const string& str) {
238 	    static const char *unsafechars = "<>&\n\"'";
239 	    string rv;
240 	    string::size_type p=0;
241 	    while(true) {
242 		string::size_type us = str.find_first_of(unsafechars,p);
243 		if(us==string::npos) {
244 		    if(p!=str.length())
245 			rv.append(str,p,str.length()-p);
246 		    return rv;
247 		}
248 		rv.append(str,p,us-p);
249 		rv += "&#";
250 		rv += long_to_string((long)str[us]);
251 		rv += ';';
252 		p = us+1;
253 	    }
254 	}
255 
long_to_string(long l)256 	string long_to_string(long l) {
257 	    char rv[32];
258 	    int r=snprintf(rv,sizeof(rv),"%ld",l);
259 	    if(r<0 || r>=(int)sizeof(rv))
260 		throw failed_conversion(OPKELE_CP_ "failed to snprintf()");
261 	    return rv;
262 	}
263 
string_to_long(const string & s)264 	long string_to_long(const string& s) {
265 	    char *endptr = 0;
266 	    long rv = strtol(s.c_str(),&endptr,10);
267 	    if((!endptr) || endptr==s.c_str())
268 		throw failed_conversion(OPKELE_CP_ "failed to strtol()");
269 	    return rv;
270 	}
271 
272 	/*
273 	 * Normalize URL according to the rules, described in rfc 3986, section 6
274 	 *
275 	 * - uppercase hex triplets (e.g. %ab -> %AB)
276 	 * - lowercase scheme and host
277 	 * - decode %-encoded characters, specified as unreserved in rfc 3986, section 2.3,
278 	 *   that is - [:alpha:][:digit:]._~-
279 	 * - remove dot segments
280 	 * - remove empty and default ports
281 	 * - if there's no path component, add '/'
282 	 */
rfc_3986_normalize_uri(const string & uri)283 	 string rfc_3986_normalize_uri(const string& uri) {
284 	     string rv;
285 	     string::size_type ns = uri.find_first_not_of(data::_whitespace_chars);
286 	     if(ns==string::npos)
287 		 throw bad_input(OPKELE_CP_ "Can't normalize empty URI");
288 	     string::size_type colon = uri.find(':',ns);
289 	     if(colon==string::npos)
290 		 throw bad_input(OPKELE_CP_ "No scheme specified in URI");
291 	     transform(
292 		     uri.begin()+ns, uri.begin()+colon+1,
293 		     back_inserter(rv), ::tolower );
294 	     bool s;
295 	     string::size_type ul = uri.find_last_not_of(data::_whitespace_chars)+1;
296 	     if(ul <= (colon+3))
297 		 throw bad_input(OPKELE_CP_ "Unexpected end of URI being normalized encountered");
298 	     if(uri[colon+1]!='/' || uri[colon+2]!='/')
299 		 throw bad_input(OPKELE_CP_ "Unexpected input in URI being normalized after scheme component");
300 	     if(rv=="http:")
301 		 s = false;
302 	     else if(rv=="https:")
303 		 s = true;
304 	     else{
305 		 /* TODO: support more schemes.  e.g. xri. How do we normalize
306 		  * xri?
307 		  */
308 		 rv.append(uri,colon+1,ul-colon-1);
309 		 return rv;
310 	     }
311 	     rv += "//";
312 	     string::size_type interesting = uri.find_first_of(":/#?",colon+3);
313 	     if(interesting==string::npos) {
314 		 transform(
315 			 uri.begin()+colon+3,uri.begin()+ul,
316 			 back_inserter(rv), ::tolower );
317 		 rv += '/'; return rv;
318 	     }
319 	     transform(
320 		     uri.begin()+colon+3,uri.begin()+interesting,
321 		     back_inserter(rv), ::tolower );
322 	     bool qf = false;
323 	     char ic = uri[interesting];
324 	     if(ic==':') {
325 		 string::size_type ni = uri.find_first_of("/#?%",interesting+1);
326 		 const char *nptr = uri.data()+interesting+1;
327 		 char *eptr = 0;
328 		 long port = strtol(nptr,&eptr,10);
329 		 if( (port>0) && (port<65535) && port!=(s?443:80) ) {
330 		     char tmp[8];
331 		     snprintf(tmp,sizeof(tmp),":%ld",port);
332 		     rv += tmp;
333 		 }
334 		 if(ni==string::npos) {
335 		     rv += '/'; return rv;
336 		 }
337 		 interesting = ni;
338 	     }else if(ic!='/') {
339 		 rv += '/'; rv += ic;
340 		 qf = true;
341 		 ++interesting;
342 	     }
343 	     string::size_type n = interesting;
344 	     char tmp[3] = { 0,0,0 };
345 	     stack<string::size_type> psegs; psegs.push(rv.length());
346 	     string pseg;
347 	     for(;n<ul;) {
348 		 string::size_type unsafe = uri.find_first_of(qf?"%":"%/?#",n);
349 		 if(unsafe==string::npos) {
350 		     pseg.append(uri,n,ul-n-1); n = ul-1;
351 		 }else{
352 		     pseg.append(uri,n,unsafe-n);
353 		     n = unsafe;
354 		 }
355 		 char c = uri[n++];
356 		 if(c=='%') {
357 		     if((n+1)>=ul)
358 			 throw bad_input(OPKELE_CP_ "Unexpected end of URI encountered while parsing percent-encoded character");
359 		     tmp[0] = uri[n++];
360 		     tmp[1] = uri[n++];
361 		     if(!( isxdigit(tmp[0]) && isxdigit(tmp[1]) ))
362 			 throw bad_input(OPKELE_CP_ "Invalid percent-encoded character in URI being normalized");
363 		     int cc = strtol(tmp,0,16);
364 		     if( isalpha(cc) || isdigit(cc) || strchr("._~-",cc) )
365 			 pseg += (char)cc;
366 		     else{
367 			 pseg += '%';
368 			 pseg += (char)toupper(tmp[0]); pseg += (char)toupper(tmp[1]);
369 		     }
370 		 }else if(qf) {
371 		     rv += pseg; rv += c;
372 		     pseg.clear();
373 		 }else if(n>=ul || strchr("?/#",c)) {
374 		     if( (unsafe!=string::npos && pseg.empty()) || pseg==".") {
375 		     }else if(pseg=="..") {
376 			 if(psegs.size()>1) {
377 			     rv.resize(psegs.top()); psegs.pop();
378 			 }
379 		     }else{
380 			 psegs.push(rv.length());
381 			 if(c!='/') {
382 			     pseg += c;
383 			     qf = true;
384 			 }
385 			 rv += '/'; rv += pseg;
386 		     }
387 		     if(c=='/' && (n>=ul || strchr("?#",uri[n])) ) {
388 			 rv += '/';
389 			 if(n<ul)
390 			     qf = true;
391 		     }else if(strchr("?#",c)) {
392 			 if(psegs.size()==1 && psegs.top()==rv.length())
393 			     rv += '/';
394 			 if(pseg.empty())
395 			     rv += c;
396 			 qf = true;
397 		     }
398 		     pseg.clear();
399 		 }else{
400 		     pseg += c;
401 		 }
402 	     }
403 	     if(!pseg.empty()) {
404 		 if(!qf) rv += '/';
405 		 rv += pseg;
406 	     }
407 	     return rv;
408 	 }
409 
strip_uri_fragment_part(string & u)410 	string& strip_uri_fragment_part(string& u) {
411 	    string::size_type q = u.find('?'), f = u.find('#');
412 	    if(q==string::npos) {
413 		if(f!=string::npos)
414 		    u.erase(f);
415 	    }else{
416 		if(f!=string::npos) {
417 		    if(f<q)
418 			u.erase(f,q-f);
419 		    else
420 			u.erase(f);
421 		}
422 	    }
423 	    return u;
424 	}
425 
uri_matches_realm(const string & uri,const string & realm)426 	bool uri_matches_realm(const string& uri,const string& realm) {
427 	    string nrealm = opkele::util::rfc_3986_normalize_uri(realm);
428 	    string nu = opkele::util::rfc_3986_normalize_uri(uri);
429 	    string::size_type pr = nrealm.find("://");
430 	    string::size_type pu = nu.find("://");
431 	    assert(!(pr==string::npos || pu==string::npos));
432 	    pr += sizeof("://")-1;
433 	    pu += sizeof("://")-1;
434 	    if(!strncmp(nrealm.c_str()+pr,"*.",2)) {
435 		pr = nrealm.find('.',pr);
436 		pu = nu.find('.',pu);
437 		assert(pr!=string::npos);
438 		if(pu==string::npos)
439 		    return false;
440 		// TODO: check for overgeneralized realm
441 	    }
442 	    string::size_type lr = nrealm.length();
443 	    string::size_type lu = nu.length();
444 	    if( (lu-pu) < (lr-pr) )
445 		return false;
446 	    pair<const char*,const char*> mp = mismatch(
447 		    nrealm.c_str()+pr,nrealm.c_str()+lr,
448 		    nu.c_str()+pu);
449 	    if( (*(mp.first-1))!='/'
450 		    && !strchr("/?#",*mp.second) )
451 		return false;
452 	    return true;
453 	}
454 
abi_demangle(const char * mn)455 	string abi_demangle(const char *mn) {
456 #ifndef HAVE_DEMANGLE
457 	    return mn;
458 #else /* !HAVE_DEMANGLE */
459 	    int dstat;
460 	    char *demangled = abi::__cxa_demangle(mn,0,0,&dstat);
461 	    if(dstat)
462 		return mn;
463 	    string rv = demangled;
464 	    free(demangled);
465 	    return rv;
466 #endif /* !HAVE_DEMANGLE */
467 	}
468 
base64_signature(const assoc_t & assoc,const basic_openid_message & om)469 	string base64_signature(const assoc_t& assoc,const basic_openid_message& om) {
470 	    const string& slist = om.get_field("signed");
471 	    string kv;
472 	    string::size_type p=0;
473 	    while(true) {
474 		string::size_type co = slist.find(',',p);
475 		string f = (co==string::npos)
476 		    ?slist.substr(p):slist.substr(p,co-p);
477 		kv += f;
478 		kv += ':';
479 		kv += om.get_field(f);
480 		kv += '\n';
481 		if(co==string::npos) break;
482 		p = co+1;
483 	    }
484 	    const secret_t& secret = assoc->secret();
485 	    const EVP_MD *evpmd;
486 	    const string& at = assoc->assoc_type();
487 	    if(at=="HMAC-SHA256")
488 		evpmd = EVP_sha256();
489 	    else if(at=="HMAC-SHA1")
490 		evpmd = EVP_sha1();
491 	    else
492 		throw unsupported(OPKELE_CP_ "unknown association type");
493 	    unsigned int md_len = 0;
494 	    unsigned char md[SHA256_DIGEST_LENGTH];
495 	    HMAC(evpmd,
496 		    &(secret.front()),secret.size(),
497 		    (const unsigned char*)kv.data(),kv.length(),
498 		    md,&md_len);
499 	    return encode_base64(md,md_len);
500 	}
501 
normalize_identifier(const string & usi,bool strip_fragment)502 	string normalize_identifier(const string& usi,bool strip_fragment) {
503 	    if(usi.empty())
504 		return usi;
505 	    string rv;
506 	    string::size_type fsc = usi.find_first_not_of(data::_whitespace_chars);
507 	    if(fsc==string::npos)
508 		return rv;
509 	    string::size_type lsc = usi.find_last_not_of(data::_whitespace_chars);
510 	    assert(lsc!=string::npos);
511 	    if(!strncasecmp(usi.c_str()+fsc,"xri://",sizeof("xri://")-1))
512 		fsc += sizeof("xri://")-1;
513 	    if( (fsc+1) >= lsc )
514 		return rv;
515 	    rv.assign(usi,fsc,lsc-fsc+1);
516 	    if(strchr(data::_iname_leaders,rv[0])) {
517 		/* TODO: further normalize xri identity, fold case or
518 		 * whatever... */
519 	    }else{
520 		if(rv.find("://")==string::npos)
521 		    rv.insert(0,"http://");
522 		if(strip_fragment) {
523 		    string::size_type fp = rv.find('#');
524 		    if(fp!=string::npos) {
525 			string::size_type qp = rv.find('?');
526 			if(qp==string::npos || qp<fp)
527 			    rv.erase(fp);
528 			else if(qp>fp)
529 			    rv.erase(fp,qp-fp);
530 		    }
531 		}
532 		rv = rfc_3986_normalize_uri(rv);
533 	    }
534 	    return rv;
535 	}
536 
537     }
538 
539 }
540