Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 :
4 : PAC Glue between Samba and the KDC
5 :
6 : Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
7 : Copyright (C) Simo Sorce <idra@samba.org> 2010
8 :
9 : This program is free software; you can redistribute it and/or modify
10 : it under the terms of the GNU General Public License as published by
11 : the Free Software Foundation; either version 3 of the License, or
12 : (at your option) any later version.
13 :
14 : This program is distributed in the hope that it will be useful,
15 : but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : GNU General Public License for more details.
18 :
19 :
20 : You should have received a copy of the GNU General Public License
21 : along with this program. If not, see <http://www.gnu.org/licenses/>.
22 : */
23 :
24 : #include "includes.h"
25 : #include "kdc/kdc-glue.h"
26 : #include "kdc/db-glue.h"
27 : #include "kdc/pac-glue.h"
28 : #include "sdb.h"
29 : #include "sdb_hdb.h"
30 : #include "librpc/gen_ndr/auth.h"
31 : #include <krb5_locl.h>
32 :
33 55003 : static bool samba_wdc_is_s4u2self_req(astgs_request_t r)
34 : {
35 55003 : krb5_kdc_configuration *config = kdc_request_get_config((kdc_request_t)r);
36 55003 : const KDC_REQ *req = kdc_request_get_req(r);
37 55003 : const PA_DATA *pa_for_user = NULL;
38 :
39 55003 : if (req->msg_type != krb_tgs_req) {
40 18327 : return false;
41 : }
42 :
43 36676 : if (config->enable_fast && req->padata != NULL) {
44 32848 : const PA_DATA *pa_fx_fast = NULL;
45 32848 : int idx = 0;
46 :
47 32848 : pa_fx_fast = krb5_find_padata(req->padata->val,
48 32848 : req->padata->len,
49 : KRB5_PADATA_FX_FAST,
50 : &idx);
51 32848 : if (pa_fx_fast != NULL) {
52 : /*
53 : * We're in the outer request
54 : * with KRB5_PADATA_FX_FAST
55 : * if fast is enabled we'll
56 : * process the s4u2self
57 : * request only in the
58 : * inner request.
59 : */
60 0 : return false;
61 : }
62 : }
63 :
64 36676 : if (req->padata != NULL) {
65 36676 : int idx = 0;
66 :
67 36676 : pa_for_user = krb5_find_padata(req->padata->val,
68 36676 : req->padata->len,
69 : KRB5_PADATA_FOR_USER,
70 : &idx);
71 : }
72 :
73 36676 : if (pa_for_user != NULL) {
74 1278 : return true;
75 : }
76 :
77 35398 : return false;
78 : }
79 :
80 : /*
81 : * Given the right private pointer from hdb_samba4,
82 : * get a PAC from the attached ldb messages.
83 : *
84 : * For PKINIT we also get pk_reply_key and can add PAC_CREDENTIAL_INFO.
85 : */
86 18963 : static krb5_error_code samba_wdc_get_pac(void *priv,
87 : astgs_request_t r,
88 : hdb_entry *client,
89 : hdb_entry *server,
90 : const krb5_keyblock *pk_reply_key,
91 : uint64_t pac_attributes,
92 : krb5_pac *pac)
93 : {
94 18963 : krb5_context context = kdc_request_get_context((kdc_request_t)r);
95 : TALLOC_CTX *mem_ctx;
96 18963 : DATA_BLOB *logon_blob = NULL;
97 18963 : DATA_BLOB *cred_ndr = NULL;
98 18963 : DATA_BLOB **cred_ndr_ptr = NULL;
99 18963 : DATA_BLOB _cred_blob = data_blob_null;
100 18963 : DATA_BLOB *cred_blob = NULL;
101 18963 : DATA_BLOB *upn_blob = NULL;
102 18963 : DATA_BLOB *pac_attrs_blob = NULL;
103 18963 : DATA_BLOB *requester_sid_blob = NULL;
104 : krb5_error_code ret;
105 : NTSTATUS nt_status;
106 18963 : struct samba_kdc_entry *skdc_entry =
107 18963 : talloc_get_type_abort(client->context,
108 : struct samba_kdc_entry);
109 : bool is_krbtgt;
110 18963 : bool is_s4u2self = samba_wdc_is_s4u2self_req(r);
111 18963 : enum samba_asserted_identity asserted_identity =
112 : (is_s4u2self) ?
113 18963 : SAMBA_ASSERTED_IDENTITY_SERVICE :
114 : SAMBA_ASSERTED_IDENTITY_AUTHENTICATION_AUTHORITY;
115 :
116 18963 : mem_ctx = talloc_named(client->context, 0, "samba_get_pac context");
117 18963 : if (!mem_ctx) {
118 0 : return ENOMEM;
119 : }
120 :
121 18963 : if (pk_reply_key != NULL) {
122 0 : cred_ndr_ptr = &cred_ndr;
123 : }
124 :
125 18963 : is_krbtgt = krb5_principal_is_krbtgt(context, server->principal);
126 :
127 18963 : nt_status = samba_kdc_get_pac_blobs(mem_ctx, skdc_entry,
128 : asserted_identity,
129 : &logon_blob,
130 : cred_ndr_ptr,
131 : &upn_blob,
132 : is_krbtgt ? &pac_attrs_blob : NULL,
133 : pac_attributes,
134 : is_krbtgt ? &requester_sid_blob : NULL);
135 18963 : if (!NT_STATUS_IS_OK(nt_status)) {
136 0 : talloc_free(mem_ctx);
137 0 : return EINVAL;
138 : }
139 :
140 18963 : if (pk_reply_key != NULL && cred_ndr != NULL) {
141 0 : ret = samba_kdc_encrypt_pac_credentials(context,
142 : pk_reply_key,
143 : cred_ndr,
144 : mem_ctx,
145 : &_cred_blob);
146 0 : if (ret != 0) {
147 0 : talloc_free(mem_ctx);
148 0 : return ret;
149 : }
150 0 : cred_blob = &_cred_blob;
151 : }
152 :
153 18963 : ret = krb5_pac_init(context, pac);
154 18963 : if (ret != 0) {
155 0 : talloc_free(mem_ctx);
156 0 : return ret;
157 : }
158 :
159 18963 : ret = samba_make_krb5_pac(context, logon_blob, cred_blob,
160 : upn_blob, pac_attrs_blob,
161 : requester_sid_blob, NULL, *pac);
162 :
163 18963 : talloc_free(mem_ctx);
164 18963 : return ret;
165 : }
166 :
167 36040 : static krb5_error_code samba_wdc_reget_pac2(astgs_request_t r,
168 : const krb5_principal delegated_proxy_principal,
169 : hdb_entry *client,
170 : hdb_entry *server,
171 : hdb_entry *krbtgt,
172 : krb5_pac *pac,
173 : krb5_cksumtype ctype)
174 : {
175 36040 : krb5_context context = kdc_request_get_context((kdc_request_t)r);
176 36040 : struct samba_kdc_entry *client_skdc_entry = NULL;
177 36040 : struct samba_kdc_entry *server_skdc_entry =
178 36040 : talloc_get_type_abort(server->context, struct samba_kdc_entry);
179 36040 : struct samba_kdc_entry *krbtgt_skdc_entry =
180 36040 : talloc_get_type_abort(krbtgt->context, struct samba_kdc_entry);
181 36040 : TALLOC_CTX *mem_ctx = NULL;
182 36040 : krb5_pac new_pac = NULL;
183 : krb5_error_code ret;
184 36040 : bool is_s4u2self = samba_wdc_is_s4u2self_req(r);
185 36040 : bool is_in_db = false;
186 36040 : bool is_untrusted = false;
187 36040 : uint32_t flags = 0;
188 :
189 36040 : mem_ctx = talloc_named(NULL, 0, "samba_kdc_reget_pac2 context");
190 36040 : if (mem_ctx == NULL) {
191 0 : return ENOMEM;
192 : }
193 :
194 36040 : if (client != NULL) {
195 35991 : client_skdc_entry = talloc_get_type_abort(client->context,
196 : struct samba_kdc_entry);
197 : }
198 :
199 : /*
200 : * If the krbtgt was generated by an RODC, and we are not that
201 : * RODC, then we need to regenerate the PAC - we can't trust
202 : * it, and confirm that the RODC was permitted to print this ticket
203 : *
204 : * Becasue of the samba_kdc_validate_pac_blob() step we can be
205 : * sure that the record in 'client' matches the SID in the
206 : * original PAC.
207 : */
208 36040 : ret = samba_krbtgt_is_in_db(krbtgt_skdc_entry, &is_in_db, &is_untrusted);
209 36040 : if (ret != 0) {
210 0 : goto out;
211 : }
212 :
213 36040 : if (is_s4u2self) {
214 642 : flags |= SAMBA_KDC_FLAG_PROTOCOL_TRANSITION;
215 : }
216 :
217 36040 : if (delegated_proxy_principal != NULL) {
218 : krb5_enctype etype;
219 76 : Key *key = NULL;
220 :
221 76 : if (!is_in_db) {
222 : /*
223 : * The RODC-issued PAC was signed by a KDC entry that we
224 : * don't have a key for. The server signature is not
225 : * trustworthy, since it could have been created by the
226 : * server we got the ticket from. We must not proceed as
227 : * otherwise the ticket signature is unchecked.
228 : */
229 0 : ret = HDB_ERR_NOT_FOUND_HERE;
230 25 : goto out;
231 : }
232 :
233 : /* Fetch the correct key depending on the checksum type. */
234 76 : if (ctype == CKSUMTYPE_HMAC_MD5) {
235 34 : etype = ENCTYPE_ARCFOUR_HMAC;
236 : } else {
237 42 : ret = krb5_cksumtype_to_enctype(context,
238 : ctype,
239 : &etype);
240 42 : if (ret != 0) {
241 4 : goto out;
242 : }
243 : }
244 72 : ret = hdb_enctype2key(context, krbtgt, NULL, etype, &key);
245 72 : if (ret != 0) {
246 1 : goto out;
247 : }
248 :
249 : /* Check the KDC, whole-PAC and ticket signatures. */
250 71 : ret = krb5_pac_verify(context,
251 : *pac,
252 : 0,
253 : NULL,
254 : NULL,
255 71 : &key->key);
256 71 : if (ret != 0) {
257 20 : DEBUG(1, ("PAC KDC signature failed to verify\n"));
258 20 : goto out;
259 : }
260 :
261 51 : flags |= SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION;
262 : }
263 :
264 36015 : if (is_untrusted) {
265 47 : flags |= SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED;
266 : }
267 :
268 36015 : if (is_in_db) {
269 35967 : flags |= SAMBA_KDC_FLAG_KRBTGT_IN_DB;
270 : }
271 :
272 36015 : ret = krb5_pac_init(context, &new_pac);
273 36015 : if (ret != 0) {
274 0 : new_pac = NULL;
275 0 : goto out;
276 : }
277 :
278 72030 : ret = samba_kdc_update_pac(mem_ctx,
279 : context,
280 36015 : krbtgt_skdc_entry->kdc_db_ctx->samdb,
281 : flags,
282 : client_skdc_entry,
283 36015 : server->principal,
284 : server_skdc_entry,
285 : krbtgt_skdc_entry,
286 : delegated_proxy_principal,
287 : *pac,
288 : new_pac);
289 36015 : if (ret != 0) {
290 9 : krb5_pac_free(context, new_pac);
291 9 : if (ret == ENODATA) {
292 9 : krb5_pac_free(context, *pac);
293 9 : *pac = NULL;
294 9 : ret = 0;
295 : }
296 9 : goto out;
297 : }
298 :
299 : /* Replace the pac */
300 36006 : krb5_pac_free(context, *pac);
301 36006 : *pac = new_pac;
302 :
303 36040 : out:
304 36040 : talloc_free(mem_ctx);
305 36040 : return ret;
306 : }
307 :
308 : /* Resign (and reform, including possibly new groups) a PAC */
309 :
310 36042 : static krb5_error_code samba_wdc_reget_pac(void *priv, astgs_request_t r,
311 : const krb5_principal client_principal,
312 : const krb5_principal delegated_proxy_principal,
313 : hdb_entry *client,
314 : hdb_entry *server,
315 : hdb_entry *krbtgt,
316 : krb5_pac *pac)
317 : {
318 36042 : krb5_context context = kdc_request_get_context((kdc_request_t)r);
319 36042 : krb5_kdc_configuration *config = kdc_request_get_config((kdc_request_t)r);
320 36042 : struct samba_kdc_entry *krbtgt_skdc_entry =
321 36042 : talloc_get_type_abort(krbtgt->context,
322 : struct samba_kdc_entry);
323 : krb5_error_code ret;
324 36042 : krb5_cksumtype ctype = CKSUMTYPE_NONE;
325 : hdb_entry signing_krbtgt_hdb;
326 :
327 36042 : if (delegated_proxy_principal) {
328 : uint16_t rodc_id;
329 : unsigned int my_krbtgt_number;
330 :
331 : /*
332 : * We're using delegated_proxy_principal for the moment to
333 : * indicate cases where the ticket was encrypted with the server
334 : * key, and not a krbtgt key. This cannot be trusted, so we need
335 : * to find a krbtgt key that signs the PAC in order to trust the
336 : * ticket.
337 : *
338 : * The krbtgt passed in to this function refers to the krbtgt
339 : * used to decrypt the ticket of the server requesting
340 : * S4U2Proxy.
341 : *
342 : * When we implement service ticket renewal, we need to check
343 : * the PAC, and this will need to be updated.
344 : */
345 78 : ret = krb5_pac_get_kdc_checksum_info(context,
346 : *pac,
347 : &ctype,
348 : &rodc_id);
349 78 : if (ret != 0) {
350 2 : DEBUG(1, ("Failed to get PAC checksum info\n"));
351 4 : return ret;
352 : }
353 :
354 : /*
355 : * We need to check the KDC and ticket signatures, fetching the
356 : * correct key based on the enctype.
357 : */
358 :
359 76 : my_krbtgt_number = krbtgt_skdc_entry->kdc_db_ctx->my_krbtgt_number;
360 :
361 76 : if (my_krbtgt_number != 0) {
362 : /*
363 : * If we are an RODC, and we are not the KDC that signed
364 : * the evidence ticket, then we need to proxy the
365 : * request.
366 : */
367 0 : if (rodc_id != my_krbtgt_number) {
368 0 : return HDB_ERR_NOT_FOUND_HERE;
369 : }
370 : } else {
371 : /*
372 : * If we are a DC, the ticket may have been signed by a
373 : * different KDC than the one that issued the header
374 : * ticket.
375 : */
376 76 : if (rodc_id != krbtgt->kvno >> 16) {
377 : struct sdb_entry signing_krbtgt_sdb;
378 :
379 : /*
380 : * If we didn't sign the ticket, then return an
381 : * error.
382 : */
383 0 : if (rodc_id != 0) {
384 0 : return KRB5KRB_AP_ERR_MODIFIED;
385 : }
386 :
387 : /*
388 : * Fetch our key from the database. To support
389 : * key rollover, we're going to need to try
390 : * multiple keys by trial and error. For now,
391 : * krbtgt keys aren't assumed to change.
392 : */
393 0 : ret = samba_kdc_fetch(context,
394 : krbtgt_skdc_entry->kdc_db_ctx,
395 0 : krbtgt->principal,
396 : SDB_F_GET_KRBTGT | SDB_F_CANON,
397 : 0,
398 : &signing_krbtgt_sdb);
399 0 : if (ret != 0) {
400 0 : return ret;
401 : }
402 :
403 0 : ret = sdb_entry_to_hdb_entry(context,
404 : &signing_krbtgt_sdb,
405 : &signing_krbtgt_hdb);
406 0 : sdb_entry_free(&signing_krbtgt_sdb);
407 0 : if (ret != 0) {
408 0 : return ret;
409 : }
410 :
411 : /*
412 : * Replace the krbtgt entry with our own entry
413 : * for further processing.
414 : */
415 0 : krbtgt = &signing_krbtgt_hdb;
416 : }
417 : }
418 35964 : } else if (!krbtgt_skdc_entry->is_trust) {
419 : /*
420 : * We expect to have received a TGT, so check that we haven't
421 : * been given a kpasswd ticket instead. We don't need to do this
422 : * check for an incoming trust, as they use a different secret
423 : * and can't be confused with a normal TGT.
424 : */
425 35916 : krb5_ticket *tgt = kdc_request_get_ticket(r);
426 :
427 35916 : struct timeval now = krb5_kdc_get_time();
428 :
429 : /*
430 : * Check if the ticket is in the last two minutes of its
431 : * life.
432 : */
433 35916 : KerberosTime lifetime = rk_time_sub(tgt->ticket.endtime, now.tv_sec);
434 35916 : if (lifetime <= CHANGEPW_LIFETIME) {
435 : /*
436 : * This ticket has at most two minutes left to live. It
437 : * may be a kpasswd ticket rather than a TGT, so don't
438 : * accept it.
439 : */
440 0 : kdc_audit_addreason((kdc_request_t)r,
441 : "Ticket is not a ticket-granting ticket");
442 0 : return KRB5KRB_AP_ERR_TKT_EXPIRED;
443 : }
444 : }
445 :
446 36040 : ret = samba_wdc_reget_pac2(r,
447 : delegated_proxy_principal,
448 : client,
449 : server,
450 : krbtgt,
451 : pac,
452 : ctype);
453 :
454 36040 : if (krbtgt == &signing_krbtgt_hdb) {
455 0 : hdb_free_entry(context, config->db[0], &signing_krbtgt_hdb);
456 : }
457 :
458 36040 : return ret;
459 : }
460 :
461 18413 : static char *get_netbios_name(TALLOC_CTX *mem_ctx, HostAddresses *addrs)
462 : {
463 18413 : char *nb_name = NULL;
464 : size_t len;
465 : unsigned int i;
466 :
467 18413 : for (i = 0; addrs && i < addrs->len; i++) {
468 90 : if (addrs->val[i].addr_type != KRB5_ADDRESS_NETBIOS) {
469 0 : continue;
470 : }
471 90 : len = MIN(addrs->val[i].address.length, 15);
472 90 : nb_name = talloc_strndup(mem_ctx,
473 90 : addrs->val[i].address.data, len);
474 90 : if (nb_name) {
475 90 : break;
476 : }
477 : }
478 :
479 18413 : if ((nb_name == NULL) || (nb_name[0] == '\0')) {
480 18323 : return NULL;
481 : }
482 :
483 : /* Strip space padding */
484 540 : for (len = strlen(nb_name) - 1;
485 450 : (len > 0) && (nb_name[len] == ' ');
486 360 : --len) {
487 360 : nb_name[len] = '\0';
488 : }
489 :
490 90 : return nb_name;
491 : }
492 :
493 : /* this function allocates 'data' using malloc.
494 : * The caller is responsible for freeing it */
495 4 : static void samba_kdc_build_edata_reply(NTSTATUS nt_status, krb5_data *e_data)
496 : {
497 4 : e_data->data = malloc(12);
498 4 : if (e_data->data == NULL) {
499 0 : e_data->length = 0;
500 0 : e_data->data = NULL;
501 0 : return;
502 : }
503 4 : e_data->length = 12;
504 :
505 4 : SIVAL(e_data->data, 0, NT_STATUS_V(nt_status));
506 4 : SIVAL(e_data->data, 4, 0);
507 4 : SIVAL(e_data->data, 8, 1);
508 :
509 4 : return;
510 : }
511 :
512 18413 : static krb5_error_code samba_wdc_check_client_access(void *priv,
513 : astgs_request_t r)
514 : {
515 : struct samba_kdc_entry *kdc_entry;
516 : bool password_change;
517 : char *workstation;
518 : NTSTATUS nt_status;
519 :
520 :
521 18413 : kdc_entry = talloc_get_type(kdc_request_get_client(r)->context, struct samba_kdc_entry);
522 18413 : password_change = (kdc_request_get_server(r) && kdc_request_get_server(r)->flags.change_pw);
523 18413 : workstation = get_netbios_name((TALLOC_CTX *)kdc_request_get_client(r)->context,
524 18413 : kdc_request_get_req(r)->req_body.addresses);
525 :
526 18413 : nt_status = samba_kdc_check_client_access(kdc_entry,
527 : kdc_request_get_cname((kdc_request_t)r),
528 : workstation,
529 : password_change);
530 :
531 18413 : if (!NT_STATUS_IS_OK(nt_status)) {
532 4 : if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MEMORY)) {
533 0 : return ENOMEM;
534 : }
535 :
536 4 : if (kdc_request_get_rep(r)->padata) {
537 : int ret;
538 : krb5_data kd;
539 :
540 4 : samba_kdc_build_edata_reply(nt_status, &kd);
541 4 : ret = krb5_padata_add(kdc_request_get_context((kdc_request_t)r), kdc_request_get_rep(r)->padata,
542 : KRB5_PADATA_PW_SALT,
543 : kd.data, kd.length);
544 4 : if (ret != 0) {
545 : /*
546 : * So we do not leak the allocated
547 : * memory on kd in the error case
548 : */
549 0 : krb5_data_free(&kd);
550 : }
551 : }
552 :
553 4 : return samba_kdc_map_policy_err(nt_status);
554 : }
555 :
556 : /* Now do the standard Heimdal check */
557 18409 : return KRB5_PLUGIN_NO_HANDLE;
558 : }
559 :
560 : /* this function allocates 'data' using malloc.
561 : * The caller is responsible for freeing it */
562 23439 : static krb5_error_code samba_kdc_build_supported_etypes(uint32_t supported_etypes,
563 : krb5_data *e_data)
564 : {
565 23439 : e_data->data = malloc(4);
566 23439 : if (e_data->data == NULL) {
567 0 : return ENOMEM;
568 : }
569 23439 : e_data->length = 4;
570 :
571 23439 : PUSH_LE_U32(e_data->data, 0, supported_etypes);
572 :
573 23439 : return 0;
574 : }
575 :
576 54055 : static krb5_error_code samba_wdc_finalize_reply(void *priv,
577 : astgs_request_t r)
578 : {
579 : struct samba_kdc_entry *server_kdc_entry;
580 : uint32_t supported_enctypes;
581 :
582 54055 : server_kdc_entry = talloc_get_type(kdc_request_get_server(r)->context, struct samba_kdc_entry);
583 :
584 : /*
585 : * If the canonicalize flag is set, add PA-SUPPORTED-ENCTYPES padata
586 : * type to indicate what encryption types the server supports.
587 : */
588 54055 : supported_enctypes = server_kdc_entry->supported_enctypes;
589 54055 : if (kdc_request_get_req(r)->req_body.kdc_options.canonicalize && supported_enctypes != 0) {
590 : krb5_error_code ret;
591 :
592 : PA_DATA md;
593 :
594 23439 : ret = samba_kdc_build_supported_etypes(supported_enctypes, &md.padata_value);
595 23439 : if (ret != 0) {
596 0 : return ret;
597 : }
598 :
599 23439 : md.padata_type = KRB5_PADATA_SUPPORTED_ETYPES;
600 :
601 23439 : ret = kdc_request_add_encrypted_padata(r, &md);
602 23439 : if (ret != 0) {
603 : /*
604 : * So we do not leak the allocated
605 : * memory on kd in the error case
606 : */
607 0 : krb5_data_free(&md.padata_value);
608 : }
609 : }
610 :
611 54055 : return 0;
612 : }
613 :
614 71 : static krb5_error_code samba_wdc_plugin_init(krb5_context context, void **ptr)
615 : {
616 71 : *ptr = NULL;
617 71 : return 0;
618 : }
619 :
620 0 : static void samba_wdc_plugin_fini(void *ptr)
621 : {
622 0 : return;
623 : }
624 :
625 1092 : static krb5_error_code samba_wdc_referral_policy(void *priv,
626 : astgs_request_t r)
627 : {
628 1092 : return kdc_request_get_error_code((kdc_request_t)r);
629 : }
630 :
631 : struct krb5plugin_kdc_ftable kdc_plugin_table = {
632 : .minor_version = KRB5_PLUGIN_KDC_VERSION_10,
633 : .init = samba_wdc_plugin_init,
634 : .fini = samba_wdc_plugin_fini,
635 : .pac_verify = samba_wdc_reget_pac,
636 : .client_access = samba_wdc_check_client_access,
637 : .finalize_reply = samba_wdc_finalize_reply,
638 : .pac_generate = samba_wdc_get_pac,
639 : .referral_policy = samba_wdc_referral_policy,
640 : };
641 :
642 :
|