]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/rcpgenerator.cc
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "rcpgenerator.hh"
26 #include "dnsparser.hh"
29 #include <boost/algorithm/string.hpp>
30 #include <boost/algorithm/string/classification.hpp>
31 #include <boost/algorithm/string/replace.hpp>
32 #include <boost/format.hpp>
37 #include "namespaces.hh"
39 RecordTextReader::RecordTextReader(string str
, DNSName zone
) : d_string(std::move(str
)), d_zone(std::move(zone
)), d_pos(0)
41 /* remove whitespace */
42 if(!d_string
.empty() && ( dns_isspace(*d_string
.begin()) || dns_isspace(*d_string
.rbegin()) ))
43 boost::trim_if(d_string
, dns_isspace
);
44 d_end
= d_string
.size();
47 void RecordTextReader::xfr48BitInt(uint64_t &val
)
50 if (val
> 281474976710655LL)
51 throw RecordTextException("Overflow reading 48 bit integer from record content"); // fixme improve
54 void RecordTextReader::xfrNodeOrLocatorID(NodeOrLocatorID
& val
) {
58 d_pos
+len
< d_string
.length() && (isxdigit(d_string
.at(d_pos
+len
)) || d_string
.at(d_pos
+len
) == ':');
59 len
++) ; // find length of ID
61 // Parse as v6, and then strip the final 64 zero bytes
62 struct in6_addr tmpbuf
;
63 string to_parse
= d_string
.substr(d_pos
, len
) + ":0:0:0:0";
65 if (inet_pton(AF_INET6
, to_parse
.c_str(), &tmpbuf
) != 1) {
66 throw RecordTextException("while parsing colon-delimited 64-bit field: '" + d_string
.substr(d_pos
, len
) + "' is invalid");
69 std::memcpy(&val
.content
, tmpbuf
.s6_addr
, sizeof(val
.content
));
73 void RecordTextReader::xfr64BitInt(uint64_t &val
)
77 if(!isdigit(d_string
.at(d_pos
)))
78 throw RecordTextException("expected digits at position "+std::to_string(d_pos
)+" in '"+d_string
+"'");
81 val
=std::stoull(d_string
.substr(d_pos
), &pos
);
87 void RecordTextReader::xfr32BitInt(uint32_t &val
)
91 if(!isdigit(d_string
.at(d_pos
)))
92 throw RecordTextException("expected digits at position "+std::to_string(d_pos
)+" in '"+d_string
+"'");
95 val
= pdns::checked_stoi
<uint32_t>(d_string
.c_str() + d_pos
, &pos
);
100 void RecordTextReader::xfrTime(uint32_t &val
)
103 memset(&tm
, 0, sizeof(tm
));
108 if (itmp
<= (uint32_t)~0) {
109 // formatted as seconds since epoch, not as YYYYMMDDHHmmSS:
110 val
= (uint32_t) itmp
;
118 if (sscanf(tmp
.str().c_str(), "%04d%02d%02d" "%02d%02d%02d",
119 &tm
.tm_year
, &tm
.tm_mon
, &tm
.tm_mday
,
120 &tm
.tm_hour
, &tm
.tm_min
, &tm
.tm_sec
) != 6) {
121 throw RecordTextException("unable to parse '"+std::to_string(itmp
)+"' into a valid time at position "+std::to_string(d_pos
)+" in '"+d_string
+"'");
126 // coverity[store_truncates_time_t]
127 val
=(uint32_t)Utility::timegm(&tm
);
130 void RecordTextReader::xfrIP(uint32_t &val
)
134 if(!isdigit(d_string
.at(d_pos
)))
135 throw RecordTextException("while parsing IP address, expected digits at position "+std::to_string(d_pos
)+" in '"+d_string
+"'");
140 bool last_was_digit
= false;
143 if(d_string
.at(d_pos
)=='.') {
145 throw RecordTextException(string("unable to parse IP address, dot without previous digit"));
146 last_was_digit
= false;
152 throw RecordTextException(string("unable to parse IP address, too many dots"));
154 else if(isdigit(d_string
.at(d_pos
))) {
155 last_was_digit
= true;
157 octet
+=d_string
.at(d_pos
) - '0';
159 throw RecordTextException("unable to parse IP address");
161 else if(dns_isspace(d_string
.at(d_pos
)) || d_string
.at(d_pos
) == ',')
164 throw RecordTextException(string("unable to parse IP address, strange character: ")+d_string
.at(d_pos
));
167 if(d_pos
== d_string
.length())
171 throw RecordTextException(string("unable to parse IP address, not enough dots"));
173 throw RecordTextException(string("unable to parse IP address, trailing dot"));
180 void RecordTextReader::xfrIP6(std::string
&val
)
182 struct in6_addr tmpbuf
;
187 // lookup end of value - think of ::ffff encoding too, has dots in it!
189 d_pos
+len
< d_string
.length() && (isxdigit(d_string
.at(d_pos
+len
)) || d_string
.at(d_pos
+len
) == ':' || d_string
.at(d_pos
+len
)=='.');
193 throw RecordTextException("while parsing IPv6 address, expected xdigits at position "+std::to_string(d_pos
)+" in '"+d_string
+"'");
195 // end of value is here, try parse as IPv6
196 string address
=d_string
.substr(d_pos
, len
);
198 if (inet_pton(AF_INET6
, address
.c_str(), &tmpbuf
) != 1) {
199 throw RecordTextException("while parsing IPv6 address: '" + address
+ "' is invalid");
202 val
= std::string((char*)tmpbuf
.s6_addr
, 16);
207 void RecordTextReader::xfrCAWithoutPort(uint8_t version
, ComboAddress
&val
)
212 val
= makeComboAddressFromRaw(4, string((const char*) &ip
, 4));
214 else if (version
== 6) {
217 val
= makeComboAddressFromRaw(6, ip
);
219 else throw RecordTextException("invalid address family");
222 void RecordTextReader::xfrCAPort(ComboAddress
&val
)
226 val
.sin4
.sin_port
= port
;
229 bool RecordTextReader::eof()
234 void RecordTextReader::xfr16BitInt(uint16_t &val
)
240 throw RecordTextException("Overflow reading 16 bit integer from record content"); // fixme improve
243 void RecordTextReader::xfr8BitInt(uint8_t &val
)
249 throw RecordTextException("Overflow reading 8 bit integer from record content"); // fixme improve
252 // this code should leave all the escapes around
253 void RecordTextReader::xfrName(DNSName
& val
, bool, bool)
258 string::size_type begin_pos
= d_pos
;
259 while (d_pos
< d_end
) {
260 if (d_string
[d_pos
]!='\r' && dns_isspace(d_string
[d_pos
])) {
268 std::string_view
view(d_string
);
269 sval
= DNSName(view
.substr(begin_pos
, d_pos
- begin_pos
));
275 else if (!d_zone
.empty()) {
278 val
= std::move(sval
);
281 static bool isbase64(char c
, bool acceptspace
)
285 if(c
>= '0' && c
<= '9')
287 if(c
>= 'a' && c
<= 'z')
289 if(c
>= 'A' && c
<= 'Z')
291 if(c
=='+' || c
=='/' || c
=='=')
296 void RecordTextReader::xfrBlobNoSpaces(string
& val
, int len
) {
299 const char* strptr
=d_string
.c_str();
300 while(d_pos
< d_end
&& isbase64(strptr
[d_pos
], false))
304 tmp
.assign(d_string
.c_str()+pos
, d_string
.c_str() + d_pos
);
305 boost::erase_all(tmp
," ");
309 if (len
>-1 && val
.size() != static_cast<size_t>(len
))
310 throw RecordTextException("Record length "+std::to_string(val
.size()) + " does not match expected length '"+std::to_string(len
));
313 void RecordTextReader::xfrBlob(string
& val
, int)
317 const char* strptr
=d_string
.c_str();
318 while(d_pos
< d_end
&& isbase64(strptr
[d_pos
], true))
322 tmp
.assign(d_string
.c_str()+pos
, d_string
.c_str() + d_pos
);
323 boost::erase_all(tmp
," ");
328 void RecordTextReader::xfrRFC1035CharString(string
&val
) {
329 auto ctr
= parseRFC1035CharString(d_string
.substr(d_pos
, d_end
- d_pos
), val
);
333 void RecordTextReader::xfrSVCBValueList(vector
<string
> &val
) {
334 auto ctr
= parseSVCBValueList(d_string
.substr(d_pos
, d_end
- d_pos
), val
);
338 void RecordTextReader::xfrSvcParamKeyVals(set
<SvcParam
>& val
)
340 while (d_pos
!= d_end
) {
345 // Find the SvcParamKey
347 while (d_pos
!= d_end
) {
348 if (d_string
.at(d_pos
) == '=' || d_string
.at(d_pos
) == ' ') {
354 // We've reached a space or equals-sign or the end of the string (d_pos is at this char)
355 string k
= d_string
.substr(pos
, d_pos
- pos
);
356 SvcParam::SvcParamKey key
;
359 key
= SvcParam::keyFromString(k
, generic
);
360 } catch (const std::invalid_argument
&e
) {
361 throw RecordTextException(e
.what());
364 if (key
!= SvcParam::no_default_alpn
) {
365 if (d_pos
== d_end
|| d_string
.at(d_pos
) != '=') {
366 throw RecordTextException("expected '=' after " + k
);
368 d_pos
++; // Now on the first character after '='
369 if (d_pos
== d_end
|| d_string
.at(d_pos
) == ' ') {
370 throw RecordTextException("expected value after " + k
+ "=");
375 case SvcParam::no_default_alpn
:
376 if (d_pos
!= d_end
&& d_string
.at(d_pos
) == '=') {
377 throw RecordTextException(k
+ " key can not have values");
379 val
.insert(SvcParam(key
));
381 case SvcParam::ipv4hint
: /* fall-through */
382 case SvcParam::ipv6hint
: {
383 vector
<ComboAddress
> hints
;
387 xfrRFC1035CharString(value
);
388 size_t len
= key
== SvcParam::ipv4hint
? 4 : 16;
389 if (value
.size() % len
!= 0) {
390 throw RecordTextException(k
+ " in generic format has wrong number of bytes");
392 for (size_t i
=0; i
<value
.size(); i
+= len
) {
393 auto hint
= makeComboAddressFromRaw(static_cast<uint8_t>(key
), &value
.at(i
), len
);
394 hints
.push_back(hint
);
397 vector
<string
> value
;
398 xfrSVCBValueList(value
);
399 for (auto const &v
: value
) {
405 hints
.push_back(ComboAddress(v
));
409 auto p
= SvcParam(key
, std::move(hints
));
410 p
.setAutoHint(doAuto
);
413 catch (const std::invalid_argument
& e
) {
414 throw RecordTextException(e
.what());
418 case SvcParam::alpn
: {
419 vector
<string
> value
;
422 xfrRFC1035CharString(v
);
424 while (spos
< v
.length()) {
427 if (len
> v
.length() - spos
) {
428 throw RecordTextException("Length of ALPN value goes over total length of alpn SVC Param");
430 value
.push_back(v
.substr(spos
, len
));
434 xfrSVCBValueList(value
);
436 val
.insert(SvcParam(key
, std::move(value
)));
439 case SvcParam::mandatory
: {
442 xfrRFC1035CharString(v
);
443 if (v
.length() % 2 != 0) {
444 throw RecordTextException("Wrong number of bytes in SVC Param " + k
);
446 std::set
<SvcParam::SvcParamKey
> keys
;
447 for (size_t i
=0; i
< v
.length(); i
+= 2) {
448 uint16_t mand
= (v
.at(i
) << 8);
450 keys
.insert(SvcParam::SvcParamKey(mand
));
452 val
.insert(SvcParam(key
, std::move(keys
)));
455 vector
<string
> parts
;
456 xfrSVCBValueList(parts
);
457 set
<string
> values(parts
.begin(), parts
.end());
458 val
.insert(SvcParam(key
, std::move(values
)));
461 case SvcParam::port
: {
465 xfrRFC1035CharString(v
);
466 if (v
.length() != 2) {
467 throw RecordTextException("port in generic format has the wrong length, expected 2, got " + std::to_string(v
.length()));
469 port
= (v
.at(0) << 8);
474 val
.insert(SvcParam(key
, port
));
477 case SvcParam::ech
: {
480 xfrRFC1035CharString(value
);
482 bool haveQuote
= d_string
.at(d_pos
) == '"';
486 xfrBlobNoSpaces(value
);
488 if (d_string
.at(d_pos
) != '"') {
489 throw RecordTextException("ech value starts, but does not end with a '\"' symbol");
494 val
.insert(SvcParam(key
, value
));
499 xfrRFC1035CharString(value
);
500 val
.insert(SvcParam(key
, value
));
507 static inline uint8_t hextodec(uint8_t val
)
509 if(val
>= '0' && val
<='9')
511 else if(val
>= 'A' && val
<='F')
513 else if(val
>= 'a' && val
<='f')
516 throw RecordTextException("Unknown hexadecimal character '"+std::to_string(val
)+"'");
520 static void HEXDecode(const char* begin
, const char* end
, string
& out
)
522 if(end
- begin
== 1 && *begin
=='-') {
527 out
.reserve((end
-begin
)/2);
528 uint8_t mode
=0, val
=0;
529 for(; begin
!= end
; ++begin
) {
533 val
= 16*hextodec(*begin
);
536 val
+= hextodec(*begin
);
537 out
.append(1, (char) val
);
543 out
.append(1, (char) val
);
547 void RecordTextReader::xfrHexBlob(string
& val
, bool keepReading
)
551 while(d_pos
< d_end
&& (keepReading
|| !dns_isspace(d_string
[d_pos
])))
554 HEXDecode(d_string
.c_str()+pos
, d_string
.c_str() + d_pos
, val
);
557 void RecordTextReader::xfrBase32HexBlob(string
& val
)
561 while(d_pos
< d_end
&& !dns_isspace(d_string
[d_pos
]))
564 val
=fromBase32Hex(string(d_string
.c_str()+pos
, d_pos
-pos
));
568 void RecordTextWriter::xfrBase32HexBlob(const string
& val
)
570 if(!d_string
.empty())
571 d_string
.append(1,' ');
573 d_string
.append(toUpper(toBase32Hex(val
)));
577 void RecordTextReader::xfrText(string
& val
, bool multi
, bool /* lenField */)
580 val
.reserve(d_end
- d_pos
);
582 while(d_pos
!= d_end
) {
587 if(d_string
[d_pos
]!='"') { // special case 'plenus' - without quotes
588 string::size_type pos
= d_pos
;
589 while(pos
!= d_end
&& isalnum(d_string
[pos
]))
593 val
.append(d_string
.c_str() + d_pos
, d_end
- d_pos
);
598 throw RecordTextException("Data field in DNS should start with quote (\") at position "+std::to_string(d_pos
)+" of '"+d_string
+"'");
601 while(++d_pos
< d_end
&& d_string
[d_pos
]!='"') {
602 if(d_string
[d_pos
]=='\\' && d_pos
+1!=d_end
) {
603 val
.append(1, d_string
[d_pos
++]);
605 val
.append(1, d_string
[d_pos
]);
609 throw RecordTextException("Data field in DNS should end on a quote (\") in '"+d_string
+"'");
616 void RecordTextReader::xfrUnquotedText(string
& val
, bool /* lenField */)
619 val
.reserve(d_end
- d_pos
);
625 val
.append(1, d_string
[d_pos
]);
626 while(++d_pos
< d_end
&& d_string
[d_pos
] != ' '){
627 val
.append(1, d_string
[d_pos
]);
631 void RecordTextReader::xfrType(uint16_t& val
)
635 while(d_pos
< d_end
&& !dns_isspace(d_string
[d_pos
]))
639 tmp
.assign(d_string
.c_str()+pos
, d_string
.c_str() + d_pos
);
641 val
=DNSRecordContent::TypeToNumber(tmp
);
645 void RecordTextReader::skipSpaces()
647 const char* strptr
= d_string
.c_str();
648 while(d_pos
< d_end
&& dns_isspace(strptr
[d_pos
]))
651 throw RecordTextException("missing field at the end of record content '"+d_string
+"'");
655 RecordTextWriter::RecordTextWriter(string
& str
, bool noDot
) : d_string(str
)
661 void RecordTextWriter::xfrNodeOrLocatorID(const NodeOrLocatorID
& val
)
663 if(!d_string
.empty()) {
664 d_string
.append(1,' ');
669 for (auto const &c
: val
.content
) {
670 snprintf(tmp
, sizeof(tmp
), "%02X", c
);
673 if (ctr
% 2 == 0 && ctr
!= 8) {
679 void RecordTextWriter::xfr48BitInt(const uint64_t& val
)
681 if(!d_string
.empty())
682 d_string
.append(1,' ');
683 d_string
+=std::to_string(val
);
687 void RecordTextWriter::xfr32BitInt(const uint32_t& val
)
689 if(!d_string
.empty())
690 d_string
.append(1,' ');
691 d_string
+=std::to_string(val
);
694 void RecordTextWriter::xfrType(const uint16_t& val
)
696 if(!d_string
.empty())
697 d_string
.append(1,' ');
698 d_string
+=DNSRecordContent::NumberToType(val
);
701 // this function is on the fast path for the pdns_recursor
702 void RecordTextWriter::xfrIP(const uint32_t& val
)
704 if(!d_string
.empty())
705 d_string
.append(1,' ');
711 memcpy(&vals
[0], &ip
, sizeof(ip
));
715 for(int n
=0; n
< 4; ++n
) {
717 *(pos
++)=vals
[n
]+'0';
718 } else if(vals
[n
] < 100) {
719 *(pos
++)=(vals
[n
]/10) +'0';
720 *(pos
++)=(vals
[n
]%10) +'0';
722 *(pos
++)=(vals
[n
]/100) +'0';
724 *(pos
++)=(vals
[n
]/10) +'0';
725 *(pos
++)=(vals
[n
]%10) +'0';
731 d_string
.append(tmp
, pos
);
734 void RecordTextWriter::xfrIP6(const std::string
& val
)
739 if(!d_string
.empty())
740 d_string
.append(1,' ');
744 if (inet_ntop(AF_INET6
, tmpbuf
, addrbuf
, sizeof addrbuf
) == nullptr)
745 throw RecordTextException("Unable to convert to ipv6 address");
747 d_string
+= std::string(addrbuf
);
750 void RecordTextWriter::xfrCAWithoutPort(uint8_t /* version */, ComboAddress
&val
)
752 string ip
= val
.toString();
754 if(!d_string
.empty())
755 d_string
.append(1,' ');
760 void RecordTextWriter::xfrCAPort(ComboAddress
&val
)
762 xfr16BitInt(val
.sin4
.sin_port
);
765 void RecordTextWriter::xfrTime(const uint32_t& val
)
767 if(!d_string
.empty())
768 d_string
.append(1,' ');
771 time_t time
=val
; // Y2038 bug!
772 gmtime_r(&time
, &tm
);
774 static const boost::format
fmt("%04d%02d%02d" "%02d%02d%02d");
775 d_string
+= boost::str(boost::format(fmt
) % (tm
.tm_year
+1900) % (tm
.tm_mon
+1) % tm
.tm_mday
% tm
.tm_hour
% tm
.tm_min
% tm
.tm_sec
);
779 void RecordTextWriter::xfr16BitInt(const uint16_t& val
)
784 void RecordTextWriter::xfr8BitInt(const uint8_t& val
)
789 // should not mess with the escapes
790 void RecordTextWriter::xfrName(const DNSName
& val
, bool /* unused */, bool /* noDot */)
792 if(!d_string
.empty())
793 d_string
.append(1,' ');
796 d_string
+=val
.toStringRootDot();
800 d_string
+=val
.toString();
804 void RecordTextWriter::xfrBlobNoSpaces(const string
& val
, int size
)
809 void RecordTextWriter::xfrBlob(const string
& val
, int)
811 if(!d_string
.empty())
812 d_string
.append(1,' ');
814 d_string
+=Base64Encode(val
);
817 void RecordTextWriter::xfrHexBlob(const string
& val
, bool)
819 if(!d_string
.empty())
820 d_string
.append(1,' ');
823 d_string
.append(1,'-');
827 string::size_type limit
=val
.size();
829 for(string::size_type n
= 0; n
< limit
; ++n
) {
830 snprintf(tmp
, sizeof(tmp
), "%02x", (unsigned char)val
[n
]);
835 // FIXME copied from dnsparser.cc, see #6010 and #3503 if you want a proper solution
836 static string
txtEscape(const string
&name
)
842 if((unsigned char) i
>= 127 || (unsigned char) i
< 32) {
843 snprintf(ebuf
, sizeof(ebuf
), "\\%03u", (unsigned char)i
);
846 else if(i
=='"' || i
=='\\'){
856 void RecordTextWriter::xfrSVCBValueList(const vector
<string
> &val
) {
857 bool shouldQuote
{false};
858 vector
<string
> escaped
;
859 escaped
.reserve(val
.size());
860 for (auto const &v
: val
) {
861 if (v
.find_first_of(' ') != string::npos
) {
864 string tmp
= txtEscape(v
);
866 unescaped
.reserve(tmp
.size() + 4);
867 for (auto const &ch
: tmp
) {
869 unescaped
+= R
"F(\\)F";
873 unescaped
+= R
"F(\\,)F";
878 escaped
.push_back(unescaped
);
881 d_string
.append(1, '"');
883 d_string
.append(boost::join(escaped
, ","));
885 d_string
.append(1, '"');
889 void RecordTextWriter::xfrSvcParamKeyVals(const set
<SvcParam
>& val
) {
890 for (auto const ¶m
: val
) {
891 if (!d_string
.empty())
892 d_string
.append(1, ' ');
894 d_string
.append(SvcParam::keyToString(param
.getKey()));
895 if (param
.getKey() != SvcParam::no_default_alpn
) {
896 d_string
.append(1, '=');
899 switch (param
.getKey())
901 case SvcParam::no_default_alpn
:
903 case SvcParam::ipv4hint
: /* fall-through */
904 case SvcParam::ipv6hint
:
905 // TODO use xfrCA and put commas in between?
906 if (param
.getAutoHint()) {
907 d_string
.append("auto");
910 d_string
.append(ComboAddress::caContainerToString(param
.getIPHints(), false));
913 xfrSVCBValueList(param
.getALPN());
915 case SvcParam::mandatory
:
917 bool doComma
= false;
918 for (auto const &k
: param
.getMandatory()) {
920 d_string
.append(1, ',');
921 d_string
.append(SvcParam::keyToString(k
));
926 case SvcParam::port
: {
929 xfr16BitInt(param
.getPort());
930 d_string
= str
+ d_string
;
933 case SvcParam::ech
: {
936 xfrBlobNoSpaces(param
.getECH());
937 d_string
= str
+ '"' + d_string
+ '"';
943 xfrText(param
.getValue(), false, false);
944 d_string
= str
+ '"' + txtEscape(d_string
) + '"';
950 void RecordTextWriter::xfrText(const string
& val
, bool /* multi */, bool /* lenField */)
952 if(!d_string
.empty())
953 d_string
.append(1,' ');
955 d_string
.append(val
);
958 void RecordTextWriter::xfrUnquotedText(const string
& val
, bool /* lenField */)
960 if(!d_string
.empty())
961 d_string
.append(1,' ');
962 d_string
.append(val
);
967 int main(int argc
, char**argv
)
970 RecordTextReader
rtr(argv
[1], argv
[2]);
972 unsigned int order
, pref
;
973 string flags
, services
, regexp
, replacement
;
979 rtr
.xfrText(services
);
981 rtr
.xfrName(replacement
);
983 cout
<<"order: "<<order
<<", pref: "<<pref
<<"\n";
984 cout
<<"flags: \""<<flags
<<"\", services: \""<<services
<<"\", regexp: \""<<regexp
<<"\", replacement: "<<replacement
<<"\n";
987 RecordTextWriter
rtw(out
);
992 rtw
.xfrText(services
);
994 rtw
.xfrName(replacement
);
996 cout
<<"Regenerated: '"<<out
<<"'\n";
999 catch(std::exception
& e
)
1001 cerr
<<"Fatal: "<<e
.what()<<endl
;