23b1bc8efd413ccd531c042f6d1e5e0845456ea8
[shibboleth/sp.git] / shibsp / handler / impl / SAML2Logout.cpp
1 /*
2  *  Copyright 2001-2009 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  * SAML2Logout.cpp
19  *
20  * Handles SAML 2.0 single logout protocol messages.
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "Application.h"
26 #include "ServiceProvider.h"
27 #include "SPRequest.h"
28 #include "handler/AbstractHandler.h"
29 #include "handler/LogoutHandler.h"
30 #include "util/SPConstants.h"
31
32 #ifndef SHIBSP_LITE
33 # include "SessionCacheEx.h"
34 # include "security/SecurityPolicy.h"
35 # include "metadata/MetadataProviderCriteria.h"
36 # include "util/TemplateParameters.h"
37 # include <fstream>
38 # include <saml/exceptions.h>
39 # include <saml/SAMLConfig.h>
40 # include <saml/saml2/core/Protocols.h>
41 # include <saml/saml2/metadata/EndpointManager.h>
42 # include <saml/saml2/metadata/Metadata.h>
43 # include <saml/saml2/metadata/MetadataCredentialCriteria.h>
44 # include <xmltooling/util/URLEncoder.h>
45 using namespace opensaml::saml2;
46 using namespace opensaml::saml2p;
47 using namespace opensaml::saml2md;
48 using namespace opensaml;
49 #endif
50
51 using namespace shibsp;
52 using namespace xmltooling;
53 using namespace std;
54
55 namespace shibsp {
56
57 #if defined (_MSC_VER)
58     #pragma warning( push )
59     #pragma warning( disable : 4250 )
60 #endif
61
62     class SHIBSP_DLLLOCAL SAML2Logout : public AbstractHandler, public LogoutHandler
63     {
64     public:
65         SAML2Logout(const DOMElement* e, const char* appId);
66         virtual ~SAML2Logout() {
67 #ifndef SHIBSP_LITE
68             if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
69                 delete m_decoder;
70                 XMLString::release(&m_outgoing);
71                 for_each(m_encoders.begin(), m_encoders.end(), cleanup_pair<const XMLCh*,MessageEncoder>());
72             }
73 #endif
74         }
75
76         void receive(DDF& in, ostream& out);
77         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
78
79 #ifndef SHIBSP_LITE
80         void generateMetadata(SPSSODescriptor& role, const char* handlerURL) const {
81             const char* loc = getString("Location").second;
82             string hurl(handlerURL);
83             if (*loc != '/')
84                 hurl += '/';
85             hurl += loc;
86             auto_ptr_XMLCh widen(hurl.c_str());
87             SingleLogoutService* ep = SingleLogoutServiceBuilder::buildSingleLogoutService();
88             ep->setLocation(widen.get());
89             ep->setBinding(getXMLString("Binding").second);
90             role.getSingleLogoutServices().push_back(ep);
91             role.addSupport(samlconstants::SAML20P_NS);
92         }
93
94         const char* getType() const {
95             return "SingleLogoutService";
96         }
97 #endif
98
99     private:
100         pair<bool,long> doRequest(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
101
102 #ifndef SHIBSP_LITE
103         pair<bool,long> sendResponse(
104             const XMLCh* requestID,
105             const XMLCh* code,
106             const XMLCh* subcode,
107             const char* msg,
108             const char* relayState,
109             const RoleDescriptor* role,
110             const Application& application,
111             HTTPResponse& httpResponse,
112             bool front
113             ) const;
114
115         xmltooling::QName m_role;
116         MessageDecoder* m_decoder;
117         XMLCh* m_outgoing;
118         vector<const XMLCh*> m_bindings;
119         map<const XMLCh*,MessageEncoder*> m_encoders;
120 #endif
121     };
122
123 #if defined (_MSC_VER)
124     #pragma warning( pop )
125 #endif
126
127     Handler* SHIBSP_DLLLOCAL SAML2LogoutFactory(const pair<const DOMElement*,const char*>& p)
128     {
129         return new SAML2Logout(p.first, p.second);
130     }
131 };
132
133 SAML2Logout::SAML2Logout(const DOMElement* e, const char* appId)
134     : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".Logout.SAML2"))
135 #ifndef SHIBSP_LITE
136         ,m_role(samlconstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME), m_decoder(NULL), m_outgoing(NULL)
137 #endif
138 {
139     m_initiator = false;
140 #ifndef SHIBSP_LITE
141     m_preserve.push_back("ID");
142     m_preserve.push_back("entityID");
143     m_preserve.push_back("RelayState");
144
145     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
146         SAMLConfig& conf = SAMLConfig::getConfig();
147
148         // Handle incoming binding.
149         m_decoder = conf.MessageDecoderManager.newPlugin(
150             getString("Binding").second, pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS)
151             );
152         m_decoder->setArtifactResolver(SPConfig::getConfig().getArtifactResolver());
153
154         if (m_decoder->isUserAgentPresent()) {
155             // Handle front-channel binding setup.
156             pair<bool,const XMLCh*> outgoing = getXMLString("outgoingBindings", m_configNS.get());
157             if (outgoing.first) {
158                 m_outgoing = XMLString::replicate(outgoing.second);
159                 XMLString::trim(m_outgoing);
160             }
161             else {
162                 // No override, so we'll install a default binding precedence.
163                 string prec = string(samlconstants::SAML20_BINDING_HTTP_REDIRECT) + ' ' + samlconstants::SAML20_BINDING_HTTP_POST + ' ' +
164                     samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN + ' ' + samlconstants::SAML20_BINDING_HTTP_ARTIFACT;
165                 m_outgoing = XMLString::transcode(prec.c_str());
166             }
167
168             int pos;
169             XMLCh* start = m_outgoing;
170             while (start && *start) {
171                 pos = XMLString::indexOf(start,chSpace);
172                 if (pos != -1)
173                     *(start + pos)=chNull;
174                 m_bindings.push_back(start);
175                 try {
176                     auto_ptr_char b(start);
177                     MessageEncoder * encoder = conf.MessageEncoderManager.newPlugin(
178                         b.get(), pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS)
179                         );
180                     if (encoder->isUserAgentPresent()) {
181                         m_encoders[start] = encoder;
182                         m_log.debug("supporting outgoing binding (%s)", b.get());
183                     }
184                     else {
185                         delete encoder;
186                         m_log.warn("skipping outgoing binding (%s), not a front-channel mechanism", b.get());
187                     }
188                 }
189                 catch (exception& ex) {
190                     m_log.error("error building MessageEncoder: %s", ex.what());
191                 }
192                 if (pos != -1)
193                     start = start + pos + 1;
194                 else
195                     break;
196             }
197         }
198         else {
199             MessageEncoder* encoder = conf.MessageEncoderManager.newPlugin(
200                 getString("Binding").second, pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS)
201                 );
202             m_encoders.insert(pair<const XMLCh*,MessageEncoder*>(NULL, encoder));
203         }
204     }
205 #endif
206
207     string address(appId);
208     address += getString("Location").second;
209     setAddress(address.c_str());
210 }
211
212 pair<bool,long> SAML2Logout::run(SPRequest& request, bool isHandler) const
213 {
214     // Defer to base class for front-channel loop first.
215     // This won't initiate the loop, only continue/end it.
216     pair<bool,long> ret = LogoutHandler::run(request, isHandler);
217     if (ret.first)
218         return ret;
219
220     SPConfig& conf = SPConfig::getConfig();
221     if (conf.isEnabled(SPConfig::OutOfProcess)) {
222         // When out of process, we run natively and directly process the message.
223         return doRequest(request.getApplication(), request, request);
224     }
225     else {
226         // When not out of process, we remote all the message processing.
227         vector<string> headers(1,"Cookie");
228         DDF out,in = wrap(request, &headers, true);
229         DDFJanitor jin(in), jout(out);
230         out=request.getServiceProvider().getListenerService()->send(in);
231         return unwrap(request, out);
232     }
233 }
234
235 void SAML2Logout::receive(DDF& in, ostream& out)
236 {
237     // Find application.
238     const char* aid=in["application_id"].string();
239     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
240     if (!app) {
241         // Something's horribly wrong.
242         m_log.error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
243         throw ConfigurationException("Unable to locate application for logout, deleted?");
244     }
245
246     // Unpack the request.
247     auto_ptr<HTTPRequest> req(getRequest(in));
248
249     // Wrap a response shim.
250     DDF ret(NULL);
251     DDFJanitor jout(ret);
252     auto_ptr<HTTPResponse> resp(getResponse(ret));
253
254     // Since we're remoted, the result should either be a throw, which we pass on,
255     // a false/0 return, which we just return as an empty structure, or a response/redirect,
256     // which we capture in the facade and send back.
257     doRequest(*app, *req.get(), *resp.get());
258     out << ret;
259 }
260
261 pair<bool,long> SAML2Logout::doRequest(const Application& application, const HTTPRequest& request, HTTPResponse& response) const
262 {
263 #ifndef SHIBSP_LITE
264     // First capture the active session ID.
265     SessionCache* cache = application.getServiceProvider().getSessionCache();
266     SessionCacheEx* cacheex = dynamic_cast<SessionCacheEx*>(cache);
267     string session_id = cache->active(application, request);
268
269     if (!strcmp(request.getMethod(),"GET") && request.getParameter("notifying")) {
270         // This is returning from a front-channel notification, so we have to do the back-channel and then
271         // respond. To do that, we need state from the original request.
272         if (!request.getParameter("entityID")) {
273             cache->remove(application, request, &response);
274             throw FatalProfileException("Application notification loop did not return entityID for LogoutResponse.");
275         }
276
277         // Best effort on back channel and to remove the user agent's session.
278         bool worked1 = false,worked2 = false;
279         if (!session_id.empty()) {
280             vector<string> sessions(1,session_id);
281             worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
282             try {
283                 cache->remove(application, request, &response);
284                 worked2 = true;
285             }
286             catch (exception& ex) {
287                 m_log.error("error removing session (%s): %s", session_id.c_str(), ex.what());
288             }
289         }
290         else {
291             worked1 = worked2 = true;
292         }
293
294         // We need metadata to issue a response.
295         MetadataProvider* m = application.getMetadataProvider();
296         Locker metadataLocker(m);
297         MetadataProviderCriteria mc(application, request.getParameter("entityID"), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
298         pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(mc);
299         if (!entity.first) {
300             throw MetadataException(
301                 "Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", request.getParameter("entityID"))
302                 );
303         }
304         else if (!entity.second) {
305             throw MetadataException(
306                 "Unable to locate SAML 2.0 IdP role for identity provider ($entityID).",
307                 namedparams(1, "entityID", request.getParameter("entityID"))
308                 );
309         }
310
311         auto_ptr_XMLCh reqid(request.getParameter("ID"));
312         if (worked1 && worked2) {
313             // Successful LogoutResponse. Has to be front-channel or we couldn't be here.
314             return sendResponse(
315                 reqid.get(), StatusCode::SUCCESS, NULL, NULL, request.getParameter("RelayState"), entity.second, application, response, true
316                 );
317         }
318
319         return sendResponse(
320             reqid.get(),
321             StatusCode::RESPONDER, NULL, "Unable to fully destroy principal's session.",
322             request.getParameter("RelayState"),
323             entity.second,
324             application,
325             response,
326             true
327             );
328     }
329
330     // If we get here, it's an external protocol message to decode.
331
332     // Locate policy key.
333     pair<bool,const char*> policyId = getString("policyId", m_configNS.get());  // namespace-qualified if inside handler element
334     if (!policyId.first)
335         policyId = application.getString("policyId");   // unqualified in Application(s) element
336
337     // Access policy properties.
338     const PropertySet* settings = application.getServiceProvider().getPolicySettings(policyId.second);
339     pair<bool,bool> validate = settings->getBool("validate");
340
341     // Lock metadata for use by policy.
342     Locker metadataLocker(application.getMetadataProvider());
343
344     // Create the policy.
345     shibsp::SecurityPolicy policy(application, &m_role, validate.first && validate.second, policyId.second);
346
347     // Decode the message.
348     string relayState;
349     auto_ptr<XMLObject> msg(m_decoder->decode(relayState, request, policy));
350     const LogoutRequest* logoutRequest = dynamic_cast<LogoutRequest*>(msg.get());
351     if (logoutRequest) {
352         if (!policy.isAuthenticated())
353             throw SecurityPolicyException("Security of LogoutRequest not established.");
354
355         // Message from IdP to logout one or more sessions.
356
357         // If this is front-channel, we have to have a session_id to use already.
358         if (m_decoder->isUserAgentPresent() && session_id.empty()) {
359             m_log.error("no active session");
360             return sendResponse(
361                 logoutRequest->getID(),
362                 StatusCode::REQUESTER, StatusCode::UNKNOWN_PRINCIPAL, "No active session found in request.",
363                 relayState.c_str(),
364                 policy.getIssuerMetadata(),
365                 application,
366                 response,
367                 true
368                 );
369         }
370
371         bool ownedName = false;
372         NameID* nameid = logoutRequest->getNameID();
373         if (!nameid) {
374             // Check for EncryptedID.
375             EncryptedID* encname = logoutRequest->getEncryptedID();
376             if (encname) {
377                 CredentialResolver* cr=application.getCredentialResolver();
378                 if (!cr)
379                     m_log.warn("found encrypted NameID, but no decryption credential was available");
380                 else {
381                     Locker credlocker(cr);
382                     auto_ptr<MetadataCredentialCriteria> mcc(
383                         policy.getIssuerMetadata() ? new MetadataCredentialCriteria(*policy.getIssuerMetadata()) : NULL
384                         );
385                     try {
386                         auto_ptr<XMLObject> decryptedID(
387                             encname->decrypt(
388                                 *cr,
389                                 application.getRelyingParty(policy.getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL)->getXMLString("entityID").second,
390                                 mcc.get()
391                                 )
392                             );
393                         nameid = dynamic_cast<NameID*>(decryptedID.get());
394                         if (nameid) {
395                             ownedName = true;
396                             decryptedID.release();
397                         }
398                     }
399                     catch (exception& ex) {
400                         m_log.error(ex.what());
401                     }
402                 }
403             }
404         }
405         if (!nameid) {
406             // No NameID, so must respond with an error.
407             m_log.error("NameID not found in request");
408             return sendResponse(
409                 logoutRequest->getID(),
410                 StatusCode::REQUESTER, StatusCode::UNKNOWN_PRINCIPAL, "NameID not found in request.",
411                 relayState.c_str(),
412                 policy.getIssuerMetadata(),
413                 application,
414                 response,
415                 m_decoder->isUserAgentPresent()
416                 );
417         }
418
419         auto_ptr<NameID> namewrapper(ownedName ? nameid : NULL);
420
421         // Suck indexes out of the request for next steps.
422         set<string> indexes;
423         EntityDescriptor* entity = policy.getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL;
424         const vector<SessionIndex*> sindexes = logoutRequest->getSessionIndexs();
425         for (vector<SessionIndex*>::const_iterator i = sindexes.begin(); i != sindexes.end(); ++i) {
426             auto_ptr_char sindex((*i)->getSessionIndex());
427             indexes.insert(sindex.get());
428         }
429
430         // For a front-channel LogoutRequest, we have to match the information in the request
431         // against the current session.
432         if (!session_id.empty()) {
433             if (!cache->matches(application, request, entity, *nameid, &indexes)) {
434                 return sendResponse(
435                     logoutRequest->getID(),
436                     StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active session did not match logout request.",
437                     relayState.c_str(),
438                     policy.getIssuerMetadata(),
439                     application,
440                     response,
441                     true
442                     );
443             }
444
445         }
446
447         // Now we perform "logout" by finding the matching sessions.
448         vector<string> sessions;
449         try {
450             if (cacheex) {
451                 time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0;
452                 cacheex->logout(application, entity, *nameid, &indexes, expires, sessions);
453                 m_log.debug("session cache returned %d sessions bound to NameID in logout request", sessions.size());
454
455                 // Now we actually terminate everything except for the active session,
456                 // if this is front-channel, for notification purposes.
457                 for (vector<string>::const_iterator sit = sessions.begin(); sit != sessions.end(); ++sit)
458                     if (*sit != session_id)
459                         cacheex->remove(application, sit->c_str());   // using the ID-based removal operation
460             }
461             else {
462                 m_log.warn("session cache does not support extended API, can't implement indirect logout of sessions");
463                 if (!session_id.empty())
464                     sessions.push_back(session_id);
465             }
466         }
467         catch (exception& ex) {
468             m_log.error("error while logging out matching sessions: %s", ex.what());
469             return sendResponse(
470                 logoutRequest->getID(),
471                 StatusCode::RESPONDER, NULL, ex.what(),
472                 relayState.c_str(),
473                 policy.getIssuerMetadata(),
474                 application,
475                 response,
476                 m_decoder->isUserAgentPresent()
477                 );
478         }
479
480         if (m_decoder->isUserAgentPresent()) {
481             // Pass control to the first front channel notification point, if any.
482             map<string,string> parammap;
483             if (!relayState.empty())
484                 parammap["RelayState"] = relayState;
485             auto_ptr_char entityID(entity ? entity->getEntityID() : NULL);
486             if (entityID.get())
487                 parammap["entityID"] = entityID.get();
488             auto_ptr_char reqID(logoutRequest->getID());
489             if (reqID.get())
490                 parammap["ID"] = reqID.get();
491             pair<bool,long> result = notifyFrontChannel(application, request, response, &parammap);
492             if (result.first)
493                 return result;
494         }
495
496         // For back-channel requests, or if no front-channel notification is needed...
497         bool worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
498         bool worked2 = true;
499         if (!session_id.empty()) {
500             // One last session to yoink...
501             try {
502                 cache->remove(application, request, &response);
503             }
504             catch (exception& ex) {
505                 worked2 = false;
506                 m_log.error("error removing active session (%s): %s", session_id.c_str(), ex.what());
507             }
508         }
509
510         return sendResponse(
511             logoutRequest->getID(),
512             (worked1 && worked2) ? StatusCode::SUCCESS : StatusCode::RESPONDER,
513             (worked1 && worked2) ? NULL : StatusCode::PARTIAL_LOGOUT,
514             NULL,
515             relayState.c_str(),
516             policy.getIssuerMetadata(),
517             application,
518             response,
519             m_decoder->isUserAgentPresent()
520             );
521     }
522
523     // A LogoutResponse completes an SP-initiated logout sequence.
524     const LogoutResponse* logoutResponse = dynamic_cast<LogoutResponse*>(msg.get());
525     if (logoutResponse) {
526         if (!policy.isAuthenticated()) {
527             SecurityPolicyException ex("Security of LogoutResponse not established.");
528             if (policy.getIssuerMetadata())
529                 annotateException(&ex, policy.getIssuerMetadata()); // throws it
530             ex.raise();
531         }
532         checkError(logoutResponse, policy.getIssuerMetadata()); // throws if Status doesn't look good...
533
534         // If relay state is set, recover the original return URL.
535         if (!relayState.empty())
536             recoverRelayState(application, request, response, relayState);
537
538         // Check for partial logout.
539         const StatusCode* sc = logoutResponse->getStatus() ? logoutResponse->getStatus()->getStatusCode() : NULL;
540         sc = sc ? sc->getStatusCode() : NULL;
541         if (sc && XMLString::equals(sc->getValue(), StatusCode::PARTIAL_LOGOUT))
542             return sendLogoutPage(application, request, response, "partial");
543
544         if (!relayState.empty())
545             return make_pair(true, response.sendRedirect(relayState.c_str()));
546
547         // Return template for completion of logout.
548         return sendLogoutPage(application, request, response, "global");
549     }
550
551     FatalProfileException ex("Incoming message was not a samlp:LogoutRequest or samlp:LogoutResponse.");
552     if (policy.getIssuerMetadata())
553         annotateException(&ex, policy.getIssuerMetadata()); // throws it
554     ex.raise();
555     return make_pair(false,0L);  // never happen, satisfies compiler
556 #else
557     throw ConfigurationException("Cannot process logout message using lite version of shibsp library.");
558 #endif
559 }
560
561 #ifndef SHIBSP_LITE
562
563 pair<bool,long> SAML2Logout::sendResponse(
564     const XMLCh* requestID,
565     const XMLCh* code,
566     const XMLCh* subcode,
567     const char* msg,
568     const char* relayState,
569     const RoleDescriptor* role,
570     const Application& application,
571     HTTPResponse& httpResponse,
572     bool front
573     ) const
574 {
575     // Get endpoint and encoder to use.
576     const EndpointType* ep = NULL;
577     const MessageEncoder* encoder = NULL;
578     if (front) {
579         const IDPSSODescriptor* idp = dynamic_cast<const IDPSSODescriptor*>(role);
580         for (vector<const XMLCh*>::const_iterator b = m_bindings.begin(); idp && b!=m_bindings.end(); ++b) {
581             if (ep=EndpointManager<SingleLogoutService>(idp->getSingleLogoutServices()).getByBinding(*b)) {
582                 map<const XMLCh*,MessageEncoder*>::const_iterator enc = m_encoders.find(*b);
583                 if (enc!=m_encoders.end())
584                     encoder = enc->second;
585                 break;
586             }
587         }
588         if (!ep || !encoder) {
589             auto_ptr_char id(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
590             m_log.error("unable to locate compatible SLO service for provider (%s)", id.get());
591             MetadataException ex("Unable to locate endpoint at IdP ($entityID) to send LogoutResponse.");
592             annotateException(&ex, role);   // throws it
593         }
594     }
595     else {
596         encoder = m_encoders.begin()->second;
597     }
598
599     // Prepare response.
600     auto_ptr<LogoutResponse> logout(LogoutResponseBuilder::buildLogoutResponse());
601     logout->setInResponseTo(requestID);
602     if (ep) {
603         const XMLCh* loc = ep->getResponseLocation();
604         if (!loc || !*loc)
605             loc = ep->getLocation();
606         logout->setDestination(loc);
607     }
608     Issuer* issuer = IssuerBuilder::buildIssuer();
609     logout->setIssuer(issuer);
610     issuer->setName(application.getRelyingParty(dynamic_cast<EntityDescriptor*>(role->getParent()))->getXMLString("entityID").second);
611     fillStatus(*logout.get(), code, subcode, msg);
612
613     auto_ptr_char dest(logout->getDestination());
614
615     long ret = sendMessage(*encoder, logout.get(), relayState, dest.get(), role, application, httpResponse);
616     logout.release();  // freed by encoder
617     return make_pair(true,ret);
618 }
619
620 #endif