summaryrefslogtreecommitdiffabout
authorMichael Krelin <hacker@klever.net>2008-02-04 22:39:59 (UTC)
committer Michael Krelin <hacker@klever.net>2008-02-04 22:39:59 (UTC)
commit9163a26ec8839a31df888920418280a62ebc5595 (patch) (unidiff)
tree55339b4ecf0a3f24817eb5cc1b0b24f831ac895b
parentc0eeee1cfd41d0f5f6ff6ac3d6fe021421376a69 (diff)
downloadlibopkele-9163a26ec8839a31df888920418280a62ebc5595.zip
libopkele-9163a26ec8839a31df888920418280a62ebc5595.tar.gz
libopkele-9163a26ec8839a31df888920418280a62ebc5595.tar.bz2
reworked extensions framework
* changed {checkid,id_res}_hook to {rp,op}_{checkid,id_res}_hook * deprecated older hooks, although implemented it in sreg and chain extensions * added extension processing to basic_op * added sreg to test OP Signed-off-by: Michael Krelin <hacker@klever.net>
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--include/opkele/basic_op.h3
-rw-r--r--include/opkele/extension.h49
-rw-r--r--include/opkele/extension_chain.h6
-rw-r--r--include/opkele/sreg.h31
-rw-r--r--lib/basic_op.cc5
-rw-r--r--lib/basic_rp.cc4
-rw-r--r--lib/extension.cc25
-rw-r--r--lib/extension_chain.cc27
-rw-r--r--lib/sreg.cc28
-rw-r--r--test/OP.cc10
10 files changed, 121 insertions, 67 deletions
diff --git a/include/opkele/basic_op.h b/include/opkele/basic_op.h
index 7f4e481..5bba1bf 100644
--- a/include/opkele/basic_op.h
+++ b/include/opkele/basic_op.h
@@ -1,68 +1,69 @@
1#ifndef __OPKELE_BASIC_OP_H 1#ifndef __OPKELE_BASIC_OP_H
2#define __OPKELE_BASIC_OP_H 2#define __OPKELE_BASIC_OP_H
3 3
4#include <string> 4#include <string>
5#include <opkele/types.h> 5#include <opkele/types.h>
6#include <opkele/extension.h> 6#include <opkele/extension.h>
7 7
8namespace opkele { 8namespace opkele {
9 using std::string; 9 using std::string;
10 10
11 class basic_op { 11 class basic_op {
12 public: 12 public:
13 mode_t mode; 13 mode_t mode;
14 assoc_t assoc; 14 assoc_t assoc;
15 bool openid2; 15 bool openid2;
16 string return_to; 16 string return_to;
17 string realm; 17 string realm;
18 string claimed_id; 18 string claimed_id;
19 string identity; 19 string identity;
20 string invalidate_handle; 20 string invalidate_handle;
21 21
22 void reset_vars(); 22 void reset_vars();
23 23
24 bool has_return_to() const; 24 bool has_return_to() const;
25 const string& get_return_to() const; 25 const string& get_return_to() const;
26 26
27 const string& get_realm() const; 27 const string& get_realm() const;
28 28
29 bool has_identity() const; 29 bool has_identity() const;
30 const string& get_claimed_id() const; 30 const string& get_claimed_id() const;
31 const string& get_identity() const; 31 const string& get_identity() const;
32 32
33 bool is_id_select() const; 33 bool is_id_select() const;
34 34
35 void select_identity(const string& c,const string& i); 35 void select_identity(const string& c,const string& i);
36 void set_claimed_id(const string& c); 36 void set_claimed_id(const string& c);
37 37
38 basic_openid_message& associate( 38 basic_openid_message& associate(
39 basic_openid_message& oum, 39 basic_openid_message& oum,
40 const basic_openid_message& inm); 40 const basic_openid_message& inm);
41 41
42 void checkid_(const basic_openid_message& inm,extension_t *ext=0); 42 void checkid_(const basic_openid_message& inm,extension_t *ext=0);
43 basic_openid_message& id_res(basic_openid_message& om); 43 basic_openid_message& id_res(basic_openid_message& om,
44 extension_t *ext=0);
44 basic_openid_message& cancel(basic_openid_message& om); 45 basic_openid_message& cancel(basic_openid_message& om);
45 basic_openid_message& error(basic_openid_message& om, 46 basic_openid_message& error(basic_openid_message& om,
46 const string& error,const string& contact, 47 const string& error,const string& contact,
47 const string& reference ); 48 const string& reference );
48 basic_openid_message& setup_needed( 49 basic_openid_message& setup_needed(
49 basic_openid_message& oum,const basic_openid_message& inm); 50 basic_openid_message& oum,const basic_openid_message& inm);
50 51
51 basic_openid_message& check_authentication( 52 basic_openid_message& check_authentication(
52 basic_openid_message& oum,const basic_openid_message& inm); 53 basic_openid_message& oum,const basic_openid_message& inm);
53 54
54 virtual void verify_return_to(); 55 virtual void verify_return_to();
55 56
56 virtual assoc_t alloc_assoc(const string& t,size_t kl,bool sl) = 0; 57 virtual assoc_t alloc_assoc(const string& t,size_t kl,bool sl) = 0;
57 virtual assoc_t retrieve_assoc(const string& h) = 0; 58 virtual assoc_t retrieve_assoc(const string& h) = 0;
58 59
59 virtual string& alloc_nonce(string& nonce,bool sl) = 0; 60 virtual string& alloc_nonce(string& nonce,bool sl) = 0;
60 virtual bool check_nonce(const string& nonce) = 0; 61 virtual bool check_nonce(const string& nonce) = 0;
61 virtual void invalidate_nonce(const string& nonce) = 0; 62 virtual void invalidate_nonce(const string& nonce) = 0;
62 63
63 virtual const string get_op_endpoint() const = 0; 64 virtual const string get_op_endpoint() const = 0;
64 65
65 }; 66 };
66} 67}
67 68
68#endif /* __OPKELE_BASIC_OP_H */ 69#endif /* __OPKELE_BASIC_OP_H */
diff --git a/include/opkele/extension.h b/include/opkele/extension.h
index 3ee25ee..37bcb90 100644
--- a/include/opkele/extension.h
+++ b/include/opkele/extension.h
@@ -1,66 +1,61 @@
1#ifndef __OPKELE_EXTENSION_H 1#ifndef __OPKELE_EXTENSION_H
2#define __OPKELE_EXTENSION_H 2#define __OPKELE_EXTENSION_H
3 3
4/** 4/**
5 * @file 5 * @file
6 * @brief extensions framework basics 6 * @brief extensions framework basics
7 */ 7 */
8 8
9#include <opkele/opkele-config.h>
9#include <opkele/types.h> 10#include <opkele/types.h>
10 11
11namespace opkele { 12namespace opkele {
12 13
13 /** 14 /**
14 * OpenID extension hooks base class 15 * OpenID extension hooks base class
15 */ 16 */
16 class extension_t { 17 class extension_t {
17 public: 18 public:
18 19
19 virtual ~extension_t() { } 20 virtual ~extension_t() { }
20 21
21 /** 22 /**
22 * hook called by consumer before submitting data to OpenID server. 23 * hook called by RP before submitting the message to OP.
23 * It is supposed to manipulate parameters list. 24 * @param om openid message to be submit
24 * @param p parameters about to be submitted to server
25 * @param identity identity being verified. It may differ from the
26 * one available in parameters list in case of delegation
27 * @see consumer_t::checkid_
28 * @see consumer_t::checkid_immediate
29 * @see consumer_t::checkid_setup
30 */ 25 */
31 virtual void checkid_hook(basic_openid_message& om); 26 virtual void rp_checkid_hook(basic_openid_message& om);
27
32 /** 28 /**
33 * hook called by consumer after identity information received from 29 * hook called by RP after verifying information received from OP.
34 * OpenID server is verified. 30 * @param om openid message received
35 * @param p parameters received from server 31 * @param sp signed part of the message
36 * @param sp signed parameters received from server with 'openid.'
37 * leader stripped
38 * @param identity identity confirmed. May differ from the one
39 * available in parameters list in case of delegation. May also be
40 * empty which means - extract one from parameters
41 * @see consumer_t::id_res
42 */ 32 */
43 virtual void id_res_hook(const basic_openid_message& om,const basic_openid_message& sp); 33 virtual void rp_id_res_hook(const basic_openid_message& om,
34 const basic_openid_message& sp);
44 35
45 /** 36 /**
46 * hook called by server before returning information to consumer. 37 * hook called by OP after parsing incoming message
47 * The hook may manipulate output parameters. It is important to 38 * @param inm message received from RP
48 * note that modified pout["signed"] is used for signing response. 39 */
49 * @param pin request parameters list with "openid." prefix 40 virtual void op_checkid_hook(const basic_openid_message& inm);
50 * @param pout response parameters list without "openid." prefix 41 /**
51 * @see server_t::checkid_ 42 * hook called by OP before signing the reply to RP
52 * @see server_t::checkid_immediate 43 * @param oum message to be sent to RP
53 * @see server_t::checkid_setup
54 */ 44 */
45 virtual void op_id_res_hook(basic_openid_message& oum);
46
47 virtual void checkid_hook(basic_openid_message& om) OPKELE_DEPRECATE;
48 virtual void id_res_hook(const basic_openid_message& om,
49 const basic_openid_message& sp) OPKELE_DEPRECATE;
55 virtual void checkid_hook(const basic_openid_message& inm,basic_openid_message& oum); 50 virtual void checkid_hook(const basic_openid_message& inm,basic_openid_message& oum);
56 51
57 /** 52 /**
58 * Casts the object to pointer to itself. For convenient passing 53 * Casts the object to pointer to itself. For convenient passing
59 * of pointer. 54 * of pointer.
60 */ 55 */
61 operator extension_t*(void) { return this; } 56 operator extension_t*(void) { return this; }
62 }; 57 };
63 58
64} 59}
65 60
66#endif /* __OPKELE_EXTENSION_H */ 61#endif /* __OPKELE_EXTENSION_H */
diff --git a/include/opkele/extension_chain.h b/include/opkele/extension_chain.h
index fb9bc84..9692934 100644
--- a/include/opkele/extension_chain.h
+++ b/include/opkele/extension_chain.h
@@ -1,38 +1,44 @@
1#ifndef __OPKELE_EXTENSION_CHAIN_H 1#ifndef __OPKELE_EXTENSION_CHAIN_H
2#define __OPKELE_EXTENSION_CHAIN_H 2#define __OPKELE_EXTENSION_CHAIN_H
3 3
4/** 4/**
5 * @file 5 * @file
6 * @brief extension chain extension 6 * @brief extension chain extension
7 */ 7 */
8 8
9#include <list> 9#include <list>
10#include <opkele/extension.h> 10#include <opkele/extension.h>
11 11
12namespace opkele { 12namespace opkele {
13 using std::list; 13 using std::list;
14 14
15 /** 15 /**
16 * OpenID extensions chain used to combine extensions, it is actually an 16 * OpenID extensions chain used to combine extensions, it is actually an
17 * stl list of pointers to extensions. 17 * stl list of pointers to extensions.
18 */ 18 */
19 class extension_chain_t : public extension_t, public list<extension_t*> { 19 class extension_chain_t : public extension_t, public list<extension_t*> {
20 public: 20 public:
21 21
22 /** 22 /**
23 * Default constructor creates an empty chain 23 * Default constructor creates an empty chain
24 */ 24 */
25 extension_chain_t() { } 25 extension_chain_t() { }
26 /** 26 /**
27 * Create extension chain with a single extension in it 27 * Create extension chain with a single extension in it
28 */ 28 */
29 extension_chain_t(extension_t *e) { push_back(e); } 29 extension_chain_t(extension_t *e) { push_back(e); }
30 30
31 virtual void rp_checkid_hook(basic_openid_message& om);
32 virtual void rp_id_res_hook(const basic_openid_message& om,
33 const basic_openid_message& sp);
34 virtual void op_checkid_hook(const basic_openid_message& inm);
35 virtual void op_id_res_hook(basic_openid_message& oum);
36
31 virtual void checkid_hook(basic_openid_message& om); 37 virtual void checkid_hook(basic_openid_message& om);
32 virtual void id_res_hook(const basic_openid_message& om,const basic_openid_message& sp); 38 virtual void id_res_hook(const basic_openid_message& om,const basic_openid_message& sp);
33 virtual void checkid_hook(const basic_openid_message& inm,basic_openid_message& oum); 39 virtual void checkid_hook(const basic_openid_message& inm,basic_openid_message& oum);
34 }; 40 };
35 41
36} 42}
37 43
38#endif /* __OPKELE_EXTENSION_CHAIN_H */ 44#endif /* __OPKELE_EXTENSION_CHAIN_H */
diff --git a/include/opkele/sreg.h b/include/opkele/sreg.h
index 24cb315..513e221 100644
--- a/include/opkele/sreg.h
+++ b/include/opkele/sreg.h
@@ -1,203 +1,204 @@
1#ifndef __OPKELE_SREG_H 1#ifndef __OPKELE_SREG_H
2#define __OPKELE_SREG_H 2#define __OPKELE_SREG_H
3 3
4/** 4/**
5 * @file 5 * @file
6 * @brief Simple registration extension 6 * @brief Simple registration extension
7 */ 7 */
8 8
9#include <opkele/extension.h> 9#include <opkele/extension.h>
10 10
11namespace opkele { 11namespace opkele {
12 using std::map; 12 using std::map;
13 13
14 /** 14 /**
15 * OpenID simple registration extension implementation 15 * OpenID simple registration extension implementation
16 * http://openid.net/specs/openid-simple-registration-extension-1_0.html 16 * http://openid.net/specs/openid-simple-registration-extension-1_0.html
17 */ 17 */
18 class sreg_t : public extension_t { 18 class sreg_t : public extension_t {
19 public: 19 public:
20 /** 20 /**
21 * sreg fields enumeration 21 * sreg fields enumeration
22 */ 22 */
23 enum fieldbit_t { 23 enum fieldbit_t {
24 /** 24 /**
25 * Any UTF-8 string that the End User wants to use as a nickname. 25 * Any UTF-8 string that the End User wants to use as a nickname.
26 */ 26 */
27 field_nickname = 1, 27 field_nickname = 1,
28 /** 28 /**
29 * The email address of the End User as specified in section 3.4.1 of [RFC2822] 29 * The email address of the End User as specified in section 3.4.1 of [RFC2822]
30 */ 30 */
31 field_email = 2, 31 field_email = 2,
32 /** 32 /**
33 * UTF-8 string free text representation of the End User's full name. 33 * UTF-8 string free text representation of the End User's full name.
34 */ 34 */
35 field_fullname = 4, 35 field_fullname = 4,
36 /** 36 /**
37 * The End User's date of birth as YYYY-MM-DD. Any values whose 37 * The End User's date of birth as YYYY-MM-DD. Any values whose
38 * representation uses fewer than the specified number of 38 * representation uses fewer than the specified number of
39 * digits should be zero-padded. The length of this value MUST 39 * digits should be zero-padded. The length of this value MUST
40 * always be 10. If the End User user does not want to reveal 40 * always be 10. If the End User user does not want to reveal
41 * any particular component of this value, it MUST be set to 41 * any particular component of this value, it MUST be set to
42 * zero. 42 * zero.
43 * 43 *
44 * For instance, if a End User wants to specify that his date 44 * For instance, if a End User wants to specify that his date
45 * of birth is in 1980, but not the month or day, the value 45 * of birth is in 1980, but not the month or day, the value
46 * returned SHALL be "1980-00-00". 46 * returned SHALL be "1980-00-00".
47 */ 47 */
48 field_dob = 8, 48 field_dob = 8,
49 /** 49 /**
50 * Alias to field_dob 50 * Alias to field_dob
51 */ 51 */
52 field_birthdate = field_dob, 52 field_birthdate = field_dob,
53 /** 53 /**
54 * The End User's gender, "M" for male, "F" for female. 54 * The End User's gender, "M" for male, "F" for female.
55 */ 55 */
56 field_gender = 16, 56 field_gender = 16,
57 /** 57 /**
58 * Alias to field_gender 58 * Alias to field_gender
59 */ 59 */
60 field_sex = field_gender, 60 field_sex = field_gender,
61 /** 61 /**
62 * UTF-8 string free text that SHOULD conform to the End User's 62 * UTF-8 string free text that SHOULD conform to the End User's
63 * country's postal system. 63 * country's postal system.
64 */ 64 */
65 field_postcode = 32, 65 field_postcode = 32,
66 /** 66 /**
67 * The End User's country of residence as specified by ISO3166 67 * The End User's country of residence as specified by ISO3166
68 */ 68 */
69 field_country = 64, 69 field_country = 64,
70 /** 70 /**
71 * End User's preferred language as specified by ISO639 71 * End User's preferred language as specified by ISO639
72 */ 72 */
73 field_language = 128, 73 field_language = 128,
74 /** 74 /**
75 * ASCII string from TimeZone database 75 * ASCII string from TimeZone database
76 * 76 *
77 * For example, "Europe/Paris" or "America/Los_Angeles". 77 * For example, "Europe/Paris" or "America/Los_Angeles".
78 */ 78 */
79 field_timezone = 256, 79 field_timezone = 256,
80 /** 80 /**
81 * All fields bits combined 81 * All fields bits combined
82 */ 82 */
83 fields_ALL = 511, 83 fields_ALL = 511,
84 /** 84 /**
85 * No fields 85 * No fields
86 */ 86 */
87 fields_NONE = 0 87 fields_NONE = 0
88 }; 88 };
89 /** 89 /**
90 * Bitmask for fields which, if absent from the response, will 90 * Bitmask for fields which, if absent from the response, will
91 * prevent the Consumer from completing the registration without 91 * prevent the Consumer from completing the registration without
92 * End User interation. 92 * End User interation.
93 */ 93 */
94 long fields_required; 94 long fields_required;
95 /** 95 /**
96 * Bitmask for fields that will be used by the Consumer, but whose 96 * Bitmask for fields that will be used by the Consumer, but whose
97 * absence will not prevent the registration from completing. 97 * absence will not prevent the registration from completing.
98 */ 98 */
99 long fields_optional; 99 long fields_optional;
100 /** 100 /**
101 * A URL which the Consumer provides to give the End User a place 101 * A URL which the Consumer provides to give the End User a place
102 * to read about the how the profile data will be used. The 102 * to read about the how the profile data will be used. The
103 * Identity Provider SHOULD display this URL to the End User if it 103 * Identity Provider SHOULD display this URL to the End User if it
104 * is given. 104 * is given.
105 */ 105 */
106 string policy_url; 106 string policy_url;
107 107
108 /** 108 /**
109 * Bitmask for fields present in response 109 * Bitmask for fields present in response
110 */ 110 */
111 long has_fields; 111 long has_fields;
112 /** 112 /**
113 * Container type for response fields values 113 * Container type for response fields values
114 */ 114 */
115 typedef map<fieldbit_t,string> response_t; 115 typedef map<fieldbit_t,string> response_t;
116 /** 116 /**
117 * Response contents 117 * Response contents
118 */ 118 */
119 response_t response; 119 response_t response;
120 120
121 /** 121 /**
122 * Fields bitmask to send in response 122 * Fields bitmask to send in response
123 */ 123 */
124 long fields_response; 124 long fields_response;
125 125
126 /** 126 /**
127 * Consumer constructor. 127 * Consumer constructor.
128 * @param fr required fields 128 * @param fr required fields
129 * @see fields_required 129 * @see fields_required
130 * @param fo optional fields 130 * @param fo optional fields
131 * @see fields_optional 131 * @see fields_optional
132 * @param pu policy url 132 * @param pu policy url
133 * @see policy_url 133 * @see policy_url
134 */ 134 */
135 sreg_t(long fr=fields_NONE,long fo=fields_NONE,const string& pu="") 135 sreg_t(long fr=fields_NONE,long fo=fields_NONE,const string& pu="")
136 : fields_required(fr), fields_optional(fo), policy_url(pu), has_fields(0) { } 136 : fields_required(fr), fields_optional(fo), policy_url(pu), has_fields(0) { }
137 137
138 /** 138 virtual void rp_checkid_hook(basic_openid_message& om);
139 * Implementation of consumer's checkid hook 139 virtual void rp_id_res_hook(const basic_openid_message& om,
140 */ 140 const basic_openid_message& sp);
141 virtual void op_checkid_hook(const basic_openid_message& inm);
142 virtual void op_id_res_hook(basic_openid_message& oum);
143
141 virtual void checkid_hook(basic_openid_message& om); 144 virtual void checkid_hook(basic_openid_message& om);
142 /** 145 virtual void id_res_hook(const basic_openid_message& om,
143 * Implementation of consumer's id_res hook 146 const basic_openid_message& sp);
144 */ 147 virtual void checkid_hook(const basic_openid_message& inm,
145 virtual void id_res_hook(const basic_openid_message& om,const basic_openid_message& sp); 148 basic_openid_message& oum);
146 /**
147 * Implementation of server's checkid_hook
148 */
149 virtual void checkid_hook(const basic_openid_message& inm,basic_openid_message& oum);
150 149
151 /** 150 /**
152 * Check and see if we have value for some particular field. 151 * Check and see if we have value for some particular field.
153 * @param fb field in question 152 * @param fb field in question
154 * @see fieldbit_t 153 * @see fieldbit_t
155 * @return true if the value is available 154 * @return true if the value is available
156 */ 155 */
157 bool has_field(fieldbit_t fb) const { return has_fields&fb; } 156 bool has_field(fieldbit_t fb) const { return has_fields&fb; }
158 157
159 /** 158 /**
160 * Retrieve the value for a field. 159 * Retrieve the value for a field.
161 * @param fb field in question 160 * @param fb field in question
162 * @see fieldbit_t 161 * @see fieldbit_t
163 * @return field value 162 * @return field value
164 * @throw failed_lookup if no data avaialble 163 * @throw failed_lookup if no data avaialble
165 */ 164 */
166 const string& get_field(fieldbit_t fb) const; 165 const string& get_field(fieldbit_t fb) const;
167 166
168 /** 167 /**
169 * Set the value for a field. 168 * Set the value for a field.
170 * @param fb field in question 169 * @param fb field in question
171 * @see fieldbit_t 170 * @see fieldbit_t
172 * @param fv field value 171 * @param fv field value
173 */ 172 */
174 void set_field(fieldbit_t fb,const string& fv); 173 void set_field(fieldbit_t fb,const string& fv);
175 174
176 /** 175 /**
177 * Remove the value for a field. 176 * Remove the value for a field.
178 * @param fb field in question 177 * @param fb field in question
179 * @see fieldbit_t 178 * @see fieldbit_t
180 */ 179 */
181 void reset_field(fieldbit_t fb); 180 void reset_field(fieldbit_t fb);
182 181
183 /** 182 /**
184 * Reset field data 183 * Reset field data
185 */ 184 */
186 void clear(); 185 void clear();
187 186
188 /** 187 /**
189 * Function called after parsing sreg request to set up response 188 * Function called after parsing sreg request to set up response
190 * fields. The default implementation tries to send as much fields 189 * fields. The default implementation tries to send as much fields
191 * as we have. The function is supposed to set the data and 190 * as we have. The function is supposed to set the data and
192 * fields_response. 191 * fields_response.
193 * @see fields_response 192 * @see fields_response
194 * @param pin input request parameters with "openid." prefix 193 * @param inm incoming openid message
195 * @param pout output request parameters without "openid." prefix. 194 * @param oum outgoing openid message
196 * @see checkid_hook(const params_t&,params_t&)
197 */ 195 */
198 virtual void setup_response(const basic_openid_message& inm,basic_openid_message& oum); 196 virtual void setup_response(const basic_openid_message& inm,
197 basic_openid_message& oum);
198
199 virtual void setup_response();
199 200
200 }; 201 };
201} 202}
202 203
203#endif /* __OPKELE_SREG_H */ 204#endif /* __OPKELE_SREG_H */
diff --git a/lib/basic_op.cc b/lib/basic_op.cc
index c89d1d7..9e2ea5a 100644
--- a/lib/basic_op.cc
+++ b/lib/basic_op.cc
@@ -1,328 +1,331 @@
1#include <time.h> 1#include <time.h>
2#include <cassert> 2#include <cassert>
3#include <openssl/sha.h> 3#include <openssl/sha.h>
4#include <openssl/hmac.h> 4#include <openssl/hmac.h>
5#include <opkele/data.h> 5#include <opkele/data.h>
6#include <opkele/basic_op.h> 6#include <opkele/basic_op.h>
7#include <opkele/exception.h> 7#include <opkele/exception.h>
8#include <opkele/util.h> 8#include <opkele/util.h>
9#include <opkele/uris.h> 9#include <opkele/uris.h>
10 10
11namespace opkele { 11namespace opkele {
12 12
13 void basic_op::reset_vars() { 13 void basic_op::reset_vars() {
14 assoc.reset(); 14 assoc.reset();
15 return_to.clear(); realm.clear(); 15 return_to.clear(); realm.clear();
16 claimed_id.clear(); identity.clear(); 16 claimed_id.clear(); identity.clear();
17 invalidate_handle.clear(); 17 invalidate_handle.clear();
18 } 18 }
19 19
20 bool basic_op::has_return_to() const { 20 bool basic_op::has_return_to() const {
21 return !return_to.empty(); 21 return !return_to.empty();
22 } 22 }
23 const string& basic_op::get_return_to() const { 23 const string& basic_op::get_return_to() const {
24 if(return_to.empty()) 24 if(return_to.empty())
25 throw no_return_to(OPKELE_CP_ "No return_to URL provided with request"); 25 throw no_return_to(OPKELE_CP_ "No return_to URL provided with request");
26 return return_to; 26 return return_to;
27 } 27 }
28 28
29 const string& basic_op::get_realm() const { 29 const string& basic_op::get_realm() const {
30 assert(!realm.empty()); 30 assert(!realm.empty());
31 return realm; 31 return realm;
32 } 32 }
33 33
34 bool basic_op::has_identity() const { 34 bool basic_op::has_identity() const {
35 return !identity.empty(); 35 return !identity.empty();
36 } 36 }
37 const string& basic_op::get_claimed_id() const { 37 const string& basic_op::get_claimed_id() const {
38 if(claimed_id.empty()) 38 if(claimed_id.empty())
39 throw non_identity(OPKELE_CP_ "attempting to retrieve claimed_id of non-identity related request"); 39 throw non_identity(OPKELE_CP_ "attempting to retrieve claimed_id of non-identity related request");
40 assert(!identity.empty()); 40 assert(!identity.empty());
41 return claimed_id; 41 return claimed_id;
42 } 42 }
43 const string& basic_op::get_identity() const { 43 const string& basic_op::get_identity() const {
44 if(identity.empty()) 44 if(identity.empty())
45 throw non_identity(OPKELE_CP_ "attempting to retrieve identity of non-identity related request"); 45 throw non_identity(OPKELE_CP_ "attempting to retrieve identity of non-identity related request");
46 assert(!claimed_id.empty()); 46 assert(!claimed_id.empty());
47 return identity; 47 return identity;
48 } 48 }
49 49
50 bool basic_op::is_id_select() const { 50 bool basic_op::is_id_select() const {
51 return identity==IDURI_SELECT20; 51 return identity==IDURI_SELECT20;
52 } 52 }
53 53
54 void basic_op::select_identity(const string& c,const string& i) { 54 void basic_op::select_identity(const string& c,const string& i) {
55 claimed_id = c; identity = i; 55 claimed_id = c; identity = i;
56 } 56 }
57 void basic_op::set_claimed_id(const string& c) { 57 void basic_op::set_claimed_id(const string& c) {
58 claimed_id = c; 58 claimed_id = c;
59 } 59 }
60 60
61 basic_openid_message& basic_op::associate( 61 basic_openid_message& basic_op::associate(
62 basic_openid_message& oum, 62 basic_openid_message& oum,
63 const basic_openid_message& inm) try { 63 const basic_openid_message& inm) try {
64 assert(inm.get_field("mode")=="associate"); 64 assert(inm.get_field("mode")=="associate");
65 util::dh_t dh; 65 util::dh_t dh;
66 util::bignum_t c_pub; 66 util::bignum_t c_pub;
67 unsigned char key_digest[SHA256_DIGEST_LENGTH]; 67 unsigned char key_digest[SHA256_DIGEST_LENGTH];
68 size_t d_len = 0; 68 size_t d_len = 0;
69 enum { 69 enum {
70 sess_cleartext, sess_dh_sha1, sess_dh_sha256 70 sess_cleartext, sess_dh_sha1, sess_dh_sha256
71 } st = sess_cleartext; 71 } st = sess_cleartext;
72 string sts = inm.get_field("session_type"); 72 string sts = inm.get_field("session_type");
73 string ats = inm.get_field("assoc_type"); 73 string ats = inm.get_field("assoc_type");
74 if(sts=="DH-SHA1" || sts=="DH-SHA256") { 74 if(sts=="DH-SHA1" || sts=="DH-SHA256") {
75 if(!(dh = DH_new())) 75 if(!(dh = DH_new()))
76 throw exception_openssl(OPKELE_CP_ "failed to DH_new()"); 76 throw exception_openssl(OPKELE_CP_ "failed to DH_new()");
77 c_pub = util::base64_to_bignum(inm.get_field("dh_consumer_public")); 77 c_pub = util::base64_to_bignum(inm.get_field("dh_consumer_public"));
78 try { dh->p = util::base64_to_bignum(inm.get_field("dh_modulus")); 78 try { dh->p = util::base64_to_bignum(inm.get_field("dh_modulus"));
79 }catch(failed_lookup&) { 79 }catch(failed_lookup&) {
80 dh->p = util::dec_to_bignum(data::_default_p); } 80 dh->p = util::dec_to_bignum(data::_default_p); }
81 try { dh->g = util::base64_to_bignum(inm.get_field("dh_gen")); 81 try { dh->g = util::base64_to_bignum(inm.get_field("dh_gen"));
82 }catch(failed_lookup&) { 82 }catch(failed_lookup&) {
83 dh->g = util::dec_to_bignum(data::_default_g); } 83 dh->g = util::dec_to_bignum(data::_default_g); }
84 if(!DH_generate_key(dh)) 84 if(!DH_generate_key(dh))
85 throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()"); 85 throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()");
86 vector<unsigned char> ck(DH_size(dh)+1); 86 vector<unsigned char> ck(DH_size(dh)+1);
87 unsigned char *ckptr = &(ck.front())+1; 87 unsigned char *ckptr = &(ck.front())+1;
88 int cklen = DH_compute_key(ckptr,c_pub,dh); 88 int cklen = DH_compute_key(ckptr,c_pub,dh);
89 if(cklen<0) 89 if(cklen<0)
90 throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); 90 throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()");
91 if(cklen && (*ckptr)&0x80) { 91 if(cklen && (*ckptr)&0x80) {
92 (*(--ckptr)) = 0; ++cklen; } 92 (*(--ckptr)) = 0; ++cklen; }
93 if(sts=="DH-SHA1") { 93 if(sts=="DH-SHA1") {
94 SHA1(ckptr,cklen,key_digest); d_len = SHA_DIGEST_LENGTH; 94 SHA1(ckptr,cklen,key_digest); d_len = SHA_DIGEST_LENGTH;
95 }else if(sts=="DH-SHA256") { 95 }else if(sts=="DH-SHA256") {
96 SHA256(ckptr,cklen,key_digest); d_len = SHA256_DIGEST_LENGTH; 96 SHA256(ckptr,cklen,key_digest); d_len = SHA256_DIGEST_LENGTH;
97 }else 97 }else
98 throw internal_error(OPKELE_CP_ "I thought I knew the session type"); 98 throw internal_error(OPKELE_CP_ "I thought I knew the session type");
99 }else 99 }else
100 throw unsupported(OPKELE_CP_ "Unsupported session_type"); 100 throw unsupported(OPKELE_CP_ "Unsupported session_type");
101 assoc_t assoc; 101 assoc_t assoc;
102 if(ats=="HMAC-SHA1") 102 if(ats=="HMAC-SHA1")
103 assoc = alloc_assoc(ats,SHA_DIGEST_LENGTH,true); 103 assoc = alloc_assoc(ats,SHA_DIGEST_LENGTH,true);
104 else if(ats=="HMAC-SHA256") 104 else if(ats=="HMAC-SHA256")
105 assoc = alloc_assoc(ats,SHA256_DIGEST_LENGTH,true); 105 assoc = alloc_assoc(ats,SHA256_DIGEST_LENGTH,true);
106 else 106 else
107 throw unsupported(OPKELE_CP_ "Unsupported assoc_type"); 107 throw unsupported(OPKELE_CP_ "Unsupported assoc_type");
108 oum.reset_fields(); 108 oum.reset_fields();
109 oum.set_field("ns",OIURI_OPENID20); 109 oum.set_field("ns",OIURI_OPENID20);
110 oum.set_field("assoc_type",assoc->assoc_type()); 110 oum.set_field("assoc_type",assoc->assoc_type());
111 oum.set_field("assoc_handle",assoc->handle()); 111 oum.set_field("assoc_handle",assoc->handle());
112 oum.set_field("expires_in",util::long_to_string(assoc->expires_in())); 112 oum.set_field("expires_in",util::long_to_string(assoc->expires_in()));
113 secret_t secret = assoc->secret(); 113 secret_t secret = assoc->secret();
114 if(sts=="DH-SHA1" || sts=="DH-SHA256") { 114 if(sts=="DH-SHA1" || sts=="DH-SHA256") {
115 if(d_len != secret.size()) 115 if(d_len != secret.size())
116 throw bad_input(OPKELE_CP_ "Association secret and session MAC are not of the same size"); 116 throw bad_input(OPKELE_CP_ "Association secret and session MAC are not of the same size");
117 oum.set_field("session_type",sts); 117 oum.set_field("session_type",sts);
118 oum.set_field("dh_server_public",util::bignum_to_base64(dh->pub_key)); 118 oum.set_field("dh_server_public",util::bignum_to_base64(dh->pub_key));
119 string b64; secret.enxor_to_base64(key_digest,b64); 119 string b64; secret.enxor_to_base64(key_digest,b64);
120 oum.set_field("enc_mac_key",b64); 120 oum.set_field("enc_mac_key",b64);
121 }else /* TODO: support cleartext over encrypted connection */ 121 }else /* TODO: support cleartext over encrypted connection */
122 throw unsupported(OPKELE_CP_ "Unsupported session type"); 122 throw unsupported(OPKELE_CP_ "Unsupported session type");
123 return oum; 123 return oum;
124 } catch(unsupported& u) { 124 } catch(unsupported& u) {
125 oum.reset_fields(); 125 oum.reset_fields();
126 oum.set_field("ns",OIURI_OPENID20); 126 oum.set_field("ns",OIURI_OPENID20);
127 oum.set_field("error",u.what()); 127 oum.set_field("error",u.what());
128 oum.set_field("error_code","unsupported-type"); 128 oum.set_field("error_code","unsupported-type");
129 oum.set_field("session_type","DH-SHA256"); 129 oum.set_field("session_type","DH-SHA256");
130 oum.set_field("assoc_type","HMAC-SHA256"); 130 oum.set_field("assoc_type","HMAC-SHA256");
131 return oum; 131 return oum;
132 } 132 }
133 133
134 void basic_op::checkid_(const basic_openid_message& inm, 134 void basic_op::checkid_(const basic_openid_message& inm,
135 extension_t *ext) { 135 extension_t *ext) {
136 reset_vars(); 136 reset_vars();
137 string mode = inm.get_field("mode"); 137 string mode = inm.get_field("mode");
138 if(mode=="checkid_setup") 138 if(mode=="checkid_setup")
139 mode = mode_checkid_setup; 139 mode = mode_checkid_setup;
140 else if(mode=="checkid_immediate") 140 else if(mode=="checkid_immediate")
141 mode = mode_checkid_immediate; 141 mode = mode_checkid_immediate;
142 else 142 else
143 throw bad_input(OPKELE_CP_ "Invalid checkid_* mode"); 143 throw bad_input(OPKELE_CP_ "Invalid checkid_* mode");
144 try { 144 try {
145 assoc = retrieve_assoc(invalidate_handle=inm.get_field("assoc_handle")); 145 assoc = retrieve_assoc(invalidate_handle=inm.get_field("assoc_handle"));
146 invalidate_handle.clear(); 146 invalidate_handle.clear();
147 }catch(failed_lookup&) { 147 }catch(failed_lookup&) {
148 // no handle specified or no valid assoc found, go dumb 148 // no handle specified or no valid assoc found, go dumb
149 assoc = alloc_assoc("HMAC-SHA256",SHA256_DIGEST_LENGTH,true); 149 assoc = alloc_assoc("HMAC-SHA256",SHA256_DIGEST_LENGTH,true);
150 } 150 }
151 try { 151 try {
152 openid2 = (inm.get_field("ns")==OIURI_OPENID20); 152 openid2 = (inm.get_field("ns")==OIURI_OPENID20);
153 }catch(failed_lookup&) { openid2 = false; } 153 }catch(failed_lookup&) { openid2 = false; }
154 try { 154 try {
155 return_to = inm.get_field("return_to"); 155 return_to = inm.get_field("return_to");
156 }catch(failed_lookup&) { } 156 }catch(failed_lookup&) { }
157 if(openid2) { 157 if(openid2) {
158 try { 158 try {
159 realm = inm.get_field("realm"); 159 realm = inm.get_field("realm");
160 }catch(failed_lookup&) { 160 }catch(failed_lookup&) {
161 try { 161 try {
162 realm = inm.get_field("trust_root"); 162 realm = inm.get_field("trust_root");
163 }catch(failed_lookup&) { 163 }catch(failed_lookup&) {
164 if(return_to.empty()) 164 if(return_to.empty())
165 throw bad_input(OPKELE_CP_ 165 throw bad_input(OPKELE_CP_
166 "Both realm and return_to are unset"); 166 "Both realm and return_to are unset");
167 realm = return_to; 167 realm = return_to;
168 } 168 }
169 } 169 }
170 }else{ 170 }else{
171 try { 171 try {
172 realm = inm.get_field("trust_root"); 172 realm = inm.get_field("trust_root");
173 }catch(failed_lookup&) { 173 }catch(failed_lookup&) {
174 if(return_to.empty()) 174 if(return_to.empty())
175 throw bad_input(OPKELE_CP_ 175 throw bad_input(OPKELE_CP_
176 "Both realm and return_to are unset"); 176 "Both realm and return_to are unset");
177 realm = return_to; 177 realm = return_to;
178 } 178 }
179 } 179 }
180 try { 180 try {
181 identity = inm.get_field("identity"); 181 identity = inm.get_field("identity");
182 try { 182 try {
183 claimed_id = inm.get_field("claimed_id"); 183 claimed_id = inm.get_field("claimed_id");
184 }catch(failed_lookup&) { 184 }catch(failed_lookup&) {
185 if(openid2) 185 if(openid2)
186 throw bad_input(OPKELE_CP_ 186 throw bad_input(OPKELE_CP_
187 "claimed_id and identity must be either both present or both absent"); 187 "claimed_id and identity must be either both present or both absent");
188 claimed_id = identity; 188 claimed_id = identity;
189 } 189 }
190 }catch(failed_lookup&) { 190 }catch(failed_lookup&) {
191 if(openid2 && inm.has_field("claimed_id")) 191 if(openid2 && inm.has_field("claimed_id"))
192 throw bad_input(OPKELE_CP_ 192 throw bad_input(OPKELE_CP_
193 "claimed_id and identity must be either both present or both absent"); 193 "claimed_id and identity must be either both present or both absent");
194 } 194 }
195 verify_return_to(); 195 verify_return_to();
196 if(ext) ext->op_checkid_hook(inm);
196 } 197 }
197 198
198 basic_openid_message& basic_op::id_res(basic_openid_message& om) { 199 basic_openid_message& basic_op::id_res(basic_openid_message& om,
200 extension_t *ext) {
199 assert(assoc); 201 assert(assoc);
200 assert(!return_to.empty()); 202 assert(!return_to.empty());
201 assert(!is_id_select()); 203 assert(!is_id_select());
202 time_t now = time(0); 204 time_t now = time(0);
203 struct tm gmt; gmtime_r(&now,&gmt); 205 struct tm gmt; gmtime_r(&now,&gmt);
204 char w3timestr[24]; 206 char w3timestr[24];
205 if(!strftime(w3timestr,sizeof(w3timestr),"%Y-%m-%dT%H:%M:%SZ",&gmt)) 207 if(!strftime(w3timestr,sizeof(w3timestr),"%Y-%m-%dT%H:%M:%SZ",&gmt))
206 throw failed_conversion(OPKELE_CP_ 208 throw failed_conversion(OPKELE_CP_
207 "Failed to build time string for nonce" ); 209 "Failed to build time string for nonce" );
208 om.set_field("ns",OIURI_OPENID20); 210 om.set_field("ns",OIURI_OPENID20);
209 om.set_field("mode","id_res"); 211 om.set_field("mode","id_res");
210 om.set_field("op_endpoint",get_op_endpoint()); 212 om.set_field("op_endpoint",get_op_endpoint());
211 string ats = "ns,mode,op_endpoint,return_to,response_nonce," 213 string ats = "ns,mode,op_endpoint,return_to,response_nonce,"
212 "assoc_handle,signed"; 214 "assoc_handle,signed";
213 if(!identity.empty()) { 215 if(!identity.empty()) {
214 om.set_field("identity",identity); 216 om.set_field("identity",identity);
215 om.set_field("claimed_id",claimed_id); 217 om.set_field("claimed_id",claimed_id);
216 ats += ",identity,claimed_id"; 218 ats += ",identity,claimed_id";
217 } 219 }
218 om.set_field("return_to",return_to); 220 om.set_field("return_to",return_to);
219 string nonce = w3timestr; 221 string nonce = w3timestr;
220 om.set_field("response_nonce",alloc_nonce(nonce,assoc->stateless())); 222 om.set_field("response_nonce",alloc_nonce(nonce,assoc->stateless()));
221 if(!invalidate_handle.empty()) { 223 if(!invalidate_handle.empty()) {
222 om.set_field("invalidate_handle",invalidate_handle); 224 om.set_field("invalidate_handle",invalidate_handle);
223 ats += ",invalidate_handle"; 225 ats += ",invalidate_handle";
224 } 226 }
225 om.set_field("assoc_handle",assoc->handle()); 227 om.set_field("assoc_handle",assoc->handle());
226 om.add_to_signed(ats); 228 om.add_to_signed(ats);
229 if(ext) ext->op_id_res_hook(om);
227 om.set_field("sig",util::base64_signature(assoc,om)); 230 om.set_field("sig",util::base64_signature(assoc,om));
228 return om; 231 return om;
229 } 232 }
230 233
231 basic_openid_message& basic_op::cancel(basic_openid_message& om) { 234 basic_openid_message& basic_op::cancel(basic_openid_message& om) {
232 assert(!return_to.empty()); 235 assert(!return_to.empty());
233 om.set_field("ns",OIURI_OPENID20); 236 om.set_field("ns",OIURI_OPENID20);
234 om.set_field("mode","cancel"); 237 om.set_field("mode","cancel");
235 return om; 238 return om;
236 } 239 }
237 240
238 basic_openid_message& basic_op::error(basic_openid_message& om, 241 basic_openid_message& basic_op::error(basic_openid_message& om,
239 const string& error,const string& contact, 242 const string& error,const string& contact,
240 const string& reference ) { 243 const string& reference ) {
241 assert(!return_to.empty()); 244 assert(!return_to.empty());
242 om.set_field("ns",OIURI_OPENID20); 245 om.set_field("ns",OIURI_OPENID20);
243 om.set_field("mode","error"); 246 om.set_field("mode","error");
244 om.set_field("error",error); 247 om.set_field("error",error);
245 om.set_field("contact",contact); 248 om.set_field("contact",contact);
246 om.set_field("reference",reference); 249 om.set_field("reference",reference);
247 return om; 250 return om;
248 } 251 }
249 252
250 basic_openid_message& basic_op::setup_needed( 253 basic_openid_message& basic_op::setup_needed(
251 basic_openid_message& oum,const basic_openid_message& inm) { 254 basic_openid_message& oum,const basic_openid_message& inm) {
252 assert(mode==mode_checkid_immediate); 255 assert(mode==mode_checkid_immediate);
253 assert(!return_to.empty()); 256 assert(!return_to.empty());
254 if(openid2) { 257 if(openid2) {
255 oum.set_field("ns",OIURI_OPENID20); 258 oum.set_field("ns",OIURI_OPENID20);
256 oum.set_field("mode","setup_needed"); 259 oum.set_field("mode","setup_needed");
257 }else{ 260 }else{
258 oum.set_field("mode","id_res"); 261 oum.set_field("mode","id_res");
259 static const string setupmode = "checkid_setup"; 262 static const string setupmode = "checkid_setup";
260 oum.set_field("user_setup_url", 263 oum.set_field("user_setup_url",
261 util::change_mode_message_proxy(inm,setupmode) 264 util::change_mode_message_proxy(inm,setupmode)
262 .append_query(get_op_endpoint())); 265 .append_query(get_op_endpoint()));
263 } 266 }
264 return oum; 267 return oum;
265 } 268 }
266 269
267 basic_openid_message& basic_op::check_authentication( 270 basic_openid_message& basic_op::check_authentication(
268 basic_openid_message& oum, 271 basic_openid_message& oum,
269 const basic_openid_message& inm) try { 272 const basic_openid_message& inm) try {
270 assert(inm.get_field("mode")=="check_authentication"); 273 assert(inm.get_field("mode")=="check_authentication");
271 oum.reset_fields(); 274 oum.reset_fields();
272 oum.set_field("ns",OIURI_OPENID20); 275 oum.set_field("ns",OIURI_OPENID20);
273 bool o2; 276 bool o2;
274 try { 277 try {
275 o2 = (inm.get_field("ns")==OIURI_OPENID20); 278 o2 = (inm.get_field("ns")==OIURI_OPENID20);
276 }catch(failed_lookup&) { o2 = false; } 279 }catch(failed_lookup&) { o2 = false; }
277 string nonce; 280 string nonce;
278 if(o2) { 281 if(o2) {
279 try { 282 try {
280 if(!check_nonce(nonce = inm.get_field("response_nonce"))) 283 if(!check_nonce(nonce = inm.get_field("response_nonce")))
281 throw failed_check_authentication(OPKELE_CP_ "Invalid nonce"); 284 throw failed_check_authentication(OPKELE_CP_ "Invalid nonce");
282 }catch(failed_lookup&) { 285 }catch(failed_lookup&) {
283 throw failed_check_authentication(OPKELE_CP_ "No nonce provided with check_authentication request"); 286 throw failed_check_authentication(OPKELE_CP_ "No nonce provided with check_authentication request");
284 } 287 }
285 } 288 }
286 try { 289 try {
287 assoc = retrieve_assoc(inm.get_field("assoc_handle")); 290 assoc = retrieve_assoc(inm.get_field("assoc_handle"));
288 if(!assoc->stateless()) 291 if(!assoc->stateless())
289 throw failed_check_authentication(OPKELE_CP_ "Will not do check_authentication on a stateful handle"); 292 throw failed_check_authentication(OPKELE_CP_ "Will not do check_authentication on a stateful handle");
290 }catch(failed_lookup&) { 293 }catch(failed_lookup&) {
291 throw failed_check_authentication(OPKELE_CP_ "No assoc_handle or invalid assoc_handle specified with check_authentication request"); 294 throw failed_check_authentication(OPKELE_CP_ "No assoc_handle or invalid assoc_handle specified with check_authentication request");
292 } 295 }
293 static const string idresmode = "id_res"; 296 static const string idresmode = "id_res";
294 try { 297 try {
295 if(util::base64_signature(assoc,util::change_mode_message_proxy(inm,idresmode))!=inm.get_field("sig")) 298 if(util::base64_signature(assoc,util::change_mode_message_proxy(inm,idresmode))!=inm.get_field("sig"))
296 throw failed_check_authentication(OPKELE_CP_ "Signature mismatch"); 299 throw failed_check_authentication(OPKELE_CP_ "Signature mismatch");
297 }catch(failed_lookup&) { 300 }catch(failed_lookup&) {
298 throw failed_check_authentication(OPKELE_CP_ "failed to calculate signature"); 301 throw failed_check_authentication(OPKELE_CP_ "failed to calculate signature");
299 } 302 }
300 oum.set_field("is_valid","true"); 303 oum.set_field("is_valid","true");
301 try { 304 try {
302 string h = inm.get_field("invalidate_handle"); 305 string h = inm.get_field("invalidate_handle");
303 try { 306 try {
304 assoc_t ih = retrieve_assoc(h); 307 assoc_t ih = retrieve_assoc(h);
305 }catch(invalid_handle& ih) { 308 }catch(invalid_handle& ih) {
306 oum.set_field("invalidate_handle",h); 309 oum.set_field("invalidate_handle",h);
307 }catch(failed_lookup& ih) { 310 }catch(failed_lookup& ih) {
308 oum.set_field("invalidate_handle",h); 311 oum.set_field("invalidate_handle",h);
309 } 312 }
310 }catch(failed_lookup&) { } 313 }catch(failed_lookup&) { }
311 if(o2) { 314 if(o2) {
312 assert(!nonce.empty()); 315 assert(!nonce.empty());
313 invalidate_nonce(nonce); 316 invalidate_nonce(nonce);
314 } 317 }
315 return oum; 318 return oum;
316 }catch(failed_check_authentication& ) { 319 }catch(failed_check_authentication& ) {
317 oum.set_field("is_valid","false"); 320 oum.set_field("is_valid","false");
318 return oum; 321 return oum;
319 } 322 }
320 323
321 void basic_op::verify_return_to() { 324 void basic_op::verify_return_to() {
322 if(realm.find('#')!=string::npos) 325 if(realm.find('#')!=string::npos)
323 throw opkele::bad_realm(OPKELE_CP_ "authentication realm contains URI fragment"); 326 throw opkele::bad_realm(OPKELE_CP_ "authentication realm contains URI fragment");
324 if(!util::uri_matches_realm(return_to,realm)) 327 if(!util::uri_matches_realm(return_to,realm))
325 throw bad_return_to(OPKELE_CP_ "return_to URL doesn't match realm"); 328 throw bad_return_to(OPKELE_CP_ "return_to URL doesn't match realm");
326 } 329 }
327 330
328} 331}
diff --git a/lib/basic_rp.cc b/lib/basic_rp.cc
index a884583..bd45d99 100644
--- a/lib/basic_rp.cc
+++ b/lib/basic_rp.cc
@@ -1,297 +1,297 @@
1#include <openssl/sha.h> 1#include <openssl/sha.h>
2#include <openssl/hmac.h> 2#include <openssl/hmac.h>
3#include <opkele/basic_rp.h> 3#include <opkele/basic_rp.h>
4#include <opkele/exception.h> 4#include <opkele/exception.h>
5#include <opkele/uris.h> 5#include <opkele/uris.h>
6#include <opkele/data.h> 6#include <opkele/data.h>
7#include <opkele/util.h> 7#include <opkele/util.h>
8#include <opkele/curl.h> 8#include <opkele/curl.h>
9 9
10namespace opkele { 10namespace opkele {
11 11
12 static void dh_get_secret( 12 static void dh_get_secret(
13 secret_t& secret, const basic_openid_message& om, 13 secret_t& secret, const basic_openid_message& om,
14 const char *exp_assoc, const char *exp_sess, 14 const char *exp_assoc, const char *exp_sess,
15 util::dh_t& dh, 15 util::dh_t& dh,
16 size_t d_len, unsigned char *(*d_fun)(const unsigned char*,size_t,unsigned char*), 16 size_t d_len, unsigned char *(*d_fun)(const unsigned char*,size_t,unsigned char*),
17 size_t exp_s_len) try { 17 size_t exp_s_len) try {
18 if(om.get_field("assoc_type")!=exp_assoc || om.get_field("session_type")!=exp_sess) 18 if(om.get_field("assoc_type")!=exp_assoc || om.get_field("session_type")!=exp_sess)
19 throw bad_input(OPKELE_CP_ "Unexpected associate response"); 19 throw bad_input(OPKELE_CP_ "Unexpected associate response");
20 util::bignum_t s_pub = util::base64_to_bignum(om.get_field("dh_server_public")); 20 util::bignum_t s_pub = util::base64_to_bignum(om.get_field("dh_server_public"));
21 vector<unsigned char> ck(DH_size(dh)+1); 21 vector<unsigned char> ck(DH_size(dh)+1);
22 unsigned char *ckptr = &(ck.front())+1; 22 unsigned char *ckptr = &(ck.front())+1;
23 int cklen = DH_compute_key(ckptr,s_pub,dh); 23 int cklen = DH_compute_key(ckptr,s_pub,dh);
24 if(cklen<0) 24 if(cklen<0)
25 throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); 25 throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()");
26 if(cklen && (*ckptr)&0x80) { 26 if(cklen && (*ckptr)&0x80) {
27 (*(--ckptr))=0; ++cklen; } 27 (*(--ckptr))=0; ++cklen; }
28 unsigned char key_digest[d_len]; 28 unsigned char key_digest[d_len];
29 secret.enxor_from_base64((*d_fun)(ckptr,cklen,key_digest),om.get_field("enc_mac_key")); 29 secret.enxor_from_base64((*d_fun)(ckptr,cklen,key_digest),om.get_field("enc_mac_key"));
30 if(secret.size()!=exp_s_len) 30 if(secret.size()!=exp_s_len)
31 throw bad_input(OPKELE_CP_ "Secret length isn't consistent with association type"); 31 throw bad_input(OPKELE_CP_ "Secret length isn't consistent with association type");
32 }catch(opkele::failed_lookup& ofl) { 32 }catch(opkele::failed_lookup& ofl) {
33 throw bad_input(OPKELE_CP_ "Incoherent response from OP"); 33 throw bad_input(OPKELE_CP_ "Incoherent response from OP");
34 } OPKELE_RETHROW 34 } OPKELE_RETHROW
35 35
36 static void direct_request(basic_openid_message& oum,const basic_openid_message& inm,const string& OP) { 36 static void direct_request(basic_openid_message& oum,const basic_openid_message& inm,const string& OP) {
37 util::curl_pick_t curl = util::curl_pick_t::easy_init(); 37 util::curl_pick_t curl = util::curl_pick_t::easy_init();
38 if(!curl) 38 if(!curl)
39 throw exception_curl(OPKELE_CP_ "failed to initialize curl"); 39 throw exception_curl(OPKELE_CP_ "failed to initialize curl");
40 string request = inm.query_string(); 40 string request = inm.query_string();
41 CURLcode r; 41 CURLcode r;
42 (r=curl.misc_sets()) 42 (r=curl.misc_sets())
43 || (r=curl.easy_setopt(CURLOPT_URL,OP.c_str())) 43 || (r=curl.easy_setopt(CURLOPT_URL,OP.c_str()))
44 || (r=curl.easy_setopt(CURLOPT_POST,1)) 44 || (r=curl.easy_setopt(CURLOPT_POST,1))
45 || (r=curl.easy_setopt(CURLOPT_POSTFIELDS,request.data())) 45 || (r=curl.easy_setopt(CURLOPT_POSTFIELDS,request.data()))
46 || (r=curl.easy_setopt(CURLOPT_POSTFIELDSIZE,request.length())) 46 || (r=curl.easy_setopt(CURLOPT_POSTFIELDSIZE,request.length()))
47 || (r=curl.set_write()); 47 || (r=curl.set_write());
48 if(r) 48 if(r)
49 throw exception_curl(OPKELE_CP_ "failed to set curly options",r); 49 throw exception_curl(OPKELE_CP_ "failed to set curly options",r);
50 if( (r=curl.easy_perform()) ) 50 if( (r=curl.easy_perform()) )
51 throw exception_curl(OPKELE_CP_ "failed to perform curly request",r); 51 throw exception_curl(OPKELE_CP_ "failed to perform curly request",r);
52 oum.from_keyvalues(curl.response); 52 oum.from_keyvalues(curl.response);
53 } 53 }
54 54
55 55
56 assoc_t basic_RP::associate(const string& OP) { 56 assoc_t basic_RP::associate(const string& OP) {
57 util::dh_t dh = DH_new(); 57 util::dh_t dh = DH_new();
58 if(!dh) 58 if(!dh)
59 throw exception_openssl(OPKELE_CP_ "failed to DH_new()"); 59 throw exception_openssl(OPKELE_CP_ "failed to DH_new()");
60 dh->p = util::dec_to_bignum(data::_default_p); 60 dh->p = util::dec_to_bignum(data::_default_p);
61 dh->g = util::dec_to_bignum(data::_default_g); 61 dh->g = util::dec_to_bignum(data::_default_g);
62 if(!DH_generate_key(dh)) 62 if(!DH_generate_key(dh))
63 throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()"); 63 throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()");
64 openid_message_t req; 64 openid_message_t req;
65 req.set_field("ns",OIURI_OPENID20); 65 req.set_field("ns",OIURI_OPENID20);
66 req.set_field("mode","associate"); 66 req.set_field("mode","associate");
67 req.set_field("dh_modulus",util::bignum_to_base64(dh->p)); 67 req.set_field("dh_modulus",util::bignum_to_base64(dh->p));
68 req.set_field("dh_gen",util::bignum_to_base64(dh->g)); 68 req.set_field("dh_gen",util::bignum_to_base64(dh->g));
69 req.set_field("dh_consumer_public",util::bignum_to_base64(dh->pub_key)); 69 req.set_field("dh_consumer_public",util::bignum_to_base64(dh->pub_key));
70 openid_message_t res; 70 openid_message_t res;
71 req.set_field("assoc_type","HMAC-SHA256"); 71 req.set_field("assoc_type","HMAC-SHA256");
72 req.set_field("session_type","DH-SHA256"); 72 req.set_field("session_type","DH-SHA256");
73 secret_t secret; 73 secret_t secret;
74 int expires_in; 74 int expires_in;
75 try { 75 try {
76 direct_request(res,req,OP); 76 direct_request(res,req,OP);
77 dh_get_secret( secret, res, 77 dh_get_secret( secret, res,
78 "HMAC-SHA256", "DH-SHA256", 78 "HMAC-SHA256", "DH-SHA256",
79 dh, SHA256_DIGEST_LENGTH, SHA256, SHA256_DIGEST_LENGTH ); 79 dh, SHA256_DIGEST_LENGTH, SHA256, SHA256_DIGEST_LENGTH );
80 expires_in = util::string_to_long(res.get_field("expires_in")); 80 expires_in = util::string_to_long(res.get_field("expires_in"));
81 }catch(exception& e) { 81 }catch(exception& e) {
82 try { 82 try {
83 req.set_field("assoc_type","HMAC-SHA1"); 83 req.set_field("assoc_type","HMAC-SHA1");
84 req.set_field("session_type","DH-SHA1"); 84 req.set_field("session_type","DH-SHA1");
85 direct_request(res,req,OP); 85 direct_request(res,req,OP);
86 dh_get_secret( secret, res, 86 dh_get_secret( secret, res,
87 "HMAC-SHA1", "DH-SHA1", 87 "HMAC-SHA1", "DH-SHA1",
88 dh, SHA_DIGEST_LENGTH, SHA1, SHA_DIGEST_LENGTH ); 88 dh, SHA_DIGEST_LENGTH, SHA1, SHA_DIGEST_LENGTH );
89 expires_in = util::string_to_long(res.get_field("expires_in")); 89 expires_in = util::string_to_long(res.get_field("expires_in"));
90 }catch(bad_input& e) { 90 }catch(bad_input& e) {
91 throw dumb_RP(OPKELE_CP_ "OP failed to supply an association"); 91 throw dumb_RP(OPKELE_CP_ "OP failed to supply an association");
92 } 92 }
93 } 93 }
94 return store_assoc( 94 return store_assoc(
95 OP, res.get_field("assoc_handle"), 95 OP, res.get_field("assoc_handle"),
96 res.get_field("assoc_type"), secret, 96 res.get_field("assoc_type"), secret,
97 expires_in ); 97 expires_in );
98 } 98 }
99 99
100 basic_openid_message& basic_RP::checkid_( 100 basic_openid_message& basic_RP::checkid_(
101 basic_openid_message& rv, 101 basic_openid_message& rv,
102 mode_t mode, 102 mode_t mode,
103 const string& return_to,const string& realm, 103 const string& return_to,const string& realm,
104 extension_t *ext) { 104 extension_t *ext) {
105 rv.reset_fields(); 105 rv.reset_fields();
106 rv.set_field("ns",OIURI_OPENID20); 106 rv.set_field("ns",OIURI_OPENID20);
107 if(mode==mode_checkid_immediate) 107 if(mode==mode_checkid_immediate)
108 rv.set_field("mode","checkid_immediate"); 108 rv.set_field("mode","checkid_immediate");
109 else if(mode==mode_checkid_setup) 109 else if(mode==mode_checkid_setup)
110 rv.set_field("mode","checkid_setup"); 110 rv.set_field("mode","checkid_setup");
111 else 111 else
112 throw bad_input(OPKELE_CP_ "unknown checkid_* mode"); 112 throw bad_input(OPKELE_CP_ "unknown checkid_* mode");
113 if(realm.empty() && return_to.empty()) 113 if(realm.empty() && return_to.empty())
114 throw bad_input(OPKELE_CP_ "At least one of realm and return_to must be non-empty"); 114 throw bad_input(OPKELE_CP_ "At least one of realm and return_to must be non-empty");
115 if(!realm.empty()) { 115 if(!realm.empty()) {
116 rv.set_field("realm",realm); 116 rv.set_field("realm",realm);
117 rv.set_field("trust_root",realm); 117 rv.set_field("trust_root",realm);
118 } 118 }
119 if(!return_to.empty()) 119 if(!return_to.empty())
120 rv.set_field("return_to",return_to); 120 rv.set_field("return_to",return_to);
121 const openid_endpoint_t& ep = get_endpoint(); 121 const openid_endpoint_t& ep = get_endpoint();
122 rv.set_field("claimed_id",ep.claimed_id); 122 rv.set_field("claimed_id",ep.claimed_id);
123 rv.set_field("identity",ep.local_id); 123 rv.set_field("identity",ep.local_id);
124 try { 124 try {
125 rv.set_field("assoc_handle",find_assoc(ep.uri)->handle()); 125 rv.set_field("assoc_handle",find_assoc(ep.uri)->handle());
126 }catch(dumb_RP& drp) { 126 }catch(dumb_RP& drp) {
127 }catch(failed_lookup& fl) { 127 }catch(failed_lookup& fl) {
128 try { 128 try {
129 rv.set_field("assoc_handle",associate(ep.uri)->handle()); 129 rv.set_field("assoc_handle",associate(ep.uri)->handle());
130 }catch(dumb_RP& drp) { } 130 }catch(dumb_RP& drp) { }
131 } OPKELE_RETHROW 131 } OPKELE_RETHROW
132 if(ext) ext->checkid_hook(rv); 132 if(ext) ext->rp_checkid_hook(rv);
133 return rv; 133 return rv;
134 } 134 }
135 135
136 class signed_part_message_proxy : public basic_openid_message { 136 class signed_part_message_proxy : public basic_openid_message {
137 public: 137 public:
138 const basic_openid_message& x; 138 const basic_openid_message& x;
139 set<string> signeds; 139 set<string> signeds;
140 140
141 signed_part_message_proxy(const basic_openid_message& xx) : x(xx) { 141 signed_part_message_proxy(const basic_openid_message& xx) : x(xx) {
142 const string& slist = x.get_field("signed"); 142 const string& slist = x.get_field("signed");
143 string::size_type p = 0; 143 string::size_type p = 0;
144 while(true) { 144 while(true) {
145 string::size_type co = slist.find(',',p); 145 string::size_type co = slist.find(',',p);
146 string f = (co==string::npos) 146 string f = (co==string::npos)
147 ?slist.substr(p):slist.substr(p,co-p); 147 ?slist.substr(p):slist.substr(p,co-p);
148 signeds.insert(f); 148 signeds.insert(f);
149 if(co==string::npos) break; 149 if(co==string::npos) break;
150 p = co+1; 150 p = co+1;
151 } 151 }
152 } 152 }
153 153
154 bool has_field(const string& n) const { 154 bool has_field(const string& n) const {
155 return signeds.find(n)!=signeds.end() && x.has_field(n); } 155 return signeds.find(n)!=signeds.end() && x.has_field(n); }
156 const string& get_field(const string& n) const { 156 const string& get_field(const string& n) const {
157 if(signeds.find(n)==signeds.end()) 157 if(signeds.find(n)==signeds.end())
158 throw failed_lookup(OPKELE_CP_ "The field isn't known to be signed"); 158 throw failed_lookup(OPKELE_CP_ "The field isn't known to be signed");
159 return x.get_field(n); } 159 return x.get_field(n); }
160 160
161 fields_iterator fields_begin() const { 161 fields_iterator fields_begin() const {
162 return signeds.begin(); } 162 return signeds.begin(); }
163 fields_iterator fields_end() const { 163 fields_iterator fields_end() const {
164 return signeds.end(); } 164 return signeds.end(); }
165 }; 165 };
166 166
167 static void parse_query(const string& u,string::size_type q, 167 static void parse_query(const string& u,string::size_type q,
168 map<string,string>& p) { 168 map<string,string>& p) {
169 if(q==string::npos) 169 if(q==string::npos)
170 return; 170 return;
171 assert(u[q]=='?'); 171 assert(u[q]=='?');
172 ++q; 172 ++q;
173 string::size_type l = u.size(); 173 string::size_type l = u.size();
174 while(q<l) { 174 while(q<l) {
175 string::size_type eq = u.find('=',q); 175 string::size_type eq = u.find('=',q);
176 string::size_type am = u.find('&',q); 176 string::size_type am = u.find('&',q);
177 if(am==string::npos) { 177 if(am==string::npos) {
178 if(eq==string::npos) { 178 if(eq==string::npos) {
179 p[""] = u.substr(q); 179 p[""] = u.substr(q);
180 }else{ 180 }else{
181 p[u.substr(q,eq-q)] = u.substr(eq+1); 181 p[u.substr(q,eq-q)] = u.substr(eq+1);
182 } 182 }
183 break; 183 break;
184 }else{ 184 }else{
185 if(eq==string::npos || eq>am) { 185 if(eq==string::npos || eq>am) {
186 p[""] = u.substr(q,eq-q); 186 p[""] = u.substr(q,eq-q);
187 }else{ 187 }else{
188 p[u.substr(q,eq-q)] = u.substr(eq+1,am-eq-1); 188 p[u.substr(q,eq-q)] = u.substr(eq+1,am-eq-1);
189 } 189 }
190 q = ++am; 190 q = ++am;
191 } 191 }
192 } 192 }
193 } 193 }
194 194
195 void basic_RP::id_res(const basic_openid_message& om,extension_t *ext) { 195 void basic_RP::id_res(const basic_openid_message& om,extension_t *ext) {
196 bool o2 = om.has_field("ns") 196 bool o2 = om.has_field("ns")
197 && om.get_field("ns")==OIURI_OPENID20; 197 && om.get_field("ns")==OIURI_OPENID20;
198 if( (!o2) && om.has_field("user_setup_url")) 198 if( (!o2) && om.has_field("user_setup_url"))
199 throw id_res_setup(OPKELE_CP_ "assertion failed, setup url provided", 199 throw id_res_setup(OPKELE_CP_ "assertion failed, setup url provided",
200 om.get_field("user_setup_url")); 200 om.get_field("user_setup_url"));
201 string m = om.get_field("mode"); 201 string m = om.get_field("mode");
202 if(o2 && m=="setup_needed") 202 if(o2 && m=="setup_needed")
203 throw id_res_setup(OPKELE_CP_ "setup needed, no setup url provided"); 203 throw id_res_setup(OPKELE_CP_ "setup needed, no setup url provided");
204 if(m=="cancel") 204 if(m=="cancel")
205 throw id_res_cancel(OPKELE_CP_ "authentication cancelled"); 205 throw id_res_cancel(OPKELE_CP_ "authentication cancelled");
206 bool go_dumb=false; 206 bool go_dumb=false;
207 try { 207 try {
208 string OP = o2 208 string OP = o2
209 ?om.get_field("op_endpoint") 209 ?om.get_field("op_endpoint")
210 :get_endpoint().uri; 210 :get_endpoint().uri;
211 assoc_t assoc = retrieve_assoc( 211 assoc_t assoc = retrieve_assoc(
212 OP,om.get_field("assoc_handle")); 212 OP,om.get_field("assoc_handle"));
213 if(om.get_field("sig")!=util::base64_signature(assoc,om)) 213 if(om.get_field("sig")!=util::base64_signature(assoc,om))
214 throw id_res_mismatch(OPKELE_CP_ "signature mismatch"); 214 throw id_res_mismatch(OPKELE_CP_ "signature mismatch");
215 }catch(dumb_RP& drp) { 215 }catch(dumb_RP& drp) {
216 go_dumb=true; 216 go_dumb=true;
217 }catch(failed_lookup& e) { 217 }catch(failed_lookup& e) {
218 go_dumb=true; 218 go_dumb=true;
219 } OPKELE_RETHROW 219 } OPKELE_RETHROW
220 if(go_dumb) { 220 if(go_dumb) {
221 try { 221 try {
222 string OP = o2 222 string OP = o2
223 ?om.get_field("op_endpoint") 223 ?om.get_field("op_endpoint")
224 :get_endpoint().uri; 224 :get_endpoint().uri;
225 check_authentication(OP,om); 225 check_authentication(OP,om);
226 }catch(failed_check_authentication& fca) { 226 }catch(failed_check_authentication& fca) {
227 throw id_res_failed(OPKELE_CP_ "failed to check_authentication()"); 227 throw id_res_failed(OPKELE_CP_ "failed to check_authentication()");
228 } OPKELE_RETHROW 228 } OPKELE_RETHROW
229 } 229 }
230 signed_part_message_proxy signeds(om); 230 signed_part_message_proxy signeds(om);
231 if(o2) { 231 if(o2) {
232 check_nonce(om.get_field("op_endpoint"), 232 check_nonce(om.get_field("op_endpoint"),
233 om.get_field("response_nonce")); 233 om.get_field("response_nonce"));
234 static const char *mustsign[] = { 234 static const char *mustsign[] = {
235 "op_endpoint", "return_to", "response_nonce", "assoc_handle", 235 "op_endpoint", "return_to", "response_nonce", "assoc_handle",
236 "claimed_id", "identity" }; 236 "claimed_id", "identity" };
237 for(int ms=0;ms<(sizeof(mustsign)/sizeof(*mustsign));++ms) { 237 for(int ms=0;ms<(sizeof(mustsign)/sizeof(*mustsign));++ms) {
238 if(om.has_field(mustsign[ms]) && !signeds.has_field(mustsign[ms])) 238 if(om.has_field(mustsign[ms]) && !signeds.has_field(mustsign[ms]))
239 throw bad_input(OPKELE_CP_ string("Field '")+mustsign[ms]+"' is not signed against the specs"); 239 throw bad_input(OPKELE_CP_ string("Field '")+mustsign[ms]+"' is not signed against the specs");
240 } 240 }
241 if( ( 241 if( (
242 (om.has_field("claimed_id")?1:0) 242 (om.has_field("claimed_id")?1:0)
243 ^ 243 ^
244 (om.has_field("identity")?1:0) 244 (om.has_field("identity")?1:0)
245 )&1 ) 245 )&1 )
246 throw bad_input(OPKELE_CP_ "claimed_id and identity must be either both present or both absent"); 246 throw bad_input(OPKELE_CP_ "claimed_id and identity must be either both present or both absent");
247 247
248 string turl = util::rfc_3986_normalize_uri(get_this_url()); 248 string turl = util::rfc_3986_normalize_uri(get_this_url());
249 util::strip_uri_fragment_part(turl); 249 util::strip_uri_fragment_part(turl);
250 string rurl = util::rfc_3986_normalize_uri(om.get_field("return_to")); 250 string rurl = util::rfc_3986_normalize_uri(om.get_field("return_to"));
251 util::strip_uri_fragment_part(rurl); 251 util::strip_uri_fragment_part(rurl);
252 string::size_type 252 string::size_type
253 tq = turl.find('?'), rq = rurl.find('?'); 253 tq = turl.find('?'), rq = rurl.find('?');
254 if( 254 if(
255 ((tq==string::npos)?turl:turl.substr(0,tq)) 255 ((tq==string::npos)?turl:turl.substr(0,tq))
256 != 256 !=
257 ((rq==string::npos)?rurl:rurl.substr(0,rq)) 257 ((rq==string::npos)?rurl:rurl.substr(0,rq))
258 ) 258 )
259 throw id_res_bad_return_to(OPKELE_CP_ "return_to url doesn't match request url"); 259 throw id_res_bad_return_to(OPKELE_CP_ "return_to url doesn't match request url");
260 map<string,string> tp; parse_query(turl,tq,tp); 260 map<string,string> tp; parse_query(turl,tq,tp);
261 map<string,string> rp; parse_query(rurl,rq,rp); 261 map<string,string> rp; parse_query(rurl,rq,rp);
262 for(map<string,string>::const_iterator rpi=rp.begin();rpi!=rp.end();++rpi) { 262 for(map<string,string>::const_iterator rpi=rp.begin();rpi!=rp.end();++rpi) {
263 map<string,string>::const_iterator tpi = tp.find(rpi->first); 263 map<string,string>::const_iterator tpi = tp.find(rpi->first);
264 if(tpi==tp.end()) 264 if(tpi==tp.end())
265 throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to is missing from the request"); 265 throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to is missing from the request");
266 if(tpi->second!=rpi->second) 266 if(tpi->second!=rpi->second)
267 throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to doesn't matche the request"); 267 throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to doesn't matche the request");
268 } 268 }
269 269
270 if(om.has_field("claimed_id")) { 270 if(om.has_field("claimed_id")) {
271 verify_OP( 271 verify_OP(
272 om.get_field("op_endpoint"), 272 om.get_field("op_endpoint"),
273 om.get_field("claimed_id"), 273 om.get_field("claimed_id"),
274 om.get_field("identity") ); 274 om.get_field("identity") );
275 } 275 }
276 276
277 } 277 }
278 if(ext) ext->id_res_hook(om,signeds); 278 if(ext) ext->rp_id_res_hook(om,signeds);
279 } 279 }
280 280
281 void basic_RP::check_authentication(const string& OP, 281 void basic_RP::check_authentication(const string& OP,
282 const basic_openid_message& om){ 282 const basic_openid_message& om){
283 openid_message_t res; 283 openid_message_t res;
284 static const string checkauthmode = "check_authentication"; 284 static const string checkauthmode = "check_authentication";
285 direct_request(res,util::change_mode_message_proxy(om,checkauthmode),OP); 285 direct_request(res,util::change_mode_message_proxy(om,checkauthmode),OP);
286 if(res.has_field("is_valid")) { 286 if(res.has_field("is_valid")) {
287 if(res.get_field("is_valid")=="true") { 287 if(res.get_field("is_valid")=="true") {
288 if(res.has_field("invalidate_handle")) 288 if(res.has_field("invalidate_handle"))
289 invalidate_assoc(OP,res.get_field("invalidate_handle")); 289 invalidate_assoc(OP,res.get_field("invalidate_handle"));
290 return; 290 return;
291 } 291 }
292 } 292 }
293 throw failed_check_authentication( 293 throw failed_check_authentication(
294 OPKELE_CP_ "failed to verify response"); 294 OPKELE_CP_ "failed to verify response");
295 } 295 }
296 296
297} 297}
diff --git a/lib/extension.cc b/lib/extension.cc
index 6451249..f7aaea5 100644
--- a/lib/extension.cc
+++ b/lib/extension.cc
@@ -1,15 +1,26 @@
1#include <opkele/exception.h> 1#include <opkele/exception.h>
2#include <opkele/extension.h> 2#include <opkele/extension.h>
3 3
4namespace opkele { 4namespace opkele {
5 5
6 void extension_t::rp_checkid_hook(basic_openid_message&) {
7 throw not_implemented(OPKELE_CP_ "RP checkid_* hook not implemented"); }
8 void extension_t::rp_id_res_hook(const basic_openid_message&,
9 const basic_openid_message&) {
10 throw not_implemented(OPKELE_CP_ "RP id_res hook not implemented"); }
11
12 void extension_t::op_checkid_hook(const basic_openid_message&) {
13 throw not_implemented(OPKELE_CP_ "OP checkid_* hook not implemented"); }
14 void extension_t::op_id_res_hook(basic_openid_message& om) {
15 throw not_implemented(OPKELE_CP_ "OP id_res hook not implemented"); }
16
17
6 void extension_t::checkid_hook(basic_openid_message&) { 18 void extension_t::checkid_hook(basic_openid_message&) {
7 throw not_implemented(OPKELE_CP_ "Consumer checkid_hook not implemented"); 19 throw not_implemented(OPKELE_CP_ "deprecated consumer checkid_* hook not implemented"); }
8 } 20 void extension_t::id_res_hook(const basic_openid_message&,
9 void extension_t::id_res_hook(const basic_openid_message&,const basic_openid_message&) { 21 const basic_openid_message&) {
10 throw not_implemented(OPKELE_CP_ "Consumer id_res_hook not implemented"); 22 throw not_implemented(OPKELE_CP_ "deprecated consumer id_res hook not implemented"); }
11 } 23
12 void extension_t::checkid_hook(const basic_openid_message&,basic_openid_message&) { 24 void extension_t::checkid_hook(const basic_openid_message&,basic_openid_message&) {
13 throw not_implemented(OPKELE_CP_ "Server checkid_hook not implemented"); 25 throw not_implemented(OPKELE_CP_ "deprecated server checkid hook not implemented"); }
14 }
15} 26}
diff --git a/lib/extension_chain.cc b/lib/extension_chain.cc
index 5c2afd9..5483740 100644
--- a/lib/extension_chain.cc
+++ b/lib/extension_chain.cc
@@ -1,16 +1,27 @@
1#include <cstdarg> 1#include <cstdarg>
2#include <opkele/extension_chain.h> 2#include <opkele/extension_chain.h>
3 3
4namespace opkele { 4namespace opkele {
5 5
6 void extension_chain_t::rp_checkid_hook(basic_openid_message& om) {
7 for(iterator i=begin();i!=end();++i) (*i)->rp_checkid_hook(om); }
8 void extension_chain_t::rp_id_res_hook(const basic_openid_message& om,
9 const basic_openid_message& sp) {
10 for(iterator i=begin();i!=end();++i) (*i)->rp_id_res_hook(om,sp); }
11
12 void extension_chain_t::op_checkid_hook(const basic_openid_message& inm) {
13 for(iterator i=begin();i!=end();++i) (*i)->op_checkid_hook(inm); }
14 void extension_chain_t::op_id_res_hook(basic_openid_message& oum) {
15 for(iterator i=begin();i!=end();++i) (*i)->op_id_res_hook(oum); }
16
17
6 void extension_chain_t::checkid_hook(basic_openid_message& om){ 18 void extension_chain_t::checkid_hook(basic_openid_message& om){
7 for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(om); 19 for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(om); }
8 } 20 void extension_chain_t::id_res_hook(const basic_openid_message& om,
9 void extension_chain_t::id_res_hook(const basic_openid_message& om,const basic_openid_message& sp) { 21 const basic_openid_message& sp) {
10 for(iterator i=begin();i!=end();++i) (*i)->id_res_hook(om,sp); 22 for(iterator i=begin();i!=end();++i) (*i)->id_res_hook(om,sp); }
11 } 23 void extension_chain_t::checkid_hook(const basic_openid_message& inm,
12 void extension_chain_t::checkid_hook(const basic_openid_message& inm,basic_openid_message& oum) { 24 basic_openid_message& oum) {
13 for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(inm,oum); 25 for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(inm,oum); }
14 }
15 26
16} 27}
diff --git a/lib/sreg.cc b/lib/sreg.cc
index 7e2d588..b40cd45 100644
--- a/lib/sreg.cc
+++ b/lib/sreg.cc
@@ -1,140 +1,160 @@
1#include <opkele/exception.h> 1#include <opkele/exception.h>
2#include <opkele/sreg.h> 2#include <opkele/sreg.h>
3#include <opkele/uris.h> 3#include <opkele/uris.h>
4#include <algorithm> 4#include <algorithm>
5 5
6namespace opkele { 6namespace opkele {
7 using std::find; 7 using std::find;
8 8
9 static const struct _sreg_field { 9 static const struct _sreg_field {
10 const char *fieldname; 10 const char *fieldname;
11 sreg_t::fieldbit_t fieldbit; 11 sreg_t::fieldbit_t fieldbit;
12 }fields[] = { 12 }fields[] = {
13 { "nickname", sreg_t::field_nickname }, 13 { "nickname", sreg_t::field_nickname },
14 { "email", sreg_t::field_email }, 14 { "email", sreg_t::field_email },
15 { "fullname", sreg_t::field_fullname }, 15 { "fullname", sreg_t::field_fullname },
16 { "dob", sreg_t::field_dob }, 16 { "dob", sreg_t::field_dob },
17 { "gender", sreg_t::field_gender }, 17 { "gender", sreg_t::field_gender },
18 { "postcode", sreg_t::field_postcode }, 18 { "postcode", sreg_t::field_postcode },
19 { "country", sreg_t::field_country }, 19 { "country", sreg_t::field_country },
20 { "language", sreg_t::field_language }, 20 { "language", sreg_t::field_language },
21 { "timezone", sreg_t::field_timezone } 21 { "timezone", sreg_t::field_timezone }
22 }; 22 };
23 # define fields_BEGINfields 23 # define fields_BEGINfields
24# define fields_END &fields[sizeof(fields)/sizeof(*fields)] 24# define fields_END &fields[sizeof(fields)/sizeof(*fields)]
25 typedef const struct _sreg_field *fields_iterator; 25 typedef const struct _sreg_field *fields_iterator;
26 26
27 bool operator==(const struct _sreg_field& fd,const string& fn) { 27 bool operator==(const struct _sreg_field& fd,const string& fn) {
28 return fd.fieldname==fn; 28 return fd.fieldname==fn;
29 } 29 }
30 30
31 void sreg_t::checkid_hook(basic_openid_message& om) { 31 void sreg_t::rp_checkid_hook(basic_openid_message& om) {
32 string fr, fo; 32 string fr, fo;
33 for(fields_iterator f=fields_BEGIN;f<fields_END;++f) { 33 for(fields_iterator f=fields_BEGIN;f<fields_END;++f) {
34 if(f->fieldbit&fields_required) { 34 if(f->fieldbit&fields_required) {
35 if(!fr.empty()) fr+=","; 35 if(!fr.empty()) fr+=",";
36 fr += f->fieldname; 36 fr += f->fieldname;
37 } 37 }
38 if(f->fieldbit&fields_optional) { 38 if(f->fieldbit&fields_optional) {
39 if(!fo.empty()) fo+=","; 39 if(!fo.empty()) fo+=",";
40 fo += f->fieldname; 40 fo += f->fieldname;
41 } 41 }
42 } 42 }
43 string pfx = om.allocate_ns(OIURI_SREG11,"sreg"); 43 string pfx = om.allocate_ns(OIURI_SREG11,"sreg");
44 if(!fr.empty()) om.set_field(pfx+".required",fr); 44 if(!fr.empty()) om.set_field(pfx+".required",fr);
45 if(!fo.empty()) om.set_field(pfx+".optional",fo); 45 if(!fo.empty()) om.set_field(pfx+".optional",fo);
46 if(!policy_url.empty()) om.set_field(pfx+".policy_url",policy_url); 46 if(!policy_url.empty()) om.set_field(pfx+".policy_url",policy_url);
47 } 47 }
48 48
49 void sreg_t::id_res_hook(const basic_openid_message& om,const basic_openid_message& sp) { 49 void sreg_t::checkid_hook(basic_openid_message& om) {
50 rp_checkid_hook(om); }
51
52 void sreg_t::rp_id_res_hook(const basic_openid_message& om,
53 const basic_openid_message& sp) {
50 clear(); 54 clear();
51 string pfx; 55 string pfx;
52 try { 56 try {
53 pfx = om.find_ns(OIURI_SREG11,"sreg"); 57 pfx = om.find_ns(OIURI_SREG11,"sreg");
54 }catch(failed_lookup& fl) { 58 }catch(failed_lookup& fl) {
55 try { 59 try {
56 pfx = om.find_ns(OIURI_SREG10,"sreg"); 60 pfx = om.find_ns(OIURI_SREG10,"sreg");
57 }catch(failed_lookup& fl) { 61 }catch(failed_lookup& fl) {
58 return; 62 return;
59 } 63 }
60 } 64 }
61 pfx += '.'; 65 pfx += '.';
62 for(fields_iterator f=fields_BEGIN;f<fields_END;++f) { 66 for(fields_iterator f=fields_BEGIN;f<fields_END;++f) {
63 string fn = pfx; fn+=f->fieldname; 67 string fn = pfx; fn+=f->fieldname;
64 if(!sp.has_field(fn)) continue; 68 if(!sp.has_field(fn)) continue;
65 has_fields |= f->fieldbit; 69 has_fields |= f->fieldbit;
66 response[f->fieldbit]=sp.get_field(fn); 70 response[f->fieldbit]=sp.get_field(fn);
67 } 71 }
68 } 72 }
69 73
74 void sreg_t::id_res_hook(const basic_openid_message& om,
75 const basic_openid_message& sp) {
76 rp_id_res_hook(om,sp); }
77
70 const string& sreg_t::get_field(fieldbit_t fb) const { 78 const string& sreg_t::get_field(fieldbit_t fb) const {
71 response_t::const_iterator i = response.find(fb); 79 response_t::const_iterator i = response.find(fb);
72 if(i==response.end()) 80 if(i==response.end())
73 throw failed_lookup(OPKELE_CP_ "no field data available"); 81 throw failed_lookup(OPKELE_CP_ "no field data available");
74 return i->second; 82 return i->second;
75 } 83 }
76 84
77 void sreg_t::set_field(fieldbit_t fb,const string& fv) { 85 void sreg_t::set_field(fieldbit_t fb,const string& fv) {
78 response[fb] = fv; 86 response[fb] = fv;
79 has_fields |= fb; 87 has_fields |= fb;
80 } 88 }
81 89
82 void sreg_t::reset_field(fieldbit_t fb) { 90 void sreg_t::reset_field(fieldbit_t fb) {
83 has_fields &= ~fb; 91 has_fields &= ~fb;
84 response.erase(fb); 92 response.erase(fb);
85 } 93 }
86 94
87 void sreg_t::clear() { 95 void sreg_t::clear() {
88 has_fields = 0; response.clear(); 96 has_fields = 0; response.clear();
89 } 97 }
90 98
91 static long fields_list_to_bitmask(string& fl) { 99 static long fields_list_to_bitmask(string& fl) {
92 long rv = 0; 100 long rv = 0;
93 while(!fl.empty()) { 101 while(!fl.empty()) {
94 string::size_type co = fl.find(','); 102 string::size_type co = fl.find(',');
95 string fn; 103 string fn;
96 if(co==string::npos) { 104 if(co==string::npos) {
97 fn = fl; fl.erase(); 105 fn = fl; fl.erase();
98 }else{ 106 }else{
99 fn = fl.substr(0,co); fl.erase(0,co+1); 107 fn = fl.substr(0,co); fl.erase(0,co+1);
100 } 108 }
101 fields_iterator f = find(fields_BEGIN,fields_END,fn); 109 fields_iterator f = find(fields_BEGIN,fields_END,fn);
102 if(f!=fields_END) 110 if(f!=fields_END)
103 rv |= f->fieldbit; 111 rv |= f->fieldbit;
104 } 112 }
105 return rv; 113 return rv;
106 } 114 }
107 115
108 void sreg_t::checkid_hook(const basic_openid_message& inm,basic_openid_message& oum) { 116 void sreg_t::op_checkid_hook(const basic_openid_message& inm) {
109 string ins = inm.find_ns(OIURI_SREG11,"sreg"); 117 string ins = inm.find_ns(OIURI_SREG11,"sreg");
110 fields_optional = 0; fields_required = 0; policy_url.erase(); 118 fields_optional = 0; fields_required = 0; policy_url.erase();
111 fields_response = 0; 119 fields_response = 0;
112 try { 120 try {
113 string fl = inm.get_field(ins+".required"); 121 string fl = inm.get_field(ins+".required");
114 fields_required = fields_list_to_bitmask(fl); 122 fields_required = fields_list_to_bitmask(fl);
115 }catch(failed_lookup&) { } 123 }catch(failed_lookup&) { }
116 try { 124 try {
117 string fl = inm.get_field(ins+".optional"); 125 string fl = inm.get_field(ins+".optional");
118 fields_optional = fields_list_to_bitmask(fl); 126 fields_optional = fields_list_to_bitmask(fl);
119 }catch(failed_lookup&) { } 127 }catch(failed_lookup&) { }
120 try { 128 try {
121 policy_url = inm.get_field(ins+".policy_url"); 129 policy_url = inm.get_field(ins+".policy_url");
122 }catch(failed_lookup&) { } 130 }catch(failed_lookup&) { }
123 setup_response(inm,oum); 131 }
132
133 void sreg_t::op_id_res_hook(basic_openid_message& oum) {
124 string ons = oum.allocate_ns(OIURI_SREG11,"sreg"); 134 string ons = oum.allocate_ns(OIURI_SREG11,"sreg");
125 fields_response &= has_fields; 135 fields_response &= has_fields;
126 string signeds = "ns."+ons; 136 string signeds = "ns."+ons;
127 for(fields_iterator f=fields_BEGIN;f<fields_END;++f) { 137 for(fields_iterator f=fields_BEGIN;f<fields_END;++f) {
128 if(!(f->fieldbit&fields_response)) continue; 138 if(!(f->fieldbit&fields_response)) continue;
129 signeds +=','; 139 signeds +=',';
130 string pn = ons; pn += '.'; pn += f->fieldname; 140 string pn = ons; pn += '.'; pn += f->fieldname;
131 signeds += pn; 141 signeds += pn;
132 oum.set_field(pn,get_field(f->fieldbit)); 142 oum.set_field(pn,get_field(f->fieldbit));
133 } 143 }
134 oum.add_to_signed(signeds); 144 oum.add_to_signed(signeds);
135 } 145 }
136 146
147 void sreg_t::checkid_hook(const basic_openid_message& inm,
148 basic_openid_message& oum) {
149 op_checkid_hook(inm);
150 setup_response(inm,oum);
151 op_id_res_hook(oum);
152 }
153
137 void sreg_t::setup_response(const basic_openid_message& /* inm */,basic_openid_message& /* oum */) { 154 void sreg_t::setup_response(const basic_openid_message& /* inm */,basic_openid_message& /* oum */) {
155 setup_response();
156 }
157 void sreg_t::setup_response() {
138 fields_response = (fields_required|fields_optional)&has_fields; 158 fields_response = (fields_required|fields_optional)&has_fields;
139 } 159 }
140} 160}
diff --git a/test/OP.cc b/test/OP.cc
index 1196c0c..c919d7f 100644
--- a/test/OP.cc
+++ b/test/OP.cc
@@ -1,409 +1,415 @@
1#include <uuid/uuid.h> 1#include <uuid/uuid.h>
2#include <iostream> 2#include <iostream>
3#include <cassert> 3#include <cassert>
4#include <string> 4#include <string>
5#include <ext/algorithm> 5#include <ext/algorithm>
6using namespace std; 6using namespace std;
7#include <kingate/exception.h> 7#include <kingate/exception.h>
8#include <kingate/plaincgi.h> 8#include <kingate/plaincgi.h>
9#include <kingate/cgi_gateway.h> 9#include <kingate/cgi_gateway.h>
10#include <opkele/exception.h> 10#include <opkele/exception.h>
11#include <opkele/util.h> 11#include <opkele/util.h>
12#include <opkele/uris.h> 12#include <opkele/uris.h>
13#include <opkele/extension.h> 13#include <opkele/extension.h>
14#include <opkele/association.h> 14#include <opkele/association.h>
15#include <opkele/debug.h> 15#include <opkele/debug.h>
16#include <opkele/verify_op.h> 16#include <opkele/verify_op.h>
17#include <opkele/sreg.h>
17 18
18#include "sqlite.h" 19#include "sqlite.h"
19#include "kingate_openid_message.h" 20#include "kingate_openid_message.h"
20 21
21static const string get_self_url(const kingate::cgi_gateway& gw) { 22static const string get_self_url(const kingate::cgi_gateway& gw) {
22 bool s = gw.has_meta("SSL_PROTOCOL_VERSION"); 23 bool s = gw.has_meta("SSL_PROTOCOL_VERSION");
23 string rv = s?"https://":"http://"; 24 string rv = s?"https://":"http://";
24 rv += gw.http_request_header("Host"); 25 rv += gw.http_request_header("Host");
25 const string& port = gw.get_meta("SERVER_PORT"); 26 const string& port = gw.get_meta("SERVER_PORT");
26 if( port!=(s?"443":"80") ) { 27 if( port!=(s?"443":"80") ) {
27 rv += ':'; rv += port; 28 rv += ':'; rv += port;
28 } 29 }
29 rv += gw.get_meta("REQUEST_URI"); 30 rv += gw.get_meta("REQUEST_URI");
30 string::size_type q = rv.find('?'); 31 string::size_type q = rv.find('?');
31 if(q!=string::npos) 32 if(q!=string::npos)
32 rv.erase(q); 33 rv.erase(q);
33 return rv; 34 return rv;
34} 35}
35 36
36class opdb_t : public sqlite3_t { 37class opdb_t : public sqlite3_t {
37 public: 38 public:
38 opdb_t() 39 opdb_t()
39 : sqlite3_t("/tmp/OP.db") { 40 : sqlite3_t("/tmp/OP.db") {
40 assert(_D); 41 assert(_D);
41 char **resp; int nr,nc; char *errm; 42 char **resp; int nr,nc; char *errm;
42 if(sqlite3_get_table( 43 if(sqlite3_get_table(
43 _D, "SELECT a_op FROM assoc LIMIT 0", 44 _D, "SELECT a_op FROM assoc LIMIT 0",
44 &resp,&nr,&nc,&errm)!=SQLITE_OK) { 45 &resp,&nr,&nc,&errm)!=SQLITE_OK) {
45 extern const char *__OP_db_bootstrap; 46 extern const char *__OP_db_bootstrap;
46 DOUT_("Bootstrapping DB"); 47 DOUT_("Bootstrapping DB");
47 if(sqlite3_exec(_D,__OP_db_bootstrap,NULL,NULL,&errm)!=SQLITE_OK) 48 if(sqlite3_exec(_D,__OP_db_bootstrap,NULL,NULL,&errm)!=SQLITE_OK)
48 throw opkele::exception(OPKELE_CP_ string("Failed to boostrap SQLite database: ")+errm); 49 throw opkele::exception(OPKELE_CP_ string("Failed to boostrap SQLite database: ")+errm);
49 }else 50 }else
50 sqlite3_free_table(resp); 51 sqlite3_free_table(resp);
51 } 52 }
52}; 53};
53 54
54class example_op_t : public opkele::verify_op { 55class example_op_t : public opkele::verify_op {
55 public: 56 public:
56 kingate::cgi_gateway& gw; 57 kingate::cgi_gateway& gw;
57 opdb_t db; 58 opdb_t db;
58 kingate::cookie htc; 59 kingate::cookie htc;
59 60
60 61
61 example_op_t(kingate::cgi_gateway& gw) 62 example_op_t(kingate::cgi_gateway& gw)
62 : gw(gw) { 63 : gw(gw) {
63 try { 64 try {
64 htc = gw.cookies.get_cookie("htop_session"); 65 htc = gw.cookies.get_cookie("htop_session");
65 sqlite3_mem_t<char*> S = sqlite3_mprintf( 66 sqlite3_mem_t<char*> S = sqlite3_mprintf(
66 "SELECT 1 FROM ht_sessions WHERE hts_id=%Q", 67 "SELECT 1 FROM ht_sessions WHERE hts_id=%Q",
67 htc.get_value().c_str()); 68 htc.get_value().c_str());
68 sqlite3_table_t T; int nr,nc; 69 sqlite3_table_t T; int nr,nc;
69 db.get_table(S,T,&nr,&nc); 70 db.get_table(S,T,&nr,&nc);
70 if(nr<1) 71 if(nr<1)
71 throw kingate::exception_notfound(CODEPOINT,"forcing cookie generation"); 72 throw kingate::exception_notfound(CODEPOINT,"forcing cookie generation");
72 }catch(kingate::exception_notfound& kenf) { 73 }catch(kingate::exception_notfound& kenf) {
73 uuid_t uuid; uuid_generate(uuid); 74 uuid_t uuid; uuid_generate(uuid);
74 htc = kingate::cookie("htop_session",opkele::util::encode_base64(uuid,sizeof(uuid))); 75 htc = kingate::cookie("htop_session",opkele::util::encode_base64(uuid,sizeof(uuid)));
75 sqlite3_mem_t<char*> S = sqlite3_mprintf( 76 sqlite3_mem_t<char*> S = sqlite3_mprintf(
76 "INSERT INTO ht_sessions (hts_id) VALUES (%Q)", 77 "INSERT INTO ht_sessions (hts_id) VALUES (%Q)",
77 htc.get_value().c_str()); 78 htc.get_value().c_str());
78 db.exec(S); 79 db.exec(S);
79 } 80 }
80 } 81 }
81 82
82 void set_authorized(bool a) { 83 void set_authorized(bool a) {
83 sqlite3_mem_t<char*> 84 sqlite3_mem_t<char*>
84 S = sqlite3_mprintf( 85 S = sqlite3_mprintf(
85 "UPDATE ht_sessions" 86 "UPDATE ht_sessions"
86 " SET authorized=%d" 87 " SET authorized=%d"
87 " WHERE hts_id=%Q", 88 " WHERE hts_id=%Q",
88 (int)a,htc.get_value().c_str()); 89 (int)a,htc.get_value().c_str());
89 db.exec(S); 90 db.exec(S);
90 } 91 }
91 bool get_authorized() { 92 bool get_authorized() {
92 sqlite3_mem_t<char*> 93 sqlite3_mem_t<char*>
93 S = sqlite3_mprintf( 94 S = sqlite3_mprintf(
94 "SELECT authorized" 95 "SELECT authorized"
95 " FROM ht_sessions" 96 " FROM ht_sessions"
96 " WHERE hts_id=%Q", 97 " WHERE hts_id=%Q",
97 htc.get_value().c_str()); 98 htc.get_value().c_str());
98 sqlite3_table_t T; int nr,nc; 99 sqlite3_table_t T; int nr,nc;
99 db.get_table(S,T,&nr,&nc); 100 db.get_table(S,T,&nr,&nc);
100 assert(nr==1); assert(nc=1); 101 assert(nr==1); assert(nc=1);
101 return opkele::util::string_to_long(T.get(1,0,nc)); 102 return opkele::util::string_to_long(T.get(1,0,nc));
102 } 103 }
103 104
104 ostream& cookie_header(ostream& o) const { 105 ostream& cookie_header(ostream& o) const {
105 o << "Set-Cookie: " << htc.set_cookie_header() << "\n"; 106 o << "Set-Cookie: " << htc.set_cookie_header() << "\n";
106 return o; 107 return o;
107 } 108 }
108 109
109 opkele::assoc_t alloc_assoc(const string& type,size_t klength,bool sl) { 110 opkele::assoc_t alloc_assoc(const string& type,size_t klength,bool sl) {
110 uuid_t uuid; uuid_generate(uuid); 111 uuid_t uuid; uuid_generate(uuid);
111 string a_handle = opkele::util::encode_base64(uuid,sizeof(uuid)); 112 string a_handle = opkele::util::encode_base64(uuid,sizeof(uuid));
112 opkele::secret_t a_secret; 113 opkele::secret_t a_secret;
113 generate_n( 114 generate_n(
114 back_insert_iterator<opkele::secret_t>(a_secret),klength, 115 back_insert_iterator<opkele::secret_t>(a_secret),klength,
115 rand ); 116 rand );
116 string ssecret; a_secret.to_base64(ssecret); 117 string ssecret; a_secret.to_base64(ssecret);
117 time_t now = time(0); 118 time_t now = time(0);
118 int expires_in = sl?3600*2:3600*24*7*2; 119 int expires_in = sl?3600*2:3600*24*7*2;
119 sqlite3_mem_t<char*> 120 sqlite3_mem_t<char*>
120 S = sqlite3_mprintf( 121 S = sqlite3_mprintf(
121 "INSERT INTO assoc" 122 "INSERT INTO assoc"
122 " (a_handle,a_type,a_ctime,a_etime,a_secret,a_stateless)" 123 " (a_handle,a_type,a_ctime,a_etime,a_secret,a_stateless)"
123 " VALUES (" 124 " VALUES ("
124 " %Q,%Q,datetime('now')," 125 " %Q,%Q,datetime('now'),"
125 " datetime('now','+%d seconds')," 126 " datetime('now','+%d seconds'),"
126 " %Q,%d );", 127 " %Q,%d );",
127 a_handle.c_str(), type.c_str(), 128 a_handle.c_str(), type.c_str(),
128 expires_in, 129 expires_in,
129 ssecret.c_str(), sl ); 130 ssecret.c_str(), sl );
130 db.exec(S); 131 db.exec(S);
131 return opkele::assoc_t(new opkele::association( 132 return opkele::assoc_t(new opkele::association(
132 "", 133 "",
133 a_handle, type, a_secret, 134 a_handle, type, a_secret,
134 now+expires_in, sl )); 135 now+expires_in, sl ));
135 } 136 }
136 137
137 opkele::assoc_t retrieve_assoc(const string& h) { 138 opkele::assoc_t retrieve_assoc(const string& h) {
138 sqlite3_mem_t<char*> 139 sqlite3_mem_t<char*>
139 S = sqlite3_mprintf( 140 S = sqlite3_mprintf(
140 "SELECT" 141 "SELECT"
141 " a_handle,a_type,a_secret,a_stateless," 142 " a_handle,a_type,a_secret,a_stateless,"
142 " strftime('%%s',a_etime) AS a_etime," 143 " strftime('%%s',a_etime) AS a_etime,"
143 " a_itime" 144 " a_itime"
144 " FROM assoc" 145 " FROM assoc"
145 " WHERE a_handle=%Q AND a_itime IS NULL" 146 " WHERE a_handle=%Q AND a_itime IS NULL"
146 " AND datetime('now') < a_etime" 147 " AND datetime('now') < a_etime"
147 " LIMIT 1", 148 " LIMIT 1",
148 h.c_str() ); 149 h.c_str() );
149 sqlite3_table_t T; 150 sqlite3_table_t T;
150 int nr,nc; 151 int nr,nc;
151 db.get_table(S,T,&nr,&nc); 152 db.get_table(S,T,&nr,&nc);
152 if(nr<1) 153 if(nr<1)
153 throw opkele::failed_lookup(OPKELE_CP_ 154 throw opkele::failed_lookup(OPKELE_CP_
154 "couldn't retrieve valid unexpired assoc"); 155 "couldn't retrieve valid unexpired assoc");
155 assert(nr==1); assert(nc==6); 156 assert(nr==1); assert(nc==6);
156 opkele::secret_t secret; opkele::util::decode_base64(T.get(1,2,nc),secret); 157 opkele::secret_t secret; opkele::util::decode_base64(T.get(1,2,nc),secret);
157 return opkele::assoc_t(new opkele::association( 158 return opkele::assoc_t(new opkele::association(
158 "", h, T.get(1,1,nc), secret, 159 "", h, T.get(1,1,nc), secret,
159 strtol(T.get(1,4,nc),0,0), 160 strtol(T.get(1,4,nc),0,0),
160 strtol(T.get(1,3,nc),0,0) )); 161 strtol(T.get(1,3,nc),0,0) ));
161 } 162 }
162 163
163 string& alloc_nonce(string& nonce,bool stateless) { 164 string& alloc_nonce(string& nonce,bool stateless) {
164 uuid_t uuid; uuid_generate(uuid); 165 uuid_t uuid; uuid_generate(uuid);
165 nonce += opkele::util::encode_base64(uuid,sizeof(uuid)); 166 nonce += opkele::util::encode_base64(uuid,sizeof(uuid));
166 sqlite3_mem_t<char*> 167 sqlite3_mem_t<char*>
167 S = sqlite3_mprintf( 168 S = sqlite3_mprintf(
168 "INSERT INTO nonces" 169 "INSERT INTO nonces"
169 " (n_once) VALUES (%Q)", 170 " (n_once) VALUES (%Q)",
170 nonce.c_str() ); 171 nonce.c_str() );
171 db.exec(S); 172 db.exec(S);
172 return nonce; 173 return nonce;
173 } 174 }
174 bool check_nonce(const string& nonce) { 175 bool check_nonce(const string& nonce) {
175 sqlite3_mem_t<char*> 176 sqlite3_mem_t<char*>
176 S = sqlite3_mprintf( 177 S = sqlite3_mprintf(
177 "SELECT 1" 178 "SELECT 1"
178 " FROM nonces" 179 " FROM nonces"
179 " WHERE n_once=%Q AND n_itime IS NULL", 180 " WHERE n_once=%Q AND n_itime IS NULL",
180 nonce.c_str()); 181 nonce.c_str());
181 sqlite3_table_t T; 182 sqlite3_table_t T;
182 int nr,nc; 183 int nr,nc;
183 db.get_table(S,T,&nr,&nc); 184 db.get_table(S,T,&nr,&nc);
184 return nr>=1; 185 return nr>=1;
185 } 186 }
186 void invalidate_nonce(const string& nonce) { 187 void invalidate_nonce(const string& nonce) {
187 sqlite3_mem_t<char*> 188 sqlite3_mem_t<char*>
188 S = sqlite3_mprintf( 189 S = sqlite3_mprintf(
189 "UPDATE nonces" 190 "UPDATE nonces"
190 " SET n_itime=datetime('now')" 191 " SET n_itime=datetime('now')"
191 " WHERE n_once=%Q", 192 " WHERE n_once=%Q",
192 nonce.c_str()); 193 nonce.c_str());
193 db.exec(S); 194 db.exec(S);
194 } 195 }
195 196
196 const string get_op_endpoint() const { 197 const string get_op_endpoint() const {
197 return get_self_url(gw); 198 return get_self_url(gw);
198 } 199 }
199 200
200}; 201};
201 202
202int main(int argc,char *argv[]) { 203int main(int argc,char *argv[]) {
203 try { 204 try {
204 kingate::plaincgi_interface ci; 205 kingate::plaincgi_interface ci;
205 kingate::cgi_gateway gw(ci); 206 kingate::cgi_gateway gw(ci);
206 string op; 207 string op;
207 try { op = gw.get_param("op"); }catch(kingate::exception_notfound&) { } 208 try { op = gw.get_param("op"); }catch(kingate::exception_notfound&) { }
208 string message; 209 string message;
209 if(op=="set_password") { 210 if(op=="set_password") {
210 example_op_t OP(gw); 211 example_op_t OP(gw);
211 string password = gw.get_param("password"); 212 string password = gw.get_param("password");
212 sqlite3_mem_t<char*> 213 sqlite3_mem_t<char*>
213 Sget = sqlite3_mprintf("SELECT s_password FROM setup LIMIT 1"); 214 Sget = sqlite3_mprintf("SELECT s_password FROM setup LIMIT 1");
214 sqlite3_table_t T; int nr,nc; 215 sqlite3_table_t T; int nr,nc;
215 OP.db.get_table(Sget,T,&nr,&nc); 216 OP.db.get_table(Sget,T,&nr,&nc);
216 if(nr>=1) 217 if(nr>=1)
217 throw opkele::exception(OPKELE_CP_ "Password already set"); 218 throw opkele::exception(OPKELE_CP_ "Password already set");
218 sqlite3_mem_t<char*> 219 sqlite3_mem_t<char*>
219 Sset = sqlite3_mprintf( 220 Sset = sqlite3_mprintf(
220 "INSERT INTO setup (s_password) VALUES (%Q)", 221 "INSERT INTO setup (s_password) VALUES (%Q)",
221 password.c_str()); 222 password.c_str());
222 OP.db.exec(Sset); 223 OP.db.exec(Sset);
223 op.clear(); 224 op.clear();
224 message = "password set"; 225 message = "password set";
225 }else if(op=="login") { 226 }else if(op=="login") {
226 example_op_t OP(gw); 227 example_op_t OP(gw);
227 string password = gw.get_param("password"); 228 string password = gw.get_param("password");
228 sqlite3_mem_t<char*> 229 sqlite3_mem_t<char*>
229 Sget = sqlite3_mprintf("SELECT s_password FROM setup LIMIT 1"); 230 Sget = sqlite3_mprintf("SELECT s_password FROM setup LIMIT 1");
230 sqlite3_table_t T; int nr,nc; 231 sqlite3_table_t T; int nr,nc;
231 OP.db.get_table(Sget,T,&nr,&nc); 232 OP.db.get_table(Sget,T,&nr,&nc);
232 if(nr<1) 233 if(nr<1)
233 throw opkele::exception(OPKELE_CP_ "no password set"); 234 throw opkele::exception(OPKELE_CP_ "no password set");
234 if(password!=T.get(1,0,nc)) 235 if(password!=T.get(1,0,nc))
235 throw opkele::exception(OPKELE_CP_ "wrong password"); 236 throw opkele::exception(OPKELE_CP_ "wrong password");
236 OP.set_authorized(true); 237 OP.set_authorized(true);
237 op.clear(); 238 op.clear();
238 message = "logged in"; 239 message = "logged in";
239 OP.cookie_header(cout); 240 OP.cookie_header(cout);
240 }else if(op=="logout") { 241 }else if(op=="logout") {
241 example_op_t OP(gw); 242 example_op_t OP(gw);
242 OP.set_authorized(false); 243 OP.set_authorized(false);
243 op.clear(); 244 op.clear();
244 message = "logged out"; 245 message = "logged out";
245 } 246 }
246 string om; 247 string om;
247 try { om = gw.get_param("openid.mode"); }catch(kingate::exception_notfound&) { } 248 try { om = gw.get_param("openid.mode"); }catch(kingate::exception_notfound&) { }
248 if(op=="xrds") { 249 if(op=="xrds") {
249 cout << 250 cout <<
250 "Content-type: application/xrds+xml\n\n" 251 "Content-type: application/xrds+xml\n\n"
251 "<?xml version='1.0' encoding='utf-8'?>" 252 "<?xml version='1.0' encoding='utf-8'?>"
252 "<xrds:XRDS xmlns:xrds='xri://$xrds' xmlns='xri://$xrd*($v*2.0)'>" 253 "<xrds:XRDS xmlns:xrds='xri://$xrds' xmlns='xri://$xrd*($v*2.0)'>"
253 "<XRD>" 254 "<XRD>"
254 "<Service>" 255 "<Service>"
255 "<Type>" STURI_OPENID20 "</Type>" 256 "<Type>" STURI_OPENID20 "</Type>"
256 "<URI>" << get_self_url(gw) << "</URI>" 257 "<URI>" << get_self_url(gw) << "</URI>"
257 "</Service>"; 258 "</Service>";
258 if(gw.has_param("idsel")){ 259 if(gw.has_param("idsel")){
259 cout << 260 cout <<
260 "<Service>" 261 "<Service>"
261 "<Type>" STURI_OPENID20_OP "</Type>" 262 "<Type>" STURI_OPENID20_OP "</Type>"
262 "<URI>" << get_self_url(gw) << "</URI>"; 263 "<URI>" << get_self_url(gw) << "</URI>";
263 } 264 }
264 cout << 265 cout <<
265 "</XRD>" 266 "</XRD>"
266 "</xrds:XRDS>"; 267 "</xrds:XRDS>";
267 }else if(op=="id_res" || op=="cancel") { 268 }else if(op=="id_res" || op=="cancel") {
268 kingate_openid_message_t inm(gw); 269 kingate_openid_message_t inm(gw);
269 example_op_t OP(gw); 270 example_op_t OP(gw);
270 if(gw.get_param("hts_id")!=OP.htc.get_value()) 271 if(gw.get_param("hts_id")!=OP.htc.get_value())
271 throw opkele::exception(OPKELE_CP_ "toying around, huh?"); 272 throw opkele::exception(OPKELE_CP_ "toying around, huh?");
272 OP.checkid_(inm,0); 273 opkele::sreg_t sreg;
274 OP.checkid_(inm,sreg);
273 OP.cookie_header(cout); 275 OP.cookie_header(cout);
274 opkele::openid_message_t om; 276 opkele::openid_message_t om;
275 if(op=="id_res") { 277 if(op=="id_res") {
276 if(!OP.get_authorized()) 278 if(!OP.get_authorized())
277 throw opkele::exception(OPKELE_CP_ "not logged in"); 279 throw opkele::exception(OPKELE_CP_ "not logged in");
278 if(OP.is_id_select()) { 280 if(OP.is_id_select()) {
279 OP.select_identity( get_self_url(gw), get_self_url(gw) ); 281 OP.select_identity( get_self_url(gw), get_self_url(gw) );
280 } 282 }
283 sreg.set_field(opkele::sreg_t::field_nickname,"anonymous");
284 sreg.set_field(opkele::sreg_t::field_fullname,"Ann O'Nymus");
285 sreg.set_field(opkele::sreg_t::field_gender,"F");
286 sreg.setup_response();
281 cout << 287 cout <<
282 "Status: 302 Going back to RP with id_res\n" 288 "Status: 302 Going back to RP with id_res\n"
283 "Location: " << OP.id_res(om).append_query(OP.get_return_to()) 289 "Location: " << OP.id_res(om,sreg).append_query(OP.get_return_to())
284 << "\n\n"; 290 << "\n\n";
285 }else{ 291 }else{
286 cout << 292 cout <<
287 "Status: 302 Going back to RP with cancel\n" 293 "Status: 302 Going back to RP with cancel\n"
288 "Location: " << OP.cancel(om).append_query(OP.get_return_to()) 294 "Location: " << OP.cancel(om).append_query(OP.get_return_to())
289 << "\n\n"; 295 << "\n\n";
290 } 296 }
291 om.to_keyvalues(clog); 297 om.to_keyvalues(clog);
292 }else if(om=="associate") { 298 }else if(om=="associate") {
293 kingate_openid_message_t inm(gw); 299 kingate_openid_message_t inm(gw);
294 opkele::openid_message_t oum; 300 opkele::openid_message_t oum;
295 example_op_t OP(gw); 301 example_op_t OP(gw);
296 OP.associate(oum,inm); 302 OP.associate(oum,inm);
297 cout << "Content-type: text/plain\n\n"; 303 cout << "Content-type: text/plain\n\n";
298 oum.to_keyvalues(cout); 304 oum.to_keyvalues(cout);
299 }else if(om=="checkid_setup") { 305 }else if(om=="checkid_setup") {
300 kingate_openid_message_t inm(gw); 306 kingate_openid_message_t inm(gw);
301 example_op_t OP(gw); 307 example_op_t OP(gw);
302 OP.checkid_(inm,0); 308 OP.checkid_(inm,0);
303 OP.cookie_header(cout) << 309 OP.cookie_header(cout) <<
304 "Content-type: text/html\n" 310 "Content-type: text/html\n"
305 "\n" 311 "\n"
306 312
307 "<html>" 313 "<html>"
308 "<head>" 314 "<head>"
309 "<title>test OP: confirm authentication</title>" 315 "<title>test OP: confirm authentication</title>"
310 "</head>" 316 "</head>"
311 "<body>" 317 "<body>"
312 "realm: " << OP.get_realm() << "<br/>" 318 "realm: " << OP.get_realm() << "<br/>"
313 "return_to: " << OP.get_return_to() << "<br/>" 319 "return_to: " << OP.get_return_to() << "<br/>"
314 "claimed_id: " << OP.get_claimed_id() << "<br/>" 320 "claimed_id: " << OP.get_claimed_id() << "<br/>"
315 "identity: " << OP.get_identity() << "<br/>"; 321 "identity: " << OP.get_identity() << "<br/>";
316 if(OP.is_id_select()) { 322 if(OP.is_id_select()) {
317 OP.select_identity( get_self_url(gw), get_self_url(gw) ); 323 OP.select_identity( get_self_url(gw), get_self_url(gw) );
318 cout << 324 cout <<
319 "selected claimed_id: " << OP.get_claimed_id() << "<br/>" 325 "selected claimed_id: " << OP.get_claimed_id() << "<br/>"
320 "selected identity: " << OP.get_identity() << "<br/>"; 326 "selected identity: " << OP.get_identity() << "<br/>";
321 } 327 }
322 cout << 328 cout <<
323 "<form method='post'>"; 329 "<form method='post'>";
324 inm.to_htmlhiddens(cout); 330 inm.to_htmlhiddens(cout);
325 cout << 331 cout <<
326 "<input type='hidden' name='hts_id'" 332 "<input type='hidden' name='hts_id'"
327 " value='" << opkele::util::attr_escape(OP.htc.get_value()) << "'/>" 333 " value='" << opkele::util::attr_escape(OP.htc.get_value()) << "'/>"
328 "<input type='submit' name='op' value='id_res'/>" 334 "<input type='submit' name='op' value='id_res'/>"
329 "<input type='submit' name='op' value='cancel'/>" 335 "<input type='submit' name='op' value='cancel'/>"
330 "</form>" 336 "</form>"
331 "</body>" 337 "</body>"
332 "</html>"; 338 "</html>";
333 }else if(om=="check_authentication") { 339 }else if(om=="check_authentication") {
334 kingate_openid_message_t inm(gw); 340 kingate_openid_message_t inm(gw);
335 example_op_t OP(gw); 341 example_op_t OP(gw);
336 opkele::openid_message_t oum; 342 opkele::openid_message_t oum;
337 OP.check_authentication(oum,inm); 343 OP.check_authentication(oum,inm);
338 cout << "Content-type: text/plain\n\n"; 344 cout << "Content-type: text/plain\n\n";
339 oum.to_keyvalues(cout); 345 oum.to_keyvalues(cout);
340 oum.to_keyvalues(clog); 346 oum.to_keyvalues(clog);
341 }else{ 347 }else{
342 example_op_t OP(gw); 348 example_op_t OP(gw);
343 string idsel; 349 string idsel;
344 if(gw.has_param("idsel")) 350 if(gw.has_param("idsel"))
345 idsel = "&idsel=idsel"; 351 idsel = "&idsel=idsel";
346 OP.cookie_header(cout) << 352 OP.cookie_header(cout) <<
347 "Content-type: text/html\n" 353 "Content-type: text/html\n"
348 "X-XRDS-Location: " << get_self_url(gw) << "?op=xrds" << idsel << "\n" 354 "X-XRDS-Location: " << get_self_url(gw) << "?op=xrds" << idsel << "\n"
349 "\n" 355 "\n"
350 356
351 "<html>" 357 "<html>"
352 "<head>" 358 "<head>"
353 "<title>test OP</title>" 359 "<title>test OP</title>"
354 "<link rel='openid.server' href='" << get_self_url(gw) << "'/>" 360 "<link rel='openid.server' href='" << get_self_url(gw) << "'/>"
355 "</head>" 361 "</head>"
356 "<body>" 362 "<body>"
357 "test openid 2.0 endpoint" 363 "test openid 2.0 endpoint"
358 "<br/>" 364 "<br/>"
359 "<a href='" << get_self_url(gw) << "?op=xrds" << idsel << "'>XRDS document</a>" 365 "<a href='" << get_self_url(gw) << "?op=xrds" << idsel << "'>XRDS document</a>"
360 "<br/>" 366 "<br/>"
361 "<h1>" << message << "</h1>"; 367 "<h1>" << message << "</h1>";
362 sqlite3_mem_t<char*> 368 sqlite3_mem_t<char*>
363 S = sqlite3_mprintf("SELECT s_password FROM setup LIMIT 1"); 369 S = sqlite3_mprintf("SELECT s_password FROM setup LIMIT 1");
364 sqlite3_table_t T; int nr,nc; 370 sqlite3_table_t T; int nr,nc;
365 OP.db.get_table(S,T,&nr,&nc); 371 OP.db.get_table(S,T,&nr,&nc);
366 if(nr<1) { 372 if(nr<1) {
367 cout << 373 cout <<
368 "<form method='post'>" 374 "<form method='post'>"
369 "set password " 375 "set password "
370 "<input type='hidden' name='op' value='set_password'/>" 376 "<input type='hidden' name='op' value='set_password'/>"
371 "<input type='password' name='password' value=''/>" 377 "<input type='password' name='password' value=''/>"
372 "<input type='submit' name='submit' value='submit'/>" 378 "<input type='submit' name='submit' value='submit'/>"
373 "</form>"; 379 "</form>";
374 }else if(OP.get_authorized()) { 380 }else if(OP.get_authorized()) {
375 cout << 381 cout <<
376 "<br/>" 382 "<br/>"
377 "<a href='" << get_self_url(gw) << "?op=logout'>logout</a>"; 383 "<a href='" << get_self_url(gw) << "?op=logout'>logout</a>";
378 }else{ 384 }else{
379 cout << 385 cout <<
380 "<form method='post'>" 386 "<form method='post'>"
381 "login " 387 "login "
382 "<input type='hidden' name='op' value='login'/>" 388 "<input type='hidden' name='op' value='login'/>"
383 "<input type='password' name='password' value=''/>" 389 "<input type='password' name='password' value=''/>"
384 "<input type='submit' name='submit' value='submit'/>" 390 "<input type='submit' name='submit' value='submit'/>"
385 "</form>"; 391 "</form>";
386 } 392 }
387 cout << "</body>"; 393 cout << "</body>";
388 } 394 }
389#ifdef OPKELE_HAVE_KONFORKA 395#ifdef OPKELE_HAVE_KONFORKA
390 }catch(konforka::exception& e) { 396 }catch(konforka::exception& e) {
391#else 397#else
392 }catch(std::exception& e){ 398 }catch(std::exception& e){
393#endif 399#endif
394 DOUT_("Oops: " << e.what()); 400 DOUT_("Oops: " << e.what());
395 cout << "Content-Type: text/plain\n\n" 401 cout << "Content-Type: text/plain\n\n"
396 "Exception:\n" 402 "Exception:\n"
397 " what: " << e.what() << endl; 403 " what: " << e.what() << endl;
398#ifdef OPKELE_HAVE_KONFORKA 404#ifdef OPKELE_HAVE_KONFORKA
399 cout << " where: " << e.where() << endl; 405 cout << " where: " << e.where() << endl;
400 if(!e._seen.empty()) { 406 if(!e._seen.empty()) {
401 cout << " seen:" << endl; 407 cout << " seen:" << endl;
402 for(list<konforka::code_point>::const_iterator 408 for(list<konforka::code_point>::const_iterator
403 i=e._seen.begin();i!=e._seen.end();++i) { 409 i=e._seen.begin();i!=e._seen.end();++i) {
404 cout << " " << i->c_str() << endl; 410 cout << " " << i->c_str() << endl;
405 } 411 }
406 } 412 }
407#endif 413#endif
408 } 414 }
409} 415}