Finished option to set unset headers to fixed value, update docs/specs.
[shibboleth/cpp-sp.git] / nsapi_shib / nsapi_shib.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 /* nsapi_shib.cpp - Shibboleth NSAPI filter
18
19    Scott Cantor
20    12/13/04
21 */
22
23 #if defined (_MSC_VER) || defined(__BORLANDC__)
24 # include "config_win32.h"
25 #else
26 # include "config.h"
27 #endif
28
29
30 // SAML Runtime
31 #include <saml/saml.h>
32 #include <shib/shib.h>
33 #include <shib/shib-threads.h>
34 #include <shib-target/shib-target.h>
35
36 #include <ctime>
37 #include <fstream>
38 #include <sstream>
39 #include <stdexcept>
40
41 #ifdef WIN32
42 # include <process.h>
43 # define XP_WIN32
44 #else
45 # define XP_UNIX
46 #endif
47
48 #define MCC_HTTPD
49 #define NET_SSL
50
51 extern "C"
52 {
53 #include <nsapi.h>
54 }
55
56 using namespace std;
57 using namespace saml;
58 using namespace shibboleth;
59 using namespace shibtarget;
60
61 // macros to output text to client
62 #define NET_WRITE(str) \
63     if (IO_ERROR==net_write(sn->csd,str,strlen(str))) return REQ_EXIT
64
65 namespace {
66     ShibTargetConfig* g_Config=NULL;
67     string g_ServerName;
68     string g_ServerScheme;
69     string g_unsetHeaderValue;
70 }
71
72 PlugManager::Factory SunRequestMapFactory;
73
74 extern "C" NSAPI_PUBLIC void nsapi_shib_exit(void*)
75 {
76     if (g_Config)
77         g_Config->shutdown();
78     g_Config = NULL;
79 }
80
81 extern "C" NSAPI_PUBLIC int nsapi_shib_init(pblock* pb, Session* sn, Request* rq)
82 {
83     // Save off a default hostname for this virtual server.
84     char* name=pblock_findval("server-name",pb);
85     if (name)
86         g_ServerName=name;
87     else {
88         name=server_hostname;
89         if (name)
90             g_ServerName=name;
91         else {
92             name=util_hostname();
93             if (name) {
94                 g_ServerName=name;
95                 FREE(name);
96             }
97             else {
98                 pblock_nvinsert("error","unable to determine web server hostname",pb);
99                 return REQ_ABORTED;
100             }
101         }
102     }
103     name=pblock_findval("server-scheme",pb);
104     if (name)
105         g_ServerScheme=name;
106
107     log_error(LOG_INFORM,"nsapi_shib_init",sn,rq,"nsapi_shib loaded for host (%s)",g_ServerName.c_str());
108
109 #ifndef _DEBUG
110     try {
111 #endif
112         const char* schemadir=pblock_findval("shib-schemas",pb);
113         if (!schemadir)
114             schemadir=getenv("SHIBSCHEMAS");
115         if (!schemadir)
116             schemadir=SHIB_SCHEMAS;
117         const char* config=pblock_findval("shib-config",pb);
118         if (!config)
119             config=getenv("SHIBCONFIG");
120         if (!config)
121             config=SHIB_CONFIG;
122         g_Config=&ShibTargetConfig::getConfig();
123         g_Config->setFeatures(
124             ShibTargetConfig::Listener |
125             ShibTargetConfig::Metadata |
126             ShibTargetConfig::AAP |
127             ShibTargetConfig::RequestMapper |
128             ShibTargetConfig::LocalExtensions |
129             ShibTargetConfig::Logging
130             );
131         if (!g_Config->init(schemadir)) {
132             g_Config=NULL;
133             pblock_nvinsert("error","unable to initialize Shibboleth libraries",pb);
134             return REQ_ABORTED;
135         }
136
137         SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::NativeRequestMapType,&SunRequestMapFactory);
138         // We hijack the legacy type so that 1.2 config files will load this plugin
139         SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::LegacyRequestMapType,&SunRequestMapFactory);
140
141         if (!g_Config->load(config)) {
142             g_Config=NULL;
143             pblock_nvinsert("error","unable to initialize load Shibboleth configuration",pb);
144             return REQ_ABORTED;
145         }
146
147         daemon_atrestart(nsapi_shib_exit,NULL);
148
149         IConfig* conf=g_Config->getINI();
150         Locker locker(conf);
151         const IPropertySet* props=conf->getPropertySet("Local");
152         if (props) {
153             pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
154             if (unsetValue.first)
155                 g_unsetHeaderValue = unsetValue.second;
156         }
157 #ifndef _DEBUG
158     }
159     catch (...) {
160         g_Config=NULL;
161         pblock_nvinsert("error","caught exception, unable to initialize Shibboleth libraries",pb);
162         return REQ_ABORTED;
163     }
164 #endif
165     return REQ_PROCEED;
166 }
167
168 /********************************************************************************/
169 // NSAPI Shib Target Subclass
170
171 class ShibTargetNSAPI : public ShibTarget
172 {
173 public:
174   ShibTargetNSAPI(pblock* pb, Session* sn, Request* rq) {
175     m_pb = pb;
176     m_sn = sn;
177     m_rq = rq;
178
179     // Get everything but hostname...
180     const char* uri=pblock_findval("uri", rq->reqpb);
181     const char* qstr=pblock_findval("query", rq->reqpb);
182     int port=server_portnum;
183     const char* scheme=security_active ? "https" : "http";
184     const char* host=NULL;
185
186     string url;
187     if (uri)
188         url=uri;
189     if (qstr)
190         url=url + '?' + qstr;
191     
192 #ifdef vs_is_default_vs
193     // This is 6.0 or later, so we can distinguish requests to name-based vhosts.
194     if (!vs_is_default_vs)
195         // The beauty here is, a non-default vhost can *only* be accessed if the client
196         // specified the exact name in the Host header. So we can trust the Host header.
197         host=pblock_findval("host", rq->headers);
198     else
199 #endif
200     // In other cases, we're going to rely on the initialization process...
201     host=g_ServerName.c_str();
202
203     char* content_type = "";
204     request_header("content-type", &content_type, sn, rq);
205       
206     const char *remote_ip = pblock_findval("ip", sn->client);
207     const char *method = pblock_findval("method", rq->reqpb);
208
209     init(scheme, host, port, url.c_str(), content_type, remote_ip, method);
210   }
211   ~ShibTargetNSAPI() {}
212
213   virtual void log(ShibLogLevel level, const string &msg) {
214     ShibTarget::log(level,msg);
215     if (level==LogLevelError)
216         log_error(LOG_FAILURE, "nsapi_shib", m_sn, m_rq, const_cast<char*>(msg.c_str()));
217   }
218   virtual string getCookies(void) const {
219     char *cookies = NULL;
220     if (request_header("cookie", &cookies, m_sn, m_rq) == REQ_ABORTED)
221       throw("error accessing cookie header");
222     return string(cookies ? cookies : "");
223   }
224   virtual void setCookie(const string &name, const string &value) {
225     string cookie = name + '=' + value;
226     pblock_nvinsert("Set-Cookie", cookie.c_str(), m_rq->srvhdrs);
227   }
228   virtual string getArgs(void) { 
229     const char *q = pblock_findval("query", m_rq->reqpb);
230     return string(q ? q : "");
231   }
232   virtual string getPostData(void) {
233     char* content_length=NULL;
234     if (request_header("content-length", &content_length, m_sn, m_rq)!=REQ_PROCEED ||
235          atoi(content_length) > 1024*1024) // 1MB?
236       throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
237     else {
238       char ch=IO_EOF+1;
239       int cl=atoi(content_length);
240       string cgistr;
241       while (cl && ch != IO_EOF) {
242         ch=netbuf_getc(m_sn->inbuf);
243       
244         // Check for error.
245         if(ch==IO_ERROR)
246           break;
247         cgistr += ch;
248         cl--;
249       }
250       if (cl)
251         throw FatalProfileException("Error reading profile submission from browser.");
252       return cgistr;
253     }
254   }
255   virtual void clearHeader(const string &name) {
256     if (name=="REMOTE_USER") {
257         param_free(pblock_remove("auth-user",m_rq->vars));
258         param_free(pblock_remove("remote-user",m_rq->headers));
259     }
260     else {
261         param_free(pblock_remove(name.c_str(), m_rq->headers));
262         pblock_nvinsert(name.c_str(), g_unsetHeaderValue.c_str() ,m_rq->headers);
263     }
264   }
265   virtual void setHeader(const string &name, const string &value) {
266     param_free(pblock_remove(name.c_str(), m_rq->headers));
267     pblock_nvinsert(name.c_str(), value.c_str() ,m_rq->headers);
268   }
269   virtual string getHeader(const string &name) {
270     char *hdr = NULL;
271     if (request_header(const_cast<char*>(name.c_str()), &hdr, m_sn, m_rq) != REQ_PROCEED)
272       hdr = NULL;
273     return string(hdr ? hdr : "");
274   }
275   virtual void setRemoteUser(const string &user) {
276     pblock_nvinsert("remote-user", user.c_str(), m_rq->headers);
277     pblock_nvinsert("auth-user", user.c_str(), m_rq->vars);
278   }
279   virtual string getRemoteUser(void) {
280     return getHeader("remote-user");
281   }
282
283   virtual void* sendPage(
284     const string& msg,
285     int code=200,
286     const string& content_type="text/html",
287     const saml::Iterator<header_t>& headers=EMPTY(header_t)
288     ) {
289     param_free(pblock_remove("content-type", m_rq->srvhdrs));
290     pblock_nvinsert("content-type", content_type.c_str(), m_rq->srvhdrs);
291     pblock_nninsert("content-length", msg.length(), m_rq->srvhdrs);
292     pblock_nvinsert("connection","close",m_rq->srvhdrs);
293     while (headers.hasNext()) {
294         const header_t& h=headers.next();
295         pblock_nvinsert(h.first.c_str(), h.second.c_str(), m_rq->srvhdrs);
296     }
297     protocol_status(m_sn, m_rq, code, NULL);
298     protocol_start_response(m_sn, m_rq);
299     net_write(m_sn->csd,const_cast<char*>(msg.c_str()),msg.length());
300     return (void*)REQ_EXIT;
301   }
302   virtual void* sendRedirect(const string& url) {
303     param_free(pblock_remove("content-type", m_rq->srvhdrs));
304     pblock_nninsert("content-length", 0, m_rq->srvhdrs);
305     pblock_nvinsert("expires", "01-Jan-1997 12:00:00 GMT", m_rq->srvhdrs);
306     pblock_nvinsert("cache-control", "private,no-store,no-cache", m_rq->srvhdrs);
307     pblock_nvinsert("location", url.c_str(), m_rq->srvhdrs);
308     pblock_nvinsert("connection","close",m_rq->srvhdrs);
309     protocol_status(m_sn, m_rq, PROTOCOL_REDIRECT, NULL);
310     protocol_start_response(m_sn, m_rq);
311     return (void*)REQ_ABORTED;
312   }
313   virtual void* returnDecline(void) { return (void*)REQ_NOACTION; }
314   virtual void* returnOK(void) { return (void*)REQ_PROCEED; }
315
316   pblock* m_pb;
317   Session* m_sn;
318   Request* m_rq;
319 };
320
321 /********************************************************************************/
322
323 int WriteClientError(Session* sn, Request* rq, char* func, char* msg)
324 {
325     log_error(LOG_FAILURE,func,sn,rq,msg);
326     protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,msg);
327     return REQ_ABORTED;
328 }
329
330 #undef FUNC
331 #define FUNC "shibboleth"
332 extern "C" NSAPI_PUBLIC int nsapi_shib(pblock* pb, Session* sn, Request* rq)
333 {
334   ostringstream threadid;
335   threadid << "[" << getpid() << "] nsapi_shib" << '\0';
336   saml::NDC ndc(threadid.str().c_str());
337
338   try {
339     ShibTargetNSAPI stn(pb, sn, rq);
340
341     // Check user authentication
342     pair<bool,void*> res = stn.doCheckAuthN();
343     if (res.first) return (int)res.second;
344
345     // user authN was okay -- export the assertions now
346     param_free(pblock_remove("auth-user",rq->vars));
347     // This seems to be required in order to eventually set
348     // the auth-user var.
349     pblock_nvinsert("auth-type","shibboleth",rq->vars);
350     res = stn.doExportAssertions();
351     if (res.first) return (int)res.second;
352
353     // Check the Authorization
354     res = stn.doCheckAuthZ();
355     if (res.first) return (int)res.second;
356
357     // this user is ok.
358     return REQ_PROCEED;
359   }
360   catch (SAMLException& e) {
361     log_error(LOG_FAILURE,FUNC,sn,rq,const_cast<char*>(e.what()));
362     return WriteClientError(sn, rq, FUNC, "Shibboleth filter threw an exception, see web server log for error.");
363   }
364 #ifndef _DEBUG
365   catch (...) {
366     return WriteClientError(sn, rq, FUNC, "Shibboleth filter threw an uncaught exception.");
367   }
368 #endif
369 }
370
371
372 #undef FUNC
373 #define FUNC "shib_handler"
374 extern "C" NSAPI_PUBLIC int shib_handler(pblock* pb, Session* sn, Request* rq)
375 {
376   ostringstream threadid;
377   threadid << "[" << getpid() << "] shib_handler" << '\0';
378   saml::NDC ndc(threadid.str().c_str());
379
380   try {
381     ShibTargetNSAPI stn(pb, sn, rq);
382
383     pair<bool,void*> res = stn.doHandler();
384     if (res.first) return (int)res.second;
385
386     return WriteClientError(sn, rq, FUNC, "Shibboleth handler did not do anything.");
387   }
388   catch (SAMLException& e) {
389     log_error(LOG_FAILURE,FUNC,sn,rq,const_cast<char*>(e.what()));
390     return WriteClientError(sn, rq, FUNC, "Shibboleth handler threw an exception, see web server log for error.");
391   }
392 #ifndef _DEBUG
393   catch (...) {
394     return WriteClientError(sn, rq, FUNC, "Shibboleth handler threw an unknown exception.");
395   }
396 #endif
397 }
398
399
400 class SunRequestMapper : public virtual IRequestMapper, public virtual IPropertySet
401 {
402 public:
403     SunRequestMapper(const DOMElement* e);
404     ~SunRequestMapper() { delete m_mapper; delete m_stKey; delete m_propsKey; }
405     void lock() { m_mapper->lock(); }
406     void unlock() { m_stKey->setData(NULL); m_propsKey->setData(NULL); m_mapper->unlock(); }
407     Settings getSettings(ShibTarget* st) const;
408     
409     pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;
410     pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;
411     pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;
412     pair<bool,unsigned int> getUnsignedInt(const char* name, const char* ns=NULL) const;
413     pair<bool,int> getInt(const char* name, const char* ns=NULL) const;
414     const IPropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:target:config:1.0") const;
415     const DOMElement* getElement() const;
416
417 private:
418     IRequestMapper* m_mapper;
419     ThreadKey* m_stKey;
420     ThreadKey* m_propsKey;
421 };
422
423 IPlugIn* SunRequestMapFactory(const DOMElement* e)
424 {
425     return new SunRequestMapper(e);
426 }
427
428 SunRequestMapper::SunRequestMapper(const DOMElement* e) : m_mapper(NULL), m_stKey(NULL), m_propsKey(NULL)
429 {
430     IPlugIn* p=SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::XMLRequestMapType,e);
431     m_mapper=dynamic_cast<IRequestMapper*>(p);
432     if (!m_mapper) {
433         delete p;
434         throw UnsupportedExtensionException("Embedded request mapper plugin was not of correct type.");
435     }
436     m_stKey=ThreadKey::create(NULL);
437     m_propsKey=ThreadKey::create(NULL);
438 }
439
440 IRequestMapper::Settings SunRequestMapper::getSettings(ShibTarget* st) const
441 {
442     Settings s=m_mapper->getSettings(st);
443     m_stKey->setData(dynamic_cast<ShibTargetNSAPI*>(st));
444     m_propsKey->setData((void*)s.first);
445     return pair<const IPropertySet*,IAccessControl*>(this,s.second);
446 }
447
448 pair<bool,bool> SunRequestMapper::getBool(const char* name, const char* ns) const
449 {
450     ShibTargetNSAPI* stn=reinterpret_cast<ShibTargetNSAPI*>(m_stKey->getData());
451     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
452     if (stn && !ns && name) {
453         // Override boolean properties.
454         const char* param=pblock_findval(name,stn->m_pb);
455         if (param && (!strcmp(param,"1") || !strcasecmp(param,"true")))
456             return make_pair(true,true);
457     }
458     return s ? s->getBool(name,ns) : make_pair(false,false);
459 }
460
461 pair<bool,const char*> SunRequestMapper::getString(const char* name, const char* ns) const
462 {
463     ShibTargetNSAPI* stn=reinterpret_cast<ShibTargetNSAPI*>(m_stKey->getData());
464     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
465     if (stn && !ns && name) {
466         // Override string properties.
467         if (!strcmp(name,"authType"))
468             return pair<bool,const char*>(true,"shibboleth");
469         else {
470             const char* param=pblock_findval(name,stn->m_pb);
471             if (param)
472                 return make_pair(true,param);
473         }
474     }
475     return s ? s->getString(name,ns) : pair<bool,const char*>(false,NULL);
476 }
477
478 pair<bool,const XMLCh*> SunRequestMapper::getXMLString(const char* name, const char* ns) const
479 {
480     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
481     return s ? s->getXMLString(name,ns) : pair<bool,const XMLCh*>(false,NULL);
482 }
483
484 pair<bool,unsigned int> SunRequestMapper::getUnsignedInt(const char* name, const char* ns) const
485 {
486     ShibTargetNSAPI* stn=reinterpret_cast<ShibTargetNSAPI*>(m_stKey->getData());
487     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
488     if (stn && !ns && name) {
489         // Override int properties.
490         const char* param=pblock_findval(name,stn->m_pb);
491         if (param)
492             return pair<bool,unsigned int>(true,strtol(param,NULL,10));
493     }
494     return s ? s->getUnsignedInt(name,ns) : pair<bool,unsigned int>(false,0);
495 }
496
497 pair<bool,int> SunRequestMapper::getInt(const char* name, const char* ns) const
498 {
499     ShibTargetNSAPI* stn=reinterpret_cast<ShibTargetNSAPI*>(m_stKey->getData());
500     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
501     if (stn && !ns && name) {
502         // Override int properties.
503         const char* param=pblock_findval(name,stn->m_pb);
504         if (param)
505             return pair<bool,int>(true,atoi(param));
506     }
507     return s ? s->getInt(name,ns) : pair<bool,int>(false,0);
508 }
509
510 const IPropertySet* SunRequestMapper::getPropertySet(const char* name, const char* ns) const
511 {
512     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
513     return s ? s->getPropertySet(name,ns) : NULL;
514 }
515
516 const DOMElement* SunRequestMapper::getElement() const
517 {
518     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
519     return s ? s->getElement() : NULL;
520 }