2 * Copyright 2001-2005 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * RPCListener.cpp -- Handles marshalling and connection mgmt for ONC-remoted IListeners
25 #include <saml/saml.h> // need this to "prime" the xmlsec-constrained windows.h declaration
26 #include <shib-target/shibrpc.h>
35 using namespace shibboleth;
36 using namespace shibtarget;
37 using namespace shibtarget::logging;
39 namespace shibtarget {
40 // Wraps the actual RPC connection
44 RPCHandle(Category& log);
47 CLIENT* connect(const RPCListener* listener); // connects and returns the CLIENT handle
48 void disconnect(const RPCListener* listener=NULL); // disconnects, should not return disconnected handles to pool!
53 IListener::ShibSocket m_sock;
56 // Manages the pool of connections
60 RPCHandlePool(Category& log, const RPCListener* listener)
61 : m_listener(listener), m_log(log), m_lock(shibboleth::Mutex::create()) {}
67 const RPCListener* m_listener;
69 auto_ptr<Mutex> m_lock;
70 stack<RPCHandle*> m_pool;
73 // Cleans up after use
77 RPC(RPCHandlePool& pool);
78 ~RPC() {delete m_handle;}
79 RPCHandle* operator->() {return m_handle;}
80 void pool() {if (m_handle) m_pool.put(m_handle); m_handle=NULL;}
84 RPCHandlePool& m_pool;
87 // Local-wrapper for an ISessionCacheEntry
88 class EntryWrapper : public virtual ISessionCacheEntry
91 EntryWrapper(shibrpc_get_session_ret_2& ret, Category& log);
92 ~EntryWrapper() { delete statement; delete pre_response; delete post_response; }
94 void unlock() { delete this; }
95 virtual bool isValid(time_t lifetime, time_t timeout) const { return true; }
96 virtual const char* getClientAddress() const { return NULL; }
97 virtual ShibProfile getProfile() const { return profile; }
98 virtual const char* getProviderId() const { return provider_id.c_str(); }
99 virtual const saml::SAMLAuthenticationStatement* getAuthnStatement() const { return statement; }
100 virtual CachedResponse getResponse() { return CachedResponse(pre_response,post_response); }
105 SAMLAuthenticationStatement* statement;
106 SAMLResponse* pre_response;
107 SAMLResponse* post_response;
112 RPCListener::RPCListener(const DOMElement* e) : log(&Category::getInstance(SHIBT_LOGCAT".Listener"))
114 m_rpcpool=new RPCHandlePool(*log,this);
117 RPCListener::~RPCListener()
122 void RPCListener::sessionNew(
123 const IApplication* application,
124 int supported_profiles,
125 const char* recipient,
134 saml::NDC ndc("sessionNew");
137 if (!packet || !*packet) {
138 log->error("missing profile response");
139 throw FatalProfileException("Profile response missing.");
143 log->error("missing client address");
144 throw FatalProfileException("Invalid client address.");
147 if (supported_profiles <= 0) {
148 log->error("no profile support indicated");
149 throw FatalProfileException("No profile support indicated.");
152 shibrpc_new_session_args_2 arg;
153 arg.recipient = (char*)recipient;
154 arg.application_id = (char*)application->getId();
155 arg.packet = (char*)packet;
156 arg.client_addr = (char*)ip;
157 arg.supported_profiles = supported_profiles;
159 log->info("create session for user at (%s) for application (%s)", ip, arg.application_id);
161 shibrpc_new_session_ret_2 ret;
162 memset(&ret, 0, sizeof(ret));
164 // Loop on the RPC in case we lost contact the first time through
169 clnt = rpc->connect(this);
170 clnt_stat status = shibrpc_new_session_2(&arg, &ret, clnt);
171 if (status != RPC_SUCCESS) {
172 // FAILED. Release, disconnect, and retry
173 log->error("RPC Failure: (CLIENT: %p) (%d): %s", clnt, status, clnt_spcreateerror("shibrpc_new_session_2"));
174 rpc->disconnect(this);
178 throw ListenerException("Failure passing session setup information to listener.");
181 // SUCCESS. Pool and continue
186 if (ret.status && *ret.status)
187 log->debug("RPC completed with exception: %s", ret.status);
189 log->debug("RPC completed successfully");
191 SAMLException* except=NULL;
192 if (ret.status && *ret.status) {
193 // Reconstitute exception object.
195 istringstream estr(ret.status);
196 except=SAMLException::getInstance(estr);
198 catch (SAMLException& e) {
199 log->error("caught SAML Exception while building the SAMLException: %s", e.what());
200 log->error("XML was: %s", ret.status);
201 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_2, (caddr_t)&ret);
203 throw FatalProfileException("An unrecoverable error occurred while creating your session.");
207 log->error("caught unknown exception building SAMLException");
208 log->error("XML was: %s", ret.status);
209 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_2, (caddr_t)&ret);
216 log->debug("new session from IdP (%s) with key (%s)", ret.provider_id, ret.cookie);
218 provider_id = ret.provider_id;
223 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_2, (caddr_t)&ret);
226 auto_ptr<SAMLException> wrapper(except);
231 EntryWrapper::EntryWrapper(shibrpc_get_session_ret_2& ret, Category& log)
233 profile = static_cast<ShibProfile>(ret.profile);
234 int minor = (profile==SAML10_POST || profile==SAML10_ARTIFACT) ? 0 : 1;
236 provider_id = ret.provider_id;
238 istringstream authstream(ret.auth_statement);
239 log.debugStream() << "trying to decode authentication statement: "
240 << ((ret.auth_statement && *ret.auth_statement) ? ret.auth_statement : "(none)") << logging::eol;
241 auto_ptr<SAMLAuthenticationStatement> s(
242 (ret.auth_statement && *ret.auth_statement) ? new SAMLAuthenticationStatement(authstream) : NULL
245 istringstream prestream(ret.attr_response_pre);
246 log.debugStream() << "trying to decode unfiltered attribute response: "
247 << ((ret.attr_response_pre && *ret.attr_response_pre) ? ret.attr_response_pre : "(none)") << logging::eol;
248 auto_ptr<SAMLResponse> pre(
249 (ret.attr_response_pre && *ret.attr_response_pre) ? new SAMLResponse(prestream,minor) : NULL
252 istringstream poststream(ret.attr_response_post);
253 log.debugStream() << "trying to decode filtered attribute response: "
254 << ((ret.attr_response_post && *ret.attr_response_post) ? ret.attr_response_post : "(none)") << logging::eol;
255 auto_ptr<SAMLResponse> post(
256 (ret.attr_response_post && *ret.attr_response_post) ? new SAMLResponse(poststream,minor) : NULL
259 statement=s.release();
260 pre_response = pre.release();
261 post_response = post.release();
264 void RPCListener::sessionGet(
265 const IApplication* application,
268 ISessionCacheEntry** pentry
272 saml::NDC ndc("sessionGet");
275 if (!cookie || !*cookie) {
276 log->error("no session key provided");
277 throw InvalidSessionException("No session key was provided.");
279 else if (strchr(cookie,'=')) {
280 log->error("cookie value not extracted successfully, probably overlapping cookies across domains");
281 throw InvalidSessionException("The session key wasn't extracted successfully from the browser cookie.");
285 log->error("invalid client Address");
286 throw FatalProfileException("Invalid client address.");
289 log->debug("getting session for client at (%s)", ip);
290 log->debug("session cookie (%s)", cookie);
292 shibrpc_get_session_args_2 arg;
293 arg.cookie = (char*)cookie;
294 arg.client_addr = (char*)ip;
295 arg.application_id = (char*)application->getId();
297 shibrpc_get_session_ret_2 ret;
298 memset (&ret, 0, sizeof(ret));
300 // Loop on the RPC in case we lost contact the first time through
305 clnt = rpc->connect(this);
306 clnt_stat status = shibrpc_get_session_2(&arg, &ret, clnt);
307 if (status != RPC_SUCCESS) {
308 // FAILED. Release, disconnect, and try again...
309 log->error("RPC Failure: (CLIENT: %p) (%d) %s", clnt, status, clnt_spcreateerror("shibrpc_get_session_2"));
310 rpc->disconnect(this);
314 throw ListenerException("Failure requesting session information from listener.");
322 if (ret.status && *ret.status)
323 log->debug("RPC completed with exception: %s", ret.status);
325 log->debug("RPC completed successfully");
327 SAMLException* except=NULL;
328 if (ret.status && *ret.status) {
329 // Reconstitute exception object.
331 istringstream estr(ret.status);
332 except=SAMLException::getInstance(estr);
334 catch (SAMLException& e) {
335 log->error("caught SAML Exception while building the SAMLException: %s", e.what());
336 log->error("XML was: %s", ret.status);
337 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
339 throw FatalProfileException("An unrecoverable error occurred while accessing your session.");
342 log->error("caught unknown exception building SAMLException");
343 log->error("XML was: %s", ret.status);
344 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
351 *pentry=new EntryWrapper(ret,*log);
353 catch (SAMLException& e) {
354 log->error("caught SAML exception while reconstituting session objects: %s", e.what());
355 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
361 log->error("caught unknown exception while reconstituting session objects");
362 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
369 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
372 auto_ptr<SAMLException> wrapper(except);
377 void RPCListener::sessionEnd(
378 const IApplication* application,
383 saml::NDC ndc("sessionEnd");
386 if (!cookie || !*cookie) {
387 log->error("no session key provided");
388 throw InvalidSessionException("No session key was provided.");
390 else if (strchr(cookie,'=')) {
391 log->error("cookie value not extracted successfully, probably overlapping cookies across domains");
392 throw InvalidSessionException("The session key wasn't extracted successfully from the browser cookie.");
395 log->debug("ending session with cookie (%s)", cookie);
397 shibrpc_end_session_args_2 arg;
398 arg.cookie = (char*)cookie;
400 shibrpc_end_session_ret_2 ret;
401 memset (&ret, 0, sizeof(ret));
403 // Loop on the RPC in case we lost contact the first time through
408 clnt = rpc->connect(this);
409 clnt_stat status = shibrpc_end_session_2(&arg, &ret, clnt);
410 if (status != RPC_SUCCESS) {
411 // FAILED. Release, disconnect, and try again...
412 log->error("RPC Failure: (CLIENT: %p) (%d) %s", clnt, status, clnt_spcreateerror("shibrpc_end_session_2"));
413 rpc->disconnect(this);
417 throw ListenerException("Failure ending session through listener.");
425 if (ret.status && *ret.status)
426 log->debug("RPC completed with exception: %s", ret.status);
428 log->debug("RPC completed successfully");
430 SAMLException* except=NULL;
431 if (ret.status && *ret.status) {
432 // Reconstitute exception object.
434 istringstream estr(ret.status);
435 except=SAMLException::getInstance(estr);
437 catch (SAMLException& e) {
438 log->error("caught SAML Exception while building the SAMLException: %s", e.what());
439 log->error("XML was: %s", ret.status);
440 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_end_session_ret_2, (caddr_t)&ret);
442 throw FatalProfileException("An unrecoverable error occurred while accessing your session.");
445 log->error("caught unknown exception building SAMLException");
446 log->error("XML was: %s", ret.status);
447 clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_end_session_ret_2, (caddr_t)&ret);
453 clnt_freeres (clnt, (xdrproc_t)xdr_shibrpc_end_session_ret_2, (caddr_t)&ret);
456 auto_ptr<SAMLException> wrapper(except);
461 void RPCListener::ping(int& i) const
464 saml::NDC ndc("ping");
468 log->debug("pinging with (%d)", i);
470 // Loop on the RPC in case we lost contact the first time through
475 clnt = rpc->connect(this);
476 clnt_stat status = shibrpc_ping_2(&i, &result, clnt);
477 if (status != RPC_SUCCESS) {
478 // FAILED. Release, disconnect, and try again...
479 log->error("RPC Failure: (CLIENT: %p) (%d) %s", clnt, status, clnt_spcreateerror("shibrpc_end_session_2"));
480 rpc->disconnect(this);
484 throw ListenerException("Failure pinging listener.");
492 log->debug("RPC completed successfully");
497 RPCHandle::RPCHandle(Category& log) : m_log(log), m_clnt(NULL), m_sock((IListener::ShibSocket)0)
499 m_log.debug("New RPCHandle created: %p", this);
502 RPCHandle::~RPCHandle()
504 m_log.debug("Destroying RPC Handle: %p", this);
508 void RPCHandle::disconnect(const RPCListener* listener)
511 clnt_destroy(m_clnt);
514 listener->close(m_sock);
515 m_sock=(IListener::ShibSocket)0;
519 ::closesocket(m_sock);
523 m_sock=(IListener::ShibSocket)0;
528 CLIENT* RPCHandle::connect(const RPCListener* listener)
531 saml::NDC ndc("connect");
534 m_log.debug("returning existing connection: %p -> %p", this, m_clnt);
538 m_log.debug("trying to connect to socket");
540 IListener::ShibSocket sock;
541 if (!listener->create(sock)) {
542 m_log.error("cannot create socket");
543 throw ListenerException("Cannot create socket");
546 bool connected = false;
549 for (int i = num_tries-1; i >= 0; i--) {
550 if (listener->connect(sock)) {
555 m_log.warn("cannot connect %p to socket...%s", this, (i > 0 ? "retrying" : ""));
559 Sleep(2000*(num_tries-i));
561 sleep(2*(num_tries-i));
567 m_log.crit("socket server unavailable, failing");
568 listener->close(sock);
569 throw ListenerException("Cannot connect to listener process, a site adminstrator should be notified.");
572 CLIENT* clnt = (CLIENT*)listener->getClientHandle(sock, SHIBRPC_PROG, SHIBRPC_VERS_2);
574 const char* rpcerror = clnt_spcreateerror("RPCHandle::connect");
575 m_log.crit("RPC failed for %p: %s", this, rpcerror);
576 listener->close(sock);
577 throw ListenerException(rpcerror);
580 // Set the RPC timeout to a fairly high value...
582 tv.tv_sec = 300; /* change timeout to 5 minutes */
583 tv.tv_usec = 0; /* this should always be set */
584 clnt_control(clnt, CLSET_TIMEOUT, (char*)&tv);
589 m_log.debug("success: %p -> %p", this, m_clnt);
593 RPCHandlePool::~RPCHandlePool()
595 while (!m_pool.empty()) {
601 RPCHandle* RPCHandlePool::get()
604 if (m_pool.empty()) {
606 return new RPCHandle(m_log);
608 RPCHandle* ret=m_pool.top();
614 void RPCHandlePool::put(RPCHandle* handle)
621 RPC::RPC(RPCHandlePool& pool) : m_pool(pool)
623 m_handle=m_pool.get();