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