summaryrefslogtreecommitdiff
path: root/sbin
diff options
context:
space:
mode:
authorNiklas Hallqvist <niklas@cvs.openbsd.org>1999-05-01 20:43:47 +0000
committerNiklas Hallqvist <niklas@cvs.openbsd.org>1999-05-01 20:43:47 +0000
commit5a7f502ff85fa80daab3422d3d21e27a6768f2e4 (patch)
tree5ead3611b8809acc3fbaa7d07b57e50a51975e88 /sbin
parenta43578c3e198040540661d0a58d2439a31b021e4 (diff)
sysdep/openbsd/sysdep.c: Merge with EOM 1.7
DESIGN-NOTES: Merge with EOM 1.42 Makefile: Merge with EOM 1.51 app.c: Merge with EOM 1.6 conf.c: Merge with EOM 1.18 init.c: Merge with EOM 1.14 isakmpd.conf.5: Merge with EOM 1.19 pf_encap.c: Merge with EOM 1.64 pf_encap.h: Merge with EOM 1.12 pf_key_v2.h: Merge with EOM 1.3 sysdep.h: Merge with EOM 1.16 transport.c: Merge with EOM 1.40 ui.c: Merge with EOM 1.32 author: niklas A new connection abstraction
Diffstat (limited to 'sbin')
-rw-r--r--sbin/isakmpd/DESIGN-NOTES11
-rw-r--r--sbin/isakmpd/Makefile4
-rw-r--r--sbin/isakmpd/app.c19
-rw-r--r--sbin/isakmpd/conf.c9
-rw-r--r--sbin/isakmpd/init.c6
-rw-r--r--sbin/isakmpd/isakmpd.conf.59
-rw-r--r--sbin/isakmpd/pf_encap.c97
-rw-r--r--sbin/isakmpd/pf_encap.h6
-rw-r--r--sbin/isakmpd/pf_key_v2.h6
-rw-r--r--sbin/isakmpd/sysdep.h6
-rw-r--r--sbin/isakmpd/sysdep/openbsd/sysdep.c27
-rw-r--r--sbin/isakmpd/transport.c6
-rw-r--r--sbin/isakmpd/ui.c33
13 files changed, 136 insertions, 103 deletions
diff --git a/sbin/isakmpd/DESIGN-NOTES b/sbin/isakmpd/DESIGN-NOTES
index d5a89f3bc93..62dcf4264d5 100644
--- a/sbin/isakmpd/DESIGN-NOTES
+++ b/sbin/isakmpd/DESIGN-NOTES
@@ -1,5 +1,5 @@
-$OpenBSD: DESIGN-NOTES,v 1.9 1999/04/27 21:13:28 niklas Exp $
-$EOM: DESIGN-NOTES,v 1.41 1999/04/27 09:39:07 niklas Exp $
+$OpenBSD: DESIGN-NOTES,v 1.10 1999/05/01 20:43:41 niklas Exp $
+$EOM: DESIGN-NOTES,v 1.42 1999/05/01 20:21:04 niklas Exp $
General coding conventions
--------------------------
@@ -35,6 +35,7 @@ asn_useful.c ASN.1. useful structure defintions.
cert.c Dispatching certificate related functions to the according
module based on the encoding.
conf.c Interface to isakmpd configuration.
+connection.c Handle the high-level connection concept.
constants.c Value to name map of constants..
cookie.c Cookie generation.
crypto.c Generic cryptography.
@@ -88,6 +89,7 @@ x509.c Encoding/Decoding X509 Certificates and related structures.
Central datatypes
-----------------
+struct connection Persistent connections.
struct constant_map A map from constants to their ASCII names.
struct crypto_xf A crypto class
struct doi The DOI function switch
@@ -207,10 +209,11 @@ In order to control the daemon you send commands through a FIFO called
isakmpd.fifo. The commands are one-letter codes followed by arguments.
For now, only three commands are planned:
-c connect Establish a named SA with a peer
-d delete Delete a SA
+c connect Establish a connection with a peer
+d delete Delete an SA given cookies and message-IDs
D debug Toggle some debug flag
r report Report status information of the daemon
+t teardown Teardown a connection
For example you can do:
diff --git a/sbin/isakmpd/Makefile b/sbin/isakmpd/Makefile
index 1b70ba32f48..a987c9d3f6e 100644
--- a/sbin/isakmpd/Makefile
+++ b/sbin/isakmpd/Makefile
@@ -1,5 +1,5 @@
-# $OpenBSD: Makefile,v 1.15 1999/04/20 12:22:18 niklas Exp $
-# $EOM: Makefile,v 1.50 1999/04/20 12:22:46 niklas Exp $
+# $OpenBSD: Makefile,v 1.16 1999/05/01 20:43:41 niklas Exp $
+# $EOM: Makefile,v 1.51 1999/05/01 20:21:05 niklas Exp $
#
# Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved.
diff --git a/sbin/isakmpd/app.c b/sbin/isakmpd/app.c
index bd3fc75a213..2b82f0d3406 100644
--- a/sbin/isakmpd/app.c
+++ b/sbin/isakmpd/app.c
@@ -1,5 +1,5 @@
-/* $OpenBSD: app.c,v 1.5 1999/04/19 19:54:53 niklas Exp $ */
-/* $EOM: app.c,v 1.5 1999/04/02 00:57:54 niklas Exp $ */
+/* $OpenBSD: app.c,v 1.6 1999/05/01 20:43:42 niklas Exp $ */
+/* $EOM: app.c,v 1.6 1999/05/01 20:21:06 niklas Exp $ */
/*
* Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved.
@@ -50,20 +50,7 @@ int app_socket;
/* Set this to not get any applications setup. */
int app_none = 0;
-/* Called after conf_init () meat has been ran. */
-void
-app_conf_init_hook ()
-{
- if (app_none)
- return;
-
- /*
- * XXX I do not like the layering here. I will probably redo this once
- * I figure out how other applications will use ISAKMP.
- */
- sysdep_conf_init_hook ();
-}
-
+/* Initialize applications. */
void
app_init ()
{
diff --git a/sbin/isakmpd/conf.c b/sbin/isakmpd/conf.c
index fe84201e184..1d79a0e1eac 100644
--- a/sbin/isakmpd/conf.c
+++ b/sbin/isakmpd/conf.c
@@ -1,5 +1,5 @@
-/* $OpenBSD: conf.c,v 1.8 1999/04/05 21:00:40 niklas Exp $ */
-/* $EOM: conf.c,v 1.17 1999/04/05 08:30:41 niklas Exp $ */
+/* $OpenBSD: conf.c,v 1.9 1999/05/01 20:43:42 niklas Exp $ */
+/* $EOM: conf.c,v 1.18 1999/05/01 20:21:07 niklas Exp $ */
/*
* Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved.
@@ -248,11 +248,6 @@ conf_init (void)
LIST_INIT (&conf_bindings);
conf_parse ();
-
-#ifdef NEED_SYSDEP_APP
- /* Let the application layer record on-demand keyed connections. */
- app_conf_init_hook ();
-#endif
}
/*
diff --git a/sbin/isakmpd/init.c b/sbin/isakmpd/init.c
index e1f04e15dc1..50419f72483 100644
--- a/sbin/isakmpd/init.c
+++ b/sbin/isakmpd/init.c
@@ -1,5 +1,5 @@
-/* $OpenBSD: init.c,v 1.6 1999/04/19 19:54:53 niklas Exp $ */
-/* $EOM: init.c,v 1.13 1999/04/02 00:57:47 niklas Exp $ */
+/* $OpenBSD: init.c,v 1.7 1999/05/01 20:43:43 niklas Exp $ */
+/* $EOM: init.c,v 1.14 1999/05/01 20:21:09 niklas Exp $ */
/*
* Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved.
@@ -40,6 +40,7 @@
#include "app.h"
#include "conf.h"
+#include "connection.h"
#include "cookie.h"
#include "doi.h"
#include "exchange.h"
@@ -66,6 +67,7 @@ init ()
/* The following group are depending on timer_init having run. */
conf_init ();
+ connection_init ();
cookie_init ();
sa_init ();
diff --git a/sbin/isakmpd/isakmpd.conf.5 b/sbin/isakmpd/isakmpd.conf.5
index 57c166ba4ce..8c258602a17 100644
--- a/sbin/isakmpd/isakmpd.conf.5
+++ b/sbin/isakmpd/isakmpd.conf.5
@@ -1,5 +1,5 @@
-.\" $OpenBSD: isakmpd.conf.5,v 1.15 1999/04/27 20:55:52 niklas Exp $
-.\" $EOM: isakmpd.conf.5,v 1.18 1999/04/16 21:35:34 niklas Exp $
+.\" $OpenBSD: isakmpd.conf.5,v 1.16 1999/05/01 20:43:43 niklas Exp $
+.\" $EOM: isakmpd.conf.5,v 1.19 1999/05/01 20:21:10 niklas Exp $
.\"
.\" Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved.
.\"
@@ -80,6 +80,8 @@ Generic global configuration parameters
.Bl -tag -width 12n
.It Em Retransmits
How many times should a message be retransmitted before giving up.
+.It Em Check-interval
+The interval between watchdog checks of connections we want up at all times.
.It Em Exchange-max-time
How many seconds should an exchange maximally take to setup
before we give up.
@@ -147,6 +149,9 @@ handling of the IPSec SA. Currently only one flag is defined:
.Dv Stayalive ,
which means that after the expiration of an ISAKMP SA, the initiator
side will automatically renegotiate for a new SA of the same name.
+.It Em Next-hop
+A Linux FreeS/WAN specific value which should be the IP address of the
+next hop along the path to reach the peer, usually a router.
.El
.It Em <ISAKMP-configuration>
.Bl -tag -width 12n
diff --git a/sbin/isakmpd/pf_encap.c b/sbin/isakmpd/pf_encap.c
index 53a0dacc7e2..7b042c1b696 100644
--- a/sbin/isakmpd/pf_encap.c
+++ b/sbin/isakmpd/pf_encap.c
@@ -1,5 +1,5 @@
-/* $OpenBSD: pf_encap.c,v 1.12 1999/04/19 21:07:42 niklas Exp $ */
-/* $EOM: pf_encap.c,v 1.63 1999/04/15 19:03:04 niklas Exp $ */
+/* $OpenBSD: pf_encap.c,v 1.13 1999/05/01 20:43:43 niklas Exp $ */
+/* $EOM: pf_encap.c,v 1.64 1999/05/01 20:21:11 niklas Exp $ */
/*
* Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved.
@@ -889,31 +889,40 @@ pf_encap_route (in_addr_t laddr, in_addr_t lmask, in_addr_t raddr,
return -1;
}
-int
-pf_encap_connection (char *conn)
+/* Check that the CONN connection has SPI 1 routes in-place. */
+void
+pf_encap_connection_check (char *conn)
{
char *conf, *doi_str, *local_id, *remote_id, *peer, *address;
struct in_addr laddr, lmask, raddr, rmask, gwaddr;
int lid, rid, err;
+ if (sa_lookup_by_name (conn, 2))
+ {
+ log_debug (LOG_SYSDEP, 70,
+ "pf_key_v2_connection_check: SA for %s exists", conn);
+ return;
+ }
+
/* Figure out the DOI. We only handle IPsec so far. */
conf = conf_get_str (conn, "Configuration");
if (!conf)
{
- log_print ("pf_encap_connection: no \"Configuration\" specified for %s",
+ log_print ("pf_encap_connection_check: "
+ "no \"Configuration\" specified for %s",
conn);
- return -1;
+ return;
}
doi_str = conf_get_str (conf, "DOI");
if (!doi_str)
{
- log_print ("sysdep_conf_init_hook: No DOI specified for %s", conf);
- return -1;
+ log_print ("pf_encap_connection_check: No DOI specified for %s", conf);
+ return;
}
if (strcasecmp (doi_str, "IPSEC") != 0)
{
- log_print ("sysdep_conf_init_hook: DOI \"%s\" unsupported", doi_str);
- return -1;
+ log_print ("pf_encap_connection_check: DOI \"%s\" unsupported", doi_str);
+ return;
}
local_id = conf_get_str (conn, "Local-ID");
@@ -922,47 +931,60 @@ pf_encap_connection (char *conn)
/* At the moment I only do on-demand keying for modes with client IDs. */
if (!local_id || !remote_id)
{
- log_print ("sysdep_conf_init_hook: "
+ log_print ("pf_encap_connection_check: "
"both Local-ID and Remote-ID required for %s", conn);
- return -1;
+ return;
}
if (ipsec_get_id (local_id, &lid, &laddr, &lmask))
- return -1;
+ return;
if (ipsec_get_id (remote_id, &rid, &raddr, &rmask))
- return -1;
+ return;
peer = conf_get_str (conn, "ISAKMP-peer");
if (!peer)
{
- log_print ("sysdep_conf_init_hook: "
+ log_print ("pf_encap_connection_check: "
"section %s has no \"ISAKMP-peer\" tag", conn);
- return -1;
+ return;
}
address = conf_get_str (peer, "Address");
if (!address)
{
- log_print ("sysdep_conf_init_hook: section %s has no \"Address\" tag",
+ log_print ("pf_encap_connection_check: "
+ "section %s has no \"Address\" tag",
peer);
- return -1;
+ return;
}
if (!inet_aton (address, &gwaddr))
{
- log_print ("sysdep_conf_init_hook: invalid adress %s in section %s",
+ log_print ("pf_encap_connection_check: invalid adress %s in section %s",
address, peer);
- return -1;
+ return;
}
err = pf_encap_register_on_demand_connection (gwaddr.s_addr, conn);
if (err)
- return -1;
+ return;
if (pf_encap_route (laddr.s_addr, lmask.s_addr, raddr.s_addr, rmask.s_addr,
gwaddr.s_addr))
{
pf_encap_deregister_on_demand_connection (conn);
- return -1;
+ return;
}
+}
+
+/* Lookup an on-demand connection from its name: CONN. */
+static struct on_demand_connection *
+pf_encap_lookup_on_demand_connection (char *conn)
+{
+ struct on_demand_connection *node;
+
+ for (node = LIST_FIRST (&on_demand_connections); node;
+ node = LIST_NEXT (node, link))
+ if (strcasecmp (conn, node->conn) == 0)
+ return node;
return 0;
}
@@ -974,16 +996,29 @@ pf_encap_register_on_demand_connection (in_addr_t dst, char *conn)
{
struct on_demand_connection *node;
+ /* Don't add duplicates. */
+ if (pf_encap_lookup_on_demand_connection (conn))
+ return 0;
+
node = malloc (sizeof *node);
if (!node)
- return -1;
+ {
+ log_error ("pf_encap_register_on_demand_connection: malloc (%d) failed",
+ sizeof *node);
+ return -1;
+ }
+
node->dst = dst;
node->conn = strdup (conn);
if (!node->conn)
{
+ log_error ("pf_encap_register_on_demand_connection: "
+ "strdup (\"%s\") failed",
+ conn);
free (node);
return -1;
}
+
LIST_INSERT_HEAD (&on_demand_connections, node, link);
return 0;
}
@@ -996,13 +1031,11 @@ pf_encap_deregister_on_demand_connection (char *conn)
{
struct on_demand_connection *node;
- for (node = LIST_FIRST (&on_demand_connections); node;
- node = LIST_NEXT (node, link))
- if (strcasecmp (conn, node->conn) == 0)
- {
- LIST_REMOVE (node, link);
- free (node->conn);
- free (node);
- break;
- }
+ node = pf_encap_lookup_on_demand_connection (conn);
+ if (node)
+ {
+ LIST_REMOVE (node, link);
+ free (node->conn);
+ free (node);
+ }
}
diff --git a/sbin/isakmpd/pf_encap.h b/sbin/isakmpd/pf_encap.h
index ce5260723e5..5d959e9863e 100644
--- a/sbin/isakmpd/pf_encap.h
+++ b/sbin/isakmpd/pf_encap.h
@@ -1,5 +1,5 @@
-/* $OpenBSD: pf_encap.h,v 1.6 1999/04/05 21:03:01 niklas Exp $ */
-/* $EOM: pf_encap.h,v 1.11 1999/04/02 00:51:32 niklas Exp $ */
+/* $OpenBSD: pf_encap.h,v 1.7 1999/05/01 20:43:44 niklas Exp $ */
+/* $EOM: pf_encap.h,v 1.12 1999/05/01 20:21:13 niklas Exp $ */
/*
* Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved.
@@ -56,7 +56,7 @@ struct pf_encap_node {
void *arg;
};
-extern int pf_encap_connection (char *);
+extern void pf_encap_connection_check (char *);
extern int pf_encap_delete_spi (struct sa *, struct proto *, int);
extern int pf_encap_enable_sa (struct sa *);
extern int pf_encap_enable_spi (in_addr_t, in_addr_t, in_addr_t, in_addr_t,
diff --git a/sbin/isakmpd/pf_key_v2.h b/sbin/isakmpd/pf_key_v2.h
index c4fb487ad3e..8e66dd33edb 100644
--- a/sbin/isakmpd/pf_key_v2.h
+++ b/sbin/isakmpd/pf_key_v2.h
@@ -1,5 +1,5 @@
-/* $OpenBSD: pf_key_v2.h,v 1.2 1999/04/05 21:02:18 niklas Exp $ */
-/* $EOM: pf_key_v2.h,v 1.2 1999/04/02 00:51:30 niklas Exp $ */
+/* $OpenBSD: pf_key_v2.h,v 1.3 1999/05/01 20:43:44 niklas Exp $ */
+/* $EOM: pf_key_v2.h,v 1.3 1999/05/01 20:21:15 niklas Exp $ */
/*
* Copyright (c) 1999 Niklas Hallqvist. All rights reserved.
@@ -44,7 +44,7 @@ struct proto;
struct sa;
struct sockaddr;
-extern int pf_key_v2_connection (char *);
+extern void pf_key_v2_connection_check (char *);
extern int pf_key_v2_delete_spi (struct sa *, struct proto *, int);
extern int pf_key_v2_enable_sa (struct sa *);
extern int pf_key_v2_enable_spi (in_addr_t, in_addr_t, in_addr_t, in_addr_t,
diff --git a/sbin/isakmpd/sysdep.h b/sbin/isakmpd/sysdep.h
index db74964d71a..45027c40f7d 100644
--- a/sbin/isakmpd/sysdep.h
+++ b/sbin/isakmpd/sysdep.h
@@ -1,5 +1,5 @@
-/* $OpenBSD: sysdep.h,v 1.6 1999/04/19 19:54:54 niklas Exp $ */
-/* $EOM: sysdep.h,v 1.15 1999/04/02 00:58:08 niklas Exp $ */
+/* $OpenBSD: sysdep.h,v 1.7 1999/05/01 20:43:45 niklas Exp $ */
+/* $EOM: sysdep.h,v 1.16 1999/05/01 20:21:16 niklas Exp $ */
/*
* Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved.
@@ -47,8 +47,8 @@ struct sockaddr;
extern void sysdep_app_handler (int);
extern int sysdep_app_open (void);
-extern void sysdep_conf_init_hook (void);
extern int sysdep_cleartext (int);
+extern void sysdep_connection_check (char *);
extern int sysdep_ipsec_delete_spi (struct sa *, struct proto *, int);
extern int sysdep_ipsec_enable_sa (struct sa *);
extern u_int8_t *sysdep_ipsec_get_spi (size_t *, u_int8_t, struct sockaddr *,
diff --git a/sbin/isakmpd/sysdep/openbsd/sysdep.c b/sbin/isakmpd/sysdep/openbsd/sysdep.c
index 136c355fc1b..382b718ddef 100644
--- a/sbin/isakmpd/sysdep/openbsd/sysdep.c
+++ b/sbin/isakmpd/sysdep/openbsd/sysdep.c
@@ -1,5 +1,5 @@
-/* $OpenBSD: sysdep.c,v 1.4 1999/04/05 20:57:35 niklas Exp $ */
-/* $EOM: sysdep.c,v 1.6 1999/04/05 18:27:42 niklas Exp $ */
+/* $OpenBSD: sysdep.c,v 1.5 1999/05/01 20:43:40 niklas Exp $ */
+/* $EOM: sysdep.c,v 1.7 1999/05/01 20:21:23 niklas Exp $ */
/*
* Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved.
@@ -110,28 +110,11 @@ sysdep_app_handler (int fd)
KEY_API (handler) (fd);
}
-/*
- * This is where we try to set up routes that make the IP-stack request
- * SAs from us on demand.
- */
+/* Check that the connection named NAME is active, or else make it active. */
void
-sysdep_conf_init_hook ()
+sysdep_connection_check (char *name)
{
- struct conf_list *conns;
- struct conf_list_node *conn;
-
- conns = conf_get_list ("Phase 2", "Connections");
- if (conns)
- {
- for (conn = TAILQ_FIRST (&conns->fields); conn;
- conn = TAILQ_NEXT (conn, link))
- {
- if (KEY_API(connection) (conn->field))
- /* XXX What else? */
- continue;
- }
- conf_free_list (conns);
- }
+ KEY_API (connection_check) (name);
}
/*
diff --git a/sbin/isakmpd/transport.c b/sbin/isakmpd/transport.c
index 59257b6d075..4bab5f120c9 100644
--- a/sbin/isakmpd/transport.c
+++ b/sbin/isakmpd/transport.c
@@ -1,5 +1,5 @@
-/* $OpenBSD: transport.c,v 1.8 1999/04/30 22:33:09 niklas Exp $ */
-/* $EOM: transport.c,v 1.39 1999/04/30 11:59:39 niklas Exp $ */
+/* $OpenBSD: transport.c,v 1.9 1999/05/01 20:43:45 niklas Exp $ */
+/* $EOM: transport.c,v 1.40 1999/05/01 20:21:17 niklas Exp $ */
/*
* Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved.
@@ -256,7 +256,7 @@ transport_send_messages (fd_set *fds)
if ((msg->flags & MSG_LAST) == 0)
{
if (msg->xmits > conf_get_num ("General", "retransmits",
- RETRANSMIT_DEFAULT))
+ RETRANSMIT_DEFAULT))
{
log_print ("transport_send_messages: "
"giving up on message %p",
diff --git a/sbin/isakmpd/ui.c b/sbin/isakmpd/ui.c
index f19f60b29fc..893088ced55 100644
--- a/sbin/isakmpd/ui.c
+++ b/sbin/isakmpd/ui.c
@@ -1,5 +1,5 @@
-/* $OpenBSD: ui.c,v 1.7 1999/04/19 21:10:21 niklas Exp $ */
-/* $EOM: ui.c,v 1.31 1999/04/11 22:35:53 ho Exp $ */
+/* $OpenBSD: ui.c,v 1.8 1999/05/01 20:43:46 niklas Exp $ */
+/* $EOM: ui.c,v 1.32 1999/05/01 20:21:19 niklas Exp $ */
/*
* Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved.
@@ -44,6 +44,7 @@
#include "sysdep.h"
#include "conf.h"
+#include "connection.h"
#include "doi.h"
#include "exchange.h"
#include "isakmp.h"
@@ -80,7 +81,10 @@ ui_init ()
}
}
-/* New style connect. */
+/*
+ * Setup a phase 2 connection.
+ * XXX Maybe phase 1 works too, but teardown won't work then, fix?
+ */
static void
ui_connect (char *cmd)
{
@@ -91,7 +95,24 @@ ui_connect (char *cmd)
log_print ("ui_connect: command \"%s\" malformed", cmd);
return;
}
- exchange_establish (name, 0, 0);
+ connection_setup (name);
+}
+
+/* Tear down a phase 2 connection. */
+static void
+ui_teardown (char *cmd)
+{
+ char name[81];
+ struct sa *sa;
+
+ if (sscanf (cmd, "t %80s", name) != 1)
+ {
+ log_print ("ui_teardown: command \"%s\" malformed", cmd);
+ return;
+ }
+ connection_teardown (name);
+ while ((sa = sa_lookup_by_name (name, 2)) != 0)
+ sa_delete (sa, 1);
}
static void
@@ -180,6 +201,10 @@ ui_handle_command (char *line)
ui_report (line);
break;
+ case 't':
+ ui_teardown (line);
+ break;
+
default:
log_print ("ui_handle_messages: unrecognized command: '%c'", line[0]);
}