155682Smarkm/*
2233294Sstas * Copyright (c) 1997-2004 Kungliga Tekniska H��gskolan
3233294Sstas * (Royal Institute of Technology, Stockholm, Sweden).
4233294Sstas * All rights reserved.
555682Smarkm *
6233294Sstas * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
755682Smarkm *
8233294Sstas * Redistribution and use in source and binary forms, with or without
9233294Sstas * modification, are permitted provided that the following conditions
10233294Sstas * are met:
1155682Smarkm *
12233294Sstas * 1. Redistributions of source code must retain the above copyright
13233294Sstas *    notice, this list of conditions and the following disclaimer.
1455682Smarkm *
15233294Sstas * 2. Redistributions in binary form must reproduce the above copyright
16233294Sstas *    notice, this list of conditions and the following disclaimer in the
17233294Sstas *    documentation and/or other materials provided with the distribution.
1855682Smarkm *
19233294Sstas * 3. Neither the name of the Institute nor the names of its contributors
20233294Sstas *    may be used to endorse or promote products derived from this software
21233294Sstas *    without specific prior written permission.
22233294Sstas *
23233294Sstas * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24233294Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25233294Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26233294Sstas * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27233294Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28233294Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29233294Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30233294Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31233294Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32233294Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33233294Sstas * SUCH DAMAGE.
3455682Smarkm */
3555682Smarkm
3655682Smarkm#include "krb5_locl.h"
3755682Smarkm
3855682Smarkmtypedef struct krb5_mcache {
3972445Sassar    char *name;
4072445Sassar    unsigned int refcnt;
41127808Snectar    int dead;
4255682Smarkm    krb5_principal primary_principal;
4355682Smarkm    struct link {
4455682Smarkm	krb5_creds cred;
4555682Smarkm	struct link *next;
4655682Smarkm    } *creds;
4772445Sassar    struct krb5_mcache *next;
48233294Sstas    time_t mtime;
49233294Sstas    krb5_deltat kdc_offset;
5055682Smarkm} krb5_mcache;
5155682Smarkm
52178825Sdfrstatic HEIMDAL_MUTEX mcc_mutex = HEIMDAL_MUTEX_INITIALIZER;
5372445Sassarstatic struct krb5_mcache *mcc_head;
5472445Sassar
5572445Sassar#define	MCACHE(X)	((krb5_mcache *)(X)->data.data)
5672445Sassar
57127808Snectar#define MISDEAD(X)	((X)->dead)
5872445Sassar
59233294Sstasstatic const char* KRB5_CALLCONV
6055682Smarkmmcc_get_name(krb5_context context,
6155682Smarkm	     krb5_ccache id)
6255682Smarkm{
6372445Sassar    return MCACHE(id)->name;
6455682Smarkm}
6555682Smarkm
66233294Sstasstatic krb5_mcache * KRB5_CALLCONV
6772445Sassarmcc_alloc(const char *name)
6872445Sassar{
69178825Sdfr    krb5_mcache *m, *m_c;
70233294Sstas    int ret = 0;
7178527Sassar
7272445Sassar    ALLOC(m, 1);
7372445Sassar    if(m == NULL)
7472445Sassar	return NULL;
7572445Sassar    if(name == NULL)
76233294Sstas	ret = asprintf(&m->name, "%p", m);
7772445Sassar    else
7872445Sassar	m->name = strdup(name);
79233294Sstas    if(ret < 0 || m->name == NULL) {
8072445Sassar	free(m);
8172445Sassar	return NULL;
8272445Sassar    }
83178825Sdfr    /* check for dups first */
84178825Sdfr    HEIMDAL_MUTEX_lock(&mcc_mutex);
85178825Sdfr    for (m_c = mcc_head; m_c != NULL; m_c = m_c->next)
86178825Sdfr	if (strcmp(m->name, m_c->name) == 0)
87178825Sdfr	    break;
88178825Sdfr    if (m_c) {
89178825Sdfr	free(m->name);
90178825Sdfr	free(m);
91178825Sdfr	HEIMDAL_MUTEX_unlock(&mcc_mutex);
92178825Sdfr	return NULL;
93178825Sdfr    }
94178825Sdfr
95127808Snectar    m->dead = 0;
9672445Sassar    m->refcnt = 1;
9772445Sassar    m->primary_principal = NULL;
9872445Sassar    m->creds = NULL;
99233294Sstas    m->mtime = time(NULL);
100233294Sstas    m->kdc_offset = 0;
10172445Sassar    m->next = mcc_head;
10272445Sassar    mcc_head = m;
103178825Sdfr    HEIMDAL_MUTEX_unlock(&mcc_mutex);
10472445Sassar    return m;
10572445Sassar}
10672445Sassar
107233294Sstasstatic krb5_error_code KRB5_CALLCONV
10855682Smarkmmcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
10955682Smarkm{
11072445Sassar    krb5_mcache *m;
11172445Sassar
112178825Sdfr    HEIMDAL_MUTEX_lock(&mcc_mutex);
11372445Sassar    for (m = mcc_head; m != NULL; m = m->next)
11472445Sassar	if (strcmp(m->name, res) == 0)
11572445Sassar	    break;
116178825Sdfr    HEIMDAL_MUTEX_unlock(&mcc_mutex);
11772445Sassar
11872445Sassar    if (m != NULL) {
11972445Sassar	m->refcnt++;
12072445Sassar	(*id)->data.data = m;
12172445Sassar	(*id)->data.length = sizeof(*m);
12272445Sassar	return 0;
12372445Sassar    }
12472445Sassar
12572445Sassar    m = mcc_alloc(res);
12678527Sassar    if (m == NULL) {
127233294Sstas	krb5_set_error_message(context, KRB5_CC_NOMEM,
128233294Sstas			       N_("malloc: out of memory", ""));
12972445Sassar	return KRB5_CC_NOMEM;
13078527Sassar    }
131233294Sstas
13272445Sassar    (*id)->data.data = m;
13372445Sassar    (*id)->data.length = sizeof(*m);
13472445Sassar
13572445Sassar    return 0;
13655682Smarkm}
13755682Smarkm
13872445Sassar
139233294Sstasstatic krb5_error_code KRB5_CALLCONV
14055682Smarkmmcc_gen_new(krb5_context context, krb5_ccache *id)
14155682Smarkm{
14255682Smarkm    krb5_mcache *m;
14355682Smarkm
14472445Sassar    m = mcc_alloc(NULL);
14572445Sassar
14678527Sassar    if (m == NULL) {
147233294Sstas	krb5_set_error_message(context, KRB5_CC_NOMEM,
148233294Sstas			       N_("malloc: out of memory", ""));
14955682Smarkm	return KRB5_CC_NOMEM;
15078527Sassar    }
15172445Sassar
15255682Smarkm    (*id)->data.data = m;
15355682Smarkm    (*id)->data.length = sizeof(*m);
15472445Sassar
15555682Smarkm    return 0;
15655682Smarkm}
15755682Smarkm
158233294Sstasstatic krb5_error_code KRB5_CALLCONV
15955682Smarkmmcc_initialize(krb5_context context,
16055682Smarkm	       krb5_ccache id,
16155682Smarkm	       krb5_principal primary_principal)
16255682Smarkm{
163127808Snectar    krb5_mcache *m = MCACHE(id);
164127808Snectar    m->dead = 0;
165233294Sstas    m->mtime = time(NULL);
16672445Sassar    return krb5_copy_principal (context,
16772445Sassar				primary_principal,
168127808Snectar				&m->primary_principal);
16955682Smarkm}
17055682Smarkm
171178825Sdfrstatic int
172178825Sdfrmcc_close_internal(krb5_mcache *m)
17355682Smarkm{
17472445Sassar    if (--m->refcnt != 0)
17572445Sassar	return 0;
17655682Smarkm
17772445Sassar    if (MISDEAD(m)) {
17872445Sassar	free (m->name);
179178825Sdfr	return 1;
18055682Smarkm    }
181178825Sdfr    return 0;
182178825Sdfr}
18372445Sassar
184233294Sstasstatic krb5_error_code KRB5_CALLCONV
185178825Sdfrmcc_close(krb5_context context,
186178825Sdfr	  krb5_ccache id)
187178825Sdfr{
188178825Sdfr    if (mcc_close_internal(MCACHE(id)))
189178825Sdfr	krb5_data_free(&id->data);
19055682Smarkm    return 0;
19155682Smarkm}
19255682Smarkm
193233294Sstasstatic krb5_error_code KRB5_CALLCONV
19455682Smarkmmcc_destroy(krb5_context context,
19555682Smarkm	    krb5_ccache id)
19655682Smarkm{
19772445Sassar    krb5_mcache **n, *m = MCACHE(id);
19872445Sassar    struct link *l;
19972445Sassar
20072445Sassar    if (m->refcnt == 0)
20172445Sassar	krb5_abortx(context, "mcc_destroy: refcnt already 0");
20272445Sassar
20372445Sassar    if (!MISDEAD(m)) {
20472445Sassar	/* if this is an active mcache, remove it from the linked
20572445Sassar           list, and free all data */
206178825Sdfr	HEIMDAL_MUTEX_lock(&mcc_mutex);
20772445Sassar	for(n = &mcc_head; n && *n; n = &(*n)->next) {
20872445Sassar	    if(m == *n) {
20972445Sassar		*n = m->next;
21072445Sassar		break;
21172445Sassar	    }
21272445Sassar	}
213178825Sdfr	HEIMDAL_MUTEX_unlock(&mcc_mutex);
214127808Snectar	if (m->primary_principal != NULL) {
215127808Snectar	    krb5_free_principal (context, m->primary_principal);
216127808Snectar	    m->primary_principal = NULL;
217127808Snectar	}
218127808Snectar	m->dead = 1;
219127808Snectar
22072445Sassar	l = m->creds;
22172445Sassar	while (l != NULL) {
22272445Sassar	    struct link *old;
223233294Sstas
224178825Sdfr	    krb5_free_cred_contents (context, &l->cred);
22572445Sassar	    old = l;
22672445Sassar	    l = l->next;
22772445Sassar	    free (old);
22872445Sassar	}
22972445Sassar	m->creds = NULL;
23072445Sassar    }
23155682Smarkm    return 0;
23255682Smarkm}
23355682Smarkm
234233294Sstasstatic krb5_error_code KRB5_CALLCONV
23555682Smarkmmcc_store_cred(krb5_context context,
23655682Smarkm	       krb5_ccache id,
23755682Smarkm	       krb5_creds *creds)
23855682Smarkm{
23972445Sassar    krb5_mcache *m = MCACHE(id);
24055682Smarkm    krb5_error_code ret;
24155682Smarkm    struct link *l;
24255682Smarkm
24372445Sassar    if (MISDEAD(m))
24472445Sassar	return ENOENT;
24572445Sassar
24655682Smarkm    l = malloc (sizeof(*l));
24778527Sassar    if (l == NULL) {
248233294Sstas	krb5_set_error_message(context, KRB5_CC_NOMEM,
249233294Sstas			       N_("malloc: out of memory", ""));
25055682Smarkm	return KRB5_CC_NOMEM;
25178527Sassar    }
25255682Smarkm    l->next = m->creds;
25355682Smarkm    m->creds = l;
25455682Smarkm    memset (&l->cred, 0, sizeof(l->cred));
25555682Smarkm    ret = krb5_copy_creds_contents (context, creds, &l->cred);
25655682Smarkm    if (ret) {
25755682Smarkm	m->creds = l->next;
25855682Smarkm	free (l);
25955682Smarkm	return ret;
26055682Smarkm    }
261233294Sstas    m->mtime = time(NULL);
26255682Smarkm    return 0;
26355682Smarkm}
26455682Smarkm
265233294Sstasstatic krb5_error_code KRB5_CALLCONV
26655682Smarkmmcc_get_principal(krb5_context context,
26755682Smarkm		  krb5_ccache id,
26855682Smarkm		  krb5_principal *principal)
26955682Smarkm{
27072445Sassar    krb5_mcache *m = MCACHE(id);
27155682Smarkm
272127808Snectar    if (MISDEAD(m) || m->primary_principal == NULL)
27372445Sassar	return ENOENT;
27455682Smarkm    return krb5_copy_principal (context,
27555682Smarkm				m->primary_principal,
27655682Smarkm				principal);
27755682Smarkm}
27855682Smarkm
279233294Sstasstatic krb5_error_code KRB5_CALLCONV
28055682Smarkmmcc_get_first (krb5_context context,
28155682Smarkm	       krb5_ccache id,
28255682Smarkm	       krb5_cc_cursor *cursor)
28355682Smarkm{
28472445Sassar    krb5_mcache *m = MCACHE(id);
28572445Sassar
28672445Sassar    if (MISDEAD(m))
28772445Sassar	return ENOENT;
28872445Sassar
28955682Smarkm    *cursor = m->creds;
29055682Smarkm    return 0;
29155682Smarkm}
29255682Smarkm
293233294Sstasstatic krb5_error_code KRB5_CALLCONV
29455682Smarkmmcc_get_next (krb5_context context,
29555682Smarkm	      krb5_ccache id,
29655682Smarkm	      krb5_cc_cursor *cursor,
29755682Smarkm	      krb5_creds *creds)
29855682Smarkm{
29972445Sassar    krb5_mcache *m = MCACHE(id);
30055682Smarkm    struct link *l;
30155682Smarkm
30272445Sassar    if (MISDEAD(m))
30372445Sassar	return ENOENT;
30472445Sassar
30555682Smarkm    l = *cursor;
30655682Smarkm    if (l != NULL) {
30755682Smarkm	*cursor = l->next;
30855682Smarkm	return krb5_copy_creds_contents (context,
30955682Smarkm					 &l->cred,
31055682Smarkm					 creds);
31155682Smarkm    } else
31255682Smarkm	return KRB5_CC_END;
31355682Smarkm}
31455682Smarkm
315233294Sstasstatic krb5_error_code KRB5_CALLCONV
31655682Smarkmmcc_end_get (krb5_context context,
31755682Smarkm	     krb5_ccache id,
31855682Smarkm	     krb5_cc_cursor *cursor)
31955682Smarkm{
32055682Smarkm    return 0;
32155682Smarkm}
32255682Smarkm
323233294Sstasstatic krb5_error_code KRB5_CALLCONV
32455682Smarkmmcc_remove_cred(krb5_context context,
32555682Smarkm		 krb5_ccache id,
32655682Smarkm		 krb5_flags which,
32772445Sassar		 krb5_creds *mcreds)
32855682Smarkm{
32972445Sassar    krb5_mcache *m = MCACHE(id);
33072445Sassar    struct link **q, *p;
33172445Sassar    for(q = &m->creds, p = *q; p; p = *q) {
33272445Sassar	if(krb5_compare_creds(context, which, mcreds, &p->cred)) {
33372445Sassar	    *q = p->next;
334178825Sdfr	    krb5_free_cred_contents(context, &p->cred);
33572445Sassar	    free(p);
336233294Sstas	    m->mtime = time(NULL);
33772445Sassar	} else
33872445Sassar	    q = &p->next;
33972445Sassar    }
34072445Sassar    return 0;
34155682Smarkm}
34255682Smarkm
343233294Sstasstatic krb5_error_code KRB5_CALLCONV
34455682Smarkmmcc_set_flags(krb5_context context,
34555682Smarkm	      krb5_ccache id,
34655682Smarkm	      krb5_flags flags)
34755682Smarkm{
34855682Smarkm    return 0; /* XXX */
34955682Smarkm}
350233294Sstas
351178825Sdfrstruct mcache_iter {
352178825Sdfr    krb5_mcache *cache;
353178825Sdfr};
354178825Sdfr
355233294Sstasstatic krb5_error_code KRB5_CALLCONV
356178825Sdfrmcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
357178825Sdfr{
358178825Sdfr    struct mcache_iter *iter;
359178825Sdfr
360178825Sdfr    iter = calloc(1, sizeof(*iter));
361178825Sdfr    if (iter == NULL) {
362233294Sstas	krb5_set_error_message(context, ENOMEM,
363233294Sstas			       N_("malloc: out of memory", ""));
364178825Sdfr	return ENOMEM;
365233294Sstas    }
366178825Sdfr
367178825Sdfr    HEIMDAL_MUTEX_lock(&mcc_mutex);
368178825Sdfr    iter->cache = mcc_head;
369178825Sdfr    if (iter->cache)
370178825Sdfr	iter->cache->refcnt++;
371178825Sdfr    HEIMDAL_MUTEX_unlock(&mcc_mutex);
372178825Sdfr
373178825Sdfr    *cursor = iter;
374178825Sdfr    return 0;
375178825Sdfr}
376178825Sdfr
377233294Sstasstatic krb5_error_code KRB5_CALLCONV
378178825Sdfrmcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
379178825Sdfr{
380178825Sdfr    struct mcache_iter *iter = cursor;
381178825Sdfr    krb5_error_code ret;
382178825Sdfr    krb5_mcache *m;
383178825Sdfr
384178825Sdfr    if (iter->cache == NULL)
385178825Sdfr	return KRB5_CC_END;
386178825Sdfr
387178825Sdfr    HEIMDAL_MUTEX_lock(&mcc_mutex);
388178825Sdfr    m = iter->cache;
389178825Sdfr    if (m->next)
390178825Sdfr	m->next->refcnt++;
391178825Sdfr    iter->cache = m->next;
392178825Sdfr    HEIMDAL_MUTEX_unlock(&mcc_mutex);
393178825Sdfr
394178825Sdfr    ret = _krb5_cc_allocate(context, &krb5_mcc_ops, id);
395178825Sdfr    if (ret)
396178825Sdfr	return ret;
397178825Sdfr
398178825Sdfr    (*id)->data.data = m;
399178825Sdfr    (*id)->data.length = sizeof(*m);
400178825Sdfr
401178825Sdfr    return 0;
402178825Sdfr}
403178825Sdfr
404233294Sstasstatic krb5_error_code KRB5_CALLCONV
405178825Sdfrmcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
406178825Sdfr{
407178825Sdfr    struct mcache_iter *iter = cursor;
408178825Sdfr
409178825Sdfr    if (iter->cache)
410178825Sdfr	mcc_close_internal(iter->cache);
411178825Sdfr    iter->cache = NULL;
412178825Sdfr    free(iter);
413178825Sdfr    return 0;
414178825Sdfr}
415178825Sdfr
416233294Sstasstatic krb5_error_code KRB5_CALLCONV
417178825Sdfrmcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
418178825Sdfr{
419178825Sdfr    krb5_mcache *mfrom = MCACHE(from), *mto = MCACHE(to);
420178825Sdfr    struct link *creds;
421178825Sdfr    krb5_principal principal;
422178825Sdfr    krb5_mcache **n;
423178825Sdfr
424178825Sdfr    HEIMDAL_MUTEX_lock(&mcc_mutex);
425178825Sdfr
426178825Sdfr    /* drop the from cache from the linked list to avoid lookups */
427178825Sdfr    for(n = &mcc_head; n && *n; n = &(*n)->next) {
428178825Sdfr	if(mfrom == *n) {
429178825Sdfr	    *n = mfrom->next;
430178825Sdfr	    break;
431178825Sdfr	}
432178825Sdfr    }
433178825Sdfr
434178825Sdfr    /* swap creds */
435178825Sdfr    creds = mto->creds;
436178825Sdfr    mto->creds = mfrom->creds;
437178825Sdfr    mfrom->creds = creds;
438178825Sdfr    /* swap principal */
439178825Sdfr    principal = mto->primary_principal;
440178825Sdfr    mto->primary_principal = mfrom->primary_principal;
441178825Sdfr    mfrom->primary_principal = principal;
442178825Sdfr
443233294Sstas    mto->mtime = mfrom->mtime = time(NULL);
444233294Sstas
445178825Sdfr    HEIMDAL_MUTEX_unlock(&mcc_mutex);
446178825Sdfr    mcc_destroy(context, from);
447178825Sdfr
448178825Sdfr    return 0;
449178825Sdfr}
450178825Sdfr
451233294Sstasstatic krb5_error_code KRB5_CALLCONV
452178825Sdfrmcc_default_name(krb5_context context, char **str)
453178825Sdfr{
454178825Sdfr    *str = strdup("MEMORY:");
455178825Sdfr    if (*str == NULL) {
456233294Sstas	krb5_set_error_message(context, ENOMEM,
457233294Sstas			       N_("malloc: out of memory", ""));
458178825Sdfr	return ENOMEM;
459178825Sdfr    }
460178825Sdfr    return 0;
461178825Sdfr}
462178825Sdfr
463233294Sstasstatic krb5_error_code KRB5_CALLCONV
464233294Sstasmcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
465233294Sstas{
466233294Sstas    *mtime = MCACHE(id)->mtime;
467233294Sstas    return 0;
468233294Sstas}
469178825Sdfr
470233294Sstasstatic krb5_error_code KRB5_CALLCONV
471233294Sstasmcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
472233294Sstas{
473233294Sstas    krb5_mcache *m = MCACHE(id);
474233294Sstas    m->kdc_offset = kdc_offset;
475233294Sstas    return 0;
476233294Sstas}
477233294Sstas
478233294Sstasstatic krb5_error_code KRB5_CALLCONV
479233294Sstasmcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
480233294Sstas{
481233294Sstas    krb5_mcache *m = MCACHE(id);
482233294Sstas    *kdc_offset = m->kdc_offset;
483233294Sstas    return 0;
484233294Sstas}
485233294Sstas
486233294Sstas
487178825Sdfr/**
488178825Sdfr * Variable containing the MEMORY based credential cache implemention.
489178825Sdfr *
490178825Sdfr * @ingroup krb5_ccache
491178825Sdfr */
492178825Sdfr
493233294SstasKRB5_LIB_VARIABLE const krb5_cc_ops krb5_mcc_ops = {
494233294Sstas    KRB5_CC_OPS_VERSION,
49555682Smarkm    "MEMORY",
49655682Smarkm    mcc_get_name,
49755682Smarkm    mcc_resolve,
49855682Smarkm    mcc_gen_new,
49955682Smarkm    mcc_initialize,
50055682Smarkm    mcc_destroy,
50155682Smarkm    mcc_close,
50255682Smarkm    mcc_store_cred,
50355682Smarkm    NULL, /* mcc_retrieve */
50455682Smarkm    mcc_get_principal,
50555682Smarkm    mcc_get_first,
50655682Smarkm    mcc_get_next,
50755682Smarkm    mcc_end_get,
50855682Smarkm    mcc_remove_cred,
509178825Sdfr    mcc_set_flags,
510178825Sdfr    NULL,
511178825Sdfr    mcc_get_cache_first,
512178825Sdfr    mcc_get_cache_next,
513178825Sdfr    mcc_end_cache_get,
514178825Sdfr    mcc_move,
515233294Sstas    mcc_default_name,
516233294Sstas    NULL,
517233294Sstas    mcc_lastchange,
518233294Sstas    mcc_set_kdc_offset,
519233294Sstas    mcc_get_kdc_offset
52055682Smarkm};
521