Next integration phase, metadata and trust conversion.
[shibboleth/sp.git] / shib-target / shib-target.cpp
index 3c6156b..acf5fce 100644 (file)
 # include <unistd.h>
 #endif
 
+#include <ctime>
 #include <sstream>
 #include <fstream>
 #include <stdexcept>
 
-#include <shib/shib-threads.h>
+#include <saml/SAMLConfig.h>
+#include <saml/binding/URLEncoder.h>
 #include <xercesc/util/Base64.hpp>
+#include <xmltooling/util/NDC.h>
+#include <xmltooling/util/TemplateEngine.h>
+#include <xmltooling/util/XMLHelper.h>
 
 #ifndef HAVE_STRCASECMP
 # define strcasecmp stricmp
 #endif
 
-using namespace std;
-using namespace saml;
-using namespace shibboleth;
+using namespace shibsp;
 using namespace shibtarget;
+using namespace shibboleth;
+using namespace saml;
+using namespace opensaml::saml2md;
 using namespace log4cpp;
+using namespace std;
+
+using xmltooling::TemplateEngine;
+using xmltooling::XMLToolingException;
+using xmltooling::XMLToolingConfig;
+using xmltooling::XMLHelper;
 
 namespace shibtarget {
     class CgiParse
@@ -64,6 +76,35 @@ namespace shibtarget {
         multimap<string,char*> kvp_map;
     };
 
+    class ExtTemplateParameters : public TemplateEngine::TemplateParameters
+    {
+        const PropertySet* m_props;
+    public:
+        ExtTemplateParameters() : m_props(NULL) {}
+        ~ExtTemplateParameters() {}
+
+        void setPropertySet(const PropertySet* props) {
+            m_props = props;
+
+            // Create a timestamp.
+            time_t now = time(NULL);
+#ifdef HAVE_CTIME_R
+            char timebuf[32];
+            m_map["now"] = ctime_r(&now,timebuf);
+#else
+            m_map["now"] = ctime(&now);
+#endif
+        }
+
+        const char* getParameter(const char* name) const {
+            const char* pch = TemplateParameters::getParameter(name);
+            if (pch || !m_props)
+                return pch;
+            pair<bool,const char*> p = m_props->getString(name);
+            return p.first ? p.second : NULL;
+        }
+    };
+
     class ShibTargetPriv
     {
     public:
@@ -72,7 +113,7 @@ namespace shibtarget {
 
         // Helper functions
         void get_application(ShibTarget* st, const string& protocol, const string& hostname, int port, const string& uri);
-        void* sendError(ShibTarget* st, const char* page, ShibMLP &mlp);
+        void* sendError(ShibTarget* st, const char* page, ExtTemplateParameters& tp, const XMLToolingException* ex=NULL);
         void clearHeaders(ShibTarget* st);
     
     private:
@@ -120,11 +161,11 @@ void ShibTarget::init(
     )
 {
 #ifdef _DEBUG
-    saml::NDC ndc("init");
+    xmltooling::NDC ndc("init");
 #endif
 
     if (m_priv->m_app)
-        throw SAMLException("Request initialization occurred twice!");
+        throw XMLToolingException("Request initialization occurred twice!");
 
     if (method) m_method = method;
     if (protocol) m_protocol = protocol;
@@ -145,12 +186,12 @@ void ShibTarget::init(
 pair<bool,void*> ShibTarget::doCheckAuthN(bool handler)
 {
 #ifdef _DEBUG
-    saml::NDC ndc("doCheckAuthN");
+    xmltooling::NDC ndc("doCheckAuthN");
 #endif
 
     const char* procState = "Request Processing Error";
     const char* targetURL = m_url.c_str();
-    ShibMLP mlp;
+    ExtTemplateParameters tp;
 
     try {
         if (!m_priv->m_app)
@@ -170,8 +211,8 @@ pair<bool,void*> ShibTarget::doCheckAuthN(bool handler)
                     return make_pair(true, sendRedirect(redirectURL));
                 }
                 else {
-                    mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
-                    return make_pair(true,m_priv->sendError(this,"ssl", mlp));
+                    tp.m_map["requestURL"] = m_url.substr(0,m_url.find('?'));
+                    return make_pair(true,m_priv->sendError(this,"ssl", tp));
                 }
             }
         }
@@ -187,7 +228,7 @@ pair<bool,void*> ShibTarget::doCheckAuthN(bool handler)
             if (handler)
                 return doHandler();
             else
-                return pair<bool,void*>(true, returnOK());
+                return make_pair(true, returnOK());
         }
 
         // Three settings dictate how to proceed.
@@ -204,7 +245,7 @@ pair<bool,void*> ShibTarget::doCheckAuthN(bool handler)
 #else
                 (!authType.first || _stricmp(authType.second,"shibboleth")))
 #endif
-            return pair<bool,void*>(true,returnDecline());
+            return make_pair(true,returnDecline());
 
         // Fix for secadv 20050901
         m_priv->clearHeaders(this);
@@ -214,7 +255,7 @@ pair<bool,void*> ShibTarget::doCheckAuthN(bool handler)
         if (!session_id || !*session_id) {
             // No session.  Maybe that's acceptable?
             if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first)
-                return pair<bool,void*>(true,returnOK());
+                return make_pair(true,returnOK());
 
             // No cookie, but we require a session. Initiate a new session using the indicated method.
             procState = "Session Initiator Error";
@@ -224,7 +265,7 @@ pair<bool,void*> ShibTarget::doCheckAuthN(bool handler)
                 if (!initiator)
                     throw ConfigurationException(
                         "No session initiator found with id ($1), check requireSessionWith command.",
-                        params(1,requireSessionWith.second)
+                        xmltooling::params(1,requireSessionWith.second)
                         );
             }
             else {
@@ -245,9 +286,9 @@ pair<bool,void*> ShibTarget::doCheckAuthN(bool handler)
                 );
             // Make a localized exception throw if the session isn't valid.
             if (!m_priv->m_cacheEntry)
-                throw InvalidSessionException("Session no longer valid.");
+                throw RetryableProfileException("Session no longer valid.");
         }
-        catch (SAMLException& e) {
+        catch (exception& e) {
             log(LogLevelError, string("session processing failed: ") + e.what());
 
             // If no session is required, bail now.
@@ -256,10 +297,10 @@ pair<bool,void*> ShibTarget::doCheckAuthN(bool handler)
                 // to fail when it can't locate anything to process the
                 // AuthType.  No session plus requireSession false means
                 // do not authenticate the user at this time.
-                return pair<bool,void*>(true, returnOK());
+                return make_pair(true, returnOK());
 
             // Try and cast down.
-            SAMLException* base = &e;
+            exception* base = &e;
             RetryableProfileException* trycast=dynamic_cast<RetryableProfileException*>(base);
             if (trycast) {
                 // Session is invalid but we can retry -- initiate a new session.
@@ -270,7 +311,7 @@ pair<bool,void*> ShibTarget::doCheckAuthN(bool handler)
                     if (!initiator)
                         throw ConfigurationException(
                             "No session initiator found with id ($1), check requireSessionWith command.",
-                            params(1,requireSessionWith.second)
+                            xmltooling::params(1,requireSessionWith.second)
                             );
                 }
                 else {
@@ -286,34 +327,35 @@ pair<bool,void*> ShibTarget::doCheckAuthN(bool handler)
         // We're done.  Everything is okay.  Nothing to report.  Nothing to do..
         // Let the caller decide how to proceed.
         log(LogLevelDebug, "doCheckAuthN succeeded");
-        return pair<bool,void*>(false,NULL);
+        return make_pair(false,(void*)NULL);
     }
-    catch (SAMLException& e) {
-        mlp.insert(e);
+    catch (XMLToolingException& e) {
+        tp.m_map["errorType"] = procState;
+        tp.m_map["errorText"] = e.what();
+        if (targetURL)
+            tp.m_map["requestURL"] = m_url.substr(0,m_url.find('?'));
+        return make_pair(true,m_priv->sendError(this, "session", tp, &e));
     }
 #ifndef _DEBUG
     catch (...) {
-        mlp.insert("errorText", "Caught an unknown exception.");
+        tp.m_map["errorType"] = procState;
+        tp.m_map["errorText"] = "Caught an unknown exception.";
+        if (targetURL)
+            tp.m_map["requestURL"] = m_url.substr(0,m_url.find('?'));
+        return make_pair(true,m_priv->sendError(this, "session", tp));
     }
 #endif
-
-    // If we get here then we've got an error.
-    mlp.insert("errorType", procState);
-    if (targetURL)
-        mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
-
-    return pair<bool,void*>(true,m_priv->sendError(this,"session", mlp));
 }
 
 pair<bool,void*> ShibTarget::doHandler(void)
 {
 #ifdef _DEBUG
-    saml::NDC ndc("doHandler");
+    xmltooling::NDC ndc("doHandler");
 #endif
 
+    ExtTemplateParameters tp;
     const char* procState = "Shibboleth Handler Error";
     const char* targetURL = m_url.c_str();
-    ShibMLP mlp;
 
     try {
         if (!m_priv->m_app)
@@ -326,9 +368,9 @@ pair<bool,void*> ShibTarget::doHandler(void)
 
         // Make sure we only process handler requests.
         if (!strstr(targetURL,handlerURL))
-            return pair<bool,void*>(true, returnDecline());
+            return make_pair(true, returnDecline());
 
-        const IPropertySet* sessionProps=m_priv->m_app->getPropertySet("Sessions");
+        const PropertySet* sessionProps=m_priv->m_app->getPropertySet("Sessions");
         if (!sessionProps)
             throw ConfigurationException("Unable to map request to application session settings, check configuration.");
 
@@ -337,21 +379,21 @@ pair<bool,void*> ShibTarget::doHandler(void)
       
         // Make sure this is SSL, if it should be
         if ((!handlerSSL.first || handlerSSL.second) && m_protocol != "https")
-            throw FatalProfileException("Blocked non-SSL access to Shibboleth handler.");
+            throw opensaml::FatalProfileException("Blocked non-SSL access to Shibboleth handler.");
 
         // We dispatch based on our path info. We know the request URL begins with or equals the handler URL,
         // so the path info is the next character (or null).
         const IHandler* handler=m_priv->m_app->getHandler(targetURL + strlen(handlerURL));
         if (!handler)
-            throw SAMLException("Shibboleth handler invoked at an unconfigured location.");
+            throw opensaml::BindingException("Shibboleth handler invoked at an unconfigured location.");
 
-        if (saml::XML::isElementNamed(handler->getProperties()->getElement(),shibtarget::XML::SAML2META_NS,SHIBT_L(AssertionConsumerService)))
+        if (XMLHelper::isNodeNamed(handler->getProperties()->getElement(),samlconstants::SAML20MD_NS,SHIBT_L(AssertionConsumerService)))
             procState = "Session Creation Error";
-        else if (saml::XML::isElementNamed(handler->getProperties()->getElement(),shibtarget::XML::SHIBTARGET_NS,SHIBT_L(SessionInitiator)))
+        else if (XMLHelper::isNodeNamed(handler->getProperties()->getElement(),shibtarget::XML::SHIBTARGET_NS,SHIBT_L(SessionInitiator)))
             procState = "Session Initiator Error";
-        else if (saml::XML::isElementNamed(handler->getProperties()->getElement(),shibtarget::XML::SAML2META_NS,SHIBT_L(SingleLogoutService)))
+        else if (XMLHelper::isNodeNamed(handler->getProperties()->getElement(),samlconstants::SAML20MD_NS,SHIBT_L(SingleLogoutService)))
             procState = "Session Termination Error";
-        else if (saml::XML::isElementNamed(handler->getProperties()->getElement(),shibtarget::XML::SHIBTARGET_NS,SHIBT_L(DiagnosticService)))
+        else if (XMLHelper::isNodeNamed(handler->getProperties()->getElement(),shibtarget::XML::SHIBTARGET_NS,SHIBT_L(DiagnosticService)))
             procState = "Diagnostics Error";
         else
             procState = "Extension Service Error";
@@ -361,47 +403,48 @@ pair<bool,void*> ShibTarget::doHandler(void)
         if (hret.first)
             return hret;
        
-        throw SAMLException("Configured Shibboleth handler failed to process the request.");
+        throw opensaml::BindingException("Configured Shibboleth handler failed to process the request.");
     }
     catch (MetadataException& e) {
-        mlp.insert(e);
+        tp.m_map["errorText"] = e.what();
         // See if a metadata error page is installed.
-        const IPropertySet* props=m_priv->m_app->getPropertySet("Errors");
+        const PropertySet* props=m_priv->m_app->getPropertySet("Errors");
         if (props) {
             pair<bool,const char*> p=props->getString("metadata");
             if (p.first) {
-                mlp.insert("errorType", procState);
+                tp.m_map["errorType"] = procState;
                 if (targetURL)
-                    mlp.insert("requestURL", targetURL);
-                return make_pair(true,m_priv->sendError(this,"metadata", mlp));
+                    tp.m_map["requestURL"] = targetURL;
+                return make_pair(true,m_priv->sendError(this, "metadata", tp));
             }
         }
+        throw;
     }
-    catch (SAMLException& e) {
-        mlp.insert(e);
+    catch (XMLToolingException& e) {
+        tp.m_map["errorType"] = procState;
+        tp.m_map["errorText"] = e.what();
+        if (targetURL)
+            tp.m_map["requestURL"] = m_url.substr(0,m_url.find('?'));
+        return make_pair(true,m_priv->sendError(this, "session", tp, &e));
     }
 #ifndef _DEBUG
     catch (...) {
-        mlp.insert("errorText", "Caught an unknown exception.");
+        tp.m_map["errorType"] = procState;
+        tp.m_map["errorText"] = "Caught an unknown exception.";
+        if (targetURL)
+            tp.m_map["requestURL"] = m_url.substr(0,m_url.find('?'));
+        return make_pair(true,m_priv->sendError(this, "session", tp));
     }
 #endif
-
-    // If we get here then we've got an error.
-    mlp.insert("errorType", procState);
-
-    if (targetURL)
-        mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
-
-    return make_pair(true,m_priv->sendError(this,"session", mlp));
 }
 
 pair<bool,void*> ShibTarget::doCheckAuthZ(void)
 {
 #ifdef _DEBUG
-    saml::NDC ndc("doCheckAuthZ");
+    xmltooling::NDC ndc("doCheckAuthZ");
 #endif
 
-    ShibMLP mlp;
+    ExtTemplateParameters tp;
     const char* procState = "Authorization Processing Error";
     const char* targetURL = m_url.c_str();
 
@@ -423,7 +466,7 @@ pair<bool,void*> ShibTarget::doCheckAuthZ(void)
 #else
                 (!authType.first || _stricmp(authType.second,"shibboleth")))
 #endif
-            return pair<bool,void*>(true,returnDecline());
+            return make_pair(true,returnDecline());
 
         // Do we have an access control plugin?
         if (m_priv->m_settings.second) {
@@ -441,7 +484,7 @@ pair<bool,void*> ShibTarget::doCheckAuthZ(void)
                             );
                                }
                    }
-                   catch (SAMLException&) {
+                   catch (exception&) {
                        log(LogLevelError, "doCheckAuthZ: unable to obtain session information to pass to access control provider");
                    }
                }
@@ -450,43 +493,43 @@ pair<bool,void*> ShibTarget::doCheckAuthZ(void)
             if (m_priv->m_settings.second->authorized(this,m_priv->m_cacheEntry)) {
                 // Let the caller decide how to proceed.
                 log(LogLevelDebug, "doCheckAuthZ: access control provider granted access");
-                return pair<bool,void*>(false,NULL);
+                return make_pair(false,(void*)NULL);
             }
             else {
                 log(LogLevelWarn, "doCheckAuthZ: access control provider denied access");
                 if (targetURL)
-                    mlp.insert("requestURL", targetURL);
-                return make_pair(true,m_priv->sendError(this, "access", mlp));
+                    tp.m_map["requestURL"] = targetURL;
+                return make_pair(true,m_priv->sendError(this, "access", tp));
             }
         }
         else
             return make_pair(true,returnDecline());
     }
-    catch (SAMLException& e) {
-        mlp.insert(e);
+    catch (exception& e) {
+        tp.m_map["errorType"] = procState;
+        tp.m_map["errorText"] = e.what();
+        if (targetURL)
+            tp.m_map["requestURL"] = m_url.substr(0,m_url.find('?'));
+        return make_pair(true,m_priv->sendError(this, "access", tp));
     }
 #ifndef _DEBUG
     catch (...) {
-        mlp.insert("errorText", "Caught an unknown exception.");
+        tp.m_map["errorType"] = procState;
+        tp.m_map["errorText"] = "Caught an unknown exception.";
+        if (targetURL)
+            tp.m_map["requestURL"] = m_url.substr(0,m_url.find('?'));
+        return make_pair(true,m_priv->sendError(this, "access", tp));
     }
 #endif
-
-    // If we get here then we've got an error.
-    mlp.insert("errorType", procState);
-
-    if (targetURL)
-        mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
-
-    return make_pair(true,m_priv->sendError(this, "access", mlp));
 }
 
 pair<bool,void*> ShibTarget::doExportAssertions(bool requireSession)
 {
 #ifdef _DEBUG
-    saml::NDC ndc("doExportAssertions");
+    xmltooling::NDC ndc("doExportAssertions");
 #endif
 
-    ShibMLP mlp;
+    ExtTemplateParameters tp;
     const char* procState = "Attribute Processing Error";
     const char* targetURL = m_url.c_str();
 
@@ -508,7 +551,7 @@ pair<bool,void*> ShibTarget::doExportAssertions(bool requireSession)
                         );
                        }
             }
-            catch (SAMLException&) {
+            catch (exception&) {
                log(LogLevelError, "unable to obtain session information to export into request headers");
                // If we have to have a session, then this is a fatal error.
                if (requireSession)
@@ -519,9 +562,9 @@ pair<bool,void*> ShibTarget::doExportAssertions(bool requireSession)
                // Still no data?
         if (!m_priv->m_cacheEntry) {
                if (requireSession)
-                       throw InvalidSessionException("Unable to obtain session information for request.");
+                       throw RetryableProfileException("Unable to obtain session information for request.");
                else
-                       return pair<bool,void*>(false,NULL);    // just bail silently
+                       return make_pair(false,(void*)NULL);    // just bail silently
         }
         
         // Extract data from session.
@@ -620,24 +663,24 @@ pair<bool,void*> ShibTarget::doExportAssertions(bool requireSession)
             }
         }
     
-        return pair<bool,void*>(false,NULL);
+        return make_pair(false,(void*)NULL);
     }
-    catch (SAMLException& e) {
-        mlp.insert(e);
+    catch (XMLToolingException& e) {
+        tp.m_map["errorType"] = procState;
+        tp.m_map["errorText"] = e.what();
+        if (targetURL)
+            tp.m_map["requestURL"] = m_url.substr(0,m_url.find('?'));
+        return make_pair(true,m_priv->sendError(this, "rm", tp, &e));
     }
 #ifndef _DEBUG
     catch (...) {
-        mlp.insert("errorText", "Caught an unknown exception.");
+        tp.m_map["errorType"] = procState;
+        tp.m_map["errorText"] = "Caught an unknown exception.";
+        if (targetURL)
+            tp.m_map["requestURL"] = m_url.substr(0,m_url.find('?'));
+        return make_pair(true,m_priv->sendError(this, "rm", tp));
     }
 #endif
-
-    // If we get here then we've got an error.
-    mlp.insert("errorType", procState);
-
-    if (targetURL)
-        mlp.insert("requestURL", m_url.substr(0,m_url.find('?')));
-
-    return make_pair(true,m_priv->sendError(this, "rm", mlp));
 }
 
 const char* ShibTarget::getRequestParameter(const char* param, size_t index) const
@@ -691,7 +734,7 @@ pair<string,const char*> ShibTarget::getCookieNameProps(const char* prefix) cons
 {
     static const char* defProps="; path=/";
     
-    const IPropertySet* props=m_priv->m_app ? m_priv->m_app->getPropertySet("Sessions") : NULL;
+    const PropertySet* props=m_priv->m_app ? m_priv->m_app->getPropertySet("Sessions") : NULL;
     if (props) {
         pair<bool,const char*> p=props->getString("cookieProps");
         if (!p.first)
@@ -716,7 +759,7 @@ string ShibTarget::getHandlerURL(const char* resource) const
 
     bool ssl_only=false;
     const char* handler=NULL;
-    const IPropertySet* props=m_priv->m_app->getPropertySet("Sessions");
+    const PropertySet* props=m_priv->m_app->getPropertySet("Sessions");
     if (props) {
         pair<bool,bool> p=props->getBool("handlerSSL");
         if (p.first)
@@ -730,7 +773,7 @@ string ShibTarget::getHandlerURL(const char* resource) const
     if (!handler || (*handler!='/' && strncmp(handler,"http:",5) && strncmp(handler,"https:",6)))
         throw ConfigurationException(
             "Invalid handlerURL property ($1) in Application ($2)",
-            params(2, handler ? handler : "null", m_priv->m_app->getId())
+            xmltooling::params(2, handler ? handler : "null", m_priv->m_app->getId())
             );
 
     // The "handlerURL" property can be in one of three formats:
@@ -816,53 +859,6 @@ void* ShibTarget::returnOK(void)
     return NULL;
 }
 
-static char x2c(char *what)
-{
-    register char digit;
-
-    digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
-    digit *= 16;
-    digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
-    return(digit);
-}
-
-void ShibTarget::url_decode(char* s)
-{
-    register int x,y;
-
-    for(x=0,y=0;s[y];++x,++y)
-    {
-        if((s[x] = s[y]) == '%')
-        {
-            s[x] = x2c(&s[y+1]);
-            y+=2;
-        }
-    }
-    s[x] = '\0';
-}
-
-static inline char hexchar(unsigned short s)
-{
-    return (s<=9) ? ('0' + s) : ('A' + s - 10);
-}
-
-string ShibTarget::url_encode(const char* s)
-{
-    static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
-
-    string ret;
-    for (; *s; s++) {
-        if (strchr(badchars,*s) || *s<=0x20 || *s>=0x7F) {
-            ret+='%';
-        ret+=hexchar(*s >> 4);
-        ret+=hexchar(*s & 0x0F);
-        }
-        else
-            ret+=*s;
-    }
-    return ret;
-}
-
 /*************************************************************************
  * Shib Target Private implementation
  */
@@ -932,22 +928,26 @@ void ShibTargetPriv::get_application(ShibTarget* st, const string& protocol, con
   st->m_url += uri;
 }
 
-void* ShibTargetPriv::sendError(ShibTarget* st, const char* page, ShibMLP &mlp)
+void* ShibTargetPriv::sendError(
+    ShibTarget* st, const char* page, ExtTemplateParameters& tp, const XMLToolingException* ex
+    )
 {
     ShibTarget::header_t hdrs[] = {
         ShibTarget::header_t("Expires","01-Jan-1997 12:00:00 GMT"),
         ShibTarget::header_t("Cache-Control","private,no-store,no-cache")
         };
     
-    const IPropertySet* props=m_app->getPropertySet("Errors");
+    TemplateEngine* engine = XMLToolingConfig::getConfig().getTemplateEngine();
+    const PropertySet* props=m_app->getPropertySet("Errors");
     if (props) {
         pair<bool,const char*> p=props->getString(page);
         if (p.first) {
             ifstream infile(p.second);
-            if (!infile.fail()) {
-                const char* res = mlp.run(infile,props);
-                if (res)
-                    return st->sendPage(res, 200, "text/html", ArrayIterator<ShibTarget::header_t>(hdrs,2));
+            if (infile) {
+                tp.setPropertySet(props);
+                ostringstream ostr;
+                engine->run(infile, ostr, tp, ex);
+                return st->sendPage(ostr.str().c_str(), 200, "text/html", ArrayIterator<ShibTarget::header_t>(hdrs,2));
             }
         }
         else if (!strcmp(page,"access"))
@@ -1006,7 +1006,7 @@ CgiParse::CgiParse(const ShibTarget* st)
         char *value;
         value=fmakeword('&',&cl,&pch);
         plustospace(value);
-        ShibTarget::url_decode(value);
+        opensaml::SAMLConfig::getConfig().getURLEncoder()->decode(value);
         name=makeword(value,'=');
         kvp_map.insert(pair<string,char*>(name,value));
         free(name);