1 |
|
|
/* $OpenBSD: nat_traversal.c,v 1.24 2015/08/20 22:05:51 deraadt Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2004 Håkan Olsson. All rights reserved. |
5 |
|
|
* |
6 |
|
|
* Redistribution and use in source and binary forms, with or without |
7 |
|
|
* modification, are permitted provided that the following conditions |
8 |
|
|
* are met: |
9 |
|
|
* 1. Redistributions of source code must retain the above copyright |
10 |
|
|
* notice, this list of conditions and the following disclaimer. |
11 |
|
|
* 2. Redistributions in binary form must reproduce the above copyright |
12 |
|
|
* notice, this list of conditions and the following disclaimer in the |
13 |
|
|
* documentation and/or other materials provided with the distribution. |
14 |
|
|
* |
15 |
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
16 |
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
17 |
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
18 |
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
19 |
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
20 |
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
21 |
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
22 |
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
23 |
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
24 |
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 |
|
|
*/ |
26 |
|
|
|
27 |
|
|
#include <sys/types.h> |
28 |
|
|
#include <stdlib.h> |
29 |
|
|
#include <string.h> |
30 |
|
|
|
31 |
|
|
#include "conf.h" |
32 |
|
|
#include "exchange.h" |
33 |
|
|
#include "hash.h" |
34 |
|
|
#include "ipsec.h" |
35 |
|
|
#include "isakmp_fld.h" |
36 |
|
|
#include "isakmp_num.h" |
37 |
|
|
#include "ipsec_num.h" |
38 |
|
|
#include "log.h" |
39 |
|
|
#include "message.h" |
40 |
|
|
#include "nat_traversal.h" |
41 |
|
|
#include "prf.h" |
42 |
|
|
#include "sa.h" |
43 |
|
|
#include "timer.h" |
44 |
|
|
#include "transport.h" |
45 |
|
|
#include "util.h" |
46 |
|
|
#include "virtual.h" |
47 |
|
|
|
48 |
|
|
int disable_nat_t = 0; |
49 |
|
|
|
50 |
|
|
/* |
51 |
|
|
* NAT-T capability of the other peer is determined by a particular vendor |
52 |
|
|
* ID sent in the first message. This vendor ID string is supposed to be a |
53 |
|
|
* MD5 hash of "RFC 3947". |
54 |
|
|
* |
55 |
|
|
* These seem to be the "well" known variants of this string in use by |
56 |
|
|
* products today. |
57 |
|
|
* |
58 |
|
|
* Note that the VID specified in draft 2 is ambiguous: It was |
59 |
|
|
* accidentally calculated from the string "draft-ietf-ipsec-nat-t-ike-02\n" |
60 |
|
|
* although the string was documented without the trailing '\n'. The authors |
61 |
|
|
* suggested afterwards to use the string with the trailing '\n'. |
62 |
|
|
*/ |
63 |
|
|
|
64 |
|
|
static struct nat_t_cap isakmp_nat_t_cap[] = { |
65 |
|
|
{ VID_DRAFT_V2_N, EXCHANGE_FLAG_NAT_T_DRAFT, |
66 |
|
|
"draft-ietf-ipsec-nat-t-ike-02\n", NULL, 0 }, |
67 |
|
|
{ VID_DRAFT_V3, EXCHANGE_FLAG_NAT_T_DRAFT, |
68 |
|
|
"draft-ietf-ipsec-nat-t-ike-03", NULL, 0 }, |
69 |
|
|
{ VID_RFC3947, EXCHANGE_FLAG_NAT_T_RFC, |
70 |
|
|
"RFC 3947", NULL, 0 }, |
71 |
|
|
}; |
72 |
|
|
|
73 |
|
|
#define NUMNATTCAP (sizeof isakmp_nat_t_cap / sizeof isakmp_nat_t_cap[0]) |
74 |
|
|
|
75 |
|
|
/* In seconds. Recommended in draft-ietf-ipsec-udp-encaps-09. */ |
76 |
|
|
#define NAT_T_KEEPALIVE_INTERVAL 20 |
77 |
|
|
|
78 |
|
|
static int nat_t_setup_hashes(void); |
79 |
|
|
static int nat_t_add_vendor_payload(struct message *, struct nat_t_cap *); |
80 |
|
|
static int nat_t_add_nat_d(struct message *, struct sockaddr *); |
81 |
|
|
static int nat_t_match_nat_d_payload(struct message *, struct sockaddr *); |
82 |
|
|
|
83 |
|
|
void |
84 |
|
|
nat_t_init(void) |
85 |
|
|
{ |
86 |
|
|
nat_t_setup_hashes(); |
87 |
|
|
} |
88 |
|
|
|
89 |
|
|
/* Generate the NAT-T capability marker hashes. Executed only once. */ |
90 |
|
|
static int |
91 |
|
|
nat_t_setup_hashes(void) |
92 |
|
|
{ |
93 |
|
|
struct hash *hash; |
94 |
|
|
int n = NUMNATTCAP; |
95 |
|
|
int i; |
96 |
|
|
|
97 |
|
|
/* The draft says to use MD5. */ |
98 |
|
|
hash = hash_get(HASH_MD5); |
99 |
|
|
if (!hash) { |
100 |
|
|
/* Should never happen. */ |
101 |
|
|
log_print("nat_t_setup_hashes: " |
102 |
|
|
"could not find MD5 hash structure!"); |
103 |
|
|
return -1; |
104 |
|
|
} |
105 |
|
|
|
106 |
|
|
/* Populate isakmp_nat_t_cap with hashes. */ |
107 |
|
|
for (i = 0; i < n; i++) { |
108 |
|
|
isakmp_nat_t_cap[i].hashsize = hash->hashsize; |
109 |
|
|
isakmp_nat_t_cap[i].hash = malloc(hash->hashsize); |
110 |
|
|
if (!isakmp_nat_t_cap[i].hash) { |
111 |
|
|
log_error("nat_t_setup_hashes: malloc (%lu) failed", |
112 |
|
|
(unsigned long)hash->hashsize); |
113 |
|
|
goto errout; |
114 |
|
|
} |
115 |
|
|
|
116 |
|
|
hash->Init(hash->ctx); |
117 |
|
|
hash->Update(hash->ctx, |
118 |
|
|
(unsigned char *)isakmp_nat_t_cap[i].text, |
119 |
|
|
strlen(isakmp_nat_t_cap[i].text)); |
120 |
|
|
hash->Final(isakmp_nat_t_cap[i].hash, hash->ctx); |
121 |
|
|
|
122 |
|
|
LOG_DBG((LOG_EXCHANGE, 50, "nat_t_setup_hashes: " |
123 |
|
|
"MD5(\"%s\") (%lu bytes)", isakmp_nat_t_cap[i].text, |
124 |
|
|
(unsigned long)hash->hashsize)); |
125 |
|
|
LOG_DBG_BUF((LOG_EXCHANGE, 50, "nat_t_setup_hashes", |
126 |
|
|
isakmp_nat_t_cap[i].hash, hash->hashsize)); |
127 |
|
|
} |
128 |
|
|
|
129 |
|
|
return 0; |
130 |
|
|
|
131 |
|
|
errout: |
132 |
|
|
for (i = 0; i < n; i++) |
133 |
|
|
free(isakmp_nat_t_cap[i].hash); |
134 |
|
|
return -1; |
135 |
|
|
} |
136 |
|
|
|
137 |
|
|
/* Add one NAT-T VENDOR payload. */ |
138 |
|
|
static int |
139 |
|
|
nat_t_add_vendor_payload(struct message *msg, struct nat_t_cap *cap) |
140 |
|
|
{ |
141 |
|
|
size_t buflen = cap->hashsize + ISAKMP_GEN_SZ; |
142 |
|
|
u_int8_t *buf; |
143 |
|
|
|
144 |
|
|
if (disable_nat_t) |
145 |
|
|
return 0; |
146 |
|
|
|
147 |
|
|
buf = malloc(buflen); |
148 |
|
|
if (!buf) { |
149 |
|
|
log_error("nat_t_add_vendor_payload: malloc (%lu) failed", |
150 |
|
|
(unsigned long)buflen); |
151 |
|
|
return -1; |
152 |
|
|
} |
153 |
|
|
|
154 |
|
|
SET_ISAKMP_GEN_LENGTH(buf, buflen); |
155 |
|
|
memcpy(buf + ISAKMP_VENDOR_ID_OFF, cap->hash, cap->hashsize); |
156 |
|
|
if (message_add_payload(msg, ISAKMP_PAYLOAD_VENDOR, buf, buflen, 1)) { |
157 |
|
|
free(buf); |
158 |
|
|
return -1; |
159 |
|
|
} |
160 |
|
|
return 0; |
161 |
|
|
} |
162 |
|
|
|
163 |
|
|
/* Add the NAT-T capability markers (VENDOR payloads). */ |
164 |
|
|
int |
165 |
|
|
nat_t_add_vendor_payloads(struct message *msg) |
166 |
|
|
{ |
167 |
|
|
int i; |
168 |
|
|
|
169 |
|
|
if (disable_nat_t) |
170 |
|
|
return 0; |
171 |
|
|
|
172 |
|
|
for (i = 0; i < NUMNATTCAP; i++) |
173 |
|
|
if (nat_t_add_vendor_payload(msg, &isakmp_nat_t_cap[i])) |
174 |
|
|
return -1; |
175 |
|
|
return 0; |
176 |
|
|
} |
177 |
|
|
|
178 |
|
|
/* |
179 |
|
|
* Check an incoming message for NAT-T capability markers. |
180 |
|
|
*/ |
181 |
|
|
void |
182 |
|
|
nat_t_check_vendor_payload(struct message *msg, struct payload *p) |
183 |
|
|
{ |
184 |
|
|
u_int8_t *pbuf = p->p; |
185 |
|
|
size_t vlen; |
186 |
|
|
int i; |
187 |
|
|
|
188 |
|
|
if (disable_nat_t) |
189 |
|
|
return; |
190 |
|
|
|
191 |
|
|
vlen = GET_ISAKMP_GEN_LENGTH(pbuf) - ISAKMP_GEN_SZ; |
192 |
|
|
|
193 |
|
|
for (i = 0; i < NUMNATTCAP; i++) { |
194 |
|
|
if (vlen != isakmp_nat_t_cap[i].hashsize) { |
195 |
|
|
continue; |
196 |
|
|
} |
197 |
|
|
if (memcmp(isakmp_nat_t_cap[i].hash, pbuf + ISAKMP_GEN_SZ, |
198 |
|
|
vlen) == 0) { |
199 |
|
|
/* This peer is NAT-T capable. */ |
200 |
|
|
msg->exchange->flags |= EXCHANGE_FLAG_NAT_T_CAP_PEER; |
201 |
|
|
msg->exchange->flags |= isakmp_nat_t_cap[i].flags; |
202 |
|
|
LOG_DBG((LOG_EXCHANGE, 10, |
203 |
|
|
"nat_t_check_vendor_payload: " |
204 |
|
|
"NAT-T capable peer detected")); |
205 |
|
|
p->flags |= PL_MARK; |
206 |
|
|
} |
207 |
|
|
} |
208 |
|
|
|
209 |
|
|
return; |
210 |
|
|
} |
211 |
|
|
|
212 |
|
|
/* Generate the NAT-D payload hash : HASH(CKY-I | CKY-R | IP | Port). */ |
213 |
|
|
static u_int8_t * |
214 |
|
|
nat_t_generate_nat_d_hash(struct message *msg, struct sockaddr *sa, |
215 |
|
|
size_t *hashlen) |
216 |
|
|
{ |
217 |
|
|
struct ipsec_exch *ie = (struct ipsec_exch *)msg->exchange->data; |
218 |
|
|
struct hash *hash; |
219 |
|
|
u_int8_t *res; |
220 |
|
|
in_port_t port; |
221 |
|
|
|
222 |
|
|
hash = hash_get(ie->hash->type); |
223 |
|
|
if (hash == NULL) { |
224 |
|
|
log_print ("nat_t_generate_nat_d_hash: no hash"); |
225 |
|
|
return NULL; |
226 |
|
|
} |
227 |
|
|
|
228 |
|
|
*hashlen = hash->hashsize; |
229 |
|
|
|
230 |
|
|
res = malloc(*hashlen); |
231 |
|
|
if (!res) { |
232 |
|
|
log_print("nat_t_generate_nat_d_hash: malloc (%lu) failed", |
233 |
|
|
(unsigned long)*hashlen); |
234 |
|
|
*hashlen = 0; |
235 |
|
|
return NULL; |
236 |
|
|
} |
237 |
|
|
|
238 |
|
|
port = sockaddr_port(sa); |
239 |
|
|
bzero(res, *hashlen); |
240 |
|
|
|
241 |
|
|
hash->Init(hash->ctx); |
242 |
|
|
hash->Update(hash->ctx, msg->exchange->cookies, |
243 |
|
|
sizeof msg->exchange->cookies); |
244 |
|
|
hash->Update(hash->ctx, sockaddr_addrdata(sa), sockaddr_addrlen(sa)); |
245 |
|
|
hash->Update(hash->ctx, (unsigned char *)&port, sizeof port); |
246 |
|
|
hash->Final(res, hash->ctx); |
247 |
|
|
return res; |
248 |
|
|
} |
249 |
|
|
|
250 |
|
|
/* Add a NAT-D payload to our message. */ |
251 |
|
|
static int |
252 |
|
|
nat_t_add_nat_d(struct message *msg, struct sockaddr *sa) |
253 |
|
|
{ |
254 |
|
|
int ret; |
255 |
|
|
u_int8_t *hbuf, *buf; |
256 |
|
|
size_t hbuflen, buflen; |
257 |
|
|
|
258 |
|
|
hbuf = nat_t_generate_nat_d_hash(msg, sa, &hbuflen); |
259 |
|
|
if (!hbuf) { |
260 |
|
|
log_print("nat_t_add_nat_d: NAT-D hash gen failed"); |
261 |
|
|
return -1; |
262 |
|
|
} |
263 |
|
|
|
264 |
|
|
buflen = ISAKMP_NAT_D_DATA_OFF + hbuflen; |
265 |
|
|
buf = malloc(buflen); |
266 |
|
|
if (!buf) { |
267 |
|
|
log_error("nat_t_add_nat_d: malloc (%lu) failed", |
268 |
|
|
(unsigned long)buflen); |
269 |
|
|
free(hbuf); |
270 |
|
|
return -1; |
271 |
|
|
} |
272 |
|
|
|
273 |
|
|
SET_ISAKMP_GEN_LENGTH(buf, buflen); |
274 |
|
|
memcpy(buf + ISAKMP_NAT_D_DATA_OFF, hbuf, hbuflen); |
275 |
|
|
free(hbuf); |
276 |
|
|
|
277 |
|
|
if (msg->exchange->flags & EXCHANGE_FLAG_NAT_T_RFC) |
278 |
|
|
ret = message_add_payload(msg, ISAKMP_PAYLOAD_NAT_D, buf, |
279 |
|
|
buflen, 1); |
280 |
|
|
else if (msg->exchange->flags & EXCHANGE_FLAG_NAT_T_DRAFT) |
281 |
|
|
ret = message_add_payload(msg, ISAKMP_PAYLOAD_NAT_D_DRAFT, |
282 |
|
|
buf, buflen, 1); |
283 |
|
|
else |
284 |
|
|
ret = -1; |
285 |
|
|
|
286 |
|
|
if (ret) { |
287 |
|
|
free(buf); |
288 |
|
|
return -1; |
289 |
|
|
} |
290 |
|
|
return 0; |
291 |
|
|
} |
292 |
|
|
|
293 |
|
|
/* We add two NAT-D payloads, one each for src and dst. */ |
294 |
|
|
int |
295 |
|
|
nat_t_exchange_add_nat_d(struct message *msg) |
296 |
|
|
{ |
297 |
|
|
struct sockaddr *sa; |
298 |
|
|
|
299 |
|
|
/* Remote address first. */ |
300 |
|
|
msg->transport->vtbl->get_dst(msg->transport, &sa); |
301 |
|
|
if (nat_t_add_nat_d(msg, sa)) |
302 |
|
|
return -1; |
303 |
|
|
|
304 |
|
|
msg->transport->vtbl->get_src(msg->transport, &sa); |
305 |
|
|
if (nat_t_add_nat_d(msg, sa)) |
306 |
|
|
return -1; |
307 |
|
|
return 0; |
308 |
|
|
} |
309 |
|
|
|
310 |
|
|
/* Generate and match a NAT-D hash against the NAT-D payload (pl.) data. */ |
311 |
|
|
static int |
312 |
|
|
nat_t_match_nat_d_payload(struct message *msg, struct sockaddr *sa) |
313 |
|
|
{ |
314 |
|
|
struct payload *p; |
315 |
|
|
u_int8_t *hbuf; |
316 |
|
|
size_t hbuflen; |
317 |
|
|
int found = 0; |
318 |
|
|
|
319 |
|
|
/* |
320 |
|
|
* If there are no NAT-D payloads in the message, return "found" |
321 |
|
|
* as this will avoid NAT-T (see nat_t_exchange_check_nat_d()). |
322 |
|
|
*/ |
323 |
|
|
if ((p = payload_first(msg, ISAKMP_PAYLOAD_NAT_D_DRAFT)) == NULL && |
324 |
|
|
(p = payload_first(msg, ISAKMP_PAYLOAD_NAT_D)) == NULL) |
325 |
|
|
return 1; |
326 |
|
|
|
327 |
|
|
hbuf = nat_t_generate_nat_d_hash(msg, sa, &hbuflen); |
328 |
|
|
if (!hbuf) |
329 |
|
|
return 0; |
330 |
|
|
|
331 |
|
|
for (; p; p = TAILQ_NEXT(p, link)) { |
332 |
|
|
if (GET_ISAKMP_GEN_LENGTH (p->p) != |
333 |
|
|
hbuflen + ISAKMP_NAT_D_DATA_OFF) |
334 |
|
|
continue; |
335 |
|
|
|
336 |
|
|
if (memcmp(p->p + ISAKMP_NAT_D_DATA_OFF, hbuf, hbuflen) == 0) { |
337 |
|
|
found++; |
338 |
|
|
break; |
339 |
|
|
} |
340 |
|
|
} |
341 |
|
|
free(hbuf); |
342 |
|
|
return found; |
343 |
|
|
} |
344 |
|
|
|
345 |
|
|
/* |
346 |
|
|
* Check if we need to activate NAT-T, and if we need to send keepalive |
347 |
|
|
* messages to the other side, i.e if we are a nat:ed peer. |
348 |
|
|
*/ |
349 |
|
|
int |
350 |
|
|
nat_t_exchange_check_nat_d(struct message *msg) |
351 |
|
|
{ |
352 |
|
|
struct sockaddr *sa; |
353 |
|
|
int outgoing_path_is_clear, incoming_path_is_clear; |
354 |
|
|
|
355 |
|
|
/* Assume trouble, i.e NAT-boxes in our path. */ |
356 |
|
|
outgoing_path_is_clear = incoming_path_is_clear = 0; |
357 |
|
|
|
358 |
|
|
msg->transport->vtbl->get_src(msg->transport, &sa); |
359 |
|
|
if (nat_t_match_nat_d_payload(msg, sa)) |
360 |
|
|
outgoing_path_is_clear = 1; |
361 |
|
|
|
362 |
|
|
msg->transport->vtbl->get_dst(msg->transport, &sa); |
363 |
|
|
if (nat_t_match_nat_d_payload(msg, sa)) |
364 |
|
|
incoming_path_is_clear = 1; |
365 |
|
|
|
366 |
|
|
if (outgoing_path_is_clear && incoming_path_is_clear) { |
367 |
|
|
LOG_DBG((LOG_EXCHANGE, 40, "nat_t_exchange_check_nat_d: " |
368 |
|
|
"no NAT")); |
369 |
|
|
return 0; /* No NAT-T required. */ |
370 |
|
|
} |
371 |
|
|
|
372 |
|
|
/* NAT-T handling required. */ |
373 |
|
|
msg->exchange->flags |= EXCHANGE_FLAG_NAT_T_ENABLE; |
374 |
|
|
|
375 |
|
|
if (!outgoing_path_is_clear) { |
376 |
|
|
msg->exchange->flags |= EXCHANGE_FLAG_NAT_T_KEEPALIVE; |
377 |
|
|
LOG_DBG((LOG_EXCHANGE, 10, "nat_t_exchange_check_nat_d: " |
378 |
|
|
"NAT detected, we're behind it")); |
379 |
|
|
} else |
380 |
|
|
LOG_DBG ((LOG_EXCHANGE, 10, |
381 |
|
|
"nat_t_exchange_check_nat_d: NAT detected")); |
382 |
|
|
return 1; |
383 |
|
|
} |
384 |
|
|
|
385 |
|
|
static void |
386 |
|
|
nat_t_send_keepalive(void *v_arg) |
387 |
|
|
{ |
388 |
|
|
struct sa *sa = (struct sa *)v_arg; |
389 |
|
|
struct transport *t; |
390 |
|
|
struct timeval now; |
391 |
|
|
int interval; |
392 |
|
|
|
393 |
|
|
/* Send the keepalive message. */ |
394 |
|
|
t = ((struct virtual_transport *)sa->transport)->encap; |
395 |
|
|
t->vtbl->send_message(NULL, t); |
396 |
|
|
|
397 |
|
|
/* Set new timer. */ |
398 |
|
|
interval = conf_get_num("General", "NAT-T-Keepalive", 0); |
399 |
|
|
if (interval < 1) |
400 |
|
|
interval = NAT_T_KEEPALIVE_INTERVAL; |
401 |
|
|
gettimeofday(&now, 0); |
402 |
|
|
now.tv_sec += interval; |
403 |
|
|
|
404 |
|
|
sa->nat_t_keepalive = timer_add_event("nat_t_send_keepalive", |
405 |
|
|
nat_t_send_keepalive, v_arg, &now); |
406 |
|
|
if (!sa->nat_t_keepalive) |
407 |
|
|
log_print("nat_t_send_keepalive: " |
408 |
|
|
"timer_add_event() failed, will send no more keepalives"); |
409 |
|
|
} |
410 |
|
|
|
411 |
|
|
void |
412 |
|
|
nat_t_setup_keepalive(struct sa *sa) |
413 |
|
|
{ |
414 |
|
|
struct sockaddr *src; |
415 |
|
|
struct timeval now; |
416 |
|
|
|
417 |
|
|
if (sa->initiator) |
418 |
|
|
sa->transport->vtbl->get_src(sa->transport, &src); |
419 |
|
|
else |
420 |
|
|
sa->transport->vtbl->get_dst(sa->transport, &src); |
421 |
|
|
|
422 |
|
|
if (!virtual_listen_lookup(src)) |
423 |
|
|
return; |
424 |
|
|
|
425 |
|
|
gettimeofday(&now, 0); |
426 |
|
|
now.tv_sec += NAT_T_KEEPALIVE_INTERVAL; |
427 |
|
|
|
428 |
|
|
sa->nat_t_keepalive = timer_add_event("nat_t_send_keepalive", |
429 |
|
|
nat_t_send_keepalive, sa, &now); |
430 |
|
|
if (!sa->nat_t_keepalive) |
431 |
|
|
log_print("nat_t_setup_keepalive: " |
432 |
|
|
"timer_add_event() failed, will not send keepalives"); |
433 |
|
|
|
434 |
|
|
LOG_DBG((LOG_TRANSPORT, 50, "nat_t_setup_keepalive: " |
435 |
|
|
"added event for phase 1 SA %p", sa)); |
436 |
|
|
} |