ipropd_master.c revision 90926
155682Smarkm/*
278527Sassar * Copyright (c) 1997 - 2001 Kungliga Tekniska H�gskolan
355682Smarkm * (Royal Institute of Technology, Stockholm, Sweden).
455682Smarkm * All rights reserved.
555682Smarkm *
655682Smarkm * Redistribution and use in source and binary forms, with or without
755682Smarkm * modification, are permitted provided that the following conditions
855682Smarkm * are met:
955682Smarkm *
1055682Smarkm * 1. Redistributions of source code must retain the above copyright
1155682Smarkm *    notice, this list of conditions and the following disclaimer.
1255682Smarkm *
1355682Smarkm * 2. Redistributions in binary form must reproduce the above copyright
1455682Smarkm *    notice, this list of conditions and the following disclaimer in the
1555682Smarkm *    documentation and/or other materials provided with the distribution.
1655682Smarkm *
1755682Smarkm * 3. Neither the name of the Institute nor the names of its contributors
1855682Smarkm *    may be used to endorse or promote products derived from this software
1955682Smarkm *    without specific prior written permission.
2055682Smarkm *
2155682Smarkm * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
2255682Smarkm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2355682Smarkm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2455682Smarkm * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
2555682Smarkm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2655682Smarkm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2755682Smarkm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2855682Smarkm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2955682Smarkm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3055682Smarkm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3155682Smarkm * SUCH DAMAGE.
3255682Smarkm */
3355682Smarkm
3455682Smarkm#include "iprop.h"
3555682Smarkm
3690926SnectarRCSID("$Id: ipropd_master.c,v 1.24 2001/09/03 05:54:18 assar Exp $");
3755682Smarkm
3872445Sassarstatic krb5_log_facility *log_facility;
3972445Sassar
4055682Smarkmstatic int
4155682Smarkmmake_signal_socket (krb5_context context)
4255682Smarkm{
4355682Smarkm    struct sockaddr_un addr;
4455682Smarkm    int fd;
4555682Smarkm
4655682Smarkm    fd = socket (AF_UNIX, SOCK_DGRAM, 0);
4755682Smarkm    if (fd < 0)
4855682Smarkm	krb5_err (context, 1, errno, "socket AF_UNIX");
4955682Smarkm    memset (&addr, 0, sizeof(addr));
5055682Smarkm    addr.sun_family = AF_UNIX;
5172445Sassar    strlcpy (addr.sun_path, KADM5_LOG_SIGNAL, sizeof(addr.sun_path));
5255682Smarkm    unlink (addr.sun_path);
5355682Smarkm    if (bind (fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
5455682Smarkm	krb5_err (context, 1, errno, "bind %s", addr.sun_path);
5555682Smarkm    return fd;
5655682Smarkm}
5755682Smarkm
5855682Smarkmstatic int
5955682Smarkmmake_listen_socket (krb5_context context)
6055682Smarkm{
6155682Smarkm    int fd;
6255682Smarkm    int one = 1;
6355682Smarkm    struct sockaddr_in addr;
6455682Smarkm
6555682Smarkm    fd = socket (AF_INET, SOCK_STREAM, 0);
6655682Smarkm    if (fd < 0)
6755682Smarkm	krb5_err (context, 1, errno, "socket AF_INET");
6890926Snectar    setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
6955682Smarkm    memset (&addr, 0, sizeof(addr));
7055682Smarkm    addr.sin_family = AF_INET;
7172445Sassar    addr.sin_port   = krb5_getportbyname (context,
7272445Sassar					  IPROP_SERVICE, "tcp", IPROP_PORT);
7355682Smarkm    if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
7455682Smarkm	krb5_err (context, 1, errno, "bind");
7555682Smarkm    if (listen(fd, SOMAXCONN) < 0)
7655682Smarkm	krb5_err (context, 1, errno, "listen");
7755682Smarkm    return fd;
7855682Smarkm}
7955682Smarkm
8055682Smarkmstruct slave {
8155682Smarkm    int fd;
8255682Smarkm    struct sockaddr_in addr;
8355682Smarkm    char *name;
8455682Smarkm    krb5_auth_context ac;
8555682Smarkm    u_int32_t version;
8655682Smarkm    struct slave *next;
8755682Smarkm};
8855682Smarkm
8955682Smarkmtypedef struct slave slave;
9055682Smarkm
9155682Smarkmstatic int
9255682Smarkmcheck_acl (krb5_context context, const char *name)
9355682Smarkm{
9455682Smarkm    FILE *fp;
9555682Smarkm    char buf[256];
9655682Smarkm    int ret = 1;
9755682Smarkm
9855682Smarkm    fp = fopen (KADM5_SLAVE_ACL, "r");
9955682Smarkm    if (fp == NULL)
10055682Smarkm	return 1;
10155682Smarkm    while (fgets(buf, sizeof(buf), fp) != NULL) {
10255682Smarkm	if (buf[strlen(buf) - 1 ] == '\n')
10355682Smarkm	    buf[strlen(buf) - 1 ] = '\0';
10455682Smarkm	if (strcmp (buf, name) == 0) {
10555682Smarkm	    ret = 0;
10655682Smarkm	    break;
10755682Smarkm	}
10855682Smarkm    }
10955682Smarkm    fclose (fp);
11055682Smarkm    return ret;
11155682Smarkm}
11255682Smarkm
11355682Smarkmstatic void
11472445Sassaradd_slave (krb5_context context, krb5_keytab keytab, slave **root, int fd)
11555682Smarkm{
11655682Smarkm    krb5_principal server;
11755682Smarkm    krb5_error_code ret;
11855682Smarkm    slave *s;
11972445Sassar    socklen_t addr_len;
12055682Smarkm    krb5_ticket *ticket = NULL;
12155682Smarkm    char hostname[128];
12255682Smarkm
12355682Smarkm    s = malloc(sizeof(*s));
12455682Smarkm    if (s == NULL) {
12555682Smarkm	krb5_warnx (context, "add_slave: no memory");
12655682Smarkm	return;
12755682Smarkm    }
12855682Smarkm    s->name = NULL;
12955682Smarkm    s->ac = NULL;
13055682Smarkm
13155682Smarkm    addr_len = sizeof(s->addr);
13255682Smarkm    s->fd = accept (fd, (struct sockaddr *)&s->addr, &addr_len);
13355682Smarkm    if (s->fd < 0) {
13455682Smarkm	krb5_warn (context, errno, "accept");
13555682Smarkm	goto error;
13655682Smarkm    }
13755682Smarkm    gethostname(hostname, sizeof(hostname));
13855682Smarkm    ret = krb5_sname_to_principal (context, hostname, IPROP_NAME,
13955682Smarkm				   KRB5_NT_SRV_HST, &server);
14055682Smarkm    if (ret) {
14155682Smarkm	krb5_warn (context, ret, "krb5_sname_to_principal");
14255682Smarkm	goto error;
14355682Smarkm    }
14455682Smarkm
14555682Smarkm    ret = krb5_recvauth (context, &s->ac, &s->fd,
14672445Sassar			 IPROP_VERSION, server, 0, keytab, &ticket);
14755682Smarkm    krb5_free_principal (context, server);
14855682Smarkm    if (ret) {
14955682Smarkm	krb5_warn (context, ret, "krb5_recvauth");
15055682Smarkm	goto error;
15155682Smarkm    }
15255682Smarkm    ret = krb5_unparse_name (context, ticket->client, &s->name);
15355682Smarkm    if (ret) {
15455682Smarkm	krb5_warn (context, ret, "krb5_unparse_name");
15555682Smarkm	goto error;
15655682Smarkm    }
15755682Smarkm    if (check_acl (context, s->name)) {
15855682Smarkm	krb5_warnx (context, "%s not in acl", s->name);
15955682Smarkm	goto error;
16055682Smarkm    }
16155682Smarkm    krb5_free_ticket (context, ticket);
16272445Sassar    krb5_warnx (context, "connection from %s", s->name);
16355682Smarkm
16455682Smarkm    s->version = 0;
16555682Smarkm    s->next = *root;
16655682Smarkm    *root = s;
16755682Smarkm    return;
16855682Smarkmerror:
16955682Smarkm    if (s->name)
17055682Smarkm	free (s->name);
17155682Smarkm    if (s->ac)
17255682Smarkm	krb5_auth_con_free(context, s->ac);
17355682Smarkm    if (ticket)
17455682Smarkm    krb5_free_ticket (context, ticket);
17555682Smarkm    close (s->fd);
17655682Smarkm    free(s);
17755682Smarkm}
17855682Smarkm
17955682Smarkmstatic void
18055682Smarkmremove_slave (krb5_context context, slave *s, slave **root)
18155682Smarkm{
18255682Smarkm    slave **p;
18355682Smarkm
18455682Smarkm    close (s->fd);
18555682Smarkm    free (s->name);
18655682Smarkm    krb5_auth_con_free (context, s->ac);
18755682Smarkm
18855682Smarkm    for (p = root; *p; p = &(*p)->next)
18955682Smarkm	if (*p == s) {
19055682Smarkm	    *p = s->next;
19155682Smarkm	    break;
19255682Smarkm	}
19355682Smarkm    free (s);
19455682Smarkm}
19555682Smarkm
19672445Sassarstruct prop_context {
19772445Sassar    krb5_auth_context auth_context;
19872445Sassar    int fd;
19972445Sassar};
20072445Sassar
20155682Smarkmstatic int
20272445Sassarprop_one (krb5_context context, HDB *db, hdb_entry *entry, void *v)
20355682Smarkm{
20472445Sassar    krb5_error_code ret;
20572445Sassar    krb5_data data;
20672445Sassar    struct slave *slave = (struct slave *)v;
20772445Sassar
20872445Sassar    ret = hdb_entry2value (context, entry, &data);
20972445Sassar    if (ret)
21072445Sassar	return ret;
21172445Sassar    ret = krb5_data_realloc (&data, data.length + 4);
21272445Sassar    if (ret) {
21372445Sassar	krb5_data_free (&data);
21472445Sassar	return ret;
21572445Sassar    }
21672445Sassar    memmove ((char *)data.data + 4, data.data, data.length - 4);
21772445Sassar    _krb5_put_int (data.data, ONE_PRINC, 4);
21872445Sassar
21972445Sassar    ret = krb5_write_priv_message (context, slave->ac, &slave->fd, &data);
22072445Sassar    krb5_data_free (&data);
22172445Sassar    return ret;
22255682Smarkm}
22355682Smarkm
22455682Smarkmstatic int
22572445Sassarsend_complete (krb5_context context, slave *s,
22672445Sassar	       const char *database, u_int32_t current_version)
22772445Sassar{
22872445Sassar    krb5_error_code ret;
22972445Sassar    HDB *db;
23072445Sassar    krb5_data data;
23172445Sassar    char buf[8];
23272445Sassar
23372445Sassar    ret = hdb_create (context, &db, database);
23472445Sassar    if (ret)
23572445Sassar	krb5_err (context, 1, ret, "hdb_create: %s", database);
23672445Sassar    ret = db->open (context, db, O_RDONLY, 0);
23772445Sassar    if (ret)
23872445Sassar	krb5_err (context, 1, ret, "db->open");
23972445Sassar
24072445Sassar    _krb5_put_int(buf, TELL_YOU_EVERYTHING, 4);
24172445Sassar
24272445Sassar    data.data   = buf;
24372445Sassar    data.length = 4;
24472445Sassar
24572445Sassar    ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
24672445Sassar
24772445Sassar    if (ret)
24872445Sassar	krb5_err (context, 1, ret, "krb5_write_priv_message");
24972445Sassar
25072445Sassar    ret = hdb_foreach (context, db, 0, prop_one, s);
25172445Sassar    if (ret)
25272445Sassar	krb5_err (context, 1, ret, "hdb_foreach");
25372445Sassar
25472445Sassar    _krb5_put_int (buf, NOW_YOU_HAVE, 4);
25572445Sassar    _krb5_put_int (buf + 4, current_version, 4);
25672445Sassar    data.length = 8;
25772445Sassar
25872445Sassar    ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
25972445Sassar
26072445Sassar    if (ret)
26172445Sassar	krb5_err (context, 1, ret, "krb5_write_priv_message");
26272445Sassar
26372445Sassar    return 0;
26472445Sassar}
26572445Sassar
26672445Sassarstatic int
26755682Smarkmsend_diffs (krb5_context context, slave *s, int log_fd,
26872445Sassar	    const char *database, u_int32_t current_version)
26955682Smarkm{
27072445Sassar    krb5_storage *sp;
27155682Smarkm    u_int32_t ver;
27255682Smarkm    time_t timestamp;
27355682Smarkm    enum kadm_ops op;
27455682Smarkm    u_int32_t len;
27555682Smarkm    off_t right, left;
27655682Smarkm    krb5_data data;
27755682Smarkm    int ret = 0;
27855682Smarkm
27955682Smarkm    if (s->version == current_version)
28055682Smarkm	return 0;
28155682Smarkm
28255682Smarkm    sp = kadm5_log_goto_end (log_fd);
28355682Smarkm    right = sp->seek(sp, 0, SEEK_CUR);
28455682Smarkm    for (;;) {
28555682Smarkm	if (kadm5_log_previous (sp, &ver, &timestamp, &op, &len))
28655682Smarkm	    abort ();
28755682Smarkm	left = sp->seek(sp, -16, SEEK_CUR);
28855682Smarkm	if (ver == s->version)
28955682Smarkm	    return 0;
29055682Smarkm	if (ver == s->version + 1)
29155682Smarkm	    break;
29255682Smarkm	if (left == 0)
29372445Sassar	    return send_complete (context, s, database, current_version);
29455682Smarkm    }
29555682Smarkm    krb5_data_alloc (&data, right - left + 4);
29655682Smarkm    sp->fetch (sp, (char *)data.data + 4, data.length - 4);
29755682Smarkm    krb5_storage_free(sp);
29855682Smarkm
29955682Smarkm    _krb5_put_int(data.data, FOR_YOU, 4);
30055682Smarkm
30172445Sassar    ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
30255682Smarkm
30355682Smarkm    if (ret) {
30472445Sassar	krb5_warn (context, ret, "krb5_write_priv_message");
30555682Smarkm	return 1;
30655682Smarkm    }
30755682Smarkm    return 0;
30855682Smarkm}
30955682Smarkm
31055682Smarkmstatic int
31155682Smarkmprocess_msg (krb5_context context, slave *s, int log_fd,
31272445Sassar	     const char *database, u_int32_t current_version)
31355682Smarkm{
31455682Smarkm    int ret = 0;
31572445Sassar    krb5_data out;
31655682Smarkm    krb5_storage *sp;
31755682Smarkm    int32_t tmp;
31855682Smarkm
31972445Sassar    ret = krb5_read_priv_message(context, s->ac, &s->fd, &out);
32072445Sassar    if(ret) {
32172445Sassar	krb5_warn (context, ret, "error reading message from %s", s->name);
32255682Smarkm	return 1;
32355682Smarkm    }
32455682Smarkm
32555682Smarkm    sp = krb5_storage_from_mem (out.data, out.length);
32655682Smarkm    krb5_ret_int32 (sp, &tmp);
32755682Smarkm    switch (tmp) {
32855682Smarkm    case I_HAVE :
32955682Smarkm	krb5_ret_int32 (sp, &tmp);
33055682Smarkm	s->version = tmp;
33172445Sassar	ret = send_diffs (context, s, log_fd, database, current_version);
33255682Smarkm	break;
33355682Smarkm    case FOR_YOU :
33455682Smarkm    default :
33555682Smarkm	krb5_warnx (context, "Ignoring command %d", tmp);
33655682Smarkm	break;
33755682Smarkm    }
33855682Smarkm
33955682Smarkm    krb5_data_free (&out);
34055682Smarkm    return ret;
34155682Smarkm}
34255682Smarkm
34372445Sassarstatic char *realm;
34472445Sassarstatic int version_flag;
34572445Sassarstatic int help_flag;
34672445Sassarstatic char *keytab_str = "HDB:";
34772445Sassarstatic char *database;
34872445Sassar
34972445Sassarstatic struct getargs args[] = {
35055682Smarkm    { "realm", 'r', arg_string, &realm },
35172445Sassar    { "keytab", 'k', arg_string, &keytab_str,
35272445Sassar      "keytab to get authentication from", "kspec" },
35372445Sassar    { "database", 'd', arg_string, &database, "database", "file"},
35455682Smarkm    { "version", 0, arg_flag, &version_flag },
35555682Smarkm    { "help", 0, arg_flag, &help_flag }
35655682Smarkm};
35772445Sassarstatic int num_args = sizeof(args) / sizeof(args[0]);
35855682Smarkm
35955682Smarkmint
36055682Smarkmmain(int argc, char **argv)
36155682Smarkm{
36255682Smarkm    krb5_error_code ret;
36355682Smarkm    krb5_context context;
36455682Smarkm    void *kadm_handle;
36555682Smarkm    kadm5_server_context *server_context;
36655682Smarkm    kadm5_config_params conf;
36755682Smarkm    int signal_fd, listen_fd;
36855682Smarkm    int log_fd;
36955682Smarkm    slave *slaves = NULL;
37055682Smarkm    u_int32_t current_version, old_version = 0;
37172445Sassar    krb5_keytab keytab;
37255682Smarkm    int optind;
37355682Smarkm
37455682Smarkm    optind = krb5_program_setup(&context, argc, argv, args, num_args, NULL);
37555682Smarkm
37655682Smarkm    if(help_flag)
37755682Smarkm	krb5_std_usage(0, args, num_args);
37855682Smarkm    if(version_flag) {
37955682Smarkm	print_version(NULL);
38055682Smarkm	exit(0);
38155682Smarkm    }
38255682Smarkm
38390926Snectar    pidfile (NULL);
38472445Sassar    krb5_openlog (context, "ipropd-master", &log_facility);
38572445Sassar    krb5_set_warn_dest(context, log_facility);
38672445Sassar
38772445Sassar    ret = krb5_kt_register(context, &hdb_kt_ops);
38872445Sassar    if(ret)
38972445Sassar	krb5_err(context, 1, ret, "krb5_kt_register");
39072445Sassar
39172445Sassar    ret = krb5_kt_resolve(context, keytab_str, &keytab);
39272445Sassar    if(ret)
39372445Sassar	krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str);
39472445Sassar
39555682Smarkm    memset(&conf, 0, sizeof(conf));
39655682Smarkm    if(realm) {
39755682Smarkm	conf.mask |= KADM5_CONFIG_REALM;
39855682Smarkm	conf.realm = realm;
39955682Smarkm    }
40072445Sassar    ret = kadm5_init_with_skey_ctx (context,
40172445Sassar				    KADM5_ADMIN_SERVICE,
40272445Sassar				    NULL,
40372445Sassar				    KADM5_ADMIN_SERVICE,
40472445Sassar				    &conf, 0, 0,
40572445Sassar				    &kadm_handle);
40655682Smarkm    if (ret)
40755682Smarkm	krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
40855682Smarkm
40955682Smarkm    server_context = (kadm5_server_context *)kadm_handle;
41055682Smarkm
41155682Smarkm    log_fd = open (server_context->log_context.log_file, O_RDONLY, 0);
41255682Smarkm    if (log_fd < 0)
41355682Smarkm	krb5_err (context, 1, errno, "open %s",
41455682Smarkm		  server_context->log_context.log_file);
41555682Smarkm
41655682Smarkm    signal_fd = make_signal_socket (context);
41755682Smarkm    listen_fd = make_listen_socket (context);
41855682Smarkm
41972445Sassar    signal (SIGPIPE, SIG_IGN);
42072445Sassar
42155682Smarkm    for (;;) {
42255682Smarkm	slave *p;
42355682Smarkm	fd_set readset;
42455682Smarkm	int max_fd = 0;
42555682Smarkm	struct timeval to = {30, 0};
42655682Smarkm	u_int32_t vers;
42755682Smarkm
42872445Sassar	if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE)
42972445Sassar	    krb5_errx (context, 1, "fd too large");
43072445Sassar
43155682Smarkm	FD_ZERO(&readset);
43255682Smarkm	FD_SET(signal_fd, &readset);
43355682Smarkm	max_fd = max(max_fd, signal_fd);
43455682Smarkm	FD_SET(listen_fd, &readset);
43555682Smarkm	max_fd = max(max_fd, listen_fd);
43655682Smarkm
43755682Smarkm	for (p = slaves; p != NULL; p = p->next) {
43855682Smarkm	    FD_SET(p->fd, &readset);
43955682Smarkm	    max_fd = max(max_fd, p->fd);
44055682Smarkm	}
44155682Smarkm
44255682Smarkm	ret = select (max_fd + 1,
44355682Smarkm		      &readset, NULL, NULL, &to);
44455682Smarkm	if (ret < 0) {
44555682Smarkm	    if (errno == EINTR)
44655682Smarkm		continue;
44755682Smarkm	    else
44855682Smarkm		krb5_err (context, 1, errno, "select");
44955682Smarkm	}
45055682Smarkm
45155682Smarkm	if (ret == 0) {
45255682Smarkm	    old_version = current_version;
45372445Sassar	    kadm5_log_get_version_fd (log_fd, &current_version);
45455682Smarkm
45555682Smarkm	    if (current_version > old_version)
45655682Smarkm		for (p = slaves; p != NULL; p = p->next)
45772445Sassar		    send_diffs (context, p, log_fd, database, current_version);
45855682Smarkm	}
45955682Smarkm
46055682Smarkm	if (ret && FD_ISSET(signal_fd, &readset)) {
46155682Smarkm	    struct sockaddr_un peer_addr;
46272445Sassar	    socklen_t peer_len = sizeof(peer_addr);
46355682Smarkm
46490926Snectar	    if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0,
46555682Smarkm			(struct sockaddr *)&peer_addr, &peer_len) < 0) {
46655682Smarkm		krb5_warn (context, errno, "recvfrom");
46755682Smarkm		continue;
46855682Smarkm	    }
46955682Smarkm	    --ret;
47055682Smarkm	    old_version = current_version;
47172445Sassar	    kadm5_log_get_version_fd (log_fd, &current_version);
47255682Smarkm	    for (p = slaves; p != NULL; p = p->next)
47372445Sassar		send_diffs (context, p, log_fd, database, current_version);
47455682Smarkm	}
47555682Smarkm
47678527Sassar	for(p = slaves; p != NULL; p = p->next)
47755682Smarkm	    if (FD_ISSET(p->fd, &readset)) {
47878527Sassar		--ret;
47972445Sassar		if(process_msg (context, p, log_fd, database, current_version))
48055682Smarkm		    remove_slave (context, p, &slaves);
48155682Smarkm	    }
48255682Smarkm
48355682Smarkm	if (ret && FD_ISSET(listen_fd, &readset)) {
48472445Sassar	    add_slave (context, keytab, &slaves, listen_fd);
48555682Smarkm	    --ret;
48655682Smarkm	}
48755682Smarkm
48855682Smarkm    }
48955682Smarkm
49055682Smarkm    return 0;
49155682Smarkm}
492