1 martin 1.28 //%LICENSE////////////////////////////////////////////////////////////////
|
2 martin 1.29 //
|
3 martin 1.28 // Licensed to The Open Group (TOG) under one or more contributor license
4 // agreements. Refer to the OpenPegasusNOTICE.txt file distributed with
5 // this work for additional information regarding copyright ownership.
6 // Each contributor licenses this file to you under the OpenPegasus Open
7 // Source License; you may not use this file except in compliance with the
8 // License.
|
9 martin 1.29 //
|
10 martin 1.28 // Permission is hereby granted, free of charge, to any person obtaining a
11 // copy of this software and associated documentation files (the "Software"),
12 // to deal in the Software without restriction, including without limitation
13 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 // and/or sell copies of the Software, and to permit persons to whom the
15 // Software is furnished to do so, subject to the following conditions:
|
16 martin 1.29 //
|
17 martin 1.28 // The above copyright notice and this permission notice shall be included
18 // in all copies or substantial portions of the Software.
|
19 martin 1.29 //
|
20 martin 1.28 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
21 martin 1.29 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22 martin 1.28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27 martin 1.29 //
|
28 martin 1.28 //////////////////////////////////////////////////////////////////////////
|
29 mike 1.2 //
30 //%/////////////////////////////////////////////////////////////////////////////
31
|
32 kumpf 1.3 #include <Pegasus/Common/System.h>
|
33 kumpf 1.12 #include <Pegasus/Common/ArrayInternal.h>
34 #include <Pegasus/Common/InternalException.h>
|
35 mike 1.2 #include "snmpDeliverTrap_emanate.h"
|
36 kumpf 1.12
|
37 humberto 1.14 #include <Pegasus/Common/MessageLoader.h>
38
|
39 kumpf 1.12 // EMANATE specific declarations and entry points - MUST be in begining
40 // and outside NAMESPACE.
41
42 // master agent needs these two declarations for communication with sub-agent.
|
43 kumpf 1.24 // Following two declarations must be in the CODE.
|
44 kumpf 1.12
45 #include <prnt_lib.h> // MUST be at the end in include list.
|
46 mike 1.2
|
47 kumpf 1.24 static const char* sr_filename = __FILE__;
|
48 kumpf 1.3
|
49 kumpf 1.24 IPCFunctionP IPCfp; /* IPC functions pointer */
|
50 kumpf 1.3
|
51 kumpf 1.24 // This code would normally be generated by Emanate from defined MIB objects.
52 // Since we do not have MIB objects defined, just defined here to load
53 // subagent as library. OidList[] provides objects for entry point to master
54 // agent.
|
55 kumpf 1.3
|
56 kumpf 1.24 // The objects internal to the agent
57 ObjectInfo OidList[] =
|
58 kumpf 1.3 {
|
59 kumpf 1.24 {
60 { 0, NULL },
|
61 kumpf 1.3 #ifndef LIGHT
|
62 kumpf 1.24 NULL,
|
63 kumpf 1.3 #endif /* LIGHT */
|
64 kumpf 1.24 0, 0, 0, 0,
65 NULL, NULL
66 }
|
67 kumpf 1.3 };
68
|
69 kumpf 1.24 // This code would normally be generated by Emanate in k_* routine from
70 // defined MIB objects. Since we do not have MIB objects defined, just
71 // define here to pass compile and enable entry point for master agent to
72 // start communication with library.
|
73 kumpf 1.3
74 // Called by the master agent during initialization */
75 int k_initialize()
76 {
|
77 kumpf 1.24 return 1;
|
78 kumpf 1.3 }
|
79 kumpf 1.9
|
80 kumpf 1.12 // END EMANATE specific declarations.
81
|
82 kumpf 1.9 PEGASUS_NAMESPACE_BEGIN
83
84 PEGASUS_USING_STD;
|
85 kumpf 1.3
86 snmpDeliverTrap_emanate::snmpDeliverTrap_emanate()
87 {
88 }
89
90 snmpDeliverTrap_emanate::~snmpDeliverTrap_emanate()
91 {
92 }
93
|
94 kumpf 1.12
95 // initialize sub-agent
96
97 // This also defines the communication protocol to be used between master
98 // and sub-agent.
99
|
100 kumpf 1.3 void snmpDeliverTrap_emanate::initialize()
101 {
102 #ifndef SR_UDS_IPC
103 InitIPCArrayTCP(&IPCfp);
104 #else /* SR_UDS_IPC */
105 InitIPCArrayUDS(&IPCfp);
106 #endif /* SR_UDS_IPC */
107
|
108 kumpf 1.24 if (InitSubagent() == -1)
|
109 kumpf 1.3 {
|
110 kumpf 1.24 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, MessageLoaderParms(
111 _MSG_INITSUBAGENT_FAILED_KEY,
112 _MSG_INITSUBAGENT_FAILED));
|
113 humberto 1.14
|
114 kumpf 1.3 }
115 }
116
|
117 kumpf 1.23 void snmpDeliverTrap_emanate::terminate()
118 {
119 //
|
120 kumpf 1.24 // Close the connection to the Master Agent and shut down the
|
121 kumpf 1.23 // Subagent
122 //
123 EndSubagent();
124 }
125
|
126 humberto 1.14
|
127 kumpf 1.3 void snmpDeliverTrap_emanate::deliverTrap(
|
128 kumpf 1.12 const String& trapOid,
|
129 kumpf 1.24 const String& securityName,
130 const String& targetHost,
131 const Uint16& targetHostFormat,
132 const String& otherTargetHostFormat,
|
133 kumpf 1.12 const Uint32& portNumber,
|
134 kumpf 1.24 const Uint16& snmpVersion,
|
135 kumpf 1.12 const String& engineID,
|
136 kumpf 1.17 const Array<String>& vbOids,
137 const Array<String>& vbTypes,
138 const Array<String>& vbValues)
|
139 mike 1.2 {
|
140 kumpf 1.24 VarBind* vbhead = NULL;
141 VarBind* vb = NULL;
142 VarBind* vblast = NULL;
143
144 OID* object = NULL;
|
145 kumpf 1.12
146 // Translate a string into an OID
|
147 kumpf 1.24 OID* sendtrapOid = MakeOIDFromDot(trapOid.getCString());
|
148 kumpf 1.12
149 if (sendtrapOid == NULL)
150 {
|
151 kumpf 1.24 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, MessageLoaderParms(
152 _MSG_INVALID_KEY,
153 _MSG_INVALID_TRAPOID));
|
154 kumpf 1.12 }
155
156 // Destination : convert targetHost into Transport
|
157 kumpf 1.5
|
158 kumpf 1.12 CString trap_dest = targetHost.getCString();
|
159 kumpf 1.5
|
160 kumpf 1.24 TransportInfo global_ti;
|
161 kumpf 1.5 global_ti.type = SR_IP_TRANSPORT;
162
|
163 kumpf 1.12 switch (targetHostFormat)
164 {
|
165 kumpf 1.24 case _HOST_NAME:
166 {
167 char* ipAddr = _getIPAddress(trap_dest);
168
169 if (ipAddr == NULL)
170 {
171 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED,
172 MessageLoaderParms(
173 _MSG_DESTINATION_NOT_FOUND_KEY,
174 _MSG_DESTINATION_NOT_FOUND));
175
176 }
177 global_ti.t_ipAddr = inet_addr(trap_dest);
178 break;
179 }
180 case _IPV4_ADDRESS:
181 {
182 global_ti.t_ipAddr = inet_addr(trap_dest);
183 break;
184 }
185 default:
186 kumpf 1.24 {
187 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_NOT_SUPPORTED,
188 MessageLoaderParms(
189 _MSG_TARGETHOSTFORMAT_NOT_SUPPORTED_KEY,
190 _MSG_TARGETHOSTFORMAT_NOT_SUPPORTED));
191 break;
192 }
193 }
|
194 kumpf 1.12
195 global_ti.t_ipPort = htons((unsigned short)portNumber);
196
197 // Community Name, default is public
198 CString _community;
199 if (securityName.size() == 0)
200 {
|
201 kumpf 1.24 String community;
202 community.assign("public");
203 _community = community.getCString();
|
204 kumpf 1.12 }
205 else
206 {
|
207 kumpf 1.24 _community = securityName.getCString();
|
208 kumpf 1.12 }
209
210 OctetString* community_name = MakeOctetStringFromText(_community);
211
212 if (community_name == NULL)
213 {
|
214 kumpf 1.24 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, MessageLoaderParms(
215 _MSG_INVALID_SECURITY_NAME_KEY,
216 _MSG_INVALID_SECURITY_NAME));
|
217 kumpf 1.12 }
|
218 kumpf 1.5
|
219 kumpf 1.3 // getting IP address of the host
|
220 kumpf 1.12
221 CString hostname = System::getHostName().getCString();
222 char* IP_string = _getIPAddress(hostname);
223
|
224 kumpf 1.3 // formatting agent(host) address into OctetString format
|
225 kumpf 1.12
|
226 kumpf 1.3 OctetString* agent_addr;
227
228 SR_INT32 s1, s2, s3, s4;
229 SR_UINT32 ipaddr;
230
231 // pull out each of the 4 octet values from IP address
|
232 kumpf 1.12
|
233 kumpf 1.3 sscanf(IP_string,"%d.%d.%d.%d", &s1, &s2, &s3, &s4);
|
234 kumpf 1.24
|
235 kumpf 1.12 // validate the values for s1, s2, s3, and s4 to make sure values are
|
236 kumpf 1.3 // between 0 and 255
|
237 kumpf 1.12 if (!_isValidOctet(s1) || !_isValidOctet(s2) ||
|
238 kumpf 1.24 !_isValidOctet(s3) || !_isValidOctet(s4))
|
239 kumpf 1.12 {
|
240 kumpf 1.24 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, MessageLoaderParms(
241 _MSG_INVALID_OCTET_VALUE_KEY,
242 _MSG_INVALID_OCTET_VALUE));
243 }
|
244 humberto 1.14
|
245 kumpf 1.3 // create an empty 4 length OctetString
|
246 kumpf 1.12
|
247 kumpf 1.3 agent_addr = MakeOctetString(NULL,4);
|
248 kumpf 1.12
249 if (agent_addr == NULL)
250 {
|
251 kumpf 1.24 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, MessageLoaderParms(
252 _MSG_CREATE_OCTET_FAILED_KEY,
253 _MSG_CREATE_OCTET_FAILED));
254 }
|
255 humberto 1.14
|
256 kumpf 1.3 // fill in values for OctetString
|
257 kumpf 1.12
|
258 kumpf 1.3 agent_addr->octet_ptr[0] = (unsigned char)s1;
259 agent_addr->octet_ptr[1] = (unsigned char)s2;
260 agent_addr->octet_ptr[2] = (unsigned char)s3;
261 agent_addr->octet_ptr[3] = (unsigned char)s4;
262
|
263 kumpf 1.24 // specTrap from trapOid.
|
264 kumpf 1.12
|
265 kumpf 1.5 SR_INT32 genTrap = 0;
266 SR_INT32 specTrap = 0;
|
267 kumpf 1.24
|
268 kumpf 1.5 OID* enterpriseOid ;
269
270 Array<String> standard_traps;
271
272 standard_traps.append(String("1.3.6.1.6.3.1.1.5.1"));
273 standard_traps.append(String("1.3.6.1.6.3.1.1.5.2"));
274 standard_traps.append(String("1.3.6.1.6.3.1.1.5.3"));
275 standard_traps.append(String("1.3.6.1.6.3.1.1.5.4"));
276 standard_traps.append(String("1.3.6.1.6.3.1.1.5.5"));
277 standard_traps.append(String("1.3.6.1.6.3.1.1.5.6"));
278
279 Array<String> oids;
280 String tmpoid = trapOid;
281
282 while(tmpoid.find(".") != PEG_NOT_FOUND)
283 {
284 oids.append(tmpoid.subString(0, tmpoid.find(".")));
285 tmpoid = tmpoid.subString(tmpoid.find(".") + 1);
286 }
|
287 kumpf 1.12
|
288 kumpf 1.5 oids.append(tmpoid);
289
290 String ent;
291 if (Contains(standard_traps, trapOid))
292 {
|
293 kumpf 1.24 //
294 // if the trapOid is one of the standard traps,
295 // then the SNMPV1 enterprise parameter must be set
296 // to the value of the trapOid, the generic-trap
297 // parameter must be set to one of (0 - 5), and the
298 // specific-trap parameter must be set to 0
299 //
|
300 kumpf 1.5
301 enterpriseOid = sendtrapOid;
|
302 kumpf 1.12
|
303 kumpf 1.24 // the generic trap is last sub-identifier of the
304 // trapOid minus 1
305 genTrap = atoi(oids[oids.size() - 1].getCString()) - 1;
306 specTrap = 0;
|
307 kumpf 1.5 }
308 else
309 {
|
310 kumpf 1.24 //
311 // if the trapOid is not one of the standard traps:
312 // then 1) the generic-trap parameter must be set to 6,
313 // 2) if the next-to-last sub-identifier of the
314 // trapOid is zero, then the SNMPV1 enterprise
315 // parameter is the trapOid with the last 2
316 // sub-identifiers removed, otherwise, the
317 // SNMPV1 enterprise parameter is the trapOid
318 // with the last sub-identifier removed;
319 // 3) the SNMPv1 specific-trap parameter is the last
320 // sub-identifier of the trapOid;
321 //
|
322 kumpf 1.12
|
323 kumpf 1.24 genTrap = 6;
|
324 mike 1.2
|
325 kumpf 1.11 specTrap = atoi(oids[oids.size()-1].getCString());
|
326 mike 1.2
|
327 kumpf 1.24 ent = oids[0];
328 for (Uint8 i = 1; i < oids.size()-2; i++)
329 {
330 ent = ent + "." + oids[i];
331 }
332
333 if (oids[oids.size()-2] != "0")
334 {
335 ent = ent + "." + oids[oids.size()-2];
336 }
337
338 enterpriseOid = MakeOIDFromDot(ent.getCString());
339
340 if (enterpriseOid == NULL)
341 {
342 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, MessageLoaderParms(
343 _MSG_INVALID_ENTERPRISEOID_KEY,
344 _MSG_INVALID_ENTERPRISEOID));
345 }
|
346 kumpf 1.5 }
347
|
348 kumpf 1.12 // creates VarBind
|
349 kumpf 1.24 for (Uint32 i = 0; i < vbOids.size(); i++)
|
350 mike 1.2 {
|
351 kumpf 1.24 CString _vbOid = vbOids[i].getCString();
352 CString _vbValue = vbValues[i].getCString();
|
353 kumpf 1.12
|
354 kumpf 1.24 if ((object = MakeOIDFromDot(_vbOid)) == NULL)
|
355 kumpf 1.3 {
|
356 kumpf 1.24 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, MessageLoaderParms(
357 _MSG_INVALID_PROPERTYOID_KEY,
358 _MSG_INVALID_PROPERTYOID));
359 }
360
361 if (String::equalNoCase(vbTypes[i], "OctetString"))
362 {
363 OctetString* value;
|
364 humberto 1.14
|
365 kumpf 1.24 value = CloneOctetString(MakeOctetStringFromText(_vbValue));
366 if (value == NULL)
|
367 kumpf 1.12 {
|
368 kumpf 1.24 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED,
369 MessageLoaderParms(
370 _MSG_INVALID_PROPERTYVALUE_KEY,
371 _MSG_INVALID_PROPERTYVALUE));
372 }
|
373 humberto 1.14
|
374 kumpf 1.24 if ((vb = MakeVarBindWithValue(
375 object,
376 (OID*) NULL,
377 OCTET_PRIM_TYPE,
378 value)) == NULL)
379 {
380 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED,
381 MessageLoaderParms(
382 _MSG_MAKE_VARBIND_FAILED_FOR_OCTET_PRIM_TYPE_KEY,
383 _MSG_MAKE_VARBIND_FAILED_FOR_OCTET_PRIM_TYPE));
|
384 kumpf 1.12 }
|
385 kumpf 1.24 }
386 else if (String::equalNoCase(vbTypes[i], "OID"))
387 {
|
388 kumpf 1.12 void* value = MakeOIDFromDot(_vbValue);
389 if (value == NULL)
|
390 kumpf 1.4 {
|
391 kumpf 1.24 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED,
392 MessageLoaderParms(
393 _MSG_INVALID_PROPERTYVALUE_KEY,
394 _MSG_INVALID_PROPERTYVALUE));
|
395 kumpf 1.4 }
|
396 kumpf 1.12
|
397 kumpf 1.24 if ((vb = MakeVarBindWithValue(
398 object,
399 (OID*) NULL,
400 OBJECT_ID_TYPE,
401 value)) == NULL)
|
402 kumpf 1.3 {
|
403 kumpf 1.24 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED,
404 MessageLoaderParms(
405 _MSG_MAKE_VARBIND_FAILED_FOR_OBJECT_ID_TYPE_KEY,
406 _MSG_MAKE_VARBIND_FAILED_FOR_OBJECT_ID_TYPE));
|
407 kumpf 1.3 }
|
408 kumpf 1.24 }
409 else
410 {
|
411 kumpf 1.12 int vbvalue = atoi(_vbValue);
|
412 kumpf 1.6 void* value = &vbvalue;
|
413 kumpf 1.3
|
414 kumpf 1.12 if (value == NULL)
|
415 kumpf 1.4 {
|
416 kumpf 1.24 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED,
417 MessageLoaderParms(
418 _MSG_INVALID_PROPERTYVALUE_KEY,
419 _MSG_INVALID_PROPERTYVALUE));
|
420 kumpf 1.4 }
|
421 kumpf 1.12
|
422 kumpf 1.24 if ((vb = MakeVarBindWithValue(
423 object,
424 (OID*) NULL,
425 INTEGER_TYPE,
426 value)) == NULL)
|
427 mike 1.2 {
|
428 kumpf 1.24 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED,
429 MessageLoaderParms(
430 _MSG_MAKE_VARBIND_FAILED_FOR_INTEGER_TYPE_KEY,
431 _MSG_MAKE_VARBIND_FAILED_FOR_INTEGER_TYPE));
|
432 mike 1.2 }
|
433 kumpf 1.24 }
|
434 kumpf 1.12
|
435 kumpf 1.4 if (i == 0)
436 {
|
437 kumpf 1.12 vbhead = vb;
438 vblast = vb;
|
439 kumpf 1.4 }
440 else
441 {
|
442 kumpf 1.12 vblast->next_var = vb;
443 vblast = vblast->next_var;
|
444 kumpf 1.4 }
445
|
446 kumpf 1.3 }
|
447 kumpf 1.5
|
448 kumpf 1.12 vblast->next_var = NULL;
|
449 kumpf 1.3
|
450 kumpf 1.12 // Now send the trap
451 switch (snmpVersion)
|
452 kumpf 1.3 {
|
453 kumpf 1.24 case _SNMPv1_TRAP:
454 {
|
455 kumpf 1.12 SendNotificationToDestSMIv1Params(
|
456 kumpf 1.24 1, // notifyType - TRAP
457 genTrap, // genTrap
458 specTrap, // specTrap
459 enterpriseOid, // enterprise
460 agent_addr, // agent_addr
461 vbhead, // vb
462 NULL, // contextName
463 1, // retryCount
464 1, // timeout
465 community_name, // securityName,
466 SR_SECURITY_LEVEL_NOAUTH, // securityLevel
467 SR_SECURITY_MODEL_V1, // securityModel
468 &global_ti, // Transport Info
469 0); // cfg_chk
470 break;
471 }
472 case _SNMPv2C_TRAP:
473 {
|
474 kumpf 1.12 SendNotificationToDestSMIv2Params(
|
475 kumpf 1.24 (SR_INT32)SNMPv2_TRAP_TYPE, // notifyType - NOTIFICATION
476 sendtrapOid, // snmpTrapOID
477 agent_addr, // agent_addr
478 vbhead, // vb
479 NULL, // contextName
480 1, // retryCount
481 100, // timeout
482 community_name, // securityName or community
483 SR_SECURITY_LEVEL_NOAUTH, // securityLevel
484 SR_SECURITY_MODEL_V2C, // securityModel
485 &global_ti, // TransportInfo
486 0); // cfg_chk
487 break;
488 }
489 default:
490 {
491 throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_NOT_SUPPORTED,
492 MessageLoaderParms(
493 _MSG_VERSION_NOT_SUPPORTED_KEY,
494 _MSG_VERSION_NOT_SUPPORTED));
495 break;
496 kumpf 1.24 }
|
497 kumpf 1.12 }
498
499 // Free OID built by calls MakeOIDFromDot()
500 FreeOID(sendtrapOid);
501 FreeOID(enterpriseOid);
502 FreeOID(object);
503
504 // Free the data structures allocated and built by calls
505 // MakeOctetString() and MakeOctetStringFrom Text()
506 FreeOctetString(community_name);
507 FreeOctetString(agent_addr);
508
|
509 kumpf 1.24 // Free the VarBind data structures allocated and built
|
510 kumpf 1.12 // by calls MakeVarBindWithValue()
511 FreeVarBindList(vbhead);
512 FreeVarBindList(vb);
513 FreeVarBindList(vblast);
514 }
515
516 // get the IP address of a host
|
517 kumpf 1.24 char* snmpDeliverTrap_emanate::_getIPAddress(const CString& hostName)
|
518 kumpf 1.12 {
|
519 kumpf 1.24 struct hostent* targetHostInfo;
|
520 kumpf 1.12 struct in_addr in;
521
|
522 dmitry.mikulin 1.27 char hostEntryBuffer[8192];
523 struct hostent hostEntryStruct;
524 targetHostInfo = System::getHostByName(hostName,
525 &hostEntryStruct, hostEntryBuffer, sizeof (hostEntryBuffer));
|
526 kumpf 1.12
527 if (targetHostInfo == NULL)
528 {
|
529 kumpf 1.24 return NULL;
|
530 kumpf 1.12 }
|
531 kumpf 1.24
532 char** networkAddr;
|
533 kumpf 1.12 networkAddr = targetHostInfo->h_addr_list;
534 (void)memcpy(&in.s_addr, *networkAddr, sizeof(in.s_addr));
|
535 kumpf 1.24 char* ipAddr = inet_ntoa(in);
|
536 kumpf 1.12 return(ipAddr);
537 }
538
|
539 kumpf 1.24 // check the value of each part of an IP address which should be
|
540 kumpf 1.12 // between 0 and 255
541 Boolean snmpDeliverTrap_emanate::_isValidOctet(const Uint32& octetValue)
542 {
543 if (octetValue > 0 && octetValue < 255)
544 {
|
545 kumpf 1.24 return true;
|
546 kumpf 1.3 }
547 else
548 {
|
549 kumpf 1.24 return false;
|
550 mike 1.2 }
551 }
552
553 PEGASUS_NAMESPACE_END
|