https://issues.shibboleth.net/jira/browse/SSPCPP-69
[shibboleth/cpp-sp.git] / shib-target / RPCListener.cpp
1 /*
2  *  Copyright 2001-2005 Internet2
3  * 
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /*
18  * RPCListener.cpp -- Handles marshalling and connection mgmt for ONC-remoted IListeners
19  *
20  * Scott Cantor
21  * 5/1/05
22  *
23  */
24
25 #include <saml/saml.h>  // need this to "prime" the xmlsec-constrained windows.h declaration
26 #include <shib-target/shibrpc.h>
27 #include "internal.h"
28
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32
33 using namespace std;
34 using namespace saml;
35 using namespace shibboleth;
36 using namespace shibtarget;
37 using namespace shibtarget::logging;
38
39 namespace shibtarget {
40     // Wraps the actual RPC connection
41     class RPCHandle
42     {
43     public:
44         RPCHandle(Category& log);
45         ~RPCHandle();
46
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!
49
50     private:
51         Category& m_log;
52         CLIENT* m_clnt;
53         IListener::ShibSocket m_sock;
54     };
55   
56     // Manages the pool of connections
57     class RPCHandlePool
58     {
59     public:
60         RPCHandlePool(Category& log, const RPCListener* listener)
61             : m_listener(listener), m_log(log), m_lock(shibboleth::Mutex::create()) {}
62         ~RPCHandlePool();
63         RPCHandle* get();
64         void put(RPCHandle*);
65   
66     private:
67         const RPCListener* m_listener;
68         Category& m_log;
69         auto_ptr<Mutex> m_lock;
70         stack<RPCHandle*> m_pool;
71     };
72   
73     // Cleans up after use
74     class RPC
75     {
76     public:
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;}
81     
82     private:
83         RPCHandle* m_handle;
84         RPCHandlePool& m_pool;
85     };
86     
87     // Local-wrapper for an ISessionCacheEntry
88     class EntryWrapper : public virtual ISessionCacheEntry
89     {
90     public:
91         EntryWrapper(shibrpc_get_session_ret_2& ret, Category& log);
92         ~EntryWrapper() { delete statement; delete pre_response; delete post_response; }
93         void lock() {}
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); }
101     
102     private:
103         string provider_id;
104         ShibProfile profile;
105         SAMLAuthenticationStatement* statement;
106         SAMLResponse* pre_response;
107         SAMLResponse* post_response;
108     };
109 }
110
111
112 RPCListener::RPCListener(const DOMElement* e) : log(&Category::getInstance(SHIBT_LOGCAT".Listener"))
113 {
114     m_rpcpool=new RPCHandlePool(*log,this);
115 }
116
117 RPCListener::~RPCListener()
118 {
119     delete m_rpcpool;
120 }
121
122 void RPCListener::sessionNew(
123     const IApplication* application,
124     int supported_profiles,
125     const char* recipient,
126     const char* packet,
127     const char* ip,
128     string& target,
129     string& cookie,
130     string& provider_id
131     ) const
132 {
133 #ifdef _DEBUG
134     saml::NDC ndc("sessionNew");
135 #endif
136
137     if (!packet || !*packet) {
138         log->error("missing profile response");
139         throw FatalProfileException("Profile response missing.");
140     }
141
142     if (!ip || !*ip) {
143         log->error("missing client address");
144         throw FatalProfileException("Invalid client address.");
145     }
146   
147     if (supported_profiles <= 0) {
148         log->error("no profile support indicated");
149         throw FatalProfileException("No profile support indicated.");
150     }
151   
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;
158
159     log->info("create session for user at (%s) for application (%s)", ip, arg.application_id);
160
161     shibrpc_new_session_ret_2 ret;
162     memset(&ret, 0, sizeof(ret));
163
164     // Loop on the RPC in case we lost contact the first time through
165     int retry = 1;
166     CLIENT* clnt;
167     RPC rpc(*m_rpcpool);
168     do {
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);
175             if (retry)
176                 retry--;
177             else
178                 throw ListenerException("Failure passing session setup information to listener.");
179         }
180         else {
181             // SUCCESS.  Pool and continue
182             retry = -1;
183         }
184     } while (retry>=0);
185
186     if (ret.status && *ret.status)
187         log->debug("RPC completed with exception: %s", ret.status);
188     else
189         log->debug("RPC completed successfully");
190
191     SAMLException* except=NULL;
192     if (ret.status && *ret.status) {
193         // Reconstitute exception object.
194         try { 
195             istringstream estr(ret.status);
196             except=SAMLException::getInstance(estr);
197         }
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);
202             rpc.pool();
203             throw FatalProfileException("An unrecoverable error occurred while creating your session.");
204         }
205 #ifndef _DEBUG
206         catch (...) {
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);
210             rpc.pool();
211             throw;
212         }
213 #endif
214     }
215     else {
216         log->debug("new session from IdP (%s) with key (%s)", ret.provider_id, ret.cookie);
217         cookie = ret.cookie;
218         provider_id = ret.provider_id;
219         if (ret.target)
220             target = ret.target;
221     }
222
223     clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_new_session_ret_2, (caddr_t)&ret);
224     rpc.pool();
225     if (except) {
226         auto_ptr<SAMLException> wrapper(except);
227         wrapper->raise();
228     }
229 }
230
231 EntryWrapper::EntryWrapper(shibrpc_get_session_ret_2& ret, Category& log)
232 {
233     profile = static_cast<ShibProfile>(ret.profile);
234     int minor = (profile==SAML10_POST || profile==SAML10_ARTIFACT) ? 0 : 1;
235
236     provider_id = ret.provider_id;
237
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
243         );
244
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
250         );
251
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
257         );
258
259     statement=s.release();
260     pre_response = pre.release();
261     post_response = post.release();
262 }
263
264 void RPCListener::sessionGet(
265     const IApplication* application,
266     const char* cookie,
267     const char* ip,
268     ISessionCacheEntry** pentry
269     ) const
270 {
271 #ifdef _DEBUG
272     saml::NDC ndc("sessionGet");
273 #endif
274
275     if (!cookie || !*cookie) {
276         log->error("no session key provided");
277         throw InvalidSessionException("No session key was provided.");
278     }
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.");
282     }
283
284     if (!ip || !*ip) {
285         log->error("invalid client Address");
286         throw FatalProfileException("Invalid client address.");
287     }
288
289     log->debug("getting session for client at (%s)", ip);
290     log->debug("session cookie (%s)", cookie);
291
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();
296
297     shibrpc_get_session_ret_2 ret;
298     memset (&ret, 0, sizeof(ret));
299
300     // Loop on the RPC in case we lost contact the first time through
301     int retry = 1;
302     CLIENT *clnt;
303     RPC rpc(*m_rpcpool);
304     do {
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);
311             if (retry)
312                 retry--;
313             else
314                 throw ListenerException("Failure requesting session information from listener.");
315         }
316         else {
317             // SUCCESS
318             retry = -1;
319         }
320     } while (retry>=0);
321
322     if (ret.status && *ret.status)
323         log->debug("RPC completed with exception: %s", ret.status);
324     else
325         log->debug("RPC completed successfully");
326
327     SAMLException* except=NULL;
328     if (ret.status && *ret.status) {
329         // Reconstitute exception object.
330         try { 
331             istringstream estr(ret.status);
332             except=SAMLException::getInstance(estr);
333         }
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);
338             rpc.pool();
339             throw FatalProfileException("An unrecoverable error occurred while accessing your session.");
340         }
341         catch (...) {
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);
345             rpc.pool();
346             throw;
347         }
348     }
349     else {
350         try {
351             *pentry=new EntryWrapper(ret,*log);
352         }
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);
356             rpc.pool();
357             throw;
358         }
359 #ifndef _DEBUG
360         catch (...) {
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);
363             rpc.pool();
364             throw;
365         }
366 #endif
367     }
368
369     clnt_freeres(clnt, (xdrproc_t)xdr_shibrpc_get_session_ret_2, (caddr_t)&ret);
370     rpc.pool();
371     if (except) {
372         auto_ptr<SAMLException> wrapper(except);
373         wrapper->raise();
374     }
375 }
376
377 void RPCListener::sessionEnd(
378     const IApplication* application,
379     const char* cookie
380     ) const
381 {
382 #ifdef _DEBUG
383     saml::NDC ndc("sessionEnd");
384 #endif
385
386     if (!cookie || !*cookie) {
387         log->error("no session key provided");
388         throw InvalidSessionException("No session key was provided.");
389     }
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.");
393     }
394
395     log->debug("ending session with cookie (%s)", cookie);
396
397     shibrpc_end_session_args_2 arg;
398     arg.cookie = (char*)cookie;
399
400     shibrpc_end_session_ret_2 ret;
401     memset (&ret, 0, sizeof(ret));
402
403     // Loop on the RPC in case we lost contact the first time through
404     int retry = 1;
405     CLIENT *clnt;
406     RPC rpc(*m_rpcpool);
407     do {
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);
414             if (retry)
415                 retry--;
416             else
417                 throw ListenerException("Failure ending session through listener.");
418         }
419         else {
420             // SUCCESS
421             retry = -1;
422         }
423     } while (retry>=0);
424
425     if (ret.status && *ret.status)
426         log->debug("RPC completed with exception: %s", ret.status);
427     else
428         log->debug("RPC completed successfully");
429
430     SAMLException* except=NULL;
431     if (ret.status && *ret.status) {
432         // Reconstitute exception object.
433         try { 
434             istringstream estr(ret.status);
435             except=SAMLException::getInstance(estr);
436         }
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);
441             rpc.pool();
442             throw FatalProfileException("An unrecoverable error occurred while accessing your session.");
443         }
444         catch (...) {
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);
448             rpc.pool();
449             throw;
450         }
451     }
452
453     clnt_freeres (clnt, (xdrproc_t)xdr_shibrpc_end_session_ret_2, (caddr_t)&ret);
454     rpc.pool();
455     if (except) {
456         auto_ptr<SAMLException> wrapper(except);
457         wrapper->raise();
458     }
459 }
460
461 void RPCListener::ping(int& i) const
462 {
463 #ifdef _DEBUG
464     saml::NDC ndc("ping");
465 #endif
466
467     int result=-1;
468     log->debug("pinging with (%d)", i);
469
470     // Loop on the RPC in case we lost contact the first time through
471     int retry = 1;
472     CLIENT *clnt;
473     RPC rpc(*m_rpcpool);
474     do {
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);
481             if (retry)
482                 retry--;
483             else
484                 throw ListenerException("Failure pinging listener.");
485         }
486         else {
487             // SUCCESS
488             retry = -1;
489         }
490     } while (retry>=0);
491
492     log->debug("RPC completed successfully");
493     i=result;
494     rpc.pool();
495 }
496
497 RPCHandle::RPCHandle(Category& log) : m_log(log), m_clnt(NULL), m_sock((IListener::ShibSocket)0)
498 {
499     m_log.debug("New RPCHandle created: %p", this);
500 }
501
502 RPCHandle::~RPCHandle()
503 {
504     m_log.debug("Destroying RPC Handle: %p", this);
505     disconnect();
506 }
507
508 void RPCHandle::disconnect(const RPCListener* listener)
509 {
510     if (m_clnt) {
511         clnt_destroy(m_clnt);
512         m_clnt=NULL;
513         if (listener) {
514             listener->close(m_sock);
515             m_sock=(IListener::ShibSocket)0;
516         }
517         else {
518 #ifdef WIN32
519             ::closesocket(m_sock);
520 #else
521             ::close(m_sock);
522 #endif
523             m_sock=(IListener::ShibSocket)0;
524         }
525     }
526 }
527
528 CLIENT* RPCHandle::connect(const RPCListener* listener)
529 {
530 #ifdef _DEBUG
531     saml::NDC ndc("connect");
532 #endif
533     if (m_clnt) {
534         m_log.debug("returning existing connection: %p -> %p", this, m_clnt);
535         return m_clnt;
536     }
537
538     m_log.debug("trying to connect to socket");
539
540     IListener::ShibSocket sock;
541     if (!listener->create(sock)) {
542         m_log.error("cannot create socket");
543         throw ListenerException("Cannot create socket");
544     }
545
546     bool connected = false;
547     int num_tries = 3;
548
549     for (int i = num_tries-1; i >= 0; i--) {
550         if (listener->connect(sock)) {
551             connected = true;
552             break;
553         }
554     
555         m_log.warn("cannot connect %p to socket...%s", this, (i > 0 ? "retrying" : ""));
556
557         if (i) {
558 #ifdef WIN32
559             Sleep(2000*(num_tries-i));
560 #else
561             sleep(2*(num_tries-i));
562 #endif
563         }
564     }
565
566     if (!connected) {
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.");
570     }
571
572     CLIENT* clnt = (CLIENT*)listener->getClientHandle(sock, SHIBRPC_PROG, SHIBRPC_VERS_2);
573     if (!clnt) {
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);
578     }
579
580     // Set the RPC timeout to a fairly high value...
581     struct timeval tv;
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);
585
586     m_clnt = clnt;
587     m_sock = sock;
588
589     m_log.debug("success: %p -> %p", this, m_clnt);
590     return m_clnt;
591 }
592
593 RPCHandlePool::~RPCHandlePool()
594 {
595     while (!m_pool.empty()) {
596         delete m_pool.top();
597         m_pool.pop();
598     }
599 }
600
601 RPCHandle* RPCHandlePool::get()
602 {
603     m_lock->lock();
604     if (m_pool.empty()) {
605         m_lock->unlock();
606         return new RPCHandle(m_log);
607     }
608     RPCHandle* ret=m_pool.top();
609     m_pool.pop();
610     m_lock->unlock();
611     return ret;
612 }
613
614 void RPCHandlePool::put(RPCHandle* handle)
615 {
616     m_lock->lock();
617     m_pool.push(handle);
618     m_lock->unlock();
619 }
620
621 RPC::RPC(RPCHandlePool& pool) : m_pool(pool)
622 {
623     m_handle=m_pool.get();
624 }