66f33841e71209c1a789bc2eae9b837b76341c59
[shibboleth/sp.git] / shib-target / RPCListener.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
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.
22  *
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
28  *
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.
33  *
34  *
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.
48  */
49
50 /*
51  * RPCListener.cpp -- Handles marshalling and connection mgmt for ONC-remoted IListeners
52  *
53  * Scott Cantor
54  * 5/1/05
55  *
56  */
57
58 #include <saml/saml.h>  // need this to "prime" the xmlsec-constrained windows.h declaration
59 #include <shib-target/shibrpc.h>
60 #include "internal.h"
61
62 #ifdef HAVE_UNISTD_H
63 # include <unistd.h>
64 #endif
65
66 using namespace std;
67 using namespace log4cpp;
68 using namespace saml;
69 using namespace shibboleth;
70 using namespace shibtarget;
71
72 namespace shibtarget {
73     // Wraps the actual RPC connection
74     class RPCHandle
75     {
76     public:
77         RPCHandle(Category& log);
78         ~RPCHandle();
79
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!
82
83     private:
84         Category& m_log;
85         CLIENT* m_clnt;
86         IListener::ShibSocket m_sock;
87     };
88   
89     // Manages the pool of connections
90     class RPCHandlePool
91     {
92     public:
93         RPCHandlePool(Category& log, const RPCListener* listener)
94             : m_log(log), m_listener(listener), m_lock(shibboleth::Mutex::create()) {}
95         ~RPCHandlePool();
96         RPCHandle* get();
97         void put(RPCHandle*);
98   
99     private:
100         const RPCListener* m_listener;
101         Category& m_log;
102         auto_ptr<Mutex> m_lock;
103         stack<RPCHandle*> m_pool;
104     };
105   
106     // Cleans up after use
107     class RPC
108     {
109     public:
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;}
114     
115     private:
116         RPCHandle* m_handle;
117         RPCHandlePool& m_pool;
118     };
119     
120     // Local-wrapper for an ISessionCacheEntry
121     class EntryWrapper : public virtual ISessionCacheEntry
122     {
123     public:
124         EntryWrapper(shibrpc_get_session_ret_2& ret, Category& log);
125         ~EntryWrapper() { delete statement; delete pre_response; delete post_response; }
126         void lock() {}
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); }
134     
135     private:
136         string provider_id;
137         ShibProfile profile;
138         SAMLAuthenticationStatement* statement;
139         SAMLResponse* pre_response;
140         SAMLResponse* post_response;
141     };
142 }
143
144
145 RPCListener::RPCListener(const DOMElement* e) : log(&Category::getInstance(SHIBT_LOGCAT".Listener"))
146 {
147     m_rpcpool=new RPCHandlePool(*log,this);
148 }
149
150 RPCListener::~RPCListener()
151 {
152     delete m_rpcpool;
153 }
154
155 void RPCListener::sessionNew(
156     const IApplication* application,
157     int supported_profiles,
158     const char* recipient,
159     const char* packet,
160     const char* ip,
161     string& target,
162     string& cookie,
163     string& provider_id
164     ) const
165 {
166 #ifdef _DEBUG
167     saml::NDC ndc("sessionNew");
168 #endif
169
170     if (!packet || !*packet) {
171         log->error("missing profile response");
172         throw FatalProfileException("Profile response missing.");
173     }
174
175     if (!ip || !*ip) {
176         log->error("missing client address");
177         throw FatalProfileException("Invalid client address.");
178     }
179   
180     if (supported_profiles <= 0) {
181         log->error("no profile support indicated");
182         throw FatalProfileException("No profile support indicated.");
183     }
184   
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;
191
192     log->info("create session for user at (%s) for application (%s)", ip, arg.application_id);
193
194     shibrpc_new_session_ret_2 ret;
195     memset(&ret, 0, sizeof(ret));
196
197     // Loop on the RPC in case we lost contact the first time through
198     int retry = 1;
199     CLIENT* clnt;
200     RPC rpc(*m_rpcpool);
201     do {
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);
208             if (retry)
209                 retry--;
210             else
211                 throw ListenerException("Failure passing session setup information to listener.");
212         }
213         else {
214             // SUCCESS.  Pool and continue
215             retry = -1;
216         }
217     } while (retry>=0);
218
219     if (ret.status && *ret.status)
220         log->debug("RPC completed with exception: %s", ret.status);
221     else
222         log->debug("RPC completed successfully");
223
224     SAMLException* except=NULL;
225     if (ret.status && *ret.status) {
226         // Reconstitute exception object.
227         try { 
228             istringstream estr(ret.status);
229             except=SAMLException::getInstance(estr);
230         }
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);
235             rpc.pool();
236             throw FatalProfileException("An unrecoverable error occurred while creating your session.");
237         }
238 #ifndef _DEBUG
239         catch (...) {
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);
243             rpc.pool();
244             throw;
245         }
246 #endif
247     }
248     else {
249         log->debug("new session from IdP (%s) with key (%s)", ret.provider_id, ret.cookie);
250         cookie = ret.cookie;
251         provider_id = ret.provider_id;
252         if (ret.target)
253             target = ret.target;
254     }
255
256     clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_2, (caddr_t)&ret);
257     rpc.pool();
258     if (except) {
259         auto_ptr<SAMLException> wrapper(except);
260         wrapper->raise();
261     }
262 }
263
264 EntryWrapper::EntryWrapper(shibrpc_get_session_ret_2& ret, Category& log)
265 {
266     profile = static_cast<ShibProfile>(ret.profile);
267     int minor = (profile==SAML10_POST || profile==SAML10_ARTIFACT) ? 0 : 1;
268
269     provider_id = ret.provider_id;
270
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
276         );
277
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
283         );
284
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
290         );
291
292     statement=s.release();
293     pre_response = pre.release();
294     post_response = post.release();
295 }
296
297 void RPCListener::sessionGet(
298     const IApplication* application,
299     const char* cookie,
300     const char* ip,
301     ISessionCacheEntry** pentry
302     ) const
303 {
304 #ifdef _DEBUG
305     saml::NDC ndc("sessionGet");
306 #endif
307
308     if (!cookie || !*cookie) {
309         log->error("no session key provided");
310         throw InvalidSessionException("No session key was provided.");
311     }
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.");
315     }
316
317     if (!ip || !*ip) {
318         log->error("invalid client Address");
319         throw FatalProfileException("Invalid client address.");
320     }
321
322     log->debug("getting session for client at (%s)", ip);
323     log->debug("session cookie (%s)", cookie);
324
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();
329
330     shibrpc_get_session_ret_2 ret;
331     memset (&ret, 0, sizeof(ret));
332
333     // Loop on the RPC in case we lost contact the first time through
334     int retry = 1;
335     CLIENT *clnt;
336     RPC rpc(*m_rpcpool);
337     do {
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);
344             if (retry)
345                 retry--;
346             else
347                 throw ListenerException("Failure requesting session information from listener.");
348         }
349         else {
350             // SUCCESS
351             retry = -1;
352         }
353     } while (retry>=0);
354
355     if (ret.status && *ret.status)
356         log->debug("RPC completed with exception: %s", ret.status);
357     else
358         log->debug("RPC completed successfully");
359
360     SAMLException* except=NULL;
361     if (ret.status && *ret.status) {
362         // Reconstitute exception object.
363         try { 
364             istringstream estr(ret.status);
365             except=SAMLException::getInstance(estr);
366         }
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);
371             rpc.pool();
372             throw FatalProfileException("An unrecoverable error occurred while accessing your session.");
373         }
374         catch (...) {
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);
378             rpc.pool();
379             throw;
380         }
381     }
382     else {
383         try {
384             *pentry=new EntryWrapper(ret,*log);
385         }
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);
389             rpc.pool();
390             throw;
391         }
392 #ifndef _DEBUG
393         catch (...) {
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);
396             rpc.pool();
397             throw;
398         }
399 #endif
400     }
401
402     clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
403     rpc.pool();
404     if (except) {
405         auto_ptr<SAMLException> wrapper(except);
406         wrapper->raise();
407     }
408 }
409
410 void RPCListener::sessionEnd(
411     const IApplication* application,
412     const char* cookie
413     ) const
414 {
415 #ifdef _DEBUG
416     saml::NDC ndc("sessionEnd");
417 #endif
418
419     if (!cookie || !*cookie) {
420         log->error("no session key provided");
421         throw InvalidSessionException("No session key was provided.");
422     }
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.");
426     }
427
428     log->debug("ending session with cookie (%s)", cookie);
429
430     shibrpc_end_session_args_2 arg;
431     arg.cookie = (char*)cookie;
432
433     shibrpc_end_session_ret_2 ret;
434     memset (&ret, 0, sizeof(ret));
435
436     // Loop on the RPC in case we lost contact the first time through
437     int retry = 1;
438     CLIENT *clnt;
439     RPC rpc(*m_rpcpool);
440     do {
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);
447             if (retry)
448                 retry--;
449             else
450                 throw ListenerException("Failure ending session through listener.");
451         }
452         else {
453             // SUCCESS
454             retry = -1;
455         }
456     } while (retry>=0);
457
458     if (ret.status && *ret.status)
459         log->debug("RPC completed with exception: %s", ret.status);
460     else
461         log->debug("RPC completed successfully");
462
463     SAMLException* except=NULL;
464     if (ret.status && *ret.status) {
465         // Reconstitute exception object.
466         try { 
467             istringstream estr(ret.status);
468             except=SAMLException::getInstance(estr);
469         }
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);
474             rpc.pool();
475             throw FatalProfileException("An unrecoverable error occurred while accessing your session.");
476         }
477         catch (...) {
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);
481             rpc.pool();
482             throw;
483         }
484     }
485
486     clnt_freeres (clnt, (xdrproc_t)xdr_shibrpc_end_session_ret_2, (caddr_t)&ret);
487     rpc.pool();
488     if (except) {
489         auto_ptr<SAMLException> wrapper(except);
490         wrapper->raise();
491     }
492 }
493
494 void RPCListener::ping(int& i) const
495 {
496 #ifdef _DEBUG
497     saml::NDC ndc("ping");
498 #endif
499
500     int result=-1;
501     log->debug("pinging with (%d)", i);
502
503     // Loop on the RPC in case we lost contact the first time through
504     int retry = 1;
505     CLIENT *clnt;
506     RPC rpc(*m_rpcpool);
507     do {
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);
514             if (retry)
515                 retry--;
516             else
517                 throw ListenerException("Failure pinging listener.");
518         }
519         else {
520             // SUCCESS
521             retry = -1;
522         }
523     } while (retry>=0);
524
525     log->debug("RPC completed successfully");
526     i=result;
527     rpc.pool();
528 }
529
530 RPCHandle::RPCHandle(Category& log) : m_clnt(NULL), m_sock((IListener::ShibSocket)0), m_log(log)
531 {
532     m_log.debug("New RPCHandle created: %p", this);
533 }
534
535 RPCHandle::~RPCHandle()
536 {
537     m_log.debug("Destroying RPC Handle: %p", this);
538     disconnect();
539 }
540
541 void RPCHandle::disconnect(const RPCListener* listener)
542 {
543     if (m_clnt) {
544         clnt_destroy(m_clnt);
545         m_clnt=NULL;
546         if (listener) {
547             listener->close(m_sock);
548             m_sock=(IListener::ShibSocket)0;
549         }
550         else {
551 #ifdef WIN32
552             ::closesocket(m_sock);
553 #else
554             ::close(m_sock);
555 #endif
556             m_sock=(IListener::ShibSocket)0;
557         }
558     }
559 }
560
561 CLIENT* RPCHandle::connect(const RPCListener* listener)
562 {
563 #ifdef _DEBUG
564     saml::NDC ndc("connect");
565 #endif
566     if (m_clnt) {
567         m_log.debug("returning existing connection: %p -> %p", this, m_clnt);
568         return m_clnt;
569     }
570
571     m_log.debug("trying to connect to socket");
572
573     IListener::ShibSocket sock;
574     if (!listener->create(sock)) {
575         m_log.error("cannot create socket");
576         throw ListenerException("Cannot create socket");
577     }
578
579     bool connected = false;
580     int num_tries = 3;
581
582     for (int i = num_tries-1; i >= 0; i--) {
583         if (listener->connect(sock)) {
584             connected = true;
585             break;
586         }
587     
588         m_log.warn("cannot connect %p to socket...%s", this, (i > 0 ? "retrying" : ""));
589
590         if (i) {
591 #ifdef WIN32
592             Sleep(2000*(num_tries-i));
593 #else
594             sleep(2*(num_tries-i));
595 #endif
596         }
597     }
598
599     if (!connected) {
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.");
603     }
604
605     CLIENT* clnt = (CLIENT*)listener->getClientHandle(sock, SHIBRPC_PROG, SHIBRPC_VERS_2);
606     if (!clnt) {
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);
611     }
612
613     // Set the RPC timeout to a fairly high value...
614     struct timeval tv;
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);
618
619     m_clnt = clnt;
620     m_sock = sock;
621
622     m_log.debug("success: %p -> %p", this, m_clnt);
623     return m_clnt;
624 }
625
626 RPCHandlePool::~RPCHandlePool()
627 {
628     while (!m_pool.empty()) {
629         delete m_pool.top();
630         m_pool.pop();
631     }
632 }
633
634 RPCHandle* RPCHandlePool::get()
635 {
636     m_lock->lock();
637     if (m_pool.empty()) {
638         m_lock->unlock();
639         return new RPCHandle(m_log);
640     }
641     RPCHandle* ret=m_pool.top();
642     m_pool.pop();
643     m_lock->unlock();
644     return ret;
645 }
646
647 void RPCHandlePool::put(RPCHandle* handle)
648 {
649     m_lock->lock();
650     m_pool.push(handle);
651     m_lock->unlock();
652 }
653
654 RPC::RPC(RPCHandlePool& pool) : m_pool(pool)
655 {
656     m_handle=m_pool.get();
657 }