]> git.kernelconcepts.de Git - karo-tx-redboot.git/blob - packages/net/snmp/agent/v2_0/src/agent_trap.c
Initial revision
[karo-tx-redboot.git] / packages / net / snmp / agent / v2_0 / src / agent_trap.c
1 //==========================================================================
2 //
3 //      ./agent/current/src/agent_trap.c
4 //
5 //
6 //==========================================================================
7 //####ECOSGPLCOPYRIGHTBEGIN####
8 // -------------------------------------------
9 // This file is part of eCos, the Embedded Configurable Operating System.
10 // Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
11 //
12 // eCos is free software; you can redistribute it and/or modify it under
13 // the terms of the GNU General Public License as published by the Free
14 // Software Foundation; either version 2 or (at your option) any later version.
15 //
16 // eCos is distributed in the hope that it will be useful, but WITHOUT ANY
17 // WARRANTY; without even the implied warranty of MERCHANTABILITY or
18 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
19 // for more details.
20 //
21 // You should have received a copy of the GNU General Public License along
22 // with eCos; if not, write to the Free Software Foundation, Inc.,
23 // 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
24 //
25 // As a special exception, if other files instantiate templates or use macros
26 // or inline functions from this file, or you compile this file and link it
27 // with other works to produce a work based on this file, this file does not
28 // by itself cause the resulting work to be covered by the GNU General Public
29 // License. However the source code for this file must still be made available
30 // in accordance with section (3) of the GNU General Public License.
31 //
32 // This exception does not invalidate any other reasons why a work based on
33 // this file might be covered by the GNU General Public License.
34 //
35 // Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
36 // at http://sources.redhat.com/ecos/ecos-license/
37 // -------------------------------------------
38 //####ECOSGPLCOPYRIGHTEND####
39 //####UCDSNMPCOPYRIGHTBEGIN####
40 //
41 // -------------------------------------------
42 //
43 // Portions of this software may have been derived from the UCD-SNMP
44 // project,  <http://ucd-snmp.ucdavis.edu/>  from the University of
45 // California at Davis, which was originally based on the Carnegie Mellon
46 // University SNMP implementation.  Portions of this software are therefore
47 // covered by the appropriate copyright disclaimers included herein.
48 //
49 // The release used was version 4.1.2 of May 2000.  "ucd-snmp-4.1.2"
50 // -------------------------------------------
51 //
52 //####UCDSNMPCOPYRIGHTEND####
53 //==========================================================================
54 //#####DESCRIPTIONBEGIN####
55 //
56 // Author(s):    hmt
57 // Contributors: hmt
58 // Date:         2000-05-30
59 // Purpose:      Port of UCD-SNMP distribution to eCos.
60 // Description:  
61 //              
62 //
63 //####DESCRIPTIONEND####
64 //
65 //==========================================================================
66 /********************************************************************
67        Copyright 1989, 1991, 1992 by Carnegie Mellon University
68
69                           Derivative Work -
70 Copyright 1996, 1998, 1999, 2000 The Regents of the University of California
71
72                          All Rights Reserved
73
74 Permission to use, copy, modify and distribute this software and its
75 documentation for any purpose and without fee is hereby granted,
76 provided that the above copyright notice appears in all copies and
77 that both that copyright notice and this permission notice appear in
78 supporting documentation, and that the name of CMU and The Regents of
79 the University of California not be used in advertising or publicity
80 pertaining to distribution of the software without specific written
81 permission.
82
83 CMU AND THE REGENTS OF THE UNIVERSITY OF CALIFORNIA DISCLAIM ALL
84 WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED
85 WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL CMU OR
86 THE REGENTS OF THE UNIVERSITY OF CALIFORNIA BE LIABLE FOR ANY SPECIAL,
87 INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
88 FROM THE LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
89 CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
90 CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
91 *********************************************************************/
92 /* agent_trap.c: define trap generation routines for mib modules, etc,
93    to use */
94
95 #include <config.h>
96
97 #if HAVE_UNISTD_H
98 #include <unistd.h>
99 #endif
100 #if HAVE_STDLIB_H
101 #include <stdlib.h>
102 #endif
103 #if HAVE_STRING_H
104 #include <string.h>
105 #else
106 #include <strings.h>
107 #endif
108 #if TIME_WITH_SYS_TIME
109 # ifdef WIN32
110 #  include <sys/timeb.h>
111 # else
112 #  include <sys/time.h>
113 # endif
114 # include <time.h>
115 #else
116 # if HAVE_SYS_TIME_H
117 #  include <sys/time.h>
118 # else
119 #  include <time.h>
120 # endif
121 #endif
122 #if HAVE_SYS_SOCKET_H
123 #include <sys/socket.h>
124 #elif HAVE_WINSOCK_H
125 #include <winsock.h>
126 #endif
127 #if HAVE_NETINET_IN_H
128 #include <netinet/in.h>
129 #endif
130
131 #include "asn1.h"
132 #include "snmp_api.h"
133 #include "snmp_impl.h"
134 #include "snmp_client.h"
135 #include "snmp.h"
136 #include "system.h"
137 #include "read_config.h"
138 #include "snmp_debug.h"
139
140 struct trap_sink {
141     struct snmp_session *sesp;
142     struct trap_sink    *next;
143     int                 pdutype;
144     int                 version;
145 };
146
147 struct trap_sink *sinks   = NULL;
148
149 extern struct timeval   starttime;
150
151 #define OID_LENGTH(x)  (sizeof(x)/sizeof(x[0]))
152
153 oid objid_enterprisetrap[] = { EXTENSIBLEMIB, 251 };
154 oid version_id[]           = { EXTENSIBLEMIB, AGENTID, OSTYPE };
155 int enterprisetrap_len = OID_LENGTH( objid_enterprisetrap );
156 int version_id_len     = OID_LENGTH( version_id );
157
158 #define SNMPV2_TRAPS_PREFIX     SNMP_OID_SNMPMODULES,1,1,5
159 oid  cold_start_oid[] =         { SNMPV2_TRAPS_PREFIX, 1 };     /* SNMPv2-MIB */
160 oid  warm_start_oid[] =         { SNMPV2_TRAPS_PREFIX, 2 };     /* SNMPv2-MIB */
161 oid  link_down_oid[] =          { SNMPV2_TRAPS_PREFIX, 3 };     /* IF-MIB */
162 oid  link_up_oid[] =            { SNMPV2_TRAPS_PREFIX, 4 };     /* IF-MIB */
163 oid  auth_fail_oid[] =          { SNMPV2_TRAPS_PREFIX, 5 };     /* SNMPv2-MIB */
164 oid  egp_xxx_oid[] =            { SNMPV2_TRAPS_PREFIX, 99 };    /* ??? */
165
166 #define SNMPV2_TRAP_OBJS_PREFIX SNMP_OID_SNMPMODULES,1,1,4
167 oid  snmptrap_oid[]           = { SNMPV2_TRAP_OBJS_PREFIX, 1, 0 };
168 oid  snmptrapenterprise_oid[] = { SNMPV2_TRAP_OBJS_PREFIX, 3, 0 };
169 oid  sysuptime_oid[]          = { SNMP_OID_MIB2,1,3,0 };
170 int  snmptrap_oid_len         = OID_LENGTH(snmptrap_oid);
171 int  snmptrapenterprise_oid_len = OID_LENGTH(snmptrapenterprise_oid);
172 int  sysuptime_oid_len        = OID_LENGTH(sysuptime_oid);
173
174
175 #define SNMP_AUTHENTICATED_TRAPS_ENABLED        1
176 #define SNMP_AUTHENTICATED_TRAPS_DISABLED       2
177
178 int      snmp_enableauthentraps = SNMP_AUTHENTICATED_TRAPS_DISABLED;
179 char    *snmp_trapcommunity     = NULL;
180
181 /* Prototypes */
182  /*
183 static int create_v1_trap_session (const char *, u_short, const char *);
184 static int create_v2_trap_session (const char *, u_short, const char *);
185 static int create_v2_inform_session (const char *, u_short, const char *);
186 static void free_trap_session (struct trap_sink *sp);
187 static void send_v1_trap (struct snmp_session *, int, int);
188 static void send_v2_trap (struct snmp_session *, int, int, int);
189  */
190
191
192         /*******************
193          *
194          * Trap session handling
195          *
196          *******************/
197 int add_trap_session( struct snmp_session *ss, int pdutype, int version )
198 {
199     struct trap_sink *new_sink =
200       (struct trap_sink *) malloc (sizeof (*new_sink));
201     if ( new_sink == NULL )
202         return 0;
203
204     new_sink->sesp    = ss;
205     new_sink->pdutype = pdutype;
206     new_sink->version = version;
207     new_sink->next    = sinks;
208     sinks = new_sink;
209     return 1;
210 }
211
212 int create_trap_session (char *sink, u_short sinkport,
213                                 char *com,
214                                 int version, int pdutype)
215 {
216     struct snmp_session  session, *sesp;
217
218     memset (&session, 0, sizeof (struct snmp_session));
219     session.peername = sink;
220     session.version = version;
221     if (com) {
222         session.community = (u_char *)com;
223         session.community_len = strlen (com);
224     }
225     session.remote_port = sinkport;
226     sesp = snmp_open (&session);
227
228     if (sesp) {
229         return( add_trap_session( sesp, pdutype, version ));
230     }
231
232     /* diagnose snmp_open errors with the input struct snmp_session pointer */
233     snmp_sess_perror("snmpd: create_trap_session", &session);
234     return 0;
235 }
236
237 static int create_v1_trap_session (char *sink, u_short sinkport,
238                                    char *com)
239 {
240     return create_trap_session( sink, sinkport, com,
241                                 SNMP_VERSION_1, SNMP_MSG_TRAP );
242 }
243
244 static int create_v2_trap_session (char *sink,  u_short sinkport,
245                                    char *com)
246 {
247     return create_trap_session( sink, sinkport, com,
248                                 SNMP_VERSION_2c, SNMP_MSG_TRAP2 );
249 }
250
251 static int create_v2_inform_session (char *sink,  u_short sinkport,
252                                      char *com)
253 {
254     return create_trap_session( sink, sinkport, com,
255                                 SNMP_VERSION_2c, SNMP_MSG_INFORM );
256 }
257
258
259 static void free_trap_session (struct trap_sink *sp)
260 {
261     snmp_close(sp->sesp);
262     free (sp);
263 }
264
265
266 void snmpd_free_trapsinks (void)
267 {
268     struct trap_sink *sp = sinks;
269     while (sp) {
270         sinks = sinks->next;
271         free_trap_session(sp);
272         sp = sinks;
273     }
274 }
275
276         /*******************
277          *
278          * Trap handling
279          *
280          *******************/
281
282 void send_enterprise_trap_vars (int trap, 
283                      int specific,
284                      oid *enterprise, int enterprise_length,
285                      struct variable_list *vars)
286 {
287     struct variable_list uptime_var, snmptrap_var, enterprise_var;
288     struct variable_list *v2_vars, *last_var=NULL;
289     struct snmp_pdu     *template_pdu, *pdu;
290     struct timeval       now;
291     long uptime;
292     struct sockaddr_in *pduIp;
293     struct trap_sink *sink;
294     oid temp_oid[MAX_OID_LEN];
295     
296                 /*
297                  * Initialise SNMPv2 required variables
298                  */
299     gettimeofday(&now, NULL);
300     uptime = calculate_time_diff(&now, &starttime);
301     memset (&uptime_var, 0, sizeof (struct variable_list));
302     snmp_set_var_objid( &uptime_var, sysuptime_oid, OID_LENGTH(sysuptime_oid));
303     snmp_set_var_value( &uptime_var, (u_char *)&uptime, sizeof(uptime) );
304     uptime_var.type           = ASN_TIMETICKS;
305     uptime_var.next_variable  = &snmptrap_var;
306
307     memset (&snmptrap_var, 0, sizeof (struct variable_list));
308     snmp_set_var_objid( &snmptrap_var, snmptrap_oid, OID_LENGTH(snmptrap_oid));
309         /* value set later .... */
310     snmptrap_var.type           = ASN_OBJECT_ID;
311     if ( vars )
312         snmptrap_var.next_variable  = vars;
313     else
314         snmptrap_var.next_variable  = &enterprise_var;
315
316                         /* find end of provided varbind list,
317                            ready to append the enterprise info if necessary */
318     last_var = vars;
319     while ( last_var && last_var->next_variable )
320         last_var = last_var->next_variable;
321
322     memset (&enterprise_var, 0, sizeof (struct variable_list));
323     snmp_set_var_objid( &enterprise_var,
324                  snmptrapenterprise_oid, OID_LENGTH(snmptrapenterprise_oid));
325     snmp_set_var_value( &enterprise_var, (u_char *)enterprise, enterprise_length*sizeof(oid));
326     enterprise_var.type           = ASN_OBJECT_ID;
327     enterprise_var.next_variable  = NULL;
328
329     v2_vars = &uptime_var;
330
331                 /*
332                  *  Create a template PDU, ready for sending
333                  */
334     template_pdu = snmp_pdu_create( SNMP_MSG_TRAP );
335     if ( template_pdu == NULL ) {
336                 /* Free memory if value stored dynamically */
337         snmp_set_var_value( &enterprise_var, NULL, 0);
338         return;
339     }
340     template_pdu->trap_type     = trap;
341     template_pdu->specific_type = specific;
342     if ( snmp_clone_mem((void **)&template_pdu->enterprise,
343                                 enterprise, enterprise_length*sizeof(oid))) {
344         snmp_free_pdu( template_pdu );
345         snmp_set_var_value( &enterprise_var, NULL, 0);
346         return;
347     }
348     template_pdu->enterprise_length = enterprise_length;
349     template_pdu->flags |= UCD_MSG_FLAG_FORCE_PDU_COPY;
350     pduIp = (struct sockaddr_in *)&template_pdu->agent_addr;
351     pduIp->sin_family            = AF_INET;
352     pduIp->sin_len               = sizeof(*pduIp);
353     pduIp->sin_addr.s_addr       = get_myaddr();
354     template_pdu->time                   = uptime;
355
356                 /*
357                  *  Now use the parameters to determine
358                  *    which v2 variables are needed,
359                  *    and what values they should take.
360                  */
361     switch ( trap ) {
362         case -1:        /*
363                          *      SNMPv2 only
364                          *  Check to see whether the variables provided
365                          *    are sufficient for SNMPv2 notifications
366                          */
367                 if (vars && snmp_oid_compare(vars->name, vars->name_length,
368                                 sysuptime_oid, OID_LENGTH(sysuptime_oid)) == 0 )
369                         v2_vars = vars;
370                 else
371                 if (vars && snmp_oid_compare(vars->name, vars->name_length,
372                                 snmptrap_oid, OID_LENGTH(snmptrap_oid)) == 0 )
373                         uptime_var.next_variable = vars;
374                 else {
375                         /* Hmmm... we don't seem to have a value - oops! */
376                         snmptrap_var.next_variable = vars;
377                 }
378                 last_var = NULL;        /* Don't need enterprise info */
379                 break;
380
381                         /* "Standard" SNMPv1 traps */
382
383         case SNMP_TRAP_COLDSTART:
384                 snmp_set_var_value( &snmptrap_var,
385                                     (u_char *)cold_start_oid,
386                                     sizeof(cold_start_oid));
387                 break;
388         case SNMP_TRAP_WARMSTART:
389                 snmp_set_var_value( &snmptrap_var,
390                                     (u_char *)warm_start_oid,
391                                     sizeof(warm_start_oid));
392                 break;
393         case SNMP_TRAP_LINKDOWN:
394                 snmp_set_var_value( &snmptrap_var,
395                                     (u_char *)link_down_oid,
396                                     sizeof(link_down_oid));
397                 break;
398         case SNMP_TRAP_LINKUP:
399                 snmp_set_var_value( &snmptrap_var,
400                                     (u_char *)link_up_oid,
401                                     sizeof(link_up_oid));
402                 break;
403         case SNMP_TRAP_AUTHFAIL:
404                 if (snmp_enableauthentraps == SNMP_AUTHENTICATED_TRAPS_DISABLED) {
405                     snmp_free_pdu( template_pdu );
406                     snmp_set_var_value( &enterprise_var, NULL, 0);
407                     return;
408                 }
409                 snmp_set_var_value( &snmptrap_var,
410                                     (u_char *)auth_fail_oid,
411                                     sizeof(auth_fail_oid));
412                 break;
413         case SNMP_TRAP_EGPNEIGHBORLOSS:
414                 snmp_set_var_value( &snmptrap_var,
415                                     (u_char *)egp_xxx_oid,
416                                     sizeof(egp_xxx_oid));
417                 break;
418
419         case SNMP_TRAP_ENTERPRISESPECIFIC:
420                 memcpy( &temp_oid,
421                                     (char *)enterprise,
422                                     (enterprise_length)*sizeof(oid));
423                 temp_oid[ enterprise_length   ] = 0;
424                 temp_oid[ enterprise_length+1 ] = specific;
425                 snmp_set_var_value( &snmptrap_var,
426                                     (u_char *)&temp_oid,
427                                     (enterprise_length+2)*sizeof(oid));
428                 snmptrap_var.next_variable  = vars;
429                 last_var = NULL;        /* Don't need version info */
430                 break;
431     }
432     
433
434                 /*
435                  *  Now loop through the list of trap sinks,
436                  *   sending an appropriately formatted PDU to each
437                  */
438     for ( sink = sinks ; sink ; sink=sink->next ) {
439         if ( sink->version == SNMP_VERSION_1 && trap == -1 )
440                 continue;       /* Skip v1 sinks for v2 only traps */
441         template_pdu->version = sink->version;
442         template_pdu->command = sink->pdutype;
443         if ( sink->version != SNMP_VERSION_1 ) {
444             template_pdu->variables = v2_vars;
445             if ( last_var )
446                 last_var->next_variable = &enterprise_var;
447         }
448         else
449             template_pdu->variables = vars;
450
451         pdu = snmp_clone_pdu( template_pdu );
452         pdu->sessid = sink->sesp->sessid;       /* AgentX only ? */
453         if ( snmp_send( sink->sesp, pdu) == 0 ) {
454             snmp_sess_perror ("snmpd: send_trap", sink->sesp);
455             snmp_free_pdu( pdu );
456         }
457         else {
458             snmp_increment_statistic(STAT_SNMPOUTTRAPS);
459             snmp_increment_statistic(STAT_SNMPOUTPKTS);
460         }
461                 
462         if ( sink->version != SNMP_VERSION_1 && last_var )
463             last_var->next_variable = NULL;
464     }
465
466                 /* Free memory if values stored dynamically */
467     snmp_set_var_value( &enterprise_var, NULL, 0);
468     snmp_set_var_value( &snmptrap_var, NULL, 0);
469         /* Ensure we don't free anything we shouldn't */
470     if ( last_var )
471         last_var->next_variable = NULL;
472     template_pdu->variables = NULL;
473     snmp_free_pdu( template_pdu );
474 }
475
476 void send_trap_vars (int trap, 
477                      int specific,
478                      struct variable_list *vars)
479 {
480     if ( trap == SNMP_TRAP_ENTERPRISESPECIFIC )
481         send_enterprise_trap_vars( trap, specific, objid_enterprisetrap,
482                         OID_LENGTH(objid_enterprisetrap), vars );
483     else
484         send_enterprise_trap_vars( trap, specific, version_id,
485                         OID_LENGTH(version_id), vars );
486 }
487
488 void send_easy_trap (int trap, 
489                      int specific)
490 {
491     send_trap_vars( trap, specific, NULL );
492 }
493
494 void send_v2trap ( struct variable_list *vars)
495 {
496     send_trap_vars( -1, -1, vars );
497 }
498
499 void
500 send_trap_pdu(struct snmp_pdu *pdu)
501 {
502     send_trap_vars( -1, -1, pdu->variables );
503 }
504
505
506
507         /*******************
508          *
509          * Config file handling
510          *
511          *******************/
512
513 void snmpd_parse_config_authtrap(const char *token, 
514                                  char *cptr)
515 {
516     int i;
517
518     i = atoi(cptr);
519     if ( i == 0 ) {
520         if ( !strcmp( cptr, "enable" ))
521             i = SNMP_AUTHENTICATED_TRAPS_ENABLED;
522         else if ( !strcmp( cptr, "disable" ))
523             i = SNMP_AUTHENTICATED_TRAPS_DISABLED;
524     }
525     if (i < 1 || i > 2)
526         config_perror("authtrapenable must be 1 or 2");
527     else
528         snmp_enableauthentraps = i;
529 }
530
531 void snmpd_parse_config_trapsink(const char *token, 
532                                  char *cptr)
533 {
534     char tmpbuf[1024];
535     char *sp, *cp, *pp = NULL;
536     u_short sinkport;
537     
538     if (!snmp_trapcommunity) snmp_trapcommunity = strdup("public");
539     sp = strtok(cptr, " \t\n");
540     cp = strtok(NULL, " \t\n");
541     if (cp) pp = strtok(NULL, " \t\n");
542     if (cp && pp) {
543         sinkport = atoi(pp);
544         if ((sinkport < 1) || (sinkport > 0xffff)) {
545             config_perror("trapsink port out of range");
546             sinkport = SNMP_TRAP_PORT;
547         }
548     } else {
549         sinkport = SNMP_TRAP_PORT;
550     }
551     if (create_v1_trap_session(sp, sinkport,
552                                cp ? cp : snmp_trapcommunity) == 0) {
553         sprintf(tmpbuf,"cannot create trapsink: %s", cptr);
554         config_perror(tmpbuf);
555     }
556 }
557
558
559 void
560 snmpd_parse_config_trap2sink(const char *word, char *cptr)
561 {
562     char tmpbuf[1024];
563     char *sp, *cp, *pp = NULL;
564     u_short sinkport;
565   
566     if (!snmp_trapcommunity) snmp_trapcommunity = strdup("public");
567     sp = strtok(cptr, " \t\n");
568     cp = strtok(NULL, " \t\n");
569     if (cp) pp = strtok(NULL, " \t\n");
570     if (cp && pp) {
571         sinkport = atoi(pp);
572         if ((sinkport < 1) || (sinkport > 0xffff)) {
573             config_perror("trapsink port out of range");
574             sinkport = SNMP_TRAP_PORT;
575         }
576     } else {
577         sinkport = SNMP_TRAP_PORT;
578     }
579     if (create_v2_trap_session(sp, sinkport,
580                                cp ? cp : snmp_trapcommunity) == 0) {
581         sprintf(tmpbuf,"cannot create trap2sink: %s", cptr);
582         config_perror(tmpbuf);
583     }
584 }
585
586 void
587 snmpd_parse_config_informsink(const char *word, char *cptr)
588 {
589     char tmpbuf[1024];
590     char *sp, *cp, *pp = NULL;
591     u_short sinkport;
592   
593     if (!snmp_trapcommunity) snmp_trapcommunity = strdup("public");
594     sp = strtok(cptr, " \t\n");
595     cp = strtok(NULL, " \t\n");
596     if (cp) pp = strtok(NULL, " \t\n");
597     if (cp && pp) {
598         sinkport = atoi(pp);
599         if ((sinkport < 1) || (sinkport > 0xffff)) {
600             config_perror("trapsink port out of range");
601             sinkport = SNMP_TRAP_PORT;
602         }
603     } else {
604         sinkport = SNMP_TRAP_PORT;
605     }
606     if (create_v2_inform_session(sp, sinkport,
607                                  cp ? cp : snmp_trapcommunity) == 0) {
608         sprintf(tmpbuf,"cannot create informsink: %s", cptr);
609         config_perror(tmpbuf);
610     }
611 }
612
613 void
614 snmpd_parse_config_trapcommunity(const char *word, char *cptr)
615 {
616     if (snmp_trapcommunity) free(snmp_trapcommunity);
617     snmp_trapcommunity = malloc (strlen(cptr)+1);
618     copy_word(cptr, snmp_trapcommunity);
619 }
620
621 void snmpd_free_trapcommunity (void)
622 {
623     if (snmp_trapcommunity) {
624         free(snmp_trapcommunity);
625         snmp_trapcommunity = NULL;
626     }
627 }