Add quiet option to suppress some output.
[shibboleth/cpp-sp.git] / adfs / adfs.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 /* adfs.cpp - bootstraps the ADFS extension library
18
19    Scott Cantor
20    10/10/05
21 */
22
23 #ifdef WIN32
24 # define ADFS_EXPORTS __declspec(dllexport)
25 #else
26 # define ADFS_EXPORTS
27 #endif
28
29 #include "internal.h"
30
31 #include <xercesc/util/Base64.hpp>
32
33
34 using namespace std;
35 using namespace saml;
36 using namespace shibboleth;
37 using namespace shibtarget;
38 using namespace adfs;
39 using namespace adfs::logging;
40
41
42 // Plugin Factories
43 PlugManager::Factory ADFSListenerFactory;
44 PlugManager::Factory ADFSSessionInitiatorFactory;
45 PlugManager::Factory ADFSHandlerFactory;
46
47 IListener* adfs::g_MemoryListener = NULL;
48
49 extern "C" int ADFS_EXPORTS saml_extension_init(void*)
50 {
51     SAMLConfig& conf=SAMLConfig::getConfig();
52
53     if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::Caching)) {
54         // Build an internal "listener" to handle the work.
55         IPlugIn* plugin=conf.getPlugMgr().newPlugin(shibtarget::XML::MemoryListenerType,NULL);
56         g_MemoryListener=dynamic_cast<IListener*>(plugin);
57         if (!g_MemoryListener) {
58             delete plugin;
59             fprintf(stderr, "Basic MemoryListener plugin failed to load");
60             return -1;
61         }
62     }
63     
64     // Register extension schema.
65     saml::XML::registerSchema(adfs::XML::WSTRUST_NS,adfs::XML::WSTRUST_SCHEMA_ID);
66
67     // Register plugin factories (some override existing Shib functionality).
68     conf.getPlugMgr().regFactory(shibtarget::XML::MemoryListenerType,&ADFSListenerFactory);
69
70     auto_ptr_char temp1(Constants::SHIB_SESSIONINIT_PROFILE_URI);
71     conf.getPlugMgr().regFactory(temp1.get(),&ADFSSessionInitiatorFactory);
72
73     auto_ptr_char temp2(adfs::XML::WSFED_NS);
74     conf.getPlugMgr().regFactory(temp2.get(),&ADFSHandlerFactory);
75
76     return 0;
77 }
78
79 extern "C" void ADFS_EXPORTS saml_extension_term()
80 {
81     // Unregister metadata factories
82     SAMLConfig& conf=SAMLConfig::getConfig();
83     conf.getPlugMgr().unregFactory(shibtarget::XML::MemoryListenerType);
84     
85     auto_ptr_char temp1(Constants::SHIB_SESSIONINIT_PROFILE_URI);
86     conf.getPlugMgr().unregFactory(temp1.get());
87     
88     auto_ptr_char temp2(adfs::XML::WSFED_NS);
89     conf.getPlugMgr().unregFactory(temp2.get());
90     
91     delete g_MemoryListener;
92     g_MemoryListener=NULL;
93 }
94
95 // For now, we'll just put the meat of the profile here.
96
97 SAMLAuthenticationStatement* adfs::checkAssertionProfile(const SAMLAssertion* a)
98 {
99     // Is it signed?
100     if (!a->isSigned())
101         throw FatalProfileException("rejected unsigned ADFS assertion");
102     
103     // Is it valid?
104     time_t now=time(NULL);
105     SAMLConfig& config=SAMLConfig::getConfig();
106     if (a->getIssueInstant()->getEpoch() < now-(2*config.clock_skew_secs))
107         throw ExpiredAssertionException("rejected expired ADFS assertion");
108
109     const SAMLDateTime* notBefore=a->getNotBefore();
110     const SAMLDateTime* notOnOrAfter=a->getNotOnOrAfter();
111     if (!notBefore || !notOnOrAfter)
112         throw ExpiredAssertionException("rejected ADFS assertion without time conditions");
113     if (now+config.clock_skew_secs < notBefore->getEpoch())
114         throw ExpiredAssertionException("rejected ADFS assertion that is not yet valid");
115     if (notOnOrAfter->getEpoch() <= now-config.clock_skew_secs)
116         throw ExpiredAssertionException("rejected expired ADFS assertion");
117
118     // Look for an authentication statement.
119     SAMLAuthenticationStatement* as=NULL;
120     for (Iterator<SAMLStatement*> statements=a->getStatements(); !as && statements.hasNext();)
121         as=dynamic_cast<SAMLAuthenticationStatement*>(statements.next());
122     if (!as)
123         throw FatalProfileException("rejecting ADFS assertion without authentication statement");
124
125     return as;
126 }
127
128 /*************************************************************************
129  * CGI Parser implementation
130  */
131
132 CgiParse::CgiParse(const char* data, unsigned int len)
133 {
134     const char* pch = data;
135     unsigned int cl = len;
136         
137     while (cl && pch) {
138         char *name;
139         char *value;
140         value=fmakeword('&',&cl,&pch);
141         plustospace(value);
142         url_decode(value);
143         name=makeword(value,'=');
144         kvp_map[name]=value;
145         free(name);
146     }
147 }
148
149 CgiParse::~CgiParse()
150 {
151     for (map<string,char*>::iterator i=kvp_map.begin(); i!=kvp_map.end(); i++)
152         free(i->second);
153 }
154
155 const char*
156 CgiParse::get_value(const char* name) const
157 {
158     map<string,char*>::const_iterator i=kvp_map.find(name);
159     if (i==kvp_map.end())
160         return NULL;
161     return i->second;
162 }
163
164 /* Parsing routines modified from NCSA source. */
165 char *
166 CgiParse::makeword(char *line, char stop)
167 {
168     int x = 0,y;
169     char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));
170
171     for(x=0;((line[x]) && (line[x] != stop));x++)
172         word[x] = line[x];
173
174     word[x] = '\0';
175     if(line[x])
176         ++x;
177     y=0;
178
179     while(line[x])
180       line[y++] = line[x++];
181     line[y] = '\0';
182     return word;
183 }
184
185 char *
186 CgiParse::fmakeword(char stop, unsigned int *cl, const char** ppch)
187 {
188     int wsize;
189     char *word;
190     int ll;
191
192     wsize = 1024;
193     ll=0;
194     word = (char *) malloc(sizeof(char) * (wsize + 1));
195
196     while(1)
197     {
198         word[ll] = *((*ppch)++);
199         if(ll==wsize-1)
200         {
201             word[ll+1] = '\0';
202             wsize+=1024;
203             word = (char *)realloc(word,sizeof(char)*(wsize+1));
204         }
205         --(*cl);
206         if((word[ll] == stop) || word[ll] == EOF || (!(*cl)))
207         {
208             if(word[ll] != stop)
209                 ll++;
210             word[ll] = '\0';
211             return word;
212         }
213         ++ll;
214     }
215 }
216
217 void
218 CgiParse::plustospace(char *str)
219 {
220     register int x;
221
222     for(x=0;str[x];x++)
223         if(str[x] == '+') str[x] = ' ';
224 }
225
226 char
227 CgiParse::x2c(char *what)
228 {
229     register char digit;
230
231     digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
232     digit *= 16;
233     digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
234     return(digit);
235 }
236
237 void
238 CgiParse::url_decode(char *url)
239 {
240     register int x,y;
241
242     for(x=0,y=0;url[y];++x,++y)
243     {
244         if((url[x] = url[y]) == '%')
245         {
246             url[x] = x2c(&url[y+1]);
247             y+=2;
248         }
249     }
250     url[x] = '\0';
251 }
252
253 static inline char hexchar(unsigned short s)
254 {
255     return (s<=9) ? ('0' + s) : ('A' + s - 10);
256 }
257
258 string CgiParse::url_encode(const char* s)
259 {
260     static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
261
262     string ret;
263     for (; *s; s++) {
264         if (strchr(badchars,*s) || *s<=0x20 || *s>=0x7F) {
265             ret+='%';
266         ret+=hexchar(*s >> 4);
267         ret+=hexchar(*s & 0x0F);
268         }
269         else
270             ret+=*s;
271     }
272     return ret;
273 }
274
275 // CDC implementation
276
277 const char CommonDomainCookie::CDCName[] = "_saml_idp";
278
279 CommonDomainCookie::CommonDomainCookie(const char* cookie)
280 {
281     if (!cookie)
282         return;
283
284     Category& log=Category::getInstance(ADFS_LOGCAT".CommonDomainCookie");
285
286     // Copy it so we can URL-decode it.
287     char* b64=strdup(cookie);
288     CgiParse::url_decode(b64);
289
290     // Chop it up and save off elements.
291     vector<string> templist;
292     char* ptr=b64;
293     while (*ptr) {
294         while (*ptr && isspace(*ptr)) ptr++;
295         char* end=ptr;
296         while (*end && !isspace(*end)) end++;
297         templist.push_back(string(ptr,end-ptr));
298         ptr=end;
299     }
300     free(b64);
301
302     // Now Base64 decode the list.
303     for (vector<string>::iterator i=templist.begin(); i!=templist.end(); i++) {
304         unsigned int len;
305         XMLByte* decoded=Base64::decode(reinterpret_cast<const XMLByte*>(i->c_str()),&len);
306         if (decoded && *decoded) {
307             m_list.push_back(reinterpret_cast<char*>(decoded));
308             XMLString::release(&decoded);
309         }
310         else
311             log.warn("cookie element does not appear to be base64-encoded");
312     }
313 }
314
315 const char* CommonDomainCookie::set(const char* providerId)
316 {
317     // First scan the list for this IdP.
318     for (vector<string>::iterator i=m_list.begin(); i!=m_list.end(); i++) {
319         if (*i == providerId) {
320             m_list.erase(i);
321             break;
322         }
323     }
324     
325     // Append it to the end.
326     m_list.push_back(providerId);
327     
328     // Now rebuild the delimited list.
329     string delimited;
330     for (vector<string>::const_iterator j=m_list.begin(); j!=m_list.end(); j++) {
331         if (!delimited.empty()) delimited += ' ';
332         
333         unsigned int len;
334         XMLByte* b64=Base64::encode(reinterpret_cast<const XMLByte*>(j->c_str()),j->length(),&len);
335         XMLByte *pos, *pos2;
336         for (pos=b64, pos2=b64; *pos2; pos2++)
337             if (isgraph(*pos2))
338                 *pos++=*pos2;
339         *pos=0;
340         
341         delimited += reinterpret_cast<char*>(b64);
342         XMLString::release(&b64);
343     }
344     
345     m_encoded=CgiParse::url_encode(delimited.c_str());
346     return m_encoded.c_str();
347 }