]>
Commit | Line | Data |
---|---|---|
d7837182 TL |
1 | /* bootp.c |
2 | ||
3 | BOOTP Protocol support. */ | |
4 | ||
5 | /* | |
9deef2e7 | 6 | * Copyright (c) 2004-2016 by Internet Systems Consortium, Inc. ("ISC") |
98311e4b | 7 | * Copyright (c) 1995-2003 by Internet Software Consortium |
d7837182 | 8 | * |
98311e4b DH |
9 | * Permission to use, copy, modify, and distribute this software for any |
10 | * purpose with or without fee is hereby granted, provided that the above | |
11 | * copyright notice and this permission notice appear in all copies. | |
d7837182 | 12 | * |
98311e4b DH |
13 | * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES |
14 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
15 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR | |
16 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
17 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
18 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT | |
19 | * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
d7837182 | 20 | * |
98311e4b DH |
21 | * Internet Systems Consortium, Inc. |
22 | * 950 Charter Street | |
23 | * Redwood City, CA 94063 | |
24 | * <info@isc.org> | |
2c85ac9b | 25 | * https://www.isc.org/ |
49733f31 | 26 | * |
d7837182 TL |
27 | */ |
28 | ||
d7837182 | 29 | #include "dhcpd.h" |
fe5b0fdd | 30 | #include <errno.h> |
d7837182 | 31 | |
93016999 TL |
32 | #if defined (TRACING) |
33 | # define send_packet trace_packet_send | |
34 | #endif | |
35 | ||
d7837182 TL |
36 | void bootp (packet) |
37 | struct packet *packet; | |
38 | { | |
39 | int result; | |
20916cae | 40 | struct host_decl *hp = (struct host_decl *)0; |
cfb326fd | 41 | struct host_decl *host = (struct host_decl *)0; |
9c238be6 TL |
42 | struct packet outgoing; |
43 | struct dhcp_packet raw; | |
d7837182 | 44 | struct sockaddr_in to; |
d1c53034 | 45 | struct in_addr from; |
fde927d2 | 46 | struct hardware hto; |
7e6f3635 | 47 | struct option_state *options = (struct option_state *)0; |
c75473d8 | 48 | struct lease *lease = (struct lease *)0; |
b1b7b521 | 49 | unsigned i; |
ef0afca9 | 50 | struct data_string d1; |
5d25508c | 51 | struct option_cache *oc; |
cc70b445 | 52 | char msgbuf [1024]; |
e13ff8b2 | 53 | int ignorep; |
ac3ecc7f | 54 | int peer_has_leases = 0; |
d7837182 | 55 | |
c0257f22 TL |
56 | if (packet -> raw -> op != BOOTREQUEST) |
57 | return; | |
58 | ||
98311e4b DH |
59 | /* %Audit% This is log output. %2004.06.17,Safe% |
60 | * If we truncate we hope the user can get a hint from the log. | |
61 | */ | |
62 | snprintf (msgbuf, sizeof msgbuf, "BOOTREQUEST from %s via %s", | |
cc70b445 TL |
63 | print_hw_addr (packet -> raw -> htype, |
64 | packet -> raw -> hlen, | |
65 | packet -> raw -> chaddr), | |
66 | packet -> raw -> giaddr.s_addr | |
67 | ? inet_ntoa (packet -> raw -> giaddr) | |
68 | : packet -> interface -> name); | |
b837c98f | 69 | |
cc70b445 | 70 | if (!locate_network (packet)) { |
8ae2d595 | 71 | log_info ("%s: network unknown", msgbuf); |
d1c53034 | 72 | return; |
cc70b445 | 73 | } |
3a581108 | 74 | |
9e383163 TL |
75 | find_lease (&lease, packet, packet -> shared_network, |
76 | 0, 0, (struct lease *)0, MDL); | |
cfb326fd | 77 | |
c75473d8 DH |
78 | if (lease && lease->host) |
79 | host_reference(&hp, lease->host, MDL); | |
cfb326fd | 80 | |
c75473d8 | 81 | if (!lease || ((lease->flags & STATIC_LEASE) == 0)) { |
20916cae | 82 | struct host_decl *h; |
c104546d | 83 | |
c75473d8 DH |
84 | /* We didn't find an applicable fixed-address host |
85 | declaration. Just in case we may be able to dynamically | |
86 | assign an address, see if there's a host declaration | |
cfb326fd | 87 | that doesn't have an ip address associated with it. */ |
c75473d8 DH |
88 | |
89 | if (!hp) | |
90 | find_hosts_by_haddr(&hp, packet->raw->htype, | |
91 | packet->raw->chaddr, | |
92 | packet->raw->hlen, MDL); | |
93 | ||
20916cae TL |
94 | for (h = hp; h; h = h -> n_ipaddr) { |
95 | if (!h -> fixed_addr) { | |
c75473d8 | 96 | host_reference(&host, h, MDL); |
20916cae | 97 | break; |
cfb326fd TL |
98 | } |
99 | } | |
cfb326fd | 100 | |
c75473d8 DH |
101 | if (hp) |
102 | host_dereference(&hp, MDL); | |
103 | ||
104 | if (host) { | |
105 | host_reference(&hp, host, MDL); | |
106 | host_dereference(&host, MDL); | |
cfb326fd | 107 | } |
98311e4b | 108 | |
c75473d8 DH |
109 | /* Allocate a lease if we have not yet found one. */ |
110 | if (!lease) | |
111 | allocate_lease (&lease, packet, | |
112 | packet -> shared_network -> pools, | |
113 | &peer_has_leases); | |
114 | ||
c104546d DH |
115 | if (lease == NULL) { |
116 | log_info("%s: BOOTP from dynamic client and no " | |
117 | "dynamic leases", msgbuf); | |
118 | goto out; | |
119 | } | |
120 | ||
121 | #if defined(FAILOVER_PROTOCOL) | |
122 | if ((lease->pool != NULL) && | |
123 | (lease->pool->failover_peer != NULL)) { | |
124 | dhcp_failover_state_t *peer; | |
125 | ||
126 | peer = lease->pool->failover_peer; | |
127 | ||
128 | /* If we are in a failover state that bars us from | |
129 | * answering, do not do so. | |
130 | * If we are in a cooperative state, load balance | |
131 | * (all) responses. | |
132 | */ | |
133 | if ((peer->service_state == not_responding) || | |
134 | (peer->service_state == service_startup)) { | |
135 | log_info("%s: not responding%s", | |
136 | msgbuf, peer->nrr); | |
137 | goto out; | |
138 | } else if((peer->service_state == cooperating) && | |
139 | !load_balance_mine(packet, peer)) { | |
140 | log_info("%s: load balance to peer %s", | |
141 | msgbuf, peer->name); | |
142 | goto out; | |
143 | } | |
144 | } | |
145 | #endif | |
c75473d8 | 146 | |
c104546d | 147 | ack_lease (packet, lease, 0, 0, msgbuf, 0, hp); |
20916cae | 148 | goto out; |
d7837182 | 149 | } |
9c238be6 | 150 | |
ef0afca9 TL |
151 | /* Run the executable statements to compute the client and server |
152 | options. */ | |
12c9957e | 153 | option_state_allocate (&options, MDL); |
c75473d8 | 154 | |
ef0afca9 | 155 | /* Execute the subnet statements. */ |
a7341359 SR |
156 | execute_statements_in_scope (NULL, packet, lease, NULL, |
157 | packet->options, options, | |
158 | &lease->scope, lease->subnet->group, | |
159 | NULL, NULL); | |
c75473d8 | 160 | |
fa661adb TL |
161 | /* Execute statements from class scopes. */ |
162 | for (i = packet -> class_count; i > 0; i--) { | |
a7341359 SR |
163 | execute_statements_in_scope(NULL, packet, lease, NULL, |
164 | packet->options, options, | |
165 | &lease->scope, | |
166 | packet->classes[i - 1]->group, | |
167 | lease->subnet->group, NULL); | |
fa661adb TL |
168 | } |
169 | ||
ef0afca9 | 170 | /* Execute the host statements. */ |
d289ee68 | 171 | if (hp != NULL) { |
0a7e1a8a TM |
172 | execute_statements_in_scope(NULL, packet, lease, NULL, |
173 | packet->options, options, | |
174 | &lease->scope, hp->group, | |
175 | lease->subnet->group, NULL); | |
d289ee68 | 176 | } |
ef0afca9 TL |
177 | |
178 | /* Drop the request if it's not allowed for this client. */ | |
fe849040 | 179 | if ((oc = lookup_option (&server_universe, options, SV_ALLOW_BOOTP)) && |
0a7e1a8a TM |
180 | !evaluate_boolean_option_cache(&ignorep, packet, lease, |
181 | NULL, | |
182 | packet->options, options, | |
183 | &lease->scope, oc, MDL)) { | |
e13ff8b2 TL |
184 | if (!ignorep) |
185 | log_info ("%s: bootp disallowed", msgbuf); | |
20916cae | 186 | goto out; |
ef0afca9 TL |
187 | } |
188 | ||
0a7e1a8a | 189 | if ((oc = lookup_option(&server_universe, |
fe849040 | 190 | options, SV_ALLOW_BOOTING)) && |
0a7e1a8a TM |
191 | !evaluate_boolean_option_cache(&ignorep, packet, lease, |
192 | NULL, | |
193 | packet->options, options, | |
194 | &lease->scope, oc, MDL)) { | |
e13ff8b2 TL |
195 | if (!ignorep) |
196 | log_info ("%s: booting disallowed", msgbuf); | |
20916cae | 197 | goto out; |
e07ac89a | 198 | } |
ef0afca9 | 199 | |
9c238be6 TL |
200 | /* Set up the outgoing packet... */ |
201 | memset (&outgoing, 0, sizeof outgoing); | |
202 | memset (&raw, 0, sizeof raw); | |
203 | outgoing.raw = &raw; | |
204 | ||
d8ae14c6 TL |
205 | /* If we didn't get a known vendor magic number on the way in, |
206 | just copy the input options to the output. */ | |
0a7e1a8a TM |
207 | i = SV_ALWAYS_REPLY_RFC1048; |
208 | if (!packet->options_valid && | |
209 | !(evaluate_boolean_option_cache(&ignorep, packet, lease, NULL, | |
210 | packet->options, options, | |
211 | &lease->scope, | |
212 | lookup_option (&server_universe, | |
213 | options, i), MDL))) { | |
214 | if (packet->packet_length > DHCP_FIXED_NON_UDP) { | |
215 | memcpy(outgoing.raw->options, packet->raw->options, | |
216 | packet->packet_length - DHCP_FIXED_NON_UDP); | |
217 | } | |
218 | ||
219 | outgoing.packet_length = | |
220 | (packet->packet_length < BOOTP_MIN_LEN) | |
221 | ? BOOTP_MIN_LEN | |
222 | : packet->packet_length; | |
d8ae14c6 | 223 | } else { |
9804a56a TL |
224 | |
225 | /* Use the subnet mask from the subnet declaration if no other | |
226 | mask has been provided. */ | |
9804a56a TL |
227 | oc = (struct option_cache *)0; |
228 | i = DHO_SUBNET_MASK; | |
229 | if (!lookup_option (&dhcp_universe, options, i)) { | |
12c9957e | 230 | if (option_cache_allocate (&oc, MDL)) { |
9804a56a TL |
231 | if (make_const_data |
232 | (&oc -> expression, | |
233 | lease -> subnet -> netmask.iabuf, | |
d758ad8c TL |
234 | lease -> subnet -> netmask.len, |
235 | 0, 0, MDL)) { | |
f7fdb216 DH |
236 | option_code_hash_lookup(&oc->option, |
237 | dhcp_universe.code_hash, | |
238 | &i, 0, MDL); | |
9804a56a TL |
239 | save_option (&dhcp_universe, |
240 | options, oc); | |
241 | } | |
12c9957e | 242 | option_cache_dereference (&oc, MDL); |
9804a56a TL |
243 | } |
244 | } | |
245 | ||
0a7e1a8a TM |
246 | /* If use-host-decl-names is enabled and there is a hostname |
247 | * defined in the host delcartion, send it back in hostname | |
248 | * option */ | |
249 | use_host_decl_name(packet, lease, options); | |
250 | ||
d8ae14c6 TL |
251 | /* Pack the options into the buffer. Unlike DHCP, we |
252 | can't pack options into the filename and server | |
253 | name buffers. */ | |
9c238be6 | 254 | |
d8ae14c6 | 255 | outgoing.packet_length = |
9e383163 TL |
256 | cons_options (packet, outgoing.raw, lease, |
257 | (struct client_state *)0, 0, | |
2cc0151c | 258 | packet -> options, options, |
12c9957e | 259 | &lease -> scope, |
63b935b6 TL |
260 | 0, 0, 1, (struct data_string *)0, |
261 | (const char *)0); | |
d8ae14c6 TL |
262 | if (outgoing.packet_length < BOOTP_MIN_LEN) |
263 | outgoing.packet_length = BOOTP_MIN_LEN; | |
264 | } | |
9c238be6 | 265 | |
d7837182 | 266 | /* Take the fields that we care about... */ |
9c238be6 TL |
267 | raw.op = BOOTREPLY; |
268 | raw.htype = packet -> raw -> htype; | |
269 | raw.hlen = packet -> raw -> hlen; | |
d8ae14c6 | 270 | memcpy (raw.chaddr, packet -> raw -> chaddr, sizeof raw.chaddr); |
9c238be6 TL |
271 | raw.hops = packet -> raw -> hops; |
272 | raw.xid = packet -> raw -> xid; | |
273 | raw.secs = packet -> raw -> secs; | |
9129caed | 274 | raw.flags = packet -> raw -> flags; |
9c238be6 | 275 | raw.ciaddr = packet -> raw -> ciaddr; |
c75473d8 DH |
276 | |
277 | /* yiaddr is an ipv4 address, it must be 4 octets. */ | |
278 | memcpy (&raw.yiaddr, lease->ip_addr.iabuf, 4); | |
cfb326fd | 279 | |
9129caed TL |
280 | /* If we're always supposed to broadcast to this client, set |
281 | the broadcast bit in the bootp flags field. */ | |
e3b5f7f8 TL |
282 | if ((oc = lookup_option (&server_universe, |
283 | options, SV_ALWAYS_BROADCAST)) && | |
e13ff8b2 | 284 | evaluate_boolean_option_cache (&ignorep, packet, lease, |
9e383163 | 285 | (struct client_state *)0, |
12c9957e TL |
286 | packet -> options, options, |
287 | &lease -> scope, oc, MDL)) | |
9129caed TL |
288 | raw.flags |= htons (BOOTP_BROADCAST); |
289 | ||
d1c53034 | 290 | /* Figure out the address of the next server. */ |
e40810bf | 291 | memset (&d1, 0, sizeof d1); |
02124b3c | 292 | oc = lookup_option (&server_universe, options, SV_NEXT_SERVER); |
5d25508c | 293 | if (oc && |
2cc0151c | 294 | evaluate_option_cache (&d1, packet, lease, |
9e383163 | 295 | (struct client_state *)0, |
12c9957e TL |
296 | packet -> options, options, |
297 | &lease -> scope, oc, MDL)) { | |
ef0afca9 TL |
298 | /* If there was more than one answer, take the first. */ |
299 | if (d1.len >= 4 && d1.data) | |
300 | memcpy (&raw.siaddr, d1.data, 4); | |
12c9957e | 301 | data_string_forget (&d1, MDL); |
17f1bc9a | 302 | } else { |
98bd7ca0 DH |
303 | if ((lease->subnet->shared_network->interface != NULL) && |
304 | lease->subnet->shared_network->interface->address_count) | |
305 | raw.siaddr = | |
306 | lease->subnet->shared_network->interface->addresses[0]; | |
307 | else if (packet->interface->address_count) | |
308 | raw.siaddr = packet->interface->addresses[0]; | |
ef0afca9 | 309 | } |
cfb326fd | 310 | |
9c238be6 | 311 | raw.giaddr = packet -> raw -> giaddr; |
ef0afca9 TL |
312 | |
313 | /* Figure out the filename. */ | |
02124b3c | 314 | oc = lookup_option (&server_universe, options, SV_FILENAME); |
5d25508c | 315 | if (oc && |
2cc0151c | 316 | evaluate_option_cache (&d1, packet, lease, |
9e383163 | 317 | (struct client_state *)0, |
12c9957e TL |
318 | packet -> options, options, |
319 | &lease -> scope, oc, MDL)) { | |
ef0afca9 TL |
320 | memcpy (raw.file, d1.data, |
321 | d1.len > sizeof raw.file ? sizeof raw.file : d1.len); | |
322 | if (sizeof raw.file > d1.len) | |
323 | memset (&raw.file [d1.len], | |
324 | 0, (sizeof raw.file) - d1.len); | |
12c9957e | 325 | data_string_forget (&d1, MDL); |
339b0231 | 326 | } else |
92e3e691 | 327 | memcpy (raw.file, packet -> raw -> file, sizeof raw.file); |
ef0afca9 TL |
328 | |
329 | /* Choose a server name as above. */ | |
02124b3c | 330 | oc = lookup_option (&server_universe, options, SV_SERVER_NAME); |
5d25508c | 331 | if (oc && |
2cc0151c | 332 | evaluate_option_cache (&d1, packet, lease, |
9e383163 | 333 | (struct client_state *)0, |
12c9957e TL |
334 | packet -> options, options, |
335 | &lease -> scope, oc, MDL)) { | |
ef0afca9 TL |
336 | memcpy (raw.sname, d1.data, |
337 | d1.len > sizeof raw.sname ? sizeof raw.sname : d1.len); | |
338 | if (sizeof raw.sname > d1.len) | |
339 | memset (&raw.sname [d1.len], | |
340 | 0, (sizeof raw.sname) - d1.len); | |
12c9957e | 341 | data_string_forget (&d1, MDL); |
d7837182 | 342 | } |
d7837182 | 343 | |
26413cf1 | 344 | /* Execute the commit statements, if there are any. */ |
a7341359 SR |
345 | execute_statements (NULL, packet, lease, NULL, packet->options, |
346 | options, &lease->scope, lease->on_star.on_commit, | |
347 | NULL); | |
26413cf1 | 348 | |
7e6f3635 | 349 | /* We're done with the option state. */ |
12c9957e | 350 | option_state_dereference (&options, MDL); |
7e6f3635 | 351 | |
785c1a51 FD |
352 | #if defined(DHCPv6) && defined(DHCP4o6) |
353 | if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { | |
354 | /* Report what we're doing... */ | |
355 | log_info("%s", msgbuf); | |
356 | log_info("DHCP4o6 BOOTREPLY for %s to %s (%s) via %s", | |
357 | piaddr(lease->ip_addr), | |
358 | ((hp != NULL) && (hp->name != NULL)) ? | |
359 | hp -> name : "unknown", | |
360 | print_hw_addr (packet->raw->htype, | |
361 | packet->raw->hlen, | |
362 | packet->raw->chaddr), | |
363 | piaddr(packet->client_addr)); | |
364 | ||
365 | /* fill dhcp4o6_response */ | |
366 | packet->dhcp4o6_response->len = outgoing.packet_length; | |
367 | packet->dhcp4o6_response->buffer = NULL; | |
368 | if (!buffer_allocate(&packet->dhcp4o6_response->buffer, | |
369 | outgoing.packet_length, MDL)) { | |
370 | log_fatal("No memory to store DHCP4o6 reply."); | |
371 | } | |
372 | packet->dhcp4o6_response->data = | |
373 | packet->dhcp4o6_response->buffer->data; | |
374 | memcpy(packet->dhcp4o6_response->buffer->data, | |
375 | outgoing.raw, outgoing.packet_length); | |
376 | goto out; | |
377 | } | |
378 | #endif | |
379 | ||
cfb326fd | 380 | /* Set up the hardware destination address... */ |
9e9b2261 TL |
381 | hto.hbuf [0] = packet -> raw -> htype; |
382 | hto.hlen = packet -> raw -> hlen + 1; | |
012c4143 | 383 | memcpy (&hto.hbuf [1], packet -> raw -> chaddr, packet -> raw -> hlen); |
cfb326fd | 384 | |
dd484ced | 385 | if (packet->interface->address_count) { |
98bd7ca0 | 386 | from = packet->interface->addresses[0]; |
dd484ced DH |
387 | } else { |
388 | log_error("%s: Interface %s appears to have no IPv4 " | |
389 | "addresses, and so dhcpd cannot select a source " | |
390 | "address.", msgbuf, packet->interface->name); | |
391 | goto out; | |
392 | } | |
d1c53034 | 393 | |
cfb326fd | 394 | /* Report what we're doing... */ |
d289ee68 SR |
395 | log_info("%s", msgbuf); |
396 | log_info("BOOTREPLY for %s to %s (%s) via %s", | |
397 | piaddr(lease->ip_addr), | |
398 | ((hp != NULL) && (hp->name != NULL)) ? hp -> name : "unknown", | |
399 | print_hw_addr (packet->raw->htype, | |
400 | packet->raw->hlen, | |
401 | packet->raw->chaddr), | |
402 | packet->raw->giaddr.s_addr | |
403 | ? inet_ntoa (packet->raw->giaddr) | |
404 | : packet->interface->name); | |
cfb326fd TL |
405 | |
406 | /* Set up the parts of the address that are in common. */ | |
407 | to.sin_family = AF_INET; | |
408 | #ifdef HAVE_SA_LEN | |
409 | to.sin_len = sizeof to; | |
410 | #endif | |
411 | memset (to.sin_zero, 0, sizeof to.sin_zero); | |
412 | ||
9c238be6 TL |
413 | /* If this was gatewayed, send it back to the gateway... */ |
414 | if (raw.giaddr.s_addr) { | |
415 | to.sin_addr = raw.giaddr; | |
bfa885c9 | 416 | to.sin_port = local_port; |
cfb326fd | 417 | |
92e3e691 | 418 | if (fallback_interface) { |
dd9237c3 TM |
419 | result = send_packet (fallback_interface, NULL, &raw, |
420 | outgoing.packet_length, from, | |
421 | &to, &hto); | |
422 | if (result < 0) { | |
423 | log_error ("%s:%d: Failed to send %d byte long " | |
424 | "packet over %s interface.", MDL, | |
425 | outgoing.packet_length, | |
426 | fallback_interface->name); | |
427 | } | |
428 | ||
20916cae | 429 | goto out; |
92e3e691 | 430 | } |
fe849040 TL |
431 | |
432 | /* If it comes from a client that already knows its address | |
433 | and is not requesting a broadcast response, and we can | |
434 | unicast to a client without using the ARP protocol, sent it | |
435 | directly to that client. */ | |
436 | } else if (!(raw.flags & htons (BOOTP_BROADCAST)) && | |
437 | can_unicast_without_arp (packet -> interface)) { | |
438 | to.sin_addr = raw.yiaddr; | |
439 | to.sin_port = remote_port; | |
440 | ||
9c238be6 TL |
441 | /* Otherwise, broadcast it on the local network. */ |
442 | } else { | |
71df44f5 | 443 | to.sin_addr = limited_broadcast; |
bfa885c9 | 444 | to.sin_port = remote_port; /* XXX */ |
9c238be6 | 445 | } |
d7837182 | 446 | |
d7837182 | 447 | errno = 0; |
dd9237c3 TM |
448 | result = send_packet(packet->interface, packet, &raw, |
449 | outgoing.packet_length, from, &to, &hto); | |
450 | if (result < 0) { | |
451 | log_error ("%s:%d: Failed to send %d byte long packet over %s" | |
452 | " interface.", MDL, outgoing.packet_length, | |
453 | packet->interface->name); | |
454 | } | |
455 | ||
20916cae | 456 | out: |
dd9237c3 | 457 | |
20916cae TL |
458 | if (options) |
459 | option_state_dereference (&options, MDL); | |
31bbee78 | 460 | if (lease) |
20916cae | 461 | lease_dereference (&lease, MDL); |
20916cae TL |
462 | if (hp) |
463 | host_dereference (&hp, MDL); | |
464 | if (host) | |
465 | host_dereference (&host, MDL); | |
d7837182 | 466 | } |