]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/slavecommunicator.cc
Merge branch 'rfc2136' of https://github.com/mind04/pdns into rfc2136
[thirdparty/pdns.git] / pdns / slavecommunicator.cc
1 /*
2 PowerDNS Versatile Database Driven Nameserver
3 Copyright (C) 2002-2012 PowerDNS.COM BV
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 as
7 published by the Free Software Foundation;
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18 #include "packetcache.hh"
19 #include "utility.hh"
20 #include "dnssecinfra.hh"
21 #include "dnsseckeeper.hh"
22 #include "base32.hh"
23 #include <errno.h>
24 #include "communicator.hh"
25 #include <set>
26 #include <boost/utility.hpp>
27 #include "dnsbackend.hh"
28 #include "ueberbackend.hh"
29 #include "packethandler.hh"
30 #include "resolver.hh"
31 #include "logger.hh"
32 #include "dns.hh"
33 #include "arguments.hh"
34 #include "session.hh"
35 #include "packetcache.hh"
36 #include <boost/foreach.hpp>
37 #include <boost/lexical_cast.hpp>
38 #include "base64.hh"
39 #include "inflighter.cc"
40 #include "lua-auth.hh"
41 #include "namespaces.hh"
42 #include "common_startup.hh"
43 #include <boost/scoped_ptr.hpp>
44 using boost::scoped_ptr;
45
46
47 void CommunicatorClass::addSuckRequest(const string &domain, const string &master)
48 {
49 Lock l(&d_lock);
50 SuckRequest sr;
51 sr.domain = domain;
52 sr.master = master;
53 pair<UniQueue::iterator, bool> res;
54
55 res=d_suckdomains.push_back(sr);
56
57 if(res.second) {
58 d_suck_sem.post();
59 }
60 }
61
62 void CommunicatorClass::suck(const string &domain,const string &remote)
63 {
64 L<<Logger::Error<<"Initiating transfer of '"<<domain<<"' from remote '"<<remote<<"'"<<endl;
65 uint32_t domain_id;
66 PacketHandler P; // fresh UeberBackend
67
68 DomainInfo di;
69 di.backend=0;
70 bool first=true;
71 try {
72 UeberBackend *B=dynamic_cast<UeberBackend *>(P.getBackend()); // copy of the same UeberBackend
73 NSEC3PARAMRecordContent ns3pr, hadNs3pr;
74 bool narrow, hadNarrow=false;
75 DNSSECKeeper dk (B); // reuse our backend for DNSSECKeeper
76 bool dnssecZone = false;
77 bool haveNSEC3=false;
78 if(dk.isSecuredZone(domain)) {
79 dnssecZone=true;
80 haveNSEC3=dk.getNSEC3PARAM(domain, &ns3pr, &narrow);
81 if (haveNSEC3) {
82 hadNs3pr = ns3pr;
83 hadNarrow = narrow;
84 }
85 }
86
87 const bool hadNSEC3 = haveNSEC3;
88 const bool hadPresigned = dk.isPresigned(domain);
89 const bool hadDnssecZone = dnssecZone;
90
91 if(!B->getDomainInfo(domain, di) || !di.backend) { // di.backend and B are mostly identical
92 L<<Logger::Error<<"Can't determine backend for domain '"<<domain<<"'"<<endl;
93 return;
94 }
95 domain_id=di.id;
96
97 Resolver::res_t recs;
98 set<string> nsset, qnames;
99
100 ComboAddress raddr(remote, 53);
101
102 string tsigkeyname, tsigalgorithm, tsigsecret;
103
104 if(dk.getTSIGForAccess(domain, remote, &tsigkeyname)) {
105 string tsigsecret64;
106 if(B->getTSIGKey(tsigkeyname, &tsigalgorithm, &tsigsecret64))
107 {
108 B64Decode(tsigsecret64, tsigsecret);
109 }
110 else
111 {
112 L<<Logger::Error<<"TSIG key '"<<tsigkeyname<<"' not found, ignoring 'AXFR-MASTER-TSIG' for domain '"<<domain<<"'"<<endl;
113 tsigkeyname="";
114 }
115 }
116
117 scoped_ptr<AuthLua> pdl;
118 vector<string> scripts;
119 if(B->getDomainMetadata(domain, "LUA-AXFR-SCRIPT", scripts) && !scripts.empty()) {
120 try {
121 pdl.reset(new AuthLua(scripts[0]));
122 L<<Logger::Info<<"Loaded Lua script '"<<scripts[0]<<"' to edit the incoming AXFR of '"<<domain<<"'"<<endl;
123 }
124 catch(std::exception& e) {
125 L<<Logger::Error<<"Failed to load Lua editing script '"<<scripts[0]<<"' for incoming AXFR of '"<<domain<<"': "<<e.what()<<endl;
126 return;
127 }
128 }
129
130 vector<string> localaddr;
131 ComboAddress laddr;
132
133 if(B->getDomainMetadata(domain, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
134 try {
135 laddr = ComboAddress(localaddr[0]);
136 L<<Logger::Info<<"AXFR source for domain '"<<domain<<"' set to "<<localaddr[0]<<endl;
137 }
138 catch(std::exception& e) {
139 L<<Logger::Error<<"Failed to load AXFR source '"<<localaddr[0]<<"' for incoming AXFR of '"<<domain<<"': "<<e.what()<<endl;
140 return;
141 }
142 } else {
143 laddr.sin4.sin_family = 0;
144 }
145
146 AXFRRetriever retriever(raddr, domain.c_str(), tsigkeyname, tsigalgorithm, tsigsecret,
147 (laddr.sin4.sin_family == 0) ? NULL : &laddr);
148
149 bool gotPresigned = false;
150 bool gotNSEC3 = false;
151 bool gotOptOutFlag = false;
152 unsigned int soa_serial = 0;
153 vector<DNSResourceRecord> rrs;
154 set<string> secured;
155 while(retriever.getChunk(recs)) {
156 if(first) {
157 L<<Logger::Error<<"AXFR started for '"<<domain<<"'"<<endl;
158 first=false;
159 }
160
161 for(Resolver::res_t::iterator i=recs.begin();i!=recs.end();++i) {
162 if(i->qtype.getCode() == QType::OPT || i->qtype.getCode() == QType::TSIG) // ignore EDNS0 & TSIG
163 continue;
164
165 if(!endsOn(i->qname, domain)) {
166 L<<Logger::Error<<"Remote "<<remote<<" tried to sneak in out-of-zone data '"<<i->qname<<"'|"<<i->qtype.getName()<<" during AXFR of zone '"<<domain<<"', ignoring"<<endl;
167 continue;
168 }
169
170 if (i->qtype.getCode() == QType::NSEC3PARAM) {
171 ns3pr = NSEC3PARAMRecordContent(i->content);
172 narrow = false;
173 dnssecZone = haveNSEC3 = gotPresigned = gotNSEC3 = true;
174 continue;
175 } else if (i->qtype.getCode() == QType::NSEC3) {
176 dnssecZone = gotPresigned = true;
177 NSEC3RecordContent ns3rc(i->content);
178 gotOptOutFlag = ns3rc.d_flags & 1;
179 if (ns3rc.d_set.count(QType::NS) && !pdns_iequals(i->qname, domain))
180 secured.insert(toLower(makeRelative(i->qname, domain)));
181 continue;
182 } else if (i->qtype.getCode() == QType::NSEC) {
183 dnssecZone = gotPresigned = true;
184 continue;
185 }
186
187 if(i->qtype.getCode() == QType::SOA) {
188 if(soa_serial != 0)
189 continue; //skip the last SOA
190 SOAData sd;
191 fillSOAData(i->content,sd);
192 soa_serial = sd.serial;
193 }
194
195 i->domain_id=domain_id;
196
197 #if 0
198 if(i->qtype.getCode()>=60000)
199 throw DBException("Database can't store unknown record type "+lexical_cast<string>(i->qtype.getCode()-1024));
200 #endif
201
202 vector<DNSResourceRecord> out;
203 if(pdl && pdl->axfrfilter(raddr, domain, *i, out)) {
204 BOOST_FOREACH(const DNSResourceRecord& rr, out) {
205 rrs.push_back(rr);
206 }
207 } else {
208 rrs.push_back(*i);
209 }
210 }
211 }
212
213
214 BOOST_FOREACH(const DNSResourceRecord& rr, rrs) {
215 if(rr.qtype.getCode() == QType::NS && !pdns_iequals(rr.qname, domain))
216 nsset.insert(rr.qname);
217 qnames.insert(rr.qname);
218 }
219
220 if(dnssecZone) {
221 if(!haveNSEC3)
222 L<<Logger::Info<<"Adding NSEC ordering information"<<endl;
223 else if(!narrow)
224 L<<Logger::Info<<"Adding NSEC3 hashed ordering information for '"<<domain<<"'"<<endl;
225 else
226 L<<Logger::Info<<"Erasing NSEC3 ordering since we are narrow, only setting 'auth' fields"<<endl;
227 }
228
229 if (hadPresigned && !gotNSEC3) { // not sure why this is here
230 // we only had NSEC3 because we were a presigned zone...
231 haveNSEC3 = false;
232 }
233
234
235 L<<Logger::Error<<"Transaction started for '"<<domain<<"'"<<endl;
236 di.backend->startTransaction(domain, domain_id);
237
238 bool doent=true;
239 uint32_t maxent = ::arg().asNum("max-ent-entries");
240 string ordername, shorter;
241 set<string> nonterm, rrterm;
242
243 BOOST_FOREACH(DNSResourceRecord& rr, rrs) {
244
245 // Figure out auth and ents
246 rr.auth=true;
247 shorter=rr.qname;
248 rrterm.clear();
249 do {
250 if(doent) {
251 if (!qnames.count(shorter) && !nonterm.count(shorter) && !rrterm.count(shorter))
252 rrterm.insert(shorter);
253 }
254 if(nsset.count(shorter) && rr.qtype.getCode() != QType::DS) {
255 rr.auth=false;
256 break;
257 }
258 if (pdns_iequals(shorter, domain)) // stop at apex
259 break;
260 }while(chopOff(shorter));
261
262 // Insert ents for auth rrs
263 if(doent && rr.auth) {
264 nonterm.insert(rrterm.begin(), rrterm.end());
265 if(nonterm.size() > maxent) {
266 L<<Logger::Error<<"AXFR zone "<<domain<<" has too many empty non terminals."<<endl;
267 nonterm.clear();
268 doent=false;
269 }
270 }
271
272 // RRSIG is always auth, even inside a delegation
273 if (rr.qtype.getCode() == QType::RRSIG)
274 rr.auth=true;
275
276 // Add ordername and insert record
277 if (dnssecZone && rr.qtype.getCode() != QType::RRSIG) {
278 if (haveNSEC3) {
279 // NSEC3
280 ordername=toLower(toBase32Hex(hashQNameWithSalt(ns3pr.d_iterations, ns3pr.d_salt, rr.qname)));
281 if(!narrow && (rr.auth || (rr.qtype.getCode() == QType::NS && (!gotOptOutFlag || secured.count(ordername))))) {
282 di.backend->feedRecord(rr, &ordername);
283 } else
284 di.backend->feedRecord(rr);
285 } else {
286 // NSEC
287 if (rr.auth || rr.qtype.getCode() == QType::NS) {
288 ordername=toLower(labelReverse(makeRelative(rr.qname, domain)));
289 di.backend->feedRecord(rr, &ordername);
290 } else
291 di.backend->feedRecord(rr);
292 }
293 } else
294 di.backend->feedRecord(rr);
295 }
296
297 // Insert empty non-terminals
298 if(doent && !nonterm.empty()) {
299 if (haveNSEC3) {
300 di.backend->feedEnts3(domain_id, domain, nonterm, ns3pr.d_iterations, ns3pr.d_salt, narrow);
301 } else
302 di.backend->feedEnts(domain_id, nonterm);
303 }
304
305 // now we also need to update the presigned flag and NSEC3PARAM
306 // for the zone
307 if (gotPresigned) {
308 if (!hadDnssecZone && !hadPresigned) {
309 // zone is now presigned
310 dk.setPresigned(domain);
311 }
312
313 if (hadPresigned || !hadDnssecZone)
314 {
315 // this is a presigned zone, update NSEC3PARAM
316 if (gotNSEC3) {
317 ns3pr.d_flags = gotOptOutFlag ? 1 : 0;
318 // only update if there was a change
319 if (!hadNSEC3 || (narrow != hadNarrow) ||
320 (ns3pr.d_algorithm != hadNs3pr.d_algorithm) ||
321 (ns3pr.d_flags != hadNs3pr.d_flags) ||
322 (ns3pr.d_iterations != hadNs3pr.d_iterations) ||
323 (ns3pr.d_salt != hadNs3pr.d_salt)) {
324 dk.setNSEC3PARAM(domain, ns3pr, narrow);
325 }
326 } else if (hadNSEC3) {
327 dk.unsetNSEC3PARAM(domain);
328 }
329 }
330 } else if (hadPresigned) {
331 // zone is no longer presigned
332 dk.unsetPresigned(domain);
333 dk.unsetNSEC3PARAM(domain);
334 }
335
336 di.backend->commitTransaction();
337 di.backend->setFresh(domain_id);
338 PC.purge(domain+"$");
339
340
341 L<<Logger::Error<<"AXFR done for '"<<domain<<"', zone committed with serial number "<<soa_serial<<endl;
342 if(::arg().mustDo("slave-renotify"))
343 notifyDomain(domain);
344 }
345 catch(DBException &re) {
346 L<<Logger::Error<<"Unable to feed record during incoming AXFR of '"+domain+"': "<<re.reason<<endl;
347 if(di.backend && !first) {
348 L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl;
349 di.backend->abortTransaction();
350 }
351 }
352 catch(MOADNSException &re) {
353 L<<Logger::Error<<"Unable to parse record during incoming AXFR of '"+domain+"' (MOADNSException): "<<re.what()<<endl;
354 if(di.backend && !first) {
355 L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl;
356 di.backend->abortTransaction();
357 }
358 }
359 catch(std::exception &re) {
360 L<<Logger::Error<<"Unable to parse record during incoming AXFR of '"+domain+"' (std::exception): "<<re.what()<<endl;
361 if(di.backend && !first) {
362 L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl;
363 di.backend->abortTransaction();
364 }
365 }
366 catch(ResolverException &re) {
367 L<<Logger::Error<<"Unable to AXFR zone '"+domain+"' from remote '"<<remote<<"' (resolver): "<<re.reason<<endl;
368 if(di.backend && !first) {
369 L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl;
370 di.backend->abortTransaction();
371 }
372 }
373 catch(PDNSException &ae) {
374 L<<Logger::Error<<"Unable to AXFR zone '"+domain+"' from remote '"<<remote<<"' (PDNSException): "<<ae.reason<<endl;
375 if(di.backend && !first) {
376 L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl;
377 di.backend->abortTransaction();
378 }
379 }
380 }
381 namespace {
382 struct QueryInfo
383 {
384 struct timeval query_ttd;
385 uint16_t id;
386 };
387
388 struct DomainNotificationInfo
389 {
390 DomainInfo di;
391 bool dnssecOk;
392 string tsigkeyname, tsigalgname, tsigsecret;
393 };
394 }
395
396
397 struct SlaveSenderReceiver
398 {
399 typedef pair<string, uint16_t> Identifier;
400
401 struct Answer {
402 uint32_t theirSerial;
403 uint32_t theirInception;
404 uint32_t theirExpire;
405 };
406
407 map<uint32_t, Answer> d_freshness;
408
409 SlaveSenderReceiver()
410 {
411 }
412
413 void deliverTimeout(const Identifier& i)
414 {
415 }
416
417 Identifier send(DomainNotificationInfo& dni)
418 {
419 random_shuffle(dni.di.masters.begin(), dni.di.masters.end());
420 try {
421 ComboAddress remote(*dni.di.masters.begin());
422 return make_pair(dni.di.zone,
423 d_resolver.sendResolve(ComboAddress(*dni.di.masters.begin(), 53),
424 dni.di.zone.c_str(),
425 QType::SOA,
426 dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret)
427 );
428 }
429 catch(PDNSException& e) {
430 throw runtime_error("While attempting to query freshness of '"+dni.di.zone+"': "+e.reason);
431 }
432 }
433
434 bool receive(Identifier& id, Answer& a)
435 {
436 if(d_resolver.tryGetSOASerial(&id.first, &a.theirSerial, &a.theirInception, &a.theirExpire, &id.second)) {
437 return 1;
438 }
439 return 0;
440 }
441
442 void deliverAnswer(DomainNotificationInfo& dni, const Answer& a, unsigned int usec)
443 {
444 d_freshness[dni.di.id]=a;
445 }
446
447 Resolver d_resolver;
448 };
449
450 void CommunicatorClass::addSlaveCheckRequest(const DomainInfo& di, const ComboAddress& remote)
451 {
452 Lock l(&d_lock);
453 DomainInfo ours = di;
454 ours.backend = 0;
455 d_tocheck.insert(ours);
456 d_any_sem.post(); // kick the loop!
457 }
458
459 void CommunicatorClass::addTrySuperMasterRequest(DNSPacket *p)
460 {
461 Lock l(&d_lock);
462 DNSPacket ours = *p;
463 d_potentialsupermasters.push_back(ours);
464 d_any_sem.post(); // kick the loop!
465 }
466
467 void CommunicatorClass::slaveRefresh(PacketHandler *P)
468 {
469 UeberBackend *B=dynamic_cast<UeberBackend *>(P->getBackend());
470 vector<DomainInfo> rdomains;
471 vector<DomainNotificationInfo> sdomains; // the bool is for 'presigned'
472 vector<DNSPacket> trysuperdomains;
473
474 {
475 Lock l(&d_lock);
476 rdomains.insert(rdomains.end(), d_tocheck.begin(), d_tocheck.end());
477 d_tocheck.clear();
478 trysuperdomains.insert(trysuperdomains.end(), d_potentialsupermasters.begin(), d_potentialsupermasters.end());
479 d_potentialsupermasters.clear();
480 }
481
482 BOOST_FOREACH(DNSPacket& dp, trysuperdomains) {
483 int res;
484 res=P->trySuperMasterSynchronous(&dp);
485 if(res>=0) {
486 DNSPacket *r=dp.replyPacket();
487 r->setRcode(res);
488 r->setOpcode(Opcode::Notify);
489 N->send(r);
490 delete r;
491 }
492 }
493
494 if(rdomains.empty()) // if we have priority domains, check them first
495 B->getUnfreshSlaveInfos(&rdomains);
496
497 DNSSECKeeper dk(B); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access!
498 {
499 Lock l(&d_lock);
500 domains_by_name_t& nameindex=boost::multi_index::get<IDTag>(d_suckdomains);
501
502 BOOST_FOREACH(DomainInfo& di, rdomains) {
503 SuckRequest sr;
504 sr.domain=di.zone;
505 if(di.masters.empty()) // slave domains w/o masters are ignored
506 continue;
507 // remove unfresh domains already queued for AXFR, no sense polling them again
508 sr.master=*di.masters.begin();
509 if(nameindex.count(sr)) {
510 continue;
511 }
512 DomainNotificationInfo dni;
513 dni.di=di;
514 dni.dnssecOk = dk.isPresigned(di.zone);
515
516 if(dk.getTSIGForAccess(di.zone, sr.master, &dni.tsigkeyname)) {
517 string secret64;
518 B->getTSIGKey(dni.tsigkeyname, &dni.tsigalgname, &secret64);
519 B64Decode(secret64, dni.tsigsecret);
520 }
521 sdomains.push_back(dni);
522 }
523 }
524
525 if(sdomains.empty())
526 {
527 if(d_slaveschanged) {
528 Lock l(&d_lock);
529 L<<Logger::Warning<<"No new unfresh slave domains, "<<d_suckdomains.size()<<" queued for AXFR already"<<endl;
530 }
531 d_slaveschanged = !rdomains.empty();
532 return;
533 }
534 else {
535 Lock l(&d_lock);
536 L<<Logger::Warning<<sdomains.size()<<" slave domain"<<(sdomains.size()>1 ? "s" : "")<<" need"<<
537 (sdomains.size()>1 ? "" : "s")<<
538 " checking, "<<d_suckdomains.size()<<" queued for AXFR"<<endl;
539 }
540
541 SlaveSenderReceiver ssr;
542
543 Inflighter<vector<DomainNotificationInfo>, SlaveSenderReceiver> ifl(sdomains, ssr);
544
545 ifl.d_maxInFlight = 200;
546
547 for(;;) {
548 try {
549 ifl.run();
550 break;
551 }
552 catch(std::exception& e) {
553 L<<Logger::Error<<"While checking domain freshness: " << e.what()<<endl;
554 }
555 catch(PDNSException &re) {
556 L<<Logger::Error<<"While checking domain freshness: " << re.reason<<endl;
557 }
558 }
559 L<<Logger::Warning<<"Received serial number updates for "<<ssr.d_freshness.size()<<" zones, had "<<ifl.getTimeouts()<<" timeouts"<<endl;
560
561 typedef DomainNotificationInfo val_t;
562 BOOST_FOREACH(val_t& val, sdomains) {
563 DomainInfo& di(val.di);
564 // might've come from the packethandler
565 if(!di.backend && !B->getDomainInfo(di.zone, di)) {
566 L<<Logger::Warning<<"Ignore domain "<< di.zone<<" since it has been removed from our backend"<<endl;
567 continue;
568 }
569
570 if(!ssr.d_freshness.count(di.id))
571 continue;
572 uint32_t theirserial = ssr.d_freshness[di.id].theirSerial, ourserial = di.serial;
573
574 if(rfc1982LessThan(theirserial, ourserial)) {
575 L<<Logger::Error<<"Domain '"<<di.zone<<"' more recent than master, our serial " << ourserial << " > their serial "<< theirserial << endl;
576 di.backend->setFresh(di.id);
577 }
578 else if(theirserial == ourserial) {
579 if(!dk.isPresigned(di.zone)) {
580 L<<Logger::Warning<<"Domain '"<< di.zone<<"' is fresh (not presigned, no RRSIG check)"<<endl;
581 di.backend->setFresh(di.id);
582 }
583 else {
584 B->lookup(QType(QType::RRSIG), di.zone); // can't use DK before we are done with this lookup!
585 DNSResourceRecord rr;
586 uint32_t maxExpire=0, maxInception=0;
587 while(B->get(rr)) {
588 RRSIGRecordContent rrc(rr.content);
589 if(rrc.d_type == QType::SOA) {
590 maxInception = std::max(maxInception, rrc.d_siginception);
591 maxExpire = std::max(maxExpire, rrc.d_sigexpire);
592 }
593 }
594 if(maxInception == ssr.d_freshness[di.id].theirInception && maxExpire == ssr.d_freshness[di.id].theirExpire) {
595 L<<Logger::Warning<<"Domain '"<< di.zone<<"' is fresh and apex RRSIGs match"<<endl;
596 di.backend->setFresh(di.id);
597 }
598 else {
599 L<<Logger::Warning<<"Domain '"<< di.zone<<"' is fresh, but RRSIGS differ, so DNSSEC stale"<<endl;
600 addSuckRequest(di.zone, *di.masters.begin());
601 }
602 }
603 }
604 else {
605 L<<Logger::Warning<<"Domain '"<< di.zone<<"' is stale, master serial "<<theirserial<<", our serial "<< ourserial <<endl;
606 addSuckRequest(di.zone, *di.masters.begin());
607 }
608 }
609 }
610
611 // stub for PowerDNSLua linking
612 int directResolve(const std::string& qname, const QType& qtype, int qclass, vector<DNSResourceRecord>& ret)
613 {
614 return -1;
615 }
616
617