c717d0706c3250835e0edfeea3852fb3557a4ee7
[shibboleth/cpp-sp.git] / shib-target / shib-handlers.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  * shib-handlers.cpp -- profile handlers that plug into SP
19  *
20  * Scott Cantor
21  * 5/17/2005
22  */
23
24 #include "internal.h"
25
26 #include <ctime>
27 #include <saml/SAMLConfig.h>
28 #include <saml/binding/URLEncoder.h>
29 #include <saml/util/CommonDomainCookie.h>
30 #include <shibsp/SPConfig.h>
31
32 #ifdef HAVE_UNISTD_H
33 # include <unistd.h>
34 #endif
35
36 using namespace shibsp;
37 using namespace shibtarget;
38 using namespace shibboleth;
39 using namespace saml;
40 using namespace log4cpp;
41 using namespace std;
42
43 using opensaml::CommonDomainCookie;
44 using opensaml::URLEncoder;
45
46 namespace {
47   class SessionInitiator : virtual public IHandler
48   {
49   public:
50     SessionInitiator(const DOMElement* e) {}
51     ~SessionInitiator() {}
52     pair<bool,void*> run(ShibTarget* st, bool isHandler=true) const;
53     pair<bool,void*> ShibAuthnRequest(
54         ShibTarget* st,
55         const IHandler* shire,
56         const char* dest,
57         const char* target,
58         const char* providerId
59         ) const;
60   };
61
62   class SAML1Consumer : virtual public IHandler, public virtual Remoted
63   {
64   public:
65     SAML1Consumer(const DOMElement* e);
66     ~SAML1Consumer();
67     pair<bool,void*> run(ShibTarget* st, bool isHandler=true) const;
68     DDF receive(const DDF& in);
69   private:
70     string m_address;
71     static int counter;
72   };
73
74   int SAML1Consumer::counter = 0;
75
76   class ShibLogout : virtual public IHandler
77   {
78   public:
79     ShibLogout(const DOMElement* e) {}
80     ~ShibLogout() {}
81     pair<bool,void*> run(ShibTarget* st, bool isHandler=true) const;
82   };
83 }
84
85
86 IPlugIn* ShibSessionInitiatorFactory(const DOMElement* e)
87 {
88     return new SessionInitiator(e);
89 }
90
91 IPlugIn* SAML1POSTFactory(const DOMElement* e)
92 {
93     return new SAML1Consumer(e);
94 }
95
96 IPlugIn* SAML1ArtifactFactory(const DOMElement* e)
97 {
98     return new SAML1Consumer(e);
99 }
100
101 IPlugIn* ShibLogoutFactory(const DOMElement* e)
102 {
103     return new ShibLogout(e);
104 }
105
106 pair<bool,void*> SessionInitiator::run(ShibTarget* st, bool isHandler) const
107 {
108     string dupresource;
109     const char* resource=NULL;
110     const IHandler* ACS=NULL;
111     const IApplication* app=st->getApplication();
112     
113     if (isHandler) {
114         /* 
115          * Binding is CGI query string with:
116          *  target      the resource to direct back to later
117          *  acsIndex    optional index of an ACS to use on the way back in
118          *  providerId  optional direct invocation of a specific IdP
119          */
120         const char* option=st->getRequestParameter("acsIndex");
121         if (option)
122             ACS=app->getAssertionConsumerServiceByIndex(atoi(option));
123         option=st->getRequestParameter("providerId");
124         
125         resource=st->getRequestParameter("target");
126         if (!resource || !*resource) {
127             pair<bool,const char*> home=app->getString("homeURL");
128             if (home.first)
129                 resource=home.second;
130             else
131                 throw FatalProfileException("Session initiator requires a target parameter or a homeURL application property.");
132         }
133         else if (!option) {
134             dupresource=resource;
135             resource=dupresource.c_str();
136         }
137         
138         if (option) {
139             // Here we actually use metadata to invoke the SSO service directly.
140             // The only currently understood binding is the Shibboleth profile.
141             Metadata m(app->getMetadataProviders());
142             const IEntityDescriptor* entity=m.lookup(option);
143             if (!entity)
144                 throw MetadataException("Session initiator unable to locate metadata for provider ($1).", params(1,option));
145             const IIDPSSODescriptor* role=entity->getIDPSSODescriptor(Constants::SHIB_NS);
146             if (!role)
147                 throw MetadataException(
148                     "Session initiator unable to locate a Shibboleth-aware identity provider role for provider ($1).", params(1,option)
149                     );
150             const IEndpointManager* SSO=role->getSingleSignOnServiceManager();
151             const IEndpoint* ep=SSO->getEndpointByBinding(Constants::SHIB_AUTHNREQUEST_PROFILE_URI);
152             if (!ep)
153                 throw MetadataException(
154                     "Session initiator unable to locate compatible SSO service for provider ($1).", params(1,option)
155                     );
156             auto_ptr_char dest(ep->getLocation());
157             return ShibAuthnRequest(
158                 st,ACS ? ACS : app->getDefaultAssertionConsumerService(),dest.get(),resource,app->getString("providerId").second
159                 );
160         }
161     }
162     else {
163         // We're running as a "virtual handler" from within the filter.
164         // The target resource is the current one and everything else is defaulted.
165         resource=st->getRequestURL();
166     }
167     
168     if (!ACS) ACS=app->getDefaultAssertionConsumerService();
169     
170     // For now, we only support external session initiation via a wayfURL
171     pair<bool,const char*> wayfURL=getProperties()->getString("wayfURL");
172     if (!wayfURL.first)
173         throw ConfigurationException("Session initiator is missing wayfURL property.");
174
175     pair<bool,const XMLCh*> wayfBinding=getProperties()->getXMLString("wayfBinding");
176     if (!wayfBinding.first || !XMLString::compareString(wayfBinding.second,Constants::SHIB_AUTHNREQUEST_PROFILE_URI))
177         // Standard Shib 1.x
178         return ShibAuthnRequest(st,ACS,wayfURL.second,resource,app->getString("providerId").second);
179     else if (!XMLString::compareString(wayfBinding.second,Constants::SHIB_LEGACY_AUTHNREQUEST_PROFILE_URI))
180         // Shib pre-1.2
181         return ShibAuthnRequest(st,ACS,wayfURL.second,resource,NULL);
182     else if (!strcmp(getProperties()->getString("wayfBinding").second,"urn:mace:shibboleth:1.0:profiles:EAuth")) {
183         // TODO: Finalize E-Auth profile URI
184         pair<bool,bool> localRelayState=st->getConfig()->getPropertySet("InProcess")->getBool("localRelayState");
185         if (!localRelayState.first || !localRelayState.second)
186             throw ConfigurationException("E-Authn requests cannot include relay state, so localRelayState must be enabled.");
187
188         // Here we store the state in a cookie.
189         pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibstate_");
190         st->setCookie(shib_cookie.first,opensaml::SAMLConfig::getConfig().getURLEncoder()->encode(resource) + shib_cookie.second);
191         return make_pair(true, st->sendRedirect(wayfURL.second));
192     }
193    
194     throw UnsupportedProfileException("Unsupported WAYF binding ($1).", params(1,getProperties()->getString("wayfBinding").second));
195 }
196
197 // Handles Shib 1.x AuthnRequest profile.
198 pair<bool,void*> SessionInitiator::ShibAuthnRequest(
199     ShibTarget* st,
200     const IHandler* shire,
201     const char* dest,
202     const char* target,
203     const char* providerId
204     ) const
205 {
206     // Compute the ACS URL. We add the ACS location to the base handlerURL.
207     // Legacy configs will not have the Location property specified, so no suffix will be added.
208     string ACSloc=st->getHandlerURL(target);
209     pair<bool,const char*> loc=shire ? shire->getProperties()->getString("Location") : pair<bool,const char*>(false,NULL);
210     if (loc.first) ACSloc+=loc.second;
211     
212     URLEncoder* urlenc = opensaml::SAMLConfig::getConfig().getURLEncoder();
213
214     char timebuf[16];
215     sprintf(timebuf,"%u",time(NULL));
216     string req=string(dest) + "?shire=" + urlenc->encode(ACSloc.c_str()) + "&time=" + timebuf;
217
218     // How should the resource value be preserved?
219     pair<bool,bool> localRelayState=st->getConfig()->getPropertySet("InProcess")->getBool("localRelayState");
220     if (!localRelayState.first || !localRelayState.second) {
221         // The old way, just send it along.
222         req+="&target=" + urlenc->encode(target);
223     }
224     else {
225         // Here we store the state in a cookie and send a fixed
226         // value to the IdP so we can recognize it on the way back.
227         pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibstate_");
228         st->setCookie(shib_cookie.first,urlenc->encode(target) + shib_cookie.second);
229         req+="&target=cookie";
230     }
231     
232     // Only omitted for 1.1 style requests.
233     if (providerId)
234         req+="&providerId=" + urlenc->encode(providerId);
235
236     return make_pair(true, st->sendRedirect(req));
237 }
238
239 SAML1Consumer::SAML1Consumer(const DOMElement* e)
240 {
241     m_address += ('A' + (counter++));
242     m_address += "::SAML1Consumer::run";
243
244     // Register for remoted messages.
245     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
246         ListenerService* listener=ShibTargetConfig::getConfig().getINI()->getListener();
247         if (listener)
248             listener->regListener(m_address.c_str(),this);
249         else
250             throw ListenerException("Plugin requires a Listener service");
251     }
252 }
253
254 SAML1Consumer::~SAML1Consumer()
255 {
256     ListenerService* listener=ShibTargetConfig::getConfig().getINI()->getListener();
257     if (listener && SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess))
258         listener->unregListener(m_address.c_str(),this);
259     counter--;
260 }
261
262 /*
263  * IPC message definitions:
264  * 
265  *  [A-Z]::SAML1Consumer::run
266  * 
267  *      IN
268  *      application_id
269  *      client_address
270  *      recipient
271  *      SAMLResponse or SAMLart list
272  * 
273  *      OUT
274  *      key
275  *      provider_id
276  */
277 DDF SAML1Consumer::receive(const DDF& in)
278 {
279 #ifdef _DEBUG
280     saml::NDC ndc("receive");
281 #endif
282     Category& log=Category::getInstance(SHIBT_LOGCAT".SAML1Consumer");
283
284     // Find application.
285     const char* aid=in["application_id"].string();
286     const IApplication* app=aid ? ShibTargetConfig::getConfig().getINI()->getApplication(aid) : NULL;
287     if (!app) {
288         // Something's horribly wrong.
289         log.error("couldn't find application (%s) for new session", aid ? aid : "(missing)");
290         throw SAMLException("Unable to locate application for new session, deleted?");
291     }
292
293     // Check required parameters.
294     const char* client_address=in["client_address"].string();
295     const char* recipient=in["recipient"].string();
296     if (!client_address || !recipient)
297         throw SAMLException("Required parameters missing in call to SAML1Consumer::run");
298     
299     log.debug("processing new assertion for %s", client_address);
300     log.debug("recipient: %s", recipient);
301     log.debug("application: %s", app->getId());
302
303     // Access the application config. It's already locked behind us.
304     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
305     IConfig* conf=stc.getINI();
306
307     auto_ptr_XMLCh wrecipient(recipient);
308
309     pair<bool,bool> checkAddress=pair<bool,bool>(false,true);
310     pair<bool,bool> checkReplay=pair<bool,bool>(false,true);
311     const PropertySet* props=app->getPropertySet("Sessions");
312     if (props) {
313         checkAddress=props->getBool("checkAddress");
314         if (!checkAddress.first)
315             checkAddress.second=true;
316         checkReplay=props->getBool("checkReplay");
317         if (!checkReplay.first)
318             checkReplay.second=true;
319     }
320
321     // Supports either version...
322     pair<bool,unsigned int> version=getProperties()->getUnsignedInt("MinorVersion","urn:oasis:names:tc:SAML:1.0:protocol");
323     if (!version.first)
324         version.second=1;
325
326     const IRoleDescriptor* role=NULL;
327     Metadata m(app->getMetadataProviders());
328     SAMLBrowserProfile::BrowserProfileResponse bpr;
329
330     try {
331         const char* samlResponse=in["SAMLResponse"].string();
332         if (samlResponse) {
333             // POST profile
334             log.debug("executing Browser/POST profile...");
335             bpr=app->getBrowserProfile()->receive(
336                 samlResponse,
337                 wrecipient.get(),
338                 checkReplay.second ? conf->getReplayCache() : NULL,
339                 version.second
340                 );
341         }
342         else {
343             // Artifact profile
344             vector<const char*> SAMLart;
345             DDF arts=in["SAMLart"];
346             DDF art=arts.first();
347             while (art.isstring()) {
348                 SAMLart.push_back(art.string());
349                 art=arts.next();
350             }
351             auto_ptr<SAMLBrowserProfile::ArtifactMapper> artifactMapper(app->getArtifactMapper());
352             log.debug("executing Browser/Artifact profile...");
353             bpr=app->getBrowserProfile()->receive(
354                 SAMLart,
355                 wrecipient.get(),
356                 artifactMapper.get(),
357                 checkReplay.second ? conf->getReplayCache() : NULL,
358                 version.second
359                 );
360
361             // Blow it away to clear any locks that might be held.
362             delete artifactMapper.release();
363         }
364
365         // Try and map to metadata (again).
366         // Once the metadata layer is in the SAML core, the repetition should be fixed.
367         const IEntityDescriptor* provider=m.lookup(bpr.assertion->getIssuer());
368         if (!provider && bpr.authnStatement->getSubject()->getNameIdentifier() &&
369                 bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier())
370             provider=m.lookup(bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier());
371         if (provider) {
372             const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(
373                 version.second==1 ? saml::XML::SAML11_PROTOCOL_ENUM : saml::XML::SAML10_PROTOCOL_ENUM
374                 );
375             role=IDP;
376         }
377         
378         // This isn't likely, since the profile must have found a role.
379         if (!role) {
380             MetadataException ex("Unable to locate role-specific metadata for identity provider.");
381             annotateException(&ex,provider); // throws it
382         }
383     
384         // Maybe verify the client address....
385         if (checkAddress.second) {
386             log.debug("verifying client address");
387             // Verify the client address exists
388             const XMLCh* wip = bpr.authnStatement->getSubjectIP();
389             if (wip && *wip) {
390                 // Verify the client address matches authentication
391                 auto_ptr_char this_ip(wip);
392                 if (strcmp(client_address, this_ip.get())) {
393                     FatalProfileException ex(
394                         SESSION_E_ADDRESSMISMATCH,
395                        "Your client's current address ($1) differs from the one used when you authenticated "
396                         "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
397                         "Please contact your local support staff or help desk for assistance.",
398                         params(1,client_address)
399                         );
400                     annotateException(&ex,role); // throws it
401                 }
402             }
403         }
404     }
405     catch (SAMLException&) {
406         bpr.clear();
407         throw;
408     }
409     catch (...) {
410         log.error("caught unknown exception");
411         bpr.clear();
412 #ifdef _DEBUG
413         throw;
414 #else
415         SAMLException e("An unexpected error occurred while creating your session.");
416         annotateException(&e,role);
417 #endif
418     }
419
420     // It passes all our tests -- create a new session.
421     log.info("creating new session");
422
423     DDF out;
424     try {
425         // Insert into cache.
426         auto_ptr_char authContext(bpr.authnStatement->getAuthMethod());
427         string key=conf->getSessionCache()->insert(
428             app,
429             role->getEntityDescriptor(),
430             client_address,
431             bpr.authnStatement->getSubject(),
432             authContext.get(),
433             bpr.response
434             );
435         // objects owned by cache now
436         log.debug("new session id: %s", key.c_str());
437         auto_ptr_char oname(role->getEntityDescriptor()->getId());
438         out=DDF(NULL).structure();
439         out.addmember("key").string(key.c_str());
440         out.addmember("provider_id").string(oname.get());
441     }
442     catch (...) {
443 #ifdef _DEBUG
444         throw;
445 #else
446         SAMLException e("An unexpected error occurred while creating your session.");
447         annotateException(&e,role);
448 #endif
449     }
450
451     return out;
452 }
453
454 pair<bool,void*> SAML1Consumer::run(ShibTarget* st, bool isHandler) const
455 {
456     DDF in,out;
457     DDFJanitor jin(in),jout(out);
458
459     pair<bool,const XMLCh*> binding=getProperties()->getXMLString("Binding");
460     if (!binding.first || !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_POST)) {
461 #ifdef HAVE_STRCASECMP
462         if (strcasecmp(st->getRequestMethod(), "POST")) {
463 #else
464         if (_stricmp(st->getRequestMethod(), "POST")) {
465 #endif
466             st->log(ShibTarget::LogLevelInfo, "SAML 1.x Browser/POST handler ignoring non-POST request");
467             return pair<bool,void*>(false,NULL);
468         }
469 #ifdef HAVE_STRCASECMP
470         if (!st->getContentType() || strcasecmp(st->getContentType(),"application/x-www-form-urlencoded")) {
471 #else
472         if (!st->getContentType() || _stricmp(st->getContentType(),"application/x-www-form-urlencoded")) {
473 #endif
474             st->log(ShibTarget::LogLevelInfo, "SAML 1.x Browser/POST handler ignoring submission with unknown content-type.");
475             return pair<bool,void*>(false,NULL);
476         }
477
478         const char* samlResponse = st->getRequestParameter("SAMLResponse");
479         if (!samlResponse) {
480             st->log(ShibTarget::LogLevelInfo, "SAML 1.x Browser/POST handler ignoring request with no SAMLResponse parameter.");
481             return pair<bool,void*>(false,NULL);
482         }
483
484         in=DDF(m_address.c_str()).structure();
485         in.addmember("SAMLResponse").string(samlResponse);
486     }
487     else if (!XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_ARTIFACT)) {
488 #ifdef HAVE_STRCASECMP
489         if (strcasecmp(st->getRequestMethod(), "GET")) {
490 #else
491         if (_stricmp(st->getRequestMethod(), "GET")) {
492 #endif
493             st->log(ShibTarget::LogLevelInfo, "SAML 1.x Browser/Artifact handler ignoring non-GET request");
494             return pair<bool,void*>(false,NULL);
495         }
496
497         const char* SAMLart=st->getRequestParameter("SAMLart");
498         if (!SAMLart) {
499             st->log(ShibTarget::LogLevelInfo, "SAML 1.x Browser/Artifact handler ignoring request with no SAMLart parameter.");
500             return pair<bool,void*>(false,NULL);
501         }
502
503         in=DDF(m_address.c_str()).structure();
504         DDF artlist=in.addmember("SAMLart").list();
505
506         while (SAMLart) {
507             artlist.add(DDF(NULL).string(SAMLart));
508             SAMLart=st->getRequestParameter("SAMLart",artlist.integer());
509         }
510     }
511     
512     // Compute the endpoint location.
513     string hURL=st->getHandlerURL(st->getRequestURL());
514     pair<bool,const char*> loc=getProperties()->getString("Location");
515     string recipient=loc.first ? hURL + loc.second : hURL;
516     in.addmember("recipient").string(recipient.c_str());
517
518     // Add remaining parameters.
519     in.addmember("application_id").string(st->getApplication()->getId());
520     in.addmember("client_address").string(st->getRemoteAddr());
521
522     out=st->getConfig()->getListener()->send(in);
523     if (!out["key"].isstring())
524         throw FatalProfileException("Remote processing of SAML 1.x Browser profile did not return a usable session key.");
525     string key=out["key"].string();
526
527     st->log(ShibTarget::LogLevelDebug, string("profile processing succeeded, new session created (") + key + ")");
528
529     const char* target=st->getRequestParameter("TARGET");
530     if (target && !strcmp(target,"default")) {
531         pair<bool,const char*> homeURL=st->getApplication()->getString("homeURL");
532         target=homeURL.first ? homeURL.second : "/";
533     }
534     else if (!target || !strcmp(target,"cookie")) {
535         // Pull the target value from the "relay state" cookie.
536         pair<string,const char*> relay_cookie = st->getCookieNameProps("_shibstate_");
537         const char* relay_state = st->getCookie(relay_cookie.first);
538         if (!relay_state || !*relay_state) {
539             // No apparent relay state value to use, so fall back on the default.
540             pair<bool,const char*> homeURL=st->getApplication()->getString("homeURL");
541             target=homeURL.first ? homeURL.second : "/";
542         }
543         else {
544             char* rscopy=strdup(relay_state);
545             opensaml::SAMLConfig::getConfig().getURLEncoder()->decode(rscopy);
546             hURL=rscopy;
547             free(rscopy);
548             target=hURL.c_str();
549         }
550         st->setCookie(relay_cookie.first,relay_cookie.second);
551     }
552
553     // We've got a good session, set the session cookie.
554     pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibsession_");
555     st->setCookie(shib_cookie.first, key + shib_cookie.second);
556
557     const char* providerId=out["provider_id"].string();
558     if (providerId) {
559         const PropertySet* sessionProps=st->getApplication()->getPropertySet("Sessions");
560         pair<bool,bool> idpHistory=sessionProps->getBool("idpHistory");
561         if (!idpHistory.first || idpHistory.second) {
562             // Set an IdP history cookie locally (essentially just a CDC).
563             CommonDomainCookie cdc(st->getCookie(CommonDomainCookie::CDCName));
564
565             // Either leave in memory or set an expiration.
566             pair<bool,unsigned int> days=sessionProps->getUnsignedInt("idpHistoryDays");
567                 if (!days.first || days.second==0)
568                     st->setCookie(CommonDomainCookie::CDCName,string(cdc.set(providerId)) + shib_cookie.second);
569                 else {
570                     time_t now=time(NULL) + (days.second * 24 * 60 * 60);
571 #ifdef HAVE_GMTIME_R
572                     struct tm res;
573                     struct tm* ptime=gmtime_r(&now,&res);
574 #else
575                     struct tm* ptime=gmtime(&now);
576 #endif
577                     char timebuf[64];
578                     strftime(timebuf,64,"%a, %d %b %Y %H:%M:%S GMT",ptime);
579                     st->setCookie(
580                         CommonDomainCookie::CDCName,
581                         string(cdc.set(providerId)) + shib_cookie.second + "; expires=" + timebuf
582                         );
583             }
584         }
585     }
586
587     // Now redirect to the target.
588     return make_pair(true, st->sendRedirect(target));
589 }
590
591 pair<bool,void*> ShibLogout::run(ShibTarget* st, bool isHandler) const
592 {
593     // Recover the session key.
594     pair<string,const char*> shib_cookie = st->getCookieNameProps("_shibsession_");
595     const char* session_id = st->getCookie(shib_cookie.first);
596     
597     // Logout is best effort.
598     if (session_id && *session_id) {
599         try {
600             st->getConfig()->getSessionCache()->remove(session_id,st->getApplication(),st->getRemoteAddr());
601         }
602         catch (SAMLException& e) {
603             st->log(ShibTarget::LogLevelError, string("logout processing failed with exception: ") + e.what());
604         }
605 #ifndef _DEBUG
606         catch (...) {
607             st->log(ShibTarget::LogLevelError, "logout processing failed with unknown exception");
608         }
609 #endif
610         // We send the cookie property alone, which acts as an empty value.
611         st->setCookie(shib_cookie.first,shib_cookie.second);
612     }
613     
614     const char* ret=st->getRequestParameter("return");
615     if (!ret)
616         ret=getProperties()->getString("ResponseLocation").second;
617     if (!ret)
618         ret=st->getApplication()->getString("homeURL").second;
619     if (!ret)
620         ret="/";
621     return make_pair(true, st->sendRedirect(ret));
622 }