Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / mon / MonCap.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 /*
4  * Ceph - scalable distributed file system
5  *
6  * Copyright (C) 2013 Inktank
7  *
8  * This is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License version 2.1, as published by the Free Software
11  * Foundation.  See file COPYING.
12  *
13  */
14
15 #include <boost/config/warning_disable.hpp>
16 #include <boost/spirit/include/qi_uint.hpp>
17 #include <boost/spirit/include/qi.hpp>
18 #include <boost/fusion/include/std_pair.hpp>
19 #include <boost/spirit/include/phoenix.hpp>
20 #include <boost/fusion/adapted/struct/adapt_struct.hpp>
21 #include <boost/fusion/include/adapt_struct.hpp>
22
23 #include "MonCap.h"
24 #include "include/stringify.h"
25 #include "common/debug.h"
26 #include "common/Formatter.h"
27
28 #include <algorithm>
29
30 #include <boost/regex.hpp>
31 #include "include/assert.h"
32
33 static inline bool is_not_alnum_space(char c)
34 {
35   return !(isalpha(c) || isdigit(c) || (c == '-') || (c == '_'));
36 }
37
38 static string maybe_quote_string(const std::string& str)
39 {
40   if (find_if(str.begin(), str.end(), is_not_alnum_space) == str.end())
41     return str;
42   return string("\"") + str + string("\"");
43 }
44
45 using std::ostream;
46 using std::vector;
47
48 #define dout_subsys ceph_subsys_mon
49
50 ostream& operator<<(ostream& out, const mon_rwxa_t& p)
51
52   if (p == MON_CAP_ANY)
53     return out << "*";
54
55   if (p & MON_CAP_R)
56     out << "r";
57   if (p & MON_CAP_W)
58     out << "w";
59   if (p & MON_CAP_X)
60     out << "x";
61   return out;
62 }
63
64 ostream& operator<<(ostream& out, const StringConstraint& c)
65 {
66   switch (c.match_type) {
67   case StringConstraint::MATCH_TYPE_EQUAL:
68     return out << "value " << c.value;
69   case StringConstraint::MATCH_TYPE_PREFIX:
70     return out << "prefix " << c.value;
71   case StringConstraint::MATCH_TYPE_REGEX:
72     return out << "regex " << c.value;
73   default:
74     break;
75   }
76   return out;
77 }
78
79 ostream& operator<<(ostream& out, const MonCapGrant& m)
80 {
81   out << "allow";
82   if (m.service.length()) {
83     out << " service " << maybe_quote_string(m.service);
84   }
85   if (m.command.length()) {
86     out << " command " << maybe_quote_string(m.command);
87     if (!m.command_args.empty()) {
88       out << " with";
89       for (map<string,StringConstraint>::const_iterator p = m.command_args.begin();
90            p != m.command_args.end();
91            ++p) {
92         switch (p->second.match_type) {
93         case StringConstraint::MATCH_TYPE_EQUAL:
94           out << " " << maybe_quote_string(p->first) << "="
95               << maybe_quote_string(p->second.value);
96           break;
97         case StringConstraint::MATCH_TYPE_PREFIX:
98           out << " " << maybe_quote_string(p->first) << " prefix "
99               << maybe_quote_string(p->second.value);
100           break;
101         case StringConstraint::MATCH_TYPE_REGEX:
102           out << " " << maybe_quote_string(p->first) << " regex "
103               << maybe_quote_string(p->second.value);
104           break;
105         default:
106           break;
107         }
108       }
109     }
110   }
111   if (m.profile.length()) {
112     out << " profile " << maybe_quote_string(m.profile);
113   }
114   if (m.allow != 0)
115     out << " " << m.allow;
116   return out;
117 }
118
119
120 // <magic>
121 //  fusion lets us easily populate structs via the qi parser.
122
123 typedef map<string,StringConstraint> kvmap;
124
125 BOOST_FUSION_ADAPT_STRUCT(MonCapGrant,
126                           (std::string, service)
127                           (std::string, profile)
128                           (std::string, command)
129                           (kvmap, command_args)
130                           (mon_rwxa_t, allow))
131
132 BOOST_FUSION_ADAPT_STRUCT(StringConstraint,
133                           (StringConstraint::MatchType, match_type)
134                           (std::string, value))
135
136 // </magic>
137
138 void MonCapGrant::expand_profile(int daemon_type, const EntityName& name) const
139 {
140   // only generate this list once
141   if (!profile_grants.empty())
142     return;
143
144   if (profile == "read-only") {
145     // grants READ-ONLY caps monitor-wide
146     // 'auth' requires MON_CAP_X even for RO, which we do not grant here.
147     profile_grants.push_back(mon_rwxa_t(MON_CAP_R));
148     return;
149   }
150
151   if (profile == "read-write") {
152     // grants READ-WRITE caps monitor-wide
153     // 'auth' requires MON_CAP_X for all operations, which we do not grant.
154     profile_grants.push_back(mon_rwxa_t(MON_CAP_R | MON_CAP_W));
155     return;
156   }
157
158   switch (daemon_type) {
159   case CEPH_ENTITY_TYPE_MON:
160     expand_profile_mon(name);
161     return;
162   case CEPH_ENTITY_TYPE_MGR:
163     expand_profile_mgr(name);
164     return;
165   }
166 }
167
168 void MonCapGrant::expand_profile_mgr(const EntityName& name) const
169 {
170 }
171
172 void MonCapGrant::expand_profile_mon(const EntityName& name) const
173 {
174   if (profile == "mon") {
175     profile_grants.push_back(MonCapGrant("mon", MON_CAP_ALL));
176     profile_grants.push_back(MonCapGrant("log", MON_CAP_ALL));
177   }
178   if (profile == "osd") {
179     profile_grants.push_back(MonCapGrant("osd", MON_CAP_ALL));
180     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));
181     profile_grants.push_back(MonCapGrant("pg", MON_CAP_R | MON_CAP_W));
182     profile_grants.push_back(MonCapGrant("log", MON_CAP_W));
183   }
184   if (profile == "mds") {
185     profile_grants.push_back(MonCapGrant("mds", MON_CAP_ALL));
186     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));
187     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));
188     // This command grant is checked explicitly in MRemoveSnaps handling
189     profile_grants.push_back(MonCapGrant("osd pool rmsnap"));
190     profile_grants.push_back(MonCapGrant("osd blacklist"));
191     profile_grants.push_back(MonCapGrant("log", MON_CAP_W));
192   }
193   if (profile == "mgr") {
194     profile_grants.push_back(MonCapGrant("mgr", MON_CAP_ALL));
195     profile_grants.push_back(MonCapGrant("log", MON_CAP_R | MON_CAP_W));
196     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R | MON_CAP_W));
197     profile_grants.push_back(MonCapGrant("mds", MON_CAP_R | MON_CAP_W));
198     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R | MON_CAP_W));
199     profile_grants.push_back(MonCapGrant("auth", MON_CAP_R | MON_CAP_X));
200     profile_grants.push_back(MonCapGrant("config-key", MON_CAP_R | MON_CAP_W));
201     StringConstraint constraint(StringConstraint::MATCH_TYPE_PREFIX,
202                                 "daemon-private/mgr/");
203     profile_grants.push_back(MonCapGrant("config-key get", "key", constraint));
204     profile_grants.push_back(MonCapGrant("config-key set", "key", constraint));
205     profile_grants.push_back(MonCapGrant("config-key put", "key", constraint));
206     profile_grants.push_back(MonCapGrant("config-key exists", "key", constraint));
207     profile_grants.push_back(MonCapGrant("config-key delete", "key", constraint));
208   }
209   if (profile == "osd" || profile == "mds" || profile == "mon" ||
210       profile == "mgr") {
211     StringConstraint constraint(StringConstraint::MATCH_TYPE_PREFIX,
212                                 string("daemon-private/") + stringify(name) +
213                                 string("/"));
214     string prefix = string("daemon-private/") + stringify(name) + string("/");
215     profile_grants.push_back(MonCapGrant("config-key get", "key", constraint));
216     profile_grants.push_back(MonCapGrant("config-key put", "key", constraint));
217     profile_grants.push_back(MonCapGrant("config-key set", "key", constraint));
218     profile_grants.push_back(MonCapGrant("config-key exists", "key", constraint));
219     profile_grants.push_back(MonCapGrant("config-key delete", "key", constraint));
220   }
221   if (profile == "bootstrap-osd") {
222     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));  // read monmap
223     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));  // read osdmap
224     profile_grants.push_back(MonCapGrant("mon getmap"));
225     profile_grants.push_back(MonCapGrant("osd new"));
226   }
227   if (profile == "bootstrap-mds") {
228     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));  // read monmap
229     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));  // read osdmap
230     profile_grants.push_back(MonCapGrant("mon getmap"));
231     profile_grants.push_back(MonCapGrant("auth get-or-create"));  // FIXME: this can expose other mds keys
232     profile_grants.back().command_args["entity"] = StringConstraint(
233       StringConstraint::MATCH_TYPE_PREFIX, "mds.");
234     profile_grants.back().command_args["caps_mon"] = StringConstraint(
235       StringConstraint::MATCH_TYPE_EQUAL, "allow profile mds");
236     profile_grants.back().command_args["caps_osd"] = StringConstraint(
237       StringConstraint::MATCH_TYPE_EQUAL, "allow rwx");
238     profile_grants.back().command_args["caps_mds"] = StringConstraint(
239       StringConstraint::MATCH_TYPE_EQUAL, "allow");
240   }
241   if (profile == "bootstrap-mgr") {
242     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));  // read monmap
243     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));  // read osdmap
244     profile_grants.push_back(MonCapGrant("mon getmap"));
245     profile_grants.push_back(MonCapGrant("auth get-or-create"));  // FIXME: this can expose other mgr keys
246     profile_grants.back().command_args["entity"] = StringConstraint(
247       StringConstraint::MATCH_TYPE_PREFIX, "mgr.");
248     profile_grants.back().command_args["caps_mon"] = StringConstraint(
249       StringConstraint::MATCH_TYPE_EQUAL, "allow profile mgr");
250   }
251   if (profile == "bootstrap-rgw") {
252     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));  // read monmap
253     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));  // read osdmap
254     profile_grants.push_back(MonCapGrant("mon getmap"));
255     profile_grants.push_back(MonCapGrant("auth get-or-create"));  // FIXME: this can expose other mds keys
256     profile_grants.back().command_args["entity"] = StringConstraint(
257       StringConstraint::MATCH_TYPE_PREFIX, "client.rgw.");
258     profile_grants.back().command_args["caps_mon"] = StringConstraint(
259       StringConstraint::MATCH_TYPE_EQUAL, "allow rw");
260     profile_grants.back().command_args["caps_osd"] = StringConstraint(
261       StringConstraint::MATCH_TYPE_EQUAL, "allow rwx");
262   }
263   if (profile == "bootstrap-rbd") {
264     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));  // read monmap
265     profile_grants.push_back(MonCapGrant("auth get-or-create"));  // FIXME: this can expose other mds keys
266     profile_grants.back().command_args["entity"] = StringConstraint(
267       StringConstraint::MATCH_TYPE_PREFIX, "client.");
268     profile_grants.back().command_args["caps_mon"] = StringConstraint(
269       StringConstraint::MATCH_TYPE_EQUAL, "profile rbd");
270     profile_grants.back().command_args["caps_osd"] = StringConstraint(
271       StringConstraint::MATCH_TYPE_REGEX,
272       "^([ ,]*profile(=|[ ]+)['\"]?rbd[^ ,'\"]*['\"]?([ ]+pool(=|[ ]+)['\"]?[^,'\"]+['\"]?)?)+$");
273   }
274   if (profile == "fs-client") {
275     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));
276     profile_grants.push_back(MonCapGrant("mds", MON_CAP_R));
277     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));
278     profile_grants.push_back(MonCapGrant("pg", MON_CAP_R));
279   }
280   if (profile == "simple-rados-client") {
281     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));
282     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));
283     profile_grants.push_back(MonCapGrant("pg", MON_CAP_R));
284   }
285   if (profile == "rbd") {
286     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));
287     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));
288     profile_grants.push_back(MonCapGrant("pg", MON_CAP_R));
289
290     // exclusive lock dead-client blacklisting (IP+nonce required)
291     profile_grants.push_back(MonCapGrant("osd blacklist"));
292     profile_grants.back().command_args["blacklistop"] = StringConstraint(
293       StringConstraint::MATCH_TYPE_EQUAL, "add");
294     profile_grants.back().command_args["addr"] = StringConstraint(
295       StringConstraint::MATCH_TYPE_REGEX, "^[^/]+/[0-9]+$");
296   }
297
298   if (profile == "role-definer") {
299     // grants ALL caps to the auth subsystem, read-only on the
300     // monitor subsystem and nothing else.
301     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));
302     profile_grants.push_back(MonCapGrant("auth", MON_CAP_ALL));
303   }
304 }
305
306 mon_rwxa_t MonCapGrant::get_allowed(CephContext *cct,
307                                     int daemon_type,
308                                     EntityName name,
309                                     const std::string& s, const std::string& c,
310                                     const map<string,string>& c_args) const
311 {
312   if (profile.length()) {
313     expand_profile(daemon_type, name);
314     mon_rwxa_t a;
315     for (list<MonCapGrant>::const_iterator p = profile_grants.begin();
316          p != profile_grants.end(); ++p)
317       a = a | p->get_allowed(cct, daemon_type, name, s, c, c_args);
318     return a;
319   }
320   if (service.length()) {
321     if (service != s)
322       return 0;
323     return allow;
324   }
325   if (command.length()) {
326     if (command != c)
327       return 0;
328     for (map<string,StringConstraint>::const_iterator p = command_args.begin(); p != command_args.end(); ++p) {
329       map<string,string>::const_iterator q = c_args.find(p->first);
330       // argument must be present if a constraint exists
331       if (q == c_args.end())
332         return 0;
333       switch (p->second.match_type) {
334       case StringConstraint::MATCH_TYPE_EQUAL:
335         if (p->second.value != q->second)
336           return 0;
337         break;
338       case StringConstraint::MATCH_TYPE_PREFIX:
339         if (q->second.find(p->second.value) != 0)
340           return 0;
341         break;
342       case StringConstraint::MATCH_TYPE_REGEX:
343         {
344           boost::regex pattern(
345             p->second.value, boost::regex::extended | boost::regex::no_except);
346           if (pattern.empty() || !boost::regex_match(q->second, pattern))
347             return 0;
348         }
349         break;
350       default:
351         break;
352       }
353     }
354     return MON_CAP_ALL;
355   }
356   return allow;
357 }
358
359 ostream& operator<<(ostream&out, const MonCap& m)
360 {
361   for (vector<MonCapGrant>::const_iterator p = m.grants.begin(); p != m.grants.end(); ++p) {
362     if (p != m.grants.begin())
363       out << ", ";
364     out << *p;
365   }
366   return out;
367 }
368
369 bool MonCap::is_allow_all() const
370 {
371   for (vector<MonCapGrant>::const_iterator p = grants.begin(); p != grants.end(); ++p)
372     if (p->is_allow_all())
373       return true;
374   return false;
375 }
376
377 void MonCap::set_allow_all()
378 {
379   grants.clear();
380   grants.push_back(MonCapGrant(MON_CAP_ANY));
381   text = "allow *";
382 }
383
384 bool MonCap::is_capable(CephContext *cct,
385                         int daemon_type,
386                         EntityName name,
387                         const string& service,
388                         const string& command, const map<string,string>& command_args,
389                         bool op_may_read, bool op_may_write, bool op_may_exec) const
390 {
391   if (cct)
392     ldout(cct, 20) << "is_capable service=" << service << " command=" << command
393                    << (op_may_read ? " read":"")
394                    << (op_may_write ? " write":"")
395                    << (op_may_exec ? " exec":"")
396                    << " on cap " << *this
397                    << dendl;
398   mon_rwxa_t allow = 0;
399   for (vector<MonCapGrant>::const_iterator p = grants.begin();
400        p != grants.end(); ++p) {
401     if (cct)
402       ldout(cct, 20) << " allow so far " << allow << ", doing grant " << *p << dendl;
403
404     if (p->is_allow_all()) {
405       if (cct)
406         ldout(cct, 20) << " allow all" << dendl;
407       return true;
408     }
409
410     // check enumerated caps
411     allow = allow | p->get_allowed(cct, daemon_type, name, service, command,
412                                    command_args);
413     if ((!op_may_read || (allow & MON_CAP_R)) &&
414         (!op_may_write || (allow & MON_CAP_W)) &&
415         (!op_may_exec || (allow & MON_CAP_X))) {
416       if (cct)
417         ldout(cct, 20) << " match" << dendl;
418       return true;
419     }
420   }
421   return false;
422 }
423
424 void MonCap::encode(bufferlist& bl) const
425 {
426   ENCODE_START(4, 4, bl);   // legacy MonCaps was 3, 3
427   ::encode(text, bl);
428   ENCODE_FINISH(bl);
429 }
430
431 void MonCap::decode(bufferlist::iterator& bl)
432 {
433   string s;
434   DECODE_START(4, bl);
435   ::decode(s, bl);
436   DECODE_FINISH(bl);
437   parse(s, NULL);
438 }
439
440 void MonCap::dump(Formatter *f) const
441 {
442   f->dump_string("text", text);
443 }
444
445 void MonCap::generate_test_instances(list<MonCap*>& ls)
446 {
447   ls.push_back(new MonCap);
448   ls.push_back(new MonCap);
449   ls.back()->parse("allow *");
450   ls.push_back(new MonCap);
451   ls.back()->parse("allow rwx");
452   ls.push_back(new MonCap);
453   ls.back()->parse("allow service foo x");
454   ls.push_back(new MonCap);
455   ls.back()->parse("allow command bar x");
456   ls.push_back(new MonCap);
457   ls.back()->parse("allow service foo r, allow command bar x");
458   ls.push_back(new MonCap);
459   ls.back()->parse("allow command bar with k1=v1 x");
460   ls.push_back(new MonCap);
461   ls.back()->parse("allow command bar with k1=v1 k2=v2 x");
462 }
463
464 // grammar
465 namespace qi = boost::spirit::qi;
466 namespace ascii = boost::spirit::ascii;
467 namespace phoenix = boost::phoenix;
468
469
470 template <typename Iterator>
471 struct MonCapParser : qi::grammar<Iterator, MonCap()>
472 {
473   MonCapParser() : MonCapParser::base_type(moncap)
474   {
475     using qi::char_;
476     using qi::int_;
477     using qi::ulong_long;
478     using qi::lexeme;
479     using qi::alnum;
480     using qi::_val;
481     using qi::_1;
482     using qi::_2;
483     using qi::_3;
484     using qi::eps;
485     using qi::lit;
486
487     quoted_string %=
488       lexeme['"' >> +(char_ - '"') >> '"'] | 
489       lexeme['\'' >> +(char_ - '\'') >> '\''];
490     unquoted_word %= +char_("a-zA-Z0-9_.-");
491     str %= quoted_string | unquoted_word;
492
493     spaces = +(lit(' ') | lit('\n') | lit('\t'));
494
495     // command := command[=]cmd [k1=v1 k2=v2 ...]
496     str_match = '=' >> qi::attr(StringConstraint::MATCH_TYPE_EQUAL) >> str;
497     str_prefix = spaces >> lit("prefix") >> spaces >>
498                  qi::attr(StringConstraint::MATCH_TYPE_PREFIX) >> str;
499     str_regex = spaces >> lit("regex") >> spaces >>
500                  qi::attr(StringConstraint::MATCH_TYPE_REGEX) >> str;
501     kv_pair = str >> (str_match | str_prefix | str_regex);
502     kv_map %= kv_pair >> *(spaces >> kv_pair);
503     command_match = -spaces >> lit("allow") >> spaces >> lit("command") >> (lit('=') | spaces)
504                             >> qi::attr(string()) >> qi::attr(string())
505                             >> str
506                             >> -(spaces >> lit("with") >> spaces >> kv_map)
507                             >> qi::attr(0);
508
509     // service foo rwxa
510     service_match %= -spaces >> lit("allow") >> spaces >> lit("service") >> (lit('=') | spaces)
511                              >> str >> qi::attr(string()) >> qi::attr(string())
512                              >> qi::attr(map<string,StringConstraint>())
513                              >> spaces >> rwxa;
514
515     // profile foo
516     profile_match %= -spaces >> -(lit("allow") >> spaces)
517                              >> lit("profile") >> (lit('=') | spaces)
518                              >> qi::attr(string())
519                              >> str
520                              >> qi::attr(string())
521                              >> qi::attr(map<string,StringConstraint>())
522                              >> qi::attr(0);
523
524     // rwxa
525     rwxa_match %= -spaces >> lit("allow") >> spaces
526                           >> qi::attr(string()) >> qi::attr(string()) >> qi::attr(string())
527                           >> qi::attr(map<string,StringConstraint>())
528                           >> rwxa;
529
530     // rwxa := * | [r][w][x]
531     rwxa =
532       (lit("*")[_val = MON_CAP_ANY]) |
533       ( eps[_val = 0] >>
534         ( lit('r')[_val |= MON_CAP_R] ||
535           lit('w')[_val |= MON_CAP_W] ||
536           lit('x')[_val |= MON_CAP_X]
537           )
538         );
539
540     // grant := allow ...
541     grant = -spaces >> (rwxa_match | profile_match | service_match | command_match) >> -spaces;
542
543     // moncap := grant [grant ...]
544     grants %= (grant % (*lit(' ') >> (lit(';') | lit(',')) >> *lit(' ')));
545     moncap = grants  [_val = phoenix::construct<MonCap>(_1)]; 
546
547   }
548   qi::rule<Iterator> spaces;
549   qi::rule<Iterator, unsigned()> rwxa;
550   qi::rule<Iterator, string()> quoted_string;
551   qi::rule<Iterator, string()> unquoted_word;
552   qi::rule<Iterator, string()> str;
553
554   qi::rule<Iterator, StringConstraint()> str_match, str_prefix, str_regex;
555   qi::rule<Iterator, pair<string, StringConstraint>()> kv_pair;
556   qi::rule<Iterator, map<string, StringConstraint>()> kv_map;
557
558   qi::rule<Iterator, MonCapGrant()> rwxa_match;
559   qi::rule<Iterator, MonCapGrant()> command_match;
560   qi::rule<Iterator, MonCapGrant()> service_match;
561   qi::rule<Iterator, MonCapGrant()> profile_match;
562   qi::rule<Iterator, MonCapGrant()> grant;
563   qi::rule<Iterator, std::vector<MonCapGrant>()> grants;
564   qi::rule<Iterator, MonCap()> moncap;
565 };
566
567 bool MonCap::parse(const string& str, ostream *err)
568 {
569   string s = str;
570   string::iterator iter = s.begin();
571   string::iterator end = s.end();
572
573   MonCapParser<string::iterator> g;
574   bool r = qi::parse(iter, end, g, *this);
575   //MonCapGrant foo;
576   //bool r = qi::phrase_parse(iter, end, g, ascii::space, foo);
577   if (r && iter == end) {
578     text = str;
579     return true;
580   }
581
582   // Make sure no grants are kept after parsing failed!
583   grants.clear();
584
585   if (err) {
586     if (iter != end)
587       *err << "moncap parse failed, stopped at '" << std::string(iter, end)
588            << "' of '" << str << "'\n";
589     else
590       *err << "moncap parse failed, stopped at end of '" << str << "'\n";
591   }
592
593   return false; 
594 }
595