1 /*
2 
3                           Firewall Builder
4 
5                  Copyright (C) 2007 NetCitadel, LLC
6 
7   Author:  Vadim Kurland     vadim@vk.crocodile.org
8 
9   $Id$
10 
11   This program is free software which we release under the GNU General Public
12   License. You may redistribute and/or modify this program under the terms
13   of that license as published by the Free Software Foundation; either
14   version 2 of the License, or (at your option) any later version.
15 
16   This program is distributed in the hope that it will be useful,
17   but WITHOUT ANY WARRANTY; without even the implied warranty of
18   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   GNU General Public License for more details.
20 
21   To get a copy of the GNU General Public License, write to the Free Software
22   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 
24 */
25 
26 #include "config.h"
27 
28 #include "PolicyCompiler_junosacl.h"
29 // #include "NamedObjectsAndGroupsSupport.h"
30 
31 #include "fwbuilder/AddressTable.h"
32 #include "fwbuilder/FWObjectDatabase.h"
33 #include "fwbuilder/ICMPService.h"
34 #include "fwbuilder/IPService.h"
35 #include "fwbuilder/Interface.h"
36 #include "fwbuilder/Library.h"
37 #include "fwbuilder/Management.h"
38 #include "fwbuilder/Network.h"
39 #include "fwbuilder/ObjectMirror.h"
40 #include "fwbuilder/Policy.h"
41 #include "fwbuilder/Resources.h"
42 #include "fwbuilder/RuleElement.h"
43 #include "fwbuilder/TCPService.h"
44 #include "fwbuilder/UDPService.h"
45 #include "fwbuilder/CustomService.h"
46 
47 #include "compiler_lib/junosInterfaces.h"
48 
49 #include <assert.h>
50 
51 using namespace libfwbuilder;
52 using namespace fwcompiler;
53 using namespace std;
54 
myPlatformName()55 string PolicyCompiler_junosacl::myPlatformName() { return "junosacl"; }
56 
PolicyCompiler_junosacl(FWObjectDatabase * _db,Firewall * fw,bool ipv6_policy,OSConfigurator * _oscnf)57 PolicyCompiler_junosacl::PolicyCompiler_junosacl(FWObjectDatabase *_db,
58                                              Firewall *fw,
59                                              bool ipv6_policy,
60                                              OSConfigurator *_oscnf) :
61     PolicyCompiler_cisco(_db, fw, ipv6_policy, _oscnf)
62 {
63     resetinbound = false;
64     fragguard = false;
65     comment_symbol = "#";
66 }
67 
prolog()68 int PolicyCompiler_junosacl::prolog()
69 {
70     string version = fw->getStr("version");
71     string platform = fw->getStr("platform");
72     string host_os = fw->getStr("host_OS");
73 
74     if (platform!="junosacl")
75 	abort("Unsupported platform " + platform );
76 
77     fw->getOptionsObject()->setBool("generate_out_acl", true);
78 
79     fw->getOptionsObject()->setBool(
80         "use_acl_remarks",
81         fw->getOptionsObject()->getBool("iosacl_use_acl_remarks"));
82 
83     // object_groups = new Group();
84     // persistent_objects->add( object_groups );
85 
86     setAllNetworkZonesToNone();
87 
88     return PolicyCompiler::prolog();
89 }
90 
findDynamicInterface(PolicyRule * rule,RuleElement * rel)91 bool PolicyCompiler_junosacl::checkForDynamicInterface::findDynamicInterface(
92     PolicyRule *rule, RuleElement *rel)
93 {
94     string vers=compiler->fw->getStr("version");
95     for (list<FWObject*>::iterator i1=rel->begin(); i1!=rel->end(); ++i1)
96     {
97 	FWObject *o   = *i1;
98 	FWObject *obj = NULL;
99 	if (FWReference::cast(o)!=NULL) obj=FWReference::cast(o)->getPointer();
100         Interface *iface=Interface::cast(obj);
101         if (iface!=NULL && iface->isDyn())
102             compiler->abort(
103                 rule,
104                 "Dynamic interface can not be used in the IOS ACL rules.");
105     }
106 
107     return true;
108 }
109 
processNext()110 bool PolicyCompiler_junosacl::checkForDynamicInterface::processNext()
111 {
112     PolicyRule *rule = getNext(); if (rule==NULL) return false;
113 
114     findDynamicInterface(rule,rule->getSrc());
115     findDynamicInterface(rule,rule->getDst());
116 
117     tmp_queue.push_back(rule);
118     return true;
119 }
120 
processNext()121 bool PolicyCompiler_junosacl::ValidateInterfaceUnitName::processNext()
122 {
123     assert(compiler!=NULL);
124     assert(prev_processor!=NULL);
125 
126     slurp();
127     if (tmp_queue.size()==0) return false;
128 
129     junosInterfaces * jInterface = new junosInterfaces();
130 
131     for (std::deque<Rule*>::iterator i=tmp_queue.begin(); i!=tmp_queue.end(); ++i)
132     {
133         if (PolicyRule *rule = PolicyRule::cast(*i))
134             if (FWObject *obj = FWReference::getObject(*rule->getItf()->begin())) {
135                 if (!jInterface->parseVlan(QString::fromStdString(obj->getName()), NULL, NULL))
136                     compiler->abort(rule, QString("junosacl policy rules must use a 'unit <value>' subinterface, not the main interface. You used: ")
137                                     .append(QString::fromStdString(obj->getName()))
138                                     .toStdString());
139             }
140     }
141 
142     return true;
143 }
144 
145 /*
146  * Copy all references from rule element re1 to rule element re2.
147  */
duplicateRuleElement(RuleElement * re1,RuleElement * re2)148 void PolicyCompiler_junosacl::mirrorRule::duplicateRuleElement(
149     RuleElement *re1, RuleElement *re2)
150 {
151     re2->clearChildren();
152     for (list<FWObject*>::iterator i1=re1->begin(); i1!=re1->end(); ++i1)
153     {
154         FWObject *obj = FWReference::getObject(*i1);
155         re2->addRef(obj);
156     }
157 }
158 
processNext()159 bool PolicyCompiler_junosacl::mirrorRule::processNext()
160 {
161     //PolicyCompiler_iosacl *iosacl_comp=dynamic_cast<PolicyCompiler_iosacl*>(compiler);
162     PolicyRule *rule = getNext(); if (rule==NULL) return false;
163     if (rule->getOptionsObject()->getBool("iosacl_add_mirror_rule"))
164     {
165         PolicyRule *r= compiler->dbcopy->createPolicyRule();
166         compiler->temp_ruleset->add(r);
167         r->duplicate(rule);
168 
169         r->setAction(rule->getAction());
170 
171         switch (rule->getDirection())
172         {
173         case PolicyRule::Inbound: r->setDirection(PolicyRule::Outbound); break;
174         case PolicyRule::Outbound: r->setDirection(PolicyRule::Inbound); break;
175         default: r->setDirection(PolicyRule::Both); break;
176         }
177 
178 	RuleElementSrc *osrc = rule->getSrc();
179 	RuleElementDst *odst = rule->getDst();
180 	RuleElementSrv *osrv = rule->getSrv();
181 	RuleElementItf *oitf = rule->getItf();
182 
183 	RuleElementSrc *nsrc = r->getSrc();
184 	RuleElementDst *ndst = r->getDst();
185 	RuleElementSrv *nsrv = r->getSrv();
186 	RuleElementItf *nitf = r->getItf();
187 
188         duplicateRuleElement(osrc, ndst);
189         duplicateRuleElement(odst, nsrc);
190         duplicateRuleElement(oitf, nitf);
191 
192         if (!osrv->isAny())
193         {
194             ObjectMirror mirror;
195             nsrv->clearChildren();
196             for (list<FWObject*>::iterator i1=osrv->begin(); i1!=osrv->end(); ++i1)
197             {
198                 Service *nobj = mirror.getMirroredService(
199                     Service::cast(FWReference::getObject(*i1)));
200                 if (nobj->getParent() == NULL)
201                     compiler->persistent_objects->add(nobj, false);
202                 nsrv->addRef(nobj);
203             }
204         }
205 
206         tmp_queue.push_back(r);
207     }
208     tmp_queue.push_back(rule);
209     return true;
210 }
211 
processNext()212 bool PolicyCompiler_junosacl::SpecialServices::processNext()
213 {
214     //PolicyCompiler_iosacl *iosacl_comp=dynamic_cast<PolicyCompiler_iosacl*>(compiler);
215     PolicyRule *rule=getNext(); if (rule==NULL) return false;
216     Service *s = compiler->getFirstSrv(rule);
217 
218     if (IPService::cast(s)!=NULL)
219     {
220 	if (s->getBool("rr")        ||
221 	    s->getBool("ssrr")      ||
222 	    s->getBool("ts") )
223 	    compiler->abort(
224                     rule,
225                     "IOS ACL does not support checking for IP options in ACLs.");
226     }
227     if (TCPService::cast(s)!=NULL && TCPService::cast(s)->inspectFlags())
228     {
229         string version = compiler->fw->getStr("version");
230         if (XMLTools::version_compare(version, "12.4")<0)
231             compiler->abort(rule, "TCP flags match requires IOS v12.4 or later.");
232     }
233 
234     tmp_queue.push_back(rule);
235     return true;
236 }
237 
238 /*
239  * This rule processor is used to separate TCP service objects that
240  * match tcp flags when generated config uses object-group clause
241  */
processNext()242 bool PolicyCompiler_junosacl::splitTCPServiceWithFlags::processNext()
243 {
244     PolicyRule *rule=getNext(); if (rule==NULL) return false;
245     RuleElementSrv *srv = rule->getSrv();
246 
247     if (srv->size() > 1)
248     {
249         std::list<FWObject*> cl;
250         for (list<FWObject*>::iterator i1=srv->begin(); i1!=srv->end(); ++i1)
251         {
252             FWObject *o   = *i1;
253             FWObject *obj = NULL;
254             if (FWReference::cast(o)!=NULL) obj=FWReference::cast(o)->getPointer();
255             Service *s=Service::cast(obj);
256             assert(s!=NULL);
257 
258             TCPService *tcp_srv = TCPService::cast(s);
259             if (tcp_srv && (tcp_srv->inspectFlags() || tcp_srv->getEstablished()))
260                 cl.push_back(s);
261         }
262 
263         while (!cl.empty())
264         {
265             PolicyRule  *r = compiler->dbcopy->createPolicyRule();
266             compiler->temp_ruleset->add(r);
267             r->duplicate(rule);
268 
269             RuleElementSrv *nsrv = r->getSrv();
270             nsrv->clearChildren();
271             nsrv->addRef( cl.front() );
272             tmp_queue.push_back(r);
273 
274             srv->removeRef( cl.front() );
275             cl.pop_front();
276         }
277         if (srv->size()>0) tmp_queue.push_back(rule);
278 
279     } else
280         tmp_queue.push_back(rule);
281 
282     return true;
283 }
284 
processNext()285 bool PolicyCompiler_junosacl::checkIPv4FragmentService::processNext()
286 {
287     PolicyRule *rule=getNext(); if (rule==NULL) return false;
288     RuleElementSrv *srv = rule->getSrv();
289 
290     if (srv->size() > 1)
291     {
292         CustomService *fragment_srv = NULL;
293         for (list<FWObject*>::iterator i1=srv->begin(); i1!=srv->end(); ++i1)
294         {
295             FWObject *o   = *i1;
296             FWObject *obj = NULL;
297             if (FWReference::cast(o)!=NULL) obj=FWReference::cast(o)->getPointer();
298             Service *s=Service::cast(obj);
299             assert(s!=NULL);
300 
301             CustomService *custom_srv = CustomService::cast(s);
302             if (custom_srv && (!custom_srv->getCodeForPlatform(compiler->myPlatformName()).substr(0, 15).compare("fragment-offset")) ) {
303                 if (!fragment_srv) {
304                     fragment_srv = custom_srv;
305                 } else {
306                     if (fragment_srv->getId() != custom_srv->getId())
307                         compiler->abort(
308                                     rule,
309                                     "You have contradicting IPv4 fragmentation services in the same rule.");
310                 }
311             }
312         }
313     }
314 
315     tmp_queue.push_back(rule);
316 
317     return true;
318 }
319 
320 
compile()321 void PolicyCompiler_junosacl::compile()
322 {
323     string banner = " Compiling ruleset " + getSourceRuleSet()->getName();
324     if (ipv6) banner += ", IPv6";
325     info(banner);
326 
327     string version = fw->getStr("version");
328     bool supports_object_groups = XMLTools::version_compare(version, "12.4")>=0 &&
329         fw->getOptionsObject()->getBool("iosacl_use_object_groups") && ! ipv6;
330     Q_UNUSED(supports_object_groups);
331 
332     string vers = fw->getStr("version");
333     string platform = fw->getStr("platform");
334 
335     Compiler::compile();
336 
337     if ( fw->getOptionsObject()->getBool ("check_shading") &&
338          ! inSingleRuleCompileMode())
339     {
340         add( new Begin("Detecting rule shadowing"               ) );
341         add( new printTotalNumberOfRules());
342 
343         add( new ItfNegation("process negation in Itf"  ) );
344         add( new InterfacePolicyRules(
345                  "process interface policy rules and store interface ids"));
346 
347         add( new recursiveGroupsInSrc("check for recursive groups in SRC"));
348         add( new recursiveGroupsInDst("check for recursive groups in DST"));
349         add( new recursiveGroupsInSrv("check for recursive groups in SRV"));
350 
351         add( new emptyGroupsInSrc( "check for empty groups in SRC" ) );
352         add( new emptyGroupsInDst( "check for empty groups in DST" ) );
353         add( new emptyGroupsInSrv( "check for empty groups in SRV" ) );
354 
355         add( new ExpandGroups("expand groups"));
356         add( new dropRuleWithEmptyRE(
357                  "drop rules with empty rule elements"));
358         add( new eliminateDuplicatesInSRC("eliminate duplicates in SRC"));
359         add( new eliminateDuplicatesInDST("eliminate duplicates in DST"));
360         add( new eliminateDuplicatesInSRV("eliminate duplicates in SRV"));
361 
362         add( new checkIPv4FragmentService("Avoid contradiction IPv4 fragmentation services"));
363 
364         //add( new ExpandMultipleAddressesInSrc(
365         //         "expand objects with multiple addresses in SRC" ) );
366         //add( new ExpandMultipleAddressesInDst(
367         //         "expand objects with multiple addresses in DST" ) );
368         add( new dropRuleWithEmptyRE(
369                  "drop rules with empty rule elements"));
370 
371         add( new mirrorRule("Add mirrored rules"));
372 
373         //add( new ConvertToAtomic("convert to atomic rules"       ) );
374 
375         add( new checkForObjectsWithErrors(
376                  "check if we have objects with errors in rule elements"));
377 
378         add( new DetectShadowing("Detect shadowing"              ) );
379         add( new simplePrintProgress() );
380 
381         runRuleProcessors();
382         deleteRuleProcessors();
383     }
384 
385 
386     add( new Begin (" Start processing rules" ) );
387     add( new printTotalNumberOfRules ( ) );
388 
389 
390     add( new singleRuleFilter());
391 
392     add( new recursiveGroupsInSrc( "check for recursive groups in SRC" ) );
393     add( new recursiveGroupsInDst( "check for recursive groups in DST" ) );
394     add( new recursiveGroupsInSrv( "check for recursive groups in SRV" ) );
395 
396     add( new emptyGroupsInSrc( "check for empty groups in SRC" ) );
397     add( new emptyGroupsInDst( "check for empty groups in DST" ) );
398     add( new emptyGroupsInSrv( "check for empty groups in SRV" ) );
399 
400     add( new ExpandGroups ("expand groups" ) );
401     add( new dropRuleWithEmptyRE(
402              "drop rules with empty rule elements"));
403     add( new eliminateDuplicatesInSRC( "eliminate duplicates in SRC" ) );
404     add( new eliminateDuplicatesInDST( "eliminate duplicates in DST" ) );
405     add( new eliminateDuplicatesInSRV( "eliminate duplicates in SRV" ) );
406 
407     add( new checkIPv4FragmentService("Avoid contradiction IPv4 fragmentation services"));
408 
409     // TODO: fix processMultiAddressObjects
410 //    add( new processMultiAddressObjectsInSrc(
411 //             "process MultiAddress objects in Src") );
412 //    add( new processMultiAddressObjectsInDst(
413 //             "process MultiAddress objects in Dst") );
414 
415     add( new expandGroupsInItf("expand groups in Interface" ));
416 
417     add( new replaceClusterInterfaceInItf(
418              "replace cluster interfaces with member interfaces in the Interface rule element"));
419 
420     add( new ItfNegation( "process negation in Itf" ) );
421 
422     // TODO: does this function do what we want it to do?
423     add( new InterfacePolicyRules(
424              "process interface policy rules and store interface ids") );
425 
426     add( new groupServicesByProtocol ("split rules with different protocols" ) );
427 
428     add( new ExpandMultipleAddressesInSrc(
429              "expand objects with multiple addresses in SRC" ) );
430 //    add( new MACFiltering ("check for MAC address filtering" ) );
431 ////        add( new splitByNetworkZonesForSrc ("split rule if objects in Src belong to different network zones " ) );
432 ////        add( new replaceFWinDSTPolicy ("replace fw with its interface in DST in global policy rules") );
433 
434     add( new ExpandMultipleAddressesInDst(
435              "expand objects with multiple addresses in DST" ) );
436 //    add( new MACFiltering(
437 //             "check for MAC address filtering" ) );
438     add( new dropRuleWithEmptyRE("drop rules with empty rule elements"));
439 
440 ////        add( new splitByNetworkZonesForDst ("split rule if objects in Dst belong to different network zones " ) );
441 
442     if (ipv6)
443         add( new DropIPv4Rules("drop ipv4 rules"));
444     else
445         add( new DropIPv6Rules("drop ipv6 rules"));
446     add( new dropRuleWithEmptyRE("drop rules with empty rule elements"));
447 
448     add( new checkForUnnumbered("check for unnumbered interfaces"));
449 
450     add( new separateSrcAndDstPort("check for services with both src and dst port specified"));
451 
452     add( new separateSrcPort("split services with src port specified"));
453 
454 //    if ( ! supports_object_groups)
455 //        add( new addressRanges("process address ranges"));
456 
457 //    add( new mirrorRule("Add mirrored rules"));
458 
459 //    add( new dropRuleWithEmptyRE("drop rules with empty rule elements"));
460 
461 //    add( new setInterfaceAndDirectionBySrc(
462 //             "Set interface and direction for rules with interface 'all' using SRC"));
463 //    add( new setInterfaceAndDirectionByDst(
464 //             "Set interface and direction for rules with interface 'all' using DST"));
465 //    add( new setInterfaceAndDirectionIfInterfaceSet(
466 //             "Set direction for rules with interface not 'all'"));
467 
468 //    add( new specialCaseWithDynInterface(
469 //             "check for a special cases with dynamic interface" ) );
470 
471     // first arg is true because we use "ip access-list" for IOS.
472 //    add( new ConvertToAtomic ("convert to atomic rules" ) );
473 
474     add( new ValidateInterfaceUnitName("validate interface unit name") );
475 
476    add( new pickACL( true, "assign ACLs" ) );
477 
478 //    add( new SpecialServices( "check for special services" ) );
479 //    add( new CheckForUnsupportedUserService("check for user service") );
480 
481 //    add( new checkForZeroAddr(    "check for zero addresses" ) );
482 //    add( new checkForDynamicInterface("check for dynamic interfaces" ) );
483 
484 //    /* remove redundant objects only after all splits has been
485 //     * done, right before object groups are created
486 //     */
487 //    add( new removeRedundantAddressesFromSrc(
488 //             "remove redundant addresses from Src") );
489 //    add( new removeRedundantAddressesFromDst(
490 //             "remove redundant addresses from Dst") );
491 
492 //    add( new checkForObjectsWithErrors(
493 //             "check if we have objects with errors in rule elements"));
494 
495 //    if (supports_object_groups)
496 //    {
497 //        // "object-group service" does not seem to support
498 //        // matching of tcp flags and "established". Need to
499 //        // separate objects using these into separate rules to avoid
500 //        // object-group
501 
502 //        add( new splitTCPServiceWithFlags(
503 //                 "separate TCP service with tcp flags"));
504 
505 //        add( new CreateObjectGroupsForSrc("create object groups for Src",
506 //                                          named_objects_manager));
507 //        add( new CreateObjectGroupsForDst("create object groups for Dst",
508 //                                          named_objects_manager));
509 //        add( new CreateObjectGroupsForSrv("create object groups for Srv",
510 //                                          named_objects_manager));
511 //    } else
512 //    {
513 //        add( new ConvertToAtomic ("convert to atomic rules" ) );
514 //    }
515 
516 //    add( new simplePrintProgress());
517 //    add( new createNewCompilerPass("Creating object groups and ACLs"));
518 
519     // This processor prints each ACL separately in one block.
520     // It adds comments inside to denote original rules.
521     //
522     add( new PrintCompleteACLs("Print ACLs"));
523     add( new simplePrintProgress());
524 
525     runRuleProcessors();
526 
527 }
528 
printAccessGroupCmd(ciscoACL * acl,bool neg)529 string PolicyCompiler_junosacl::printAccessGroupCmd(ciscoACL *acl, bool neg)
530 {
531     (void) neg; // Unused
532 
533     ostringstream str;
534 
535     string addr_family_prefix = "inet";
536     if (ipv6) addr_family_prefix = "inet6";
537 
538     if (getSourceRuleSet()->isTop())
539     {
540         string dir;
541         if (acl->direction()=="in"  || acl->direction()=="Inbound")  dir="input";
542         if (acl->direction()=="out" || acl->direction()=="Outbound") dir="output";
543 
544         str << "interfaces {\n";
545         str << "    " << acl->getInterface()->getParent()->getName() << " {\n";
546         str << "        " << acl->getInterface()->getName() << " {\n";
547         str << "            family " << addr_family_prefix << " {\n";
548         str << "                filter {\n";
549 
550         string filter_prefix = fw->getOptionsObject()->getStr("filter_prefix");
551         if (filter_prefix.empty()) filter_prefix = "fwbfilter";
552         filter_prefix += "_";
553 
554         str << "                    " << dir << " " << filter_prefix << acl->workName() << ";\n";
555         str << "                }\n";
556         str << "            }\n";
557         str << "        }\n";
558         str << "    }\n";
559         str << "}\n";
560         /*
561 
562         str << "interface " << acl->getInterface()->getName() << endl;
563         if (neg) str << "  no";
564         str << "  " << addr_family_prefix << " ";
565         str << getAccessGroupCommandForAddressFamily(ipv6);
566         str << " " << acl->workName() << " " << dir << endl;
567         str << "exit" << endl;
568         */
569     }
570     return str.str();
571 }
572 
epilog()573 void PolicyCompiler_junosacl::epilog()
574 {
575     output << endl;
576 
577     // output << "Epilog, acls size: " << acls.size() << endl;
578 
579     for (map<string,ciscoACL*>::iterator i=acls.begin(); i!=acls.end(); ++i)
580     {
581         ciscoACL *acl=(*i).second;
582         if (acl->size()!=0) output << printAccessGroupCmd(acl, false);
583     }
584     output << endl;
585 
586     if ( fw->getOptionsObject()->getBool("iosacl_regroup_commands") )
587     {
588         info(" Regrouping commands");
589         regroup();
590     }
591 }
592 
getAccessGroupCommandForAddressFamily(bool ipv6)593 string PolicyCompiler_junosacl::getAccessGroupCommandForAddressFamily(bool ipv6)
594 {
595     if (ipv6) return "traffic-filter";
596     return "access-group";
597 }
598 
printClearCommands()599 string PolicyCompiler_junosacl::printClearCommands()
600 {
601     ostringstream output;
602 
603     string version = fw->getStr("version");
604     string platform = fw->getStr("platform");
605 
606     string xml_element = "clear_ip_acl";
607     if (ipv6) xml_element = "clear_ipv6_acl";
608 
609     string clearACLCmd = Resources::platform_res[platform]->getResourceStr(
610         string("/FWBuilderResources/Target/options/") +
611         "version_" + version + "/iosacl_commands/" + xml_element);
612 
613     assert( !clearACLCmd.empty());
614 
615     // No need to output "clear" commands in single rule compile mode
616     if ( fw->getOptionsObject()->getBool("iosacl_acl_basic") ||
617          fw->getOptionsObject()->getBool("iosacl_acl_substitution"))
618     {
619         for (map<string,ciscoACL*>::iterator i=acls.begin(); i!=acls.end(); ++i)
620         {
621             ciscoACL *acl = (*i).second;
622             output << clearACLCmd << " " << acl->workName() << endl;
623         }
624     }
625 
626     return output.str();
627 }
628 
629