2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51 * RPCListener.cpp -- Handles marshalling and connection mgmt for ONC-remoted IListeners
58 #include <saml/saml.h> // need this to "prime" the xmlsec-constrained windows.h declaration
59 #include <shib-target/shibrpc.h>
67 using namespace log4cpp;
69 using namespace shibboleth;
70 using namespace shibtarget;
72 namespace shibtarget {
73 // Wraps the actual RPC connection
77 RPCHandle(Category& log);
80 CLIENT* connect(const RPCListener* listener); // connects and returns the CLIENT handle
81 void disconnect(const RPCListener* listener=NULL); // disconnects, should not return disconnected handles to pool!
86 IListener::ShibSocket m_sock;
89 // Manages the pool of connections
93 RPCHandlePool(Category& log, const RPCListener* listener)
94 : m_log(log), m_listener(listener), m_lock(shibboleth::Mutex::create()) {}
100 const RPCListener* m_listener;
102 auto_ptr<Mutex> m_lock;
103 stack<RPCHandle*> m_pool;
106 // Cleans up after use
110 RPC(RPCHandlePool& pool);
111 ~RPC() {delete m_handle;}
112 RPCHandle* operator->() {return m_handle;}
113 void pool() {if (m_handle) m_pool.put(m_handle); m_handle=NULL;}
117 RPCHandlePool& m_pool;
120 // Local-wrapper for an ISessionCacheEntry
121 class EntryWrapper : public virtual ISessionCacheEntry
124 EntryWrapper(shibrpc_get_session_ret_2& ret, Category& log);
125 ~EntryWrapper() { delete statement; delete pre_response; delete post_response; }
127 void unlock() { delete this; }
128 virtual bool isValid(time_t lifetime, time_t timeout) const { return true; }
129 virtual const char* getClientAddress() const { return NULL; }
130 virtual ShibProfile getProfile() const { return profile; }
131 virtual const char* getProviderId() const { return provider_id.c_str(); }
132 virtual const saml::SAMLAuthenticationStatement* getAuthnStatement() const { return statement; }
133 virtual CachedResponse getResponse() { return CachedResponse(pre_response,post_response); }
138 SAMLAuthenticationStatement* statement;
139 SAMLResponse* pre_response;
140 SAMLResponse* post_response;
145 RPCListener::RPCListener(const DOMElement* e) : log(&Category::getInstance(SHIBT_LOGCAT".Listener"))
147 m_rpcpool=new RPCHandlePool(*log,this);
150 RPCListener::~RPCListener()
155 void RPCListener::sessionNew(
156 const IApplication* application,
157 int supported_profiles,
158 const char* recipient,
167 saml::NDC ndc("sessionNew");
170 if (!packet || !*packet) {
171 log->error("missing profile response");
172 throw FatalProfileException("Profile response missing.");
176 log->error("missing client address");
177 throw FatalProfileException("Invalid client address.");
180 if (supported_profiles <= 0) {
181 log->error("no profile support indicated");
182 throw FatalProfileException("No profile support indicated.");
185 shibrpc_new_session_args_2 arg;
186 arg.recipient = (char*)recipient;
187 arg.application_id = (char*)application->getId();
188 arg.packet = (char*)packet;
189 arg.client_addr = (char*)ip;
190 arg.supported_profiles = supported_profiles;
192 log->info("create session for user at (%s) for application (%s)", ip, arg.application_id);
194 shibrpc_new_session_ret_2 ret;
195 memset(&ret, 0, sizeof(ret));
197 // Loop on the RPC in case we lost contact the first time through
202 clnt = rpc->connect(this);
203 clnt_stat status = shibrpc_new_session_2(&arg, &ret, clnt);
204 if (status != RPC_SUCCESS) {
205 // FAILED. Release, disconnect, and retry
206 log->error("RPC Failure: (CLIENT: %p) (%d): %s", clnt, status, clnt_spcreateerror("shibrpc_new_session_2"));
207 rpc->disconnect(this);
211 throw ListenerException("Failure passing session setup information to listener.");
214 // SUCCESS. Pool and continue
219 if (ret.status && *ret.status)
220 log->debug("RPC completed with exception: %s", ret.status);
222 log->debug("RPC completed successfully");
224 SAMLException* except=NULL;
225 if (ret.status && *ret.status) {
226 // Reconstitute exception object.
228 istringstream estr(ret.status);
229 except=SAMLException::getInstance(estr);
231 catch (SAMLException& e) {
232 log->error("caught SAML Exception while building the SAMLException: %s", e.what());
233 log->error("XML was: %s", ret.status);
234 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_2, (caddr_t)&ret);
236 throw FatalProfileException("An unrecoverable error occurred while creating your session.");
240 log->error("caught unknown exception building SAMLException");
241 log->error("XML was: %s", ret.status);
242 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_2, (caddr_t)&ret);
249 log->debug("new session from IdP (%s) with key (%s)", ret.provider_id, ret.cookie);
251 provider_id = ret.provider_id;
256 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_2, (caddr_t)&ret);
259 auto_ptr<SAMLException> wrapper(except);
264 EntryWrapper::EntryWrapper(shibrpc_get_session_ret_2& ret, Category& log)
266 profile = static_cast<ShibProfile>(ret.profile);
267 int minor = (profile==SAML10_POST || profile==SAML10_ARTIFACT) ? 0 : 1;
269 provider_id = ret.provider_id;
271 istringstream authstream(ret.auth_statement);
272 log.debugStream() << "trying to decode authentication statement: "
273 << ((ret.auth_statement && *ret.auth_statement) ? ret.auth_statement : "(none)") << CategoryStream::ENDLINE;
274 auto_ptr<SAMLAuthenticationStatement> s(
275 (ret.auth_statement && *ret.auth_statement) ? new SAMLAuthenticationStatement(authstream) : NULL
278 istringstream prestream(ret.attr_response_pre);
279 log.debugStream() << "trying to decode unfiltered attribute response: "
280 << ((ret.attr_response_pre && *ret.attr_response_pre) ? ret.attr_response_pre : "(none)") << CategoryStream::ENDLINE;
281 auto_ptr<SAMLResponse> pre(
282 (ret.attr_response_pre && *ret.attr_response_pre) ? new SAMLResponse(prestream,minor) : NULL
285 istringstream poststream(ret.attr_response_post);
286 log.debugStream() << "trying to decode filtered attribute response: "
287 << ((ret.attr_response_post && *ret.attr_response_post) ? ret.attr_response_post : "(none)") << CategoryStream::ENDLINE;
288 auto_ptr<SAMLResponse> post(
289 (ret.attr_response_post && *ret.attr_response_post) ? new SAMLResponse(poststream,minor) : NULL
292 statement=s.release();
293 pre_response = pre.release();
294 post_response = post.release();
297 void RPCListener::sessionGet(
298 const IApplication* application,
301 ISessionCacheEntry** pentry
305 saml::NDC ndc("sessionGet");
308 if (!cookie || !*cookie) {
309 log->error("no session key provided");
310 throw InvalidSessionException("No session key was provided.");
312 else if (strchr(cookie,'=')) {
313 log->error("cookie value not extracted successfully, probably overlapping cookies across domains");
314 throw InvalidSessionException("The session key wasn't extracted successfully from the browser cookie.");
318 log->error("invalid client Address");
319 throw FatalProfileException("Invalid client address.");
322 log->debug("getting session for client at (%s)", ip);
323 log->debug("session cookie (%s)", cookie);
325 shibrpc_get_session_args_2 arg;
326 arg.cookie = (char*)cookie;
327 arg.client_addr = (char*)ip;
328 arg.application_id = (char*)application->getId();
330 shibrpc_get_session_ret_2 ret;
331 memset (&ret, 0, sizeof(ret));
333 // Loop on the RPC in case we lost contact the first time through
338 clnt = rpc->connect(this);
339 clnt_stat status = shibrpc_get_session_2(&arg, &ret, clnt);
340 if (status != RPC_SUCCESS) {
341 // FAILED. Release, disconnect, and try again...
342 log->error("RPC Failure: (CLIENT: %p) (%d) %s", clnt, status, clnt_spcreateerror("shibrpc_get_session_2"));
343 rpc->disconnect(this);
347 throw ListenerException("Failure requesting session information from listener.");
355 if (ret.status && *ret.status)
356 log->debug("RPC completed with exception: %s", ret.status);
358 log->debug("RPC completed successfully");
360 SAMLException* except=NULL;
361 if (ret.status && *ret.status) {
362 // Reconstitute exception object.
364 istringstream estr(ret.status);
365 except=SAMLException::getInstance(estr);
367 catch (SAMLException& e) {
368 log->error("caught SAML Exception while building the SAMLException: %s", e.what());
369 log->error("XML was: %s", ret.status);
370 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
372 throw FatalProfileException("An unrecoverable error occurred while accessing your session.");
375 log->error("caught unknown exception building SAMLException");
376 log->error("XML was: %s", ret.status);
377 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
384 *pentry=new EntryWrapper(ret,*log);
386 catch (SAMLException& e) {
387 log->error("caught SAML exception while reconstituting session objects: %s", e.what());
388 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
394 log->error("caught unknown exception while reconstituting session objects");
395 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
402 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
405 auto_ptr<SAMLException> wrapper(except);
410 void RPCListener::sessionEnd(
411 const IApplication* application,
416 saml::NDC ndc("sessionEnd");
419 if (!cookie || !*cookie) {
420 log->error("no session key provided");
421 throw InvalidSessionException("No session key was provided.");
423 else if (strchr(cookie,'=')) {
424 log->error("cookie value not extracted successfully, probably overlapping cookies across domains");
425 throw InvalidSessionException("The session key wasn't extracted successfully from the browser cookie.");
428 log->debug("ending session with cookie (%s)", cookie);
430 shibrpc_end_session_args_2 arg;
431 arg.cookie = (char*)cookie;
433 shibrpc_end_session_ret_2 ret;
434 memset (&ret, 0, sizeof(ret));
436 // Loop on the RPC in case we lost contact the first time through
441 clnt = rpc->connect(this);
442 clnt_stat status = shibrpc_end_session_2(&arg, &ret, clnt);
443 if (status != RPC_SUCCESS) {
444 // FAILED. Release, disconnect, and try again...
445 log->error("RPC Failure: (CLIENT: %p) (%d) %s", clnt, status, clnt_spcreateerror("shibrpc_end_session_2"));
446 rpc->disconnect(this);
450 throw ListenerException("Failure ending session through listener.");
458 if (ret.status && *ret.status)
459 log->debug("RPC completed with exception: %s", ret.status);
461 log->debug("RPC completed successfully");
463 SAMLException* except=NULL;
464 if (ret.status && *ret.status) {
465 // Reconstitute exception object.
467 istringstream estr(ret.status);
468 except=SAMLException::getInstance(estr);
470 catch (SAMLException& e) {
471 log->error("caught SAML Exception while building the SAMLException: %s", e.what());
472 log->error("XML was: %s", ret.status);
473 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_end_session_ret_2, (caddr_t)&ret);
475 throw FatalProfileException("An unrecoverable error occurred while accessing your session.");
478 log->error("caught unknown exception building SAMLException");
479 log->error("XML was: %s", ret.status);
480 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_end_session_ret_2, (caddr_t)&ret);
486 clnt_freeres (clnt, (xdrproc_t)xdr_shibrpc_end_session_ret_2, (caddr_t)&ret);
489 auto_ptr<SAMLException> wrapper(except);
494 void RPCListener::ping(int& i) const
497 saml::NDC ndc("ping");
501 log->debug("pinging with (%d)", i);
503 // Loop on the RPC in case we lost contact the first time through
508 clnt = rpc->connect(this);
509 clnt_stat status = shibrpc_ping_2(&i, &result, clnt);
510 if (status != RPC_SUCCESS) {
511 // FAILED. Release, disconnect, and try again...
512 log->error("RPC Failure: (CLIENT: %p) (%d) %s", clnt, status, clnt_spcreateerror("shibrpc_end_session_2"));
513 rpc->disconnect(this);
517 throw ListenerException("Failure pinging listener.");
525 log->debug("RPC completed successfully");
530 RPCHandle::RPCHandle(Category& log) : m_clnt(NULL), m_sock((IListener::ShibSocket)0), m_log(log)
532 m_log.debug("New RPCHandle created: %p", this);
535 RPCHandle::~RPCHandle()
537 m_log.debug("Destroying RPC Handle: %p", this);
541 void RPCHandle::disconnect(const RPCListener* listener)
544 clnt_destroy(m_clnt);
547 listener->close(m_sock);
548 m_sock=(IListener::ShibSocket)0;
552 ::closesocket(m_sock);
556 m_sock=(IListener::ShibSocket)0;
561 CLIENT* RPCHandle::connect(const RPCListener* listener)
564 saml::NDC ndc("connect");
567 m_log.debug("returning existing connection: %p -> %p", this, m_clnt);
571 m_log.debug("trying to connect to socket");
573 IListener::ShibSocket sock;
574 if (!listener->create(sock)) {
575 m_log.error("cannot create socket");
576 throw ListenerException("Cannot create socket");
579 bool connected = false;
582 for (int i = num_tries-1; i >= 0; i--) {
583 if (listener->connect(sock)) {
588 m_log.warn("cannot connect %p to socket...%s", this, (i > 0 ? "retrying" : ""));
592 Sleep(2000*(num_tries-i));
594 sleep(2*(num_tries-i));
600 m_log.crit("socket server unavailable, failing");
601 listener->close(sock);
602 throw ListenerException("Cannot connect to listener process, a site adminstrator should be notified.");
605 CLIENT* clnt = (CLIENT*)listener->getClientHandle(sock, SHIBRPC_PROG, SHIBRPC_VERS_2);
607 const char* rpcerror = clnt_spcreateerror("RPCHandle::connect");
608 m_log.crit("RPC failed for %p: %s", this, rpcerror);
609 listener->close(sock);
610 throw ListenerException(rpcerror);
613 // Set the RPC timeout to a fairly high value...
615 tv.tv_sec = 300; /* change timeout to 5 minutes */
616 tv.tv_usec = 0; /* this should always be set */
617 clnt_control(clnt, CLSET_TIMEOUT, (char*)&tv);
622 m_log.debug("success: %p -> %p", this, m_clnt);
626 RPCHandlePool::~RPCHandlePool()
628 while (!m_pool.empty()) {
634 RPCHandle* RPCHandlePool::get()
637 if (m_pool.empty()) {
639 return new RPCHandle(m_log);
641 RPCHandle* ret=m_pool.top();
647 void RPCHandlePool::put(RPCHandle* handle)
654 RPC::RPC(RPCHandlePool& pool) : m_pool(pool)
656 m_handle=m_pool.get();