summaryrefslogtreecommitdiff
path: root/usr.bin
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin')
-rw-r--r--usr.bin/ssh/authfd.c87
-rw-r--r--usr.bin/ssh/authfd.h26
-rw-r--r--usr.bin/ssh/ssh-add.c186
-rw-r--r--usr.bin/ssh/ssh-add/Makefile4
-rw-r--r--usr.bin/ssh/sshconnect.c4
5 files changed, 276 insertions, 31 deletions
diff --git a/usr.bin/ssh/authfd.c b/usr.bin/ssh/authfd.c
index 18fa1f19c2b..c358581b939 100644
--- a/usr.bin/ssh/authfd.c
+++ b/usr.bin/ssh/authfd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: authfd.c,v 1.128 2021/12/19 22:08:48 djm Exp $ */
+/* $OpenBSD: authfd.c,v 1.129 2021/12/19 22:10:24 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -453,12 +453,63 @@ ssh_agent_sign(int sock, const struct sshkey *key,
/* Encode key for a message to the agent. */
+static int
+encode_dest_constraint_hop(struct sshbuf *m,
+ const struct dest_constraint_hop *dch)
+{
+ struct sshbuf *b;
+ u_int i;
+ int r;
+
+ if ((b = sshbuf_new()) == NULL)
+ return SSH_ERR_ALLOC_FAIL;
+ if ((r = sshbuf_put_cstring(b, dch->user)) != 0 ||
+ (r = sshbuf_put_cstring(b, dch->hostname)) != 0 ||
+ (r = sshbuf_put_string(b, NULL, 0)) != 0) /* reserved */
+ goto out;
+ for (i = 0; i < dch->nkeys; i++) {
+ if ((r = sshkey_puts(dch->keys[i], b)) != 0 ||
+ (r = sshbuf_put_u8(b, dch->key_is_ca[i] != 0)) != 0)
+ goto out;
+ }
+ if ((r = sshbuf_put_stringb(m, b)) != 0)
+ goto out;
+ /* success */
+ r = 0;
+ out:
+ sshbuf_free(b);
+ return r;
+}
+
+static int
+encode_dest_constraint(struct sshbuf *m, const struct dest_constraint *dc)
+{
+ struct sshbuf *b;
+ int r;
+
+ if ((b = sshbuf_new()) == NULL)
+ return SSH_ERR_ALLOC_FAIL;
+ if ((r = encode_dest_constraint_hop(b, &dc->from) != 0) ||
+ (r = encode_dest_constraint_hop(b, &dc->to) != 0) ||
+ (r = sshbuf_put_string(b, NULL, 0)) != 0) /* reserved */
+ goto out;
+ if ((r = sshbuf_put_stringb(m, b)) != 0)
+ goto out;
+ /* success */
+ r = 0;
+ out:
+ sshbuf_free(b);
+ return r;
+}
static int
encode_constraints(struct sshbuf *m, u_int life, u_int confirm, u_int maxsign,
- const char *provider)
+ const char *provider, struct dest_constraint **dest_constraints,
+ size_t ndest_constraints)
{
int r;
+ struct sshbuf *b = NULL;
+ size_t i;
if (life != 0) {
if ((r = sshbuf_put_u8(m, SSH_AGENT_CONSTRAIN_LIFETIME)) != 0 ||
@@ -482,8 +533,26 @@ encode_constraints(struct sshbuf *m, u_int life, u_int confirm, u_int maxsign,
(r = sshbuf_put_cstring(m, provider)) != 0)
goto out;
}
+ if (dest_constraints != NULL && ndest_constraints > 0) {
+ if ((b = sshbuf_new()) == NULL) {
+ r = SSH_ERR_ALLOC_FAIL;
+ goto out;
+ }
+ for (i = 0; i < ndest_constraints; i++) {
+ if ((r = encode_dest_constraint(b,
+ dest_constraints[i])) != 0)
+ goto out;
+ }
+ if ((r = sshbuf_put_u8(m,
+ SSH_AGENT_CONSTRAIN_EXTENSION)) != 0 ||
+ (r = sshbuf_put_cstring(m,
+ "restrict-destination-v00@openssh.com")) != 0 ||
+ (r = sshbuf_put_stringb(m, b)) != 0)
+ goto out;
+ }
r = 0;
out:
+ sshbuf_free(b);
return r;
}
@@ -494,10 +563,12 @@ encode_constraints(struct sshbuf *m, u_int life, u_int confirm, u_int maxsign,
int
ssh_add_identity_constrained(int sock, struct sshkey *key,
const char *comment, u_int life, u_int confirm, u_int maxsign,
- const char *provider)
+ const char *provider, struct dest_constraint **dest_constraints,
+ size_t ndest_constraints)
{
struct sshbuf *msg;
- int r, constrained = (life || confirm || maxsign || provider);
+ int r, constrained = (life || confirm || maxsign ||
+ provider || dest_constraints);
u_char type;
if ((msg = sshbuf_new()) == NULL)
@@ -535,7 +606,7 @@ ssh_add_identity_constrained(int sock, struct sshkey *key,
}
if (constrained &&
(r = encode_constraints(msg, life, confirm, maxsign,
- provider)) != 0)
+ provider, dest_constraints, ndest_constraints)) != 0)
goto out;
if ((r = ssh_request_reply_decode(sock, msg)) != 0)
goto out;
@@ -589,7 +660,8 @@ ssh_remove_identity(int sock, const struct sshkey *key)
*/
int
ssh_update_card(int sock, int add, const char *reader_id, const char *pin,
- u_int life, u_int confirm)
+ u_int life, u_int confirm,
+ struct dest_constraint **dest_constraints, size_t ndest_constraints)
{
struct sshbuf *msg;
int r, constrained = (life || confirm);
@@ -609,7 +681,8 @@ ssh_update_card(int sock, int add, const char *reader_id, const char *pin,
(r = sshbuf_put_cstring(msg, pin)) != 0)
goto out;
if (constrained &&
- (r = encode_constraints(msg, life, confirm, 0, NULL)) != 0)
+ (r = encode_constraints(msg, life, confirm, 0, NULL,
+ dest_constraints, ndest_constraints)) != 0)
goto out;
if ((r = ssh_request_reply_decode(sock, msg)) != 0)
goto out;
diff --git a/usr.bin/ssh/authfd.h b/usr.bin/ssh/authfd.h
index fe1ed8a1abc..7a1c0ddff98 100644
--- a/usr.bin/ssh/authfd.h
+++ b/usr.bin/ssh/authfd.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: authfd.h,v 1.50 2021/12/19 22:08:48 djm Exp $ */
+/* $OpenBSD: authfd.h,v 1.51 2021/12/19 22:10:24 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -17,6 +17,7 @@
#define AUTHFD_H
struct sshbuf;
+struct sshkey;
/* List of identities returned by ssh_fetch_identitylist() */
struct ssh_identitylist {
@@ -25,6 +26,20 @@ struct ssh_identitylist {
char **comments;
};
+/* Key destination restrictions */
+struct dest_constraint_hop {
+ char *user; /* wildcards allowed */
+ char *hostname; /* used to matching cert principals and for display */
+ int is_ca;
+ u_int nkeys; /* number of entries in *both* 'keys' and 'key_is_ca' */
+ struct sshkey **keys;
+ int *key_is_ca;
+};
+struct dest_constraint {
+ struct dest_constraint_hop from;
+ struct dest_constraint_hop to;
+};
+
int ssh_get_authentication_socket(int *fdp);
int ssh_get_authentication_socket_path(const char *authsocket, int *fdp);
void ssh_close_authentication_socket(int sock);
@@ -33,12 +48,15 @@ int ssh_lock_agent(int sock, int lock, const char *password);
int ssh_fetch_identitylist(int sock, struct ssh_identitylist **idlp);
void ssh_free_identitylist(struct ssh_identitylist *idl);
int ssh_add_identity_constrained(int sock, struct sshkey *key,
- const char *comment, u_int life, u_int confirm, u_int maxsign,
- const char *provider);
+ const char *comment, u_int life, u_int confirm, u_int maxsign,
+ const char *provider, struct dest_constraint **dest_constraints,
+ size_t ndest_constraints);
int ssh_agent_has_key(int sock, const struct sshkey *key);
int ssh_remove_identity(int sock, const struct sshkey *key);
int ssh_update_card(int sock, int add, const char *reader_id,
- const char *pin, u_int life, u_int confirm);
+ const char *pin, u_int life, u_int confirm,
+ struct dest_constraint **dest_constraints,
+ size_t ndest_constraints);
int ssh_remove_all_identities(int sock, int version);
int ssh_agent_sign(int sock, const struct sshkey *key,
diff --git a/usr.bin/ssh/ssh-add.c b/usr.bin/ssh/ssh-add.c
index 116b5a87cdc..970d1163795 100644
--- a/usr.bin/ssh/ssh-add.c
+++ b/usr.bin/ssh/ssh-add.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-add.c,v 1.161 2021/10/28 02:54:18 djm Exp $ */
+/* $OpenBSD: ssh-add.c,v 1.162 2021/12/19 22:10:24 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -65,6 +65,7 @@
#include "digest.h"
#include "ssh-sk.h"
#include "sk-api.h"
+#include "hostfile.h"
/* argv0 */
extern char *__progname;
@@ -224,7 +225,8 @@ delete_all(int agent_fd, int qflag)
static int
add_file(int agent_fd, const char *filename, int key_only, int qflag,
- const char *skprovider)
+ const char *skprovider, struct dest_constraint **dest_constraints,
+ size_t ndest_constraints)
{
struct sshkey *private, *cert;
char *comment = NULL;
@@ -358,7 +360,8 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag,
}
if ((r = ssh_add_identity_constrained(agent_fd, private, comment,
- lifetime, confirm, maxsign, skprovider)) == 0) {
+ lifetime, confirm, maxsign, skprovider,
+ dest_constraints, ndest_constraints)) == 0) {
ret = 0;
if (!qflag) {
fprintf(stderr, "Identity added: %s (%s)\n",
@@ -410,7 +413,8 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag,
sshkey_free(cert);
if ((r = ssh_add_identity_constrained(agent_fd, private, comment,
- lifetime, confirm, maxsign, skprovider)) != 0) {
+ lifetime, confirm, maxsign, skprovider,
+ dest_constraints, ndest_constraints)) != 0) {
error_r(r, "Certificate %s (%s) add failed", certpath,
private->cert->key_id);
goto out;
@@ -438,7 +442,8 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag,
}
static int
-update_card(int agent_fd, int add, const char *id, int qflag)
+update_card(int agent_fd, int add, const char *id, int qflag,
+ struct dest_constraint **dest_constraints, size_t ndest_constraints)
{
char *pin = NULL;
int r, ret = -1;
@@ -450,7 +455,7 @@ update_card(int agent_fd, int add, const char *id, int qflag)
}
if ((r = ssh_update_card(agent_fd, add, id, pin == NULL ? "" : pin,
- lifetime, confirm)) == 0) {
+ lifetime, confirm, dest_constraints, ndest_constraints)) == 0) {
ret = 0;
if (!qflag) {
fprintf(stderr, "Card %s: %s\n",
@@ -571,7 +576,8 @@ lock_agent(int agent_fd, int lock)
}
static int
-load_resident_keys(int agent_fd, const char *skprovider, int qflag)
+load_resident_keys(int agent_fd, const char *skprovider, int qflag,
+ struct dest_constraint **dest_constraints, size_t ndest_constraints)
{
struct sshsk_resident_key **srks;
size_t nsrks, i;
@@ -591,8 +597,10 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag)
fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
fatal_f("sshkey_fingerprint failed");
if ((r = ssh_add_identity_constrained(agent_fd, key, "",
- lifetime, confirm, maxsign, skprovider)) != 0) {
- error("Unable to add key %s %s", sshkey_type(key), fp);
+ lifetime, confirm, maxsign, skprovider,
+ dest_constraints, ndest_constraints)) != 0) {
+ error("Unable to add key %s %s",
+ sshkey_type(key), fp);
free(fp);
ok = r;
continue;
@@ -621,23 +629,144 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag)
static int
do_file(int agent_fd, int deleting, int key_only, char *file, int qflag,
- const char *skprovider)
+ const char *skprovider, struct dest_constraint **dest_constraints,
+ size_t ndest_constraints)
{
if (deleting) {
if (delete_file(agent_fd, file, key_only, qflag) == -1)
return -1;
} else {
- if (add_file(agent_fd, file, key_only, qflag, skprovider) == -1)
+ if (add_file(agent_fd, file, key_only, qflag, skprovider,
+ dest_constraints, ndest_constraints) == -1)
return -1;
}
return 0;
}
+/* Append string 's' to a NULL-terminated array of strings */
+static void
+stringlist_append(char ***listp, const char *s)
+{
+ size_t i = 0;
+
+ if (*listp == NULL)
+ *listp = xcalloc(2, sizeof(**listp));
+ else {
+ for (i = 0; (*listp)[i] != NULL; i++)
+ ; /* count */
+ *listp = xrecallocarray(*listp, i + 1, i + 2, sizeof(**listp));
+ }
+ (*listp)[i] = xstrdup(s);
+}
+
+static void
+parse_dest_constraint_hop(const char *s, struct dest_constraint_hop *dch,
+ char **hostkey_files)
+{
+ char *user = NULL, *host, *os, *path;
+ size_t i;
+ struct hostkeys *hostkeys;
+ const struct hostkey_entry *hke;
+ int r, want_ca;
+
+ memset(dch, '\0', sizeof(*dch));
+ os = xstrdup(s);
+ if ((host = strchr(os, '@')) == NULL)
+ host = os;
+ else {
+ *host++ = '\0';
+ user = os;
+ }
+ cleanhostname(host);
+ /* Trivial case: username@ (all hosts) */
+ if (*host == '\0') {
+ if (user == NULL) {
+ fatal("Invalid key destination constraint \"%s\": "
+ "does not specify user or host", s);
+ }
+ dch->user = xstrdup(user);
+ /* other fields left blank */
+ free(os);
+ return;
+ }
+ if (hostkey_files == NULL)
+ fatal_f("no hostkey files");
+ /* Otherwise we need to look up the keys for this hostname */
+ hostkeys = init_hostkeys();
+ for (i = 0; hostkey_files[i]; i++) {
+ path = tilde_expand_filename(hostkey_files[i], getuid());
+ debug2_f("looking up host keys for \"%s\" in %s", host, path);
+ load_hostkeys(hostkeys, host, path, 0);
+ free(path);
+ }
+ dch->user = user == NULL ? NULL : xstrdup(user);
+ dch->hostname = xstrdup(host);
+ for (i = 0; i < hostkeys->num_entries; i++) {
+ hke = hostkeys->entries + i;
+ want_ca = hke->marker == MRK_CA;
+ if (hke->marker != MRK_NONE && !want_ca)
+ continue;
+ debug3_f("%s%s%s: adding %s %skey from %s:%lu as key %u",
+ user == NULL ? "": user, user == NULL ? "" : "@",
+ host, sshkey_type(hke->key), want_ca ? "CA " : "",
+ hke->file, hke->line, dch->nkeys);
+ dch->keys = xrecallocarray(dch->keys, dch->nkeys,
+ dch->nkeys + 1, sizeof(*dch->keys));
+ dch->key_is_ca = xrecallocarray(dch->key_is_ca, dch->nkeys,
+ dch->nkeys + 1, sizeof(*dch->key_is_ca));
+ if ((r = sshkey_from_private(hke->key,
+ &(dch->keys[dch->nkeys]))) != 0)
+ fatal_fr(r, "sshkey_from_private");
+ dch->key_is_ca[dch->nkeys] = want_ca;
+ dch->nkeys++;
+ }
+ if (dch->nkeys == 0)
+ fatal("No host keys found for destination \"%s\"", host);
+ free_hostkeys(hostkeys);
+ free(os);
+ return;
+}
+
+static void
+parse_dest_constraint(const char *s, struct dest_constraint ***dcp,
+ size_t *ndcp, char **hostkey_files)
+{
+ struct dest_constraint *dc;
+ char *os, *cp;
+
+ dc = xcalloc(1, sizeof(*dc));
+ os = xstrdup(s);
+ if ((cp = strchr(os, '>')) == NULL) {
+ /* initial hop; no 'from' hop specified */
+ parse_dest_constraint_hop(os, &dc->to, hostkey_files);
+ } else {
+ /* two hops specified */
+ *(cp++) = '\0';
+ parse_dest_constraint_hop(os, &dc->from, hostkey_files);
+ parse_dest_constraint_hop(cp, &dc->to, hostkey_files);
+ if (dc->from.user != NULL) {
+ fatal("Invalid key constraint %s: cannot specify "
+ "user on 'from' host", os);
+ }
+ }
+ /* XXX eliminate or error on duplicates */
+ debug2_f("constraint %zu: %s%s%s (%u keys) > %s%s%s (%u keys)", *ndcp,
+ dc->from.user ? dc->from.user : "", dc->from.user ? "@" : "",
+ dc->from.hostname ? dc->from.hostname : "(ORIGIN)", dc->from.nkeys,
+ dc->to.user ? dc->to.user : "", dc->to.user ? "@" : "",
+ dc->to.hostname ? dc->to.hostname : "(ANY)", dc->to.nkeys);
+ *dcp = xrecallocarray(*dcp, *ndcp, *ndcp + 1, sizeof(**dcp));
+ (*dcp)[(*ndcp)++] = dc;
+ free(os);
+}
+
+
static void
usage(void)
{
fprintf(stderr,
"usage: ssh-add [-cDdKkLlqvXx] [-E fingerprint_hash] [-S provider] [-t life]\n"
+" [-H hostkey_file] [-h destination]\n"
#ifdef WITH_XMSS
" [-M maxsign] [-m minleft]\n"
#endif
@@ -655,10 +784,13 @@ main(int argc, char **argv)
extern int optind;
int agent_fd;
char *pkcs11provider = NULL, *skprovider = NULL;
+ char **dest_constraint_strings = NULL, **hostkey_files = NULL;
int r, i, ch, deleting = 0, ret = 0, key_only = 0, do_download = 0;
int xflag = 0, lflag = 0, Dflag = 0, qflag = 0, Tflag = 0;
SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
LogLevel log_level = SYSLOG_LEVEL_INFO;
+ struct dest_constraint **dest_constraints = NULL;
+ size_t ndest_constraints = 0;
/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
sanitise_stdfd();
@@ -685,7 +817,7 @@ main(int argc, char **argv)
skprovider = getenv("SSH_SK_PROVIDER");
- while ((ch = getopt(argc, argv, "vkKlLcdDTxXE:e:M:m:qs:S:t:")) != -1) {
+ while ((ch = getopt(argc, argv, "vkKlLcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) {
switch (ch) {
case 'v':
if (log_level == SYSLOG_LEVEL_INFO)
@@ -698,6 +830,12 @@ main(int argc, char **argv)
if (fingerprint_hash == -1)
fatal("Invalid hash algorithm \"%s\"", optarg);
break;
+ case 'H':
+ stringlist_append(&hostkey_files, optarg);
+ break;
+ case 'h':
+ stringlist_append(&dest_constraint_strings, optarg);
+ break;
case 'k':
key_only = 1;
break;
@@ -791,6 +929,19 @@ main(int argc, char **argv)
if (skprovider == NULL)
skprovider = "internal";
+ if (hostkey_files == NULL) {
+ /* use defaults from readconf.c */
+ stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE);
+ stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE2);
+ stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE);
+ stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE2);
+ }
+ if (dest_constraint_strings != NULL) {
+ for (i = 0; dest_constraint_strings[i] != NULL; i++) {
+ parse_dest_constraint(dest_constraint_strings[i],
+ &dest_constraints, &ndest_constraints, hostkey_files);
+ }
+ }
argc -= optind;
argv += optind;
@@ -804,14 +955,15 @@ main(int argc, char **argv)
}
if (pkcs11provider != NULL) {
if (update_card(agent_fd, !deleting, pkcs11provider,
- qflag) == -1)
+ qflag, dest_constraints, ndest_constraints) == -1)
ret = 1;
goto done;
}
if (do_download) {
if (skprovider == NULL)
fatal("Cannot download keys without provider");
- if (load_resident_keys(agent_fd, skprovider, qflag) != 0)
+ if (load_resident_keys(agent_fd, skprovider, qflag,
+ dest_constraints, ndest_constraints) != 0)
ret = 1;
goto done;
}
@@ -834,7 +986,8 @@ main(int argc, char **argv)
if (stat(buf, &st) == -1)
continue;
if (do_file(agent_fd, deleting, key_only, buf,
- qflag, skprovider) == -1)
+ qflag, skprovider,
+ dest_constraints, ndest_constraints) == -1)
ret = 1;
else
count++;
@@ -844,7 +997,8 @@ main(int argc, char **argv)
} else {
for (i = 0; i < argc; i++) {
if (do_file(agent_fd, deleting, key_only,
- argv[i], qflag, skprovider) == -1)
+ argv[i], qflag, skprovider,
+ dest_constraints, ndest_constraints) == -1)
ret = 1;
}
}
diff --git a/usr.bin/ssh/ssh-add/Makefile b/usr.bin/ssh/ssh-add/Makefile
index 6e9a2b45869..69b50f69902 100644
--- a/usr.bin/ssh/ssh-add/Makefile
+++ b/usr.bin/ssh/ssh-add/Makefile
@@ -1,9 +1,9 @@
-# $OpenBSD: Makefile,v 1.31 2020/04/03 02:26:56 djm Exp $
+# $OpenBSD: Makefile,v 1.32 2021/12/19 22:11:06 djm Exp $
.PATH: ${.CURDIR}/..
SRCS= ssh-add.c
-SRCS+= authfd.c cleanup.c fatal.c readpass.c utf8.c
+SRCS+= authfd.c cleanup.c fatal.c readpass.c utf8.c hostfile.c hmac.c
SRCS+= ${SRCS_BASE} ${SRCS_KEY} ${SRCS_KEYP} ${SRCS_KRL} ${SRCS_UTL}
SRCS+= ${SRCS_SK_CLIENT}
diff --git a/usr.bin/ssh/sshconnect.c b/usr.bin/ssh/sshconnect.c
index 465c4d37339..52daead44d6 100644
--- a/usr.bin/ssh/sshconnect.c
+++ b/usr.bin/ssh/sshconnect.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect.c,v 1.355 2021/07/02 05:11:21 dtucker Exp $ */
+/* $OpenBSD: sshconnect.c,v 1.356 2021/12/19 22:10:24 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1662,7 +1662,7 @@ maybe_add_key_to_agent(const char *authfile, struct sshkey *private,
if ((r = ssh_add_identity_constrained(auth_sock, private,
comment == NULL ? authfile : comment,
options.add_keys_to_agent_lifespan,
- (options.add_keys_to_agent == 3), 0, skprovider)) == 0)
+ (options.add_keys_to_agent == 3), 0, skprovider, NULL, 0)) == 0)
debug("identity added to agent: %s", authfile);
else
debug("could not add identity to agent: %s (%d)", authfile, r);