]>
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"
27 #include "dnsparser.hh"
30 #include <boost/algorithm/string.hpp>
31 #include <boost/algorithm/string/classification.hpp>
32 #include <boost/algorithm/string/replace.hpp>
33 #include <boost/format.hpp>
38 #include "namespaces.hh"
40 RecordTextReader::RecordTextReader(string str
, DNSName zone
) : d_string(std::move(str
)), d_zone(std::move(zone
)), d_pos(0)
42 /* remove whitespace */
43 if(!d_string
.empty() && ( dns_isspace(*d_string
.begin()) || dns_isspace(*d_string
.rbegin()) ))
44 boost::trim_if(d_string
, dns_isspace
);
45 d_end
= d_string
.size();
48 void RecordTextReader::xfr48BitInt(uint64_t &val
)
51 if (val
> 281474976710655LL)
52 throw RecordTextException("Overflow reading 48 bit integer from record content"); // fixme improve
55 void RecordTextReader::xfrNodeOrLocatorID(NodeOrLocatorID
& val
) {
59 d_pos
+len
< d_string
.length() && (isxdigit(d_string
.at(d_pos
+len
)) || d_string
.at(d_pos
+len
) == ':');
60 len
++) ; // find length of ID
62 // Parse as v6, and then strip the final 64 zero bytes
63 struct in6_addr tmpbuf
;
64 string to_parse
= d_string
.substr(d_pos
, len
) + ":0:0:0:0";
66 if (inet_pton(AF_INET6
, to_parse
.c_str(), &tmpbuf
) != 1) {
67 throw RecordTextException("while parsing colon-delimited 64-bit field: '" + d_string
.substr(d_pos
, len
) + "' is invalid");
70 std::memcpy(&val
.content
, tmpbuf
.s6_addr
, sizeof(val
.content
));
74 void RecordTextReader::xfr64BitInt(uint64_t &val
)
78 if(!isdigit(d_string
.at(d_pos
)))
79 throw RecordTextException("expected digits at position "+std::to_string(d_pos
)+" in '"+d_string
+"'");
82 val
=std::stoull(d_string
.substr(d_pos
), &pos
);
88 void RecordTextReader::xfr32BitInt(uint32_t &val
)
92 if(!isdigit(d_string
.at(d_pos
)))
93 throw RecordTextException("expected digits at position "+std::to_string(d_pos
)+" in '"+d_string
+"'");
96 val
= pdns::checked_stoi
<uint32_t>(d_string
.c_str() + d_pos
, &pos
);
101 void RecordTextReader::xfrTime(uint32_t &val
)
104 memset(&tm
, 0, sizeof(tm
));
109 if (itmp
<= (uint32_t)~0) {
110 // formatted as seconds since epoch, not as YYYYMMDDHHmmSS:
111 val
= (uint32_t) itmp
;
119 if (sscanf(tmp
.str().c_str(), "%04d%02d%02d" "%02d%02d%02d",
120 &tm
.tm_year
, &tm
.tm_mon
, &tm
.tm_mday
,
121 &tm
.tm_hour
, &tm
.tm_min
, &tm
.tm_sec
) != 6) {
122 throw RecordTextException("unable to parse '"+std::to_string(itmp
)+"' into a valid time at position "+std::to_string(d_pos
)+" in '"+d_string
+"'");
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 const char* strptr
=d_string
.c_str();
259 string::size_type begin_pos
= d_pos
;
260 while(d_pos
< d_end
) {
261 if(strptr
[d_pos
]!='\r' && dns_isspace(strptr
[d_pos
]))
266 sval
= DNSName(std::string(strptr
+begin_pos
, strptr
+d_pos
));
270 else if(!d_zone
.empty())
275 static bool isbase64(char c
, bool acceptspace
)
279 if(c
>= '0' && c
<= '9')
281 if(c
>= 'a' && c
<= 'z')
283 if(c
>= 'A' && c
<= 'Z')
285 if(c
=='+' || c
=='/' || c
=='=')
290 void RecordTextReader::xfrBlobNoSpaces(string
& val
, int len
) {
293 const char* strptr
=d_string
.c_str();
294 while(d_pos
< d_end
&& isbase64(strptr
[d_pos
], false))
298 tmp
.assign(d_string
.c_str()+pos
, d_string
.c_str() + d_pos
);
299 boost::erase_all(tmp
," ");
303 if (len
>-1 && val
.size() != static_cast<size_t>(len
))
304 throw RecordTextException("Record length "+std::to_string(val
.size()) + " does not match expected length '"+std::to_string(len
));
307 void RecordTextReader::xfrBlob(string
& val
, int)
311 const char* strptr
=d_string
.c_str();
312 while(d_pos
< d_end
&& isbase64(strptr
[d_pos
], true))
316 tmp
.assign(d_string
.c_str()+pos
, d_string
.c_str() + d_pos
);
317 boost::erase_all(tmp
," ");
322 void RecordTextReader::xfrRFC1035CharString(string
&val
) {
323 auto ctr
= parseRFC1035CharString(d_string
.substr(d_pos
, d_end
- d_pos
), val
);
327 void RecordTextReader::xfrSVCBValueList(vector
<string
> &val
) {
328 auto ctr
= parseSVCBValueList(d_string
.substr(d_pos
, d_end
- d_pos
), val
);
332 void RecordTextReader::xfrSvcParamKeyVals(set
<SvcParam
>& val
)
334 while (d_pos
!= d_end
) {
339 // Find the SvcParamKey
341 while (d_pos
!= d_end
) {
342 if (d_string
.at(d_pos
) == '=' || d_string
.at(d_pos
) == ' ') {
348 // We've reached a space or equals-sign or the end of the string (d_pos is at this char)
349 string k
= d_string
.substr(pos
, d_pos
- pos
);
350 SvcParam::SvcParamKey key
;
353 key
= SvcParam::keyFromString(k
, generic
);
354 } catch (const std::invalid_argument
&e
) {
355 throw RecordTextException(e
.what());
358 if (key
!= SvcParam::no_default_alpn
) {
359 if (d_pos
== d_end
|| d_string
.at(d_pos
) != '=') {
360 throw RecordTextException("expected '=' after " + k
);
362 d_pos
++; // Now on the first character after '='
363 if (d_pos
== d_end
|| d_string
.at(d_pos
) == ' ') {
364 throw RecordTextException("expected value after " + k
+ "=");
369 case SvcParam::no_default_alpn
:
370 if (d_pos
!= d_end
&& d_string
.at(d_pos
) == '=') {
371 throw RecordTextException(k
+ " key can not have values");
373 val
.insert(SvcParam(key
));
375 case SvcParam::ipv4hint
: /* fall-through */
376 case SvcParam::ipv6hint
: {
377 vector
<ComboAddress
> hints
;
381 xfrRFC1035CharString(value
);
382 size_t len
= key
== SvcParam::ipv4hint
? 4 : 16;
383 if (value
.size() % len
!= 0) {
384 throw RecordTextException(k
+ " in generic format has wrong number of bytes");
386 for (size_t i
=0; i
<value
.size(); i
+= len
) {
387 auto hint
= makeComboAddressFromRaw(static_cast<uint8_t>(key
), &value
.at(i
), len
);
388 hints
.push_back(hint
);
391 vector
<string
> value
;
392 xfrSVCBValueList(value
);
393 for (auto const &v
: value
) {
399 hints
.push_back(ComboAddress(v
));
403 auto p
= SvcParam(key
, std::move(hints
));
404 p
.setAutoHint(doAuto
);
407 catch (const std::invalid_argument
& e
) {
408 throw RecordTextException(e
.what());
412 case SvcParam::alpn
: {
413 vector
<string
> value
;
416 xfrRFC1035CharString(v
);
418 while (spos
< v
.length()) {
421 if (len
> v
.length() - spos
) {
422 throw RecordTextException("Length of ALPN value goes over total length of alpn SVC Param");
424 value
.push_back(v
.substr(spos
, len
));
428 xfrSVCBValueList(value
);
430 val
.insert(SvcParam(key
, std::move(value
)));
433 case SvcParam::mandatory
: {
436 xfrRFC1035CharString(v
);
437 if (v
.length() % 2 != 0) {
438 throw RecordTextException("Wrong number of bytes in SVC Param " + k
);
440 std::set
<SvcParam::SvcParamKey
> keys
;
441 for (size_t i
=0; i
< v
.length(); i
+= 2) {
442 uint16_t mand
= (v
.at(i
) << 8);
444 keys
.insert(SvcParam::SvcParamKey(mand
));
446 val
.insert(SvcParam(key
, std::move(keys
)));
449 vector
<string
> parts
;
450 xfrSVCBValueList(parts
);
451 set
<string
> values(parts
.begin(), parts
.end());
452 val
.insert(SvcParam(key
, std::move(values
)));
455 case SvcParam::port
: {
459 xfrRFC1035CharString(v
);
460 if (v
.length() != 2) {
461 throw RecordTextException("port in generic format has the wrong length, expected 2, got " + std::to_string(v
.length()));
463 port
= (v
.at(0) << 8);
468 val
.insert(SvcParam(key
, port
));
471 case SvcParam::ech
: {
474 xfrRFC1035CharString(value
);
476 bool haveQuote
= d_string
.at(d_pos
) == '"';
480 xfrBlobNoSpaces(value
);
482 if (d_string
.at(d_pos
) != '"') {
483 throw RecordTextException("ech value starts, but does not end with a '\"' symbol");
488 val
.insert(SvcParam(key
, value
));
493 xfrRFC1035CharString(value
);
494 val
.insert(SvcParam(key
, value
));
501 static inline uint8_t hextodec(uint8_t val
)
503 if(val
>= '0' && val
<='9')
505 else if(val
>= 'A' && val
<='F')
507 else if(val
>= 'a' && val
<='f')
510 throw RecordTextException("Unknown hexadecimal character '"+std::to_string(val
)+"'");
514 static void HEXDecode(const char* begin
, const char* end
, string
& out
)
516 if(end
- begin
== 1 && *begin
=='-') {
521 out
.reserve((end
-begin
)/2);
522 uint8_t mode
=0, val
=0;
523 for(; begin
!= end
; ++begin
) {
527 val
= 16*hextodec(*begin
);
530 val
+= hextodec(*begin
);
531 out
.append(1, (char) val
);
537 out
.append(1, (char) val
);
541 void RecordTextReader::xfrHexBlob(string
& val
, bool keepReading
)
545 while(d_pos
< d_end
&& (keepReading
|| !dns_isspace(d_string
[d_pos
])))
548 HEXDecode(d_string
.c_str()+pos
, d_string
.c_str() + d_pos
, val
);
551 void RecordTextReader::xfrBase32HexBlob(string
& val
)
555 while(d_pos
< d_end
&& !dns_isspace(d_string
[d_pos
]))
558 val
=fromBase32Hex(string(d_string
.c_str()+pos
, d_pos
-pos
));
562 void RecordTextWriter::xfrBase32HexBlob(const string
& val
)
564 if(!d_string
.empty())
565 d_string
.append(1,' ');
567 d_string
.append(toUpper(toBase32Hex(val
)));
571 void RecordTextReader::xfrText(string
& val
, bool multi
, bool lenField
)
574 val
.reserve(d_end
- d_pos
);
576 while(d_pos
!= d_end
) {
581 if(d_string
[d_pos
]!='"') { // special case 'plenus' - without quotes
582 string::size_type pos
= d_pos
;
583 while(pos
!= d_end
&& isalnum(d_string
[pos
]))
587 val
.append(d_string
.c_str() + d_pos
, d_end
- d_pos
);
592 throw RecordTextException("Data field in DNS should start with quote (\") at position "+std::to_string(d_pos
)+" of '"+d_string
+"'");
595 while(++d_pos
< d_end
&& d_string
[d_pos
]!='"') {
596 if(d_string
[d_pos
]=='\\' && d_pos
+1!=d_end
) {
597 val
.append(1, d_string
[d_pos
++]);
599 val
.append(1, d_string
[d_pos
]);
603 throw RecordTextException("Data field in DNS should end on a quote (\") in '"+d_string
+"'");
610 void RecordTextReader::xfrUnquotedText(string
& val
, bool lenField
)
613 val
.reserve(d_end
- d_pos
);
619 val
.append(1, d_string
[d_pos
]);
620 while(++d_pos
< d_end
&& d_string
[d_pos
] != ' '){
621 val
.append(1, d_string
[d_pos
]);
625 void RecordTextReader::xfrType(uint16_t& val
)
629 while(d_pos
< d_end
&& !dns_isspace(d_string
[d_pos
]))
633 tmp
.assign(d_string
.c_str()+pos
, d_string
.c_str() + d_pos
);
635 val
=DNSRecordContent::TypeToNumber(tmp
);
639 void RecordTextReader::skipSpaces()
641 const char* strptr
= d_string
.c_str();
642 while(d_pos
< d_end
&& dns_isspace(strptr
[d_pos
]))
645 throw RecordTextException("missing field at the end of record content '"+d_string
+"'");
649 RecordTextWriter::RecordTextWriter(string
& str
, bool noDot
) : d_string(str
)
655 void RecordTextWriter::xfrNodeOrLocatorID(const NodeOrLocatorID
& val
)
657 if(!d_string
.empty()) {
658 d_string
.append(1,' ');
663 for (auto const &c
: val
.content
) {
664 snprintf(tmp
, sizeof(tmp
), "%02X", c
);
667 if (ctr
% 2 == 0 && ctr
!= 8) {
673 void RecordTextWriter::xfr48BitInt(const uint64_t& val
)
675 if(!d_string
.empty())
676 d_string
.append(1,' ');
677 d_string
+=std::to_string(val
);
681 void RecordTextWriter::xfr32BitInt(const uint32_t& val
)
683 if(!d_string
.empty())
684 d_string
.append(1,' ');
685 d_string
+=std::to_string(val
);
688 void RecordTextWriter::xfrType(const uint16_t& val
)
690 if(!d_string
.empty())
691 d_string
.append(1,' ');
692 d_string
+=DNSRecordContent::NumberToType(val
);
695 // this function is on the fast path for the pdns_recursor
696 void RecordTextWriter::xfrIP(const uint32_t& val
)
698 if(!d_string
.empty())
699 d_string
.append(1,' ');
705 memcpy(&vals
[0], &ip
, sizeof(ip
));
709 for(int n
=0; n
< 4; ++n
) {
711 *(pos
++)=vals
[n
]+'0';
712 } else if(vals
[n
] < 100) {
713 *(pos
++)=(vals
[n
]/10) +'0';
714 *(pos
++)=(vals
[n
]%10) +'0';
716 *(pos
++)=(vals
[n
]/100) +'0';
718 *(pos
++)=(vals
[n
]/10) +'0';
719 *(pos
++)=(vals
[n
]%10) +'0';
725 d_string
.append(tmp
, pos
);
728 void RecordTextWriter::xfrIP6(const std::string
& val
)
733 if(!d_string
.empty())
734 d_string
.append(1,' ');
738 if (inet_ntop(AF_INET6
, tmpbuf
, addrbuf
, sizeof addrbuf
) == nullptr)
739 throw RecordTextException("Unable to convert to ipv6 address");
741 d_string
+= std::string(addrbuf
);
744 void RecordTextWriter::xfrCAWithoutPort(uint8_t version
, ComboAddress
&val
)
746 string ip
= val
.toString();
748 if(!d_string
.empty())
749 d_string
.append(1,' ');
754 void RecordTextWriter::xfrCAPort(ComboAddress
&val
)
756 xfr16BitInt(val
.sin4
.sin_port
);
759 void RecordTextWriter::xfrTime(const uint32_t& val
)
761 if(!d_string
.empty())
762 d_string
.append(1,' ');
765 time_t time
=val
; // Y2038 bug!
766 gmtime_r(&time
, &tm
);
768 static const boost::format
fmt("%04d%02d%02d" "%02d%02d%02d");
769 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
);
773 void RecordTextWriter::xfr16BitInt(const uint16_t& val
)
778 void RecordTextWriter::xfr8BitInt(const uint8_t& val
)
783 // should not mess with the escapes
784 void RecordTextWriter::xfrName(const DNSName
& val
, bool, bool noDot
)
786 if(!d_string
.empty())
787 d_string
.append(1,' ');
790 d_string
+=val
.toStringRootDot();
794 d_string
+=val
.toString();
798 void RecordTextWriter::xfrBlobNoSpaces(const string
& val
, int size
)
803 void RecordTextWriter::xfrBlob(const string
& val
, int)
805 if(!d_string
.empty())
806 d_string
.append(1,' ');
808 d_string
+=Base64Encode(val
);
811 void RecordTextWriter::xfrHexBlob(const string
& val
, bool)
813 if(!d_string
.empty())
814 d_string
.append(1,' ');
817 d_string
.append(1,'-');
821 string::size_type limit
=val
.size();
823 for(string::size_type n
= 0; n
< limit
; ++n
) {
824 snprintf(tmp
, sizeof(tmp
), "%02x", (unsigned char)val
[n
]);
829 // FIXME copied from dnsparser.cc, see #6010 and #3503 if you want a proper solution
830 static string
txtEscape(const string
&name
)
836 if((unsigned char) i
>= 127 || (unsigned char) i
< 32) {
837 snprintf(ebuf
, sizeof(ebuf
), "\\%03u", (unsigned char)i
);
840 else if(i
=='"' || i
=='\\'){
850 void RecordTextWriter::xfrSVCBValueList(const vector
<string
> &val
) {
851 bool shouldQuote
{false};
852 vector
<string
> escaped
;
853 escaped
.reserve(val
.size());
854 for (auto const &v
: val
) {
855 if (v
.find_first_of(' ') != string::npos
) {
858 string tmp
= txtEscape(v
);
860 unescaped
.reserve(tmp
.size() + 4);
861 for (auto const &ch
: tmp
) {
863 unescaped
+= R
"F(\\)F";
867 unescaped
+= R
"F(\\,)F";
872 escaped
.push_back(unescaped
);
875 d_string
.append(1, '"');
877 d_string
.append(boost::join(escaped
, ","));
879 d_string
.append(1, '"');
883 void RecordTextWriter::xfrSvcParamKeyVals(const set
<SvcParam
>& val
) {
884 for (auto const ¶m
: val
) {
885 if (!d_string
.empty())
886 d_string
.append(1, ' ');
888 d_string
.append(SvcParam::keyToString(param
.getKey()));
889 if (param
.getKey() != SvcParam::no_default_alpn
) {
890 d_string
.append(1, '=');
893 switch (param
.getKey())
895 case SvcParam::no_default_alpn
:
897 case SvcParam::ipv4hint
: /* fall-through */
898 case SvcParam::ipv6hint
:
899 // TODO use xfrCA and put commas in between?
900 if (param
.getAutoHint()) {
901 d_string
.append("auto");
904 d_string
.append(ComboAddress::caContainerToString(param
.getIPHints(), false));
907 xfrSVCBValueList(param
.getALPN());
909 case SvcParam::mandatory
:
911 bool doComma
= false;
912 for (auto const &k
: param
.getMandatory()) {
914 d_string
.append(1, ',');
915 d_string
.append(SvcParam::keyToString(k
));
920 case SvcParam::port
: {
923 xfr16BitInt(param
.getPort());
924 d_string
= str
+ d_string
;
927 case SvcParam::ech
: {
930 xfrBlobNoSpaces(param
.getECH());
931 d_string
= str
+ '"' + d_string
+ '"';
937 xfrText(param
.getValue(), false, false);
938 d_string
= str
+ '"' + txtEscape(d_string
) + '"';
944 void RecordTextWriter::xfrText(const string
& val
, bool multi
, bool lenField
)
946 if(!d_string
.empty())
947 d_string
.append(1,' ');
949 d_string
.append(val
);
952 void RecordTextWriter::xfrUnquotedText(const string
& val
, bool lenField
)
954 if(!d_string
.empty())
955 d_string
.append(1,' ');
956 d_string
.append(val
);
961 int main(int argc
, char**argv
)
964 RecordTextReader
rtr(argv
[1], argv
[2]);
966 unsigned int order
, pref
;
967 string flags
, services
, regexp
, replacement
;
973 rtr
.xfrText(services
);
975 rtr
.xfrName(replacement
);
977 cout
<<"order: "<<order
<<", pref: "<<pref
<<"\n";
978 cout
<<"flags: \""<<flags
<<"\", services: \""<<services
<<"\", regexp: \""<<regexp
<<"\", replacement: "<<replacement
<<"\n";
981 RecordTextWriter
rtw(out
);
986 rtw
.xfrText(services
);
988 rtw
.xfrName(replacement
);
990 cout
<<"Regenerated: '"<<out
<<"'\n";
993 catch(std::exception
& e
)
995 cerr
<<"Fatal: "<<e
.what()<<endl
;