/* $Id: ssh_parse.c,v 1.48 2001/02/11 03:35:13 tls Exp $ */ /* * Copyright 1999 RedBack Networks, Incorporated. * All rights reserved. * * This software is not in the public domain. It is distributed * under the terms of the license in the file LICENSE in the * same directory as this file. If you have received a copy of this * software without the LICENSE file (which means that whoever gave * you this software violated its license) you may obtain a copy from * http://www.panix.com/~tls/LICENSE.txt */ /* * Copyright (c) 2000, 2001 Andrew Brown and Jason R. Thorpe. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The names of the authors may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Functions to parse data. */ #include #include #include #include #include #include #include #include "options.h" #include "ssh_buffer.h" #include "ssh_cipher.h" #include "ssh_crypto.h" #include "ssh_keyfile.h" #include "ssh_logging.h" #include "ssh_parse.h" #include "ssh_sys.h" #include "ssh_util.h" #include "ssh_types.h" #include "ssh_main.h" #include "ssh_ui.h" /* Data parsing functions: */ /* * Note: for a description of the layout of the keyfile * look in ssh_keyfile.h. * * Decode a in-memory keyfile. * If interactive is non-zero this will ask for a passphrash. * If comment is non-NULL, a pointer to the comment field in the * key is placed there. This must be free'd by the caller. */ int decode_keyfile(ssh_context_t * context, char *buf, int buflen, char *passphrase, int pplen, ssh_RSA ** keyp, char **comment, int *format) { SSH_DLOG(5, ("decode_keyfile\n")); /* Start of decoding: */ if (*keyp == NULL) *keyp = ssh_rsa_new(); if (*keyp == NULL) { SSH_ERROR("decode_keyfile: unable to allocate new " "RSA struct.\n"); return (DECODE_KEYFILE_FAILED); } if (buflen < strlen(FRESSH_PRIVKEY_IDSTR)) { SSH_ERROR("decode_keyfile: too short: %d\n", buflen); return (DECODE_KEYFILE_FAILED); } if (strncmp(buf, FRESSH_PRIVKEY_IDSTR, strlen(FRESSH_PRIVKEY_IDSTR)) == 0) { if (format) *format = FRESSH_PRIV_KEYFILE; return (decode_fressh_keyfile(context, buf, buflen, passphrase, pplen, keyp, comment)); } else if (strncmp(buf, FSECURE_PRIVKEY_IDSTR, strlen(FSECURE_PRIVKEY_IDSTR)) == 0) { if (format) *format = FSECURE_PRIV_KEYFILE; return (decode_fsecure_keyfile(context, buf, buflen, passphrase, pplen, keyp, comment)); } else { if (format) *format = 0; SSH_ERROR("decode_keyfile: Bad id string in keyfile.\n"); return (DECODE_KEYFILE_FAILED); } } int decode_fressh_keyfile(ssh_context_t * context, char *buf, int buflen, char *passphrase, int pplen, ssh_RSA ** keyp, char **comment) { FUNC_DECL(decode_fressh_keyfile); int version, minor, cipher_type, rv; char *cmtbuf; SSH_DLOG(4, ("starting\n")); cmtbuf = NULL; /* XXX be more careful with short buffers. */ /* XXX (i.e. actually use buflen. ) */ /* XXX or perhaps rewrite to use the buf_* functions */ /* XXX (add a buf_get_line function) */ /* Macro to set the buffer to point at the start of the next line. */ #define __NEXTLINE(s) \ do { \ if ((buf = strchr(buf, '\n')) == 0) { \ SSH_ERROR("missing newline after" s ".\n"); \ rv = DECODE_KEYFILE_FAILED; \ goto dfk_bad; \ } \ buf++; \ } while (/*CONSTCOND*/0) /* Macro to grab and decode the next part of the key. */ #define __GETKEYPART(getpart,partstr) \ do { \ if (bignum_hex2bn(&(getpart(*keyp)), buf) == 0) { \ SSH_ERROR("hex2bn for %s failed.\n", partstr); \ rv = DECODE_KEYFILE_FAILED; \ goto dfk_bad; \ } \ } while (/*CONSTCOND*/0) /* Skip the id string: it's been checked for us already. */ __NEXTLINE("id string"); version = atoi(buf); if (version != SSH_IDSTR_VER_MAJOR) { SSH_ERROR("bad version: %d\n", version); rv = DECODE_KEYFILE_FAILED; goto dfk_bad; } /* Look for a minor. */ if ((strchr(buf, '.') < strchr(buf, '\n')) && (strchr(buf, '.') != NULL)) { minor = atoi(strchr(buf, '.') + 1); minor = atoi(buf); } __NEXTLINE("version"); /* Grab the comment. */ { int cmtlen; cmtlen = strchr(buf, '\n') - buf; cmtbuf = malloc(cmtlen + 1); if (cmtbuf == NULL) { SSH_ERROR("unable to malloc for comment.\n"); if (comment) *comment = NULL; } else { strncpy(cmtbuf, buf, cmtlen); cmtbuf[cmtlen] = '\0'; SSH_DLOG(4, ("comment: %s\n", cmtbuf)); /* Return the comment if it is desired. */ if (comment) *comment = cmtbuf; else { free(cmtbuf); cmtbuf = NULL; } } } __NEXTLINE("comment string"); /* Grab the cipher type. */ cipher_type = atoi(buf); /* * XXX We don't handle encrypted FreSSH-format key files. This * XXX is kinda bad, but historically, FreSSH-format keys were * XXX used only for host keys, which aren't encrypted anyhow. */ if (cipher_type != 0) { if (passphrase) { /* XXX Add code to decrypt the buffer here. */ SSH_ERROR("encryption not yet supported.\n"); } else /* nothing, just return */ ; rv = DECODE_KEYFILE_PASSPHRASE; goto dfk_bad; } __NEXTLINE("cipher type"); /* Make sure the check bytes are correct. */ if ((strlen(buf) < 4) || (buf[0] != buf[2]) || (buf[1] != buf[3])) { SSH_ERROR("invalid format, check bytes fail.\n"); rv = DECODE_KEYFILE_PASSPHRASE; goto dfk_bad; } __NEXTLINE("check bytes"); __GETKEYPART(ssh_rsa_epart, "e"); __NEXTLINE("e part"); __GETKEYPART(ssh_rsa_npart, "n"); { __NEXTLINE("n part"); __GETKEYPART(ssh_rsa_dpart, "d"); __NEXTLINE("d part"); __GETKEYPART(ssh_rsa_ppart, "p"); __NEXTLINE("p part"); __GETKEYPART(ssh_rsa_qpart, "q"); /* Calculate dmp1, dmq1 and iqmp */ ssh_key_fixup(*keyp); } #undef __NEXTLINE #undef __GETKEYPART SSH_DLOG(5, ("return OK.\n")); return (DECODE_KEYFILE_OK); dfk_bad: if (cmtbuf) free(cmtbuf); return (rv); } /* * Decode a F-secure style key file. */ int decode_fsecure_keyfile(ssh_context_t *context, char *buf, int buflen, char *passphrase, int pplen, ssh_RSA **keyp, char **comment) { FUNC_DECL(decode_fsecure_keyfile); struct ssh_buf inbuf, inbuf_d, *curbuf = NULL; struct ssh_cipher keyfile_cipher; char *idstr; u_int8_t *ckbytes, *cmtbuf; u_int8_t ciphertype; u_int32_t extra, numbits, cmt_len; int rv; SSH_DLOG(4, ("starting\n")); idstr = ckbytes = cmtbuf = 0; ciphertype = 0; memset(&inbuf, 0, sizeof(struct ssh_buf)); buf_alloc(&inbuf, buflen); buf_put_nbytes(&inbuf, buflen, buf); curbuf = &inbuf; #define __IFOK(x, msg) \ do { \ if ((x) != 0) { \ SSH_ERROR("%s\n", msg); \ rv = DECODE_KEYFILE_FAILED; \ goto dfsk_bad; \ } \ } while (/*CONSTCOND*/0) #define __GETKEYPART_F(getpart,partstr) \ do { \ if (buf_get_bignum(curbuf, &(getpart(*keyp))) != 0) { \ SSH_ERROR("get_bn for %s failed.\n", partstr); \ rv = DECODE_KEYFILE_FAILED; \ goto dfsk_bad; \ } \ } while (/*CONSTCOND*/0) __IFOK(buf_get_asciiz(curbuf, &idstr, NULL), "get idstr"); __IFOK(buf_get_int8(curbuf, &ciphertype), "get ciphertype"); __IFOK(buf_get_int32(curbuf, &extra), "get extra"); __IFOK(buf_get_int32(curbuf, &numbits), "get numbits"); __GETKEYPART_F(ssh_rsa_npart, "n"); __GETKEYPART_F(ssh_rsa_epart, "e"); __IFOK(buf_get_binstr(&inbuf, &cmtbuf, &cmt_len), "get comment"); if (cmt_len > 0) { cmtbuf[cmt_len] = '\0'; SSH_DLOG(4, ("comment: %s\n", cmtbuf)); } if (comment) *comment = cmtbuf; if (ciphertype != SSH_CIPHER_NONE) { if (passphrase == NULL) { rv = DECODE_KEYFILE_PASSPHRASE; goto dfsk_bad; } memset(&keyfile_cipher, 0, sizeof(struct ssh_cipher)); if (set_cipher_type(&keyfile_cipher, ciphertype)) { SSH_ERROR("bad cipher type %d\n", ciphertype); rv = DECODE_KEYFILE_FAILED; goto dfsk_bad; } if (cipher_initialize_passphrase(&keyfile_cipher, passphrase, SSH_ROLE_NEITHER) == NULL) { SSH_ERROR("failed to initalize cipher\n"); rv = DECODE_KEYFILE_FAILED; goto dfsk_bad; } memset(&inbuf_d, 0, sizeof(struct ssh_buf)); buf_alloc(&inbuf_d, buf_len(&inbuf)); cipher_decrypt(&keyfile_cipher, buf_data(&inbuf), buf_data(&inbuf_d), buf_len(&inbuf)); buf_adjlen(&inbuf_d, buf_len(&inbuf)); curbuf = &inbuf_d; buf_cleanup(&inbuf); cipher_destroy(&keyfile_cipher); } __IFOK(buf_get_nbytes(curbuf, 4, &ckbytes), "get check bytes"); if (ckbytes[0] != ckbytes[2] || ckbytes[1] != ckbytes[3]) { rv = DECODE_KEYFILE_PASSPHRASE; free(ckbytes); goto dfsk_bad; } free(ckbytes); __GETKEYPART_F(ssh_rsa_dpart, "d"); __GETKEYPART_F(ssh_rsa_iqmppart, "iqmp"); /* * NOTE: q AND p ARE SWAPPED HERE! This is apparently * an artifact of the RSA implementation in the original * SSH implementation by Tatu Ylonen, which appears to * have swapped p and q internally. Unfortunately, it * affected the keyfile format, as well as the authentication * agent protocol. */ __GETKEYPART_F(ssh_rsa_qpart, "q"); __GETKEYPART_F(ssh_rsa_ppart, "p"); ssh_key_fixup(*keyp); #undef __GETKEYPART_F #undef __IFOK SSH_DLOG(5, ("return OK.\n")); return (DECODE_KEYFILE_OK); dfsk_bad: if (curbuf) buf_cleanup(curbuf); if (idstr) free(idstr); return (rv); } /* * Encodes an RSA key and a comment into a keyfile formatted buffer. * The buffer is allocated by this function and the size is returned. * The buffer must be freed by the caller. * Returns the size of the buffer including a terminating 0. */ int encode_keyfile(ssh_context_t * context, int format, char **buf, char *pass, int pplen, ssh_RSA * key, char *comment) { switch (format) { case FRESSH_PUB_KEYFILE: return (encode_public_keyfile(context, buf, key, comment)); break; case FRESSH_PRIV_KEYFILE: return (encode_fressh_keyfile(context, buf, pass, pplen, key, comment)); break; case FSECURE_PRIV_KEYFILE: return (encode_fsecure_keyfile(context, buf, pass, pplen, key, comment)); break; default: SSH_ERROR("encode_keyfile: unknown file format: %d\n", format); return (-1); } } int encode_public_keyfile(ssh_context_t * context, char **buf, ssh_RSA * key, const char *comment) { int nbits; char *epart, *npart; int len, newlen; SSH_DLOG(4, ("encode_public_keyfile\n")); if (!comment) comment = ""; epart = bignum_bn2dec(ssh_rsa_epart(key)); npart = bignum_bn2dec(ssh_rsa_npart(key)); nbits = bignum_num_bits(ssh_rsa_npart(key)); if (!epart || !npart) { if (epart) free(epart); if (npart) free(npart); return (-1); } len = strlen(epart) + 1 + strlen(npart) + 1 + 40 + strlen(comment); if ((*buf = malloc(len)) == NULL) { free(epart); free(npart); return (-1); } newlen = snprintf(*buf, len, "%d %s %s %s\n", nbits, epart, npart, comment); return (newlen); } int decode_public_keyfile(ssh_context_t * context, char *buf, int len, ssh_RSA ** key, char **comment) { /* Format: nbits e n comment */ int nbits; char *e_str, *n_str; char *cmt; /* XXX make this more efficient. */ /* XXX fix this. */ e_str = malloc(len); n_str = malloc(len); cmt = malloc(len); *cmt = '\0'; if (sscanf(buf, "%d %s %s %s", &nbits, e_str, n_str, cmt) < 3) { free(e_str); free(n_str); free(cmt); return (DECODE_KEYFILE_FAILED); } *key = ssh_rsa_new(); if (bignum_dec2bn(&ssh_rsa_epart(*key), e_str) == 0) { SSH_ERROR("decode_public_keyfile: dec2bn for e failed.\n"); free(e_str); free(n_str); free(cmt); ssh_rsa_free(key); return (DECODE_KEYFILE_FAILED); } if (bignum_dec2bn(&ssh_rsa_npart(*key), n_str) == 0) { SSH_ERROR("decode_public_keyfile: dec2bn for n failed.\n"); free(e_str); free(n_str); free(cmt); ssh_rsa_free(key); return (DECODE_KEYFILE_FAILED); } if (comment) { *comment = malloc(strlen(cmt) + 1); strncpy(*comment, cmt, strlen(cmt) + 1); } free(e_str); free(n_str); free(cmt); return (DECODE_KEYFILE_OK); } int encode_fressh_keyfile(ssh_context_t * context, char **buf, char *pass, int pplen, ssh_RSA * key, char *comment) { char *epart, *npart, *dpart; char *ppart, *qpart; int newlen, len; /* * XXX Should add encryption of FreSSH-format private key * XXX files -- but the format is deprecated, and historically * XXX were only used for host keys, which aren't encrypted * XXX anyhow. */ SSH_DLOG(4, ("encode_fressh_keyfile\n")); epart = bignum_bn2hex(ssh_rsa_epart(key)); npart = bignum_bn2hex(ssh_rsa_npart(key)); if (!epart || !npart) { if (epart) free(epart); if (npart) free(npart); return (-1); } { dpart = bignum_bn2hex(ssh_rsa_dpart(key)); ppart = bignum_bn2hex(ssh_rsa_ppart(key)); qpart = bignum_bn2hex(ssh_rsa_qpart(key)); if (!dpart || !ppart || !qpart) { free(epart); free(npart); if (dpart) free(dpart); if (ppart) free(ppart); if (qpart) free(qpart); return (-1); } } len = strlen(FRESSH_PRIVKEY_IDSTR) + 1 + 5 + 1 + strlen(comment) + 1 + 1 + 1 + /* Version, comment, * cipher */ 4 + 1 + /* Check bytes. */ strlen(epart) + 1 + strlen(npart) + 1; len += strlen(dpart) + 1 + strlen(ppart) + 1 + strlen(qpart) + 1; len += 1; /* Final zero. */ if ((*buf = malloc(len)) == NULL) { free(epart); free(npart); free(dpart); free(ppart); free(qpart); return (-1); } newlen = snprintf(*buf, len, "%s\n%2d.%2d\n%s\n%1d\n%s\n%s\n%s\n%s\n%s\n%s\n", FRESSH_PRIVKEY_IDSTR, SSH_IDSTR_VER_MAJOR, SSH_IDSTR_VER_MINOR, comment, SSH_CIPHER_NONE, "CKCK", epart, npart, dpart, ppart, qpart); free(epart); free(npart); free(dpart); free(ppart); free(qpart); if ((newlen + 1) != len) SSH_DLOG(0, ("encode_fressh_keyfile: Warning: snprintf " "len(%d) != calculate(%d)\n", newlen + 1, len)); return (newlen); } int encode_fsecure_keyfile(ssh_context_t * context, char **buf, char *pass, int pplen, ssh_RSA * key, char *comment) { char *tmprand; ssh_cipher_t keyfile_cipher; struct ssh_buf sbuf, privbuf; u_int8_t ciphertype; SSH_DLOG(4, ("encode_fsecure_keyfile\n")); /* * A zero-length passphrase causes us to use a cipher type of NONE, * otherwise we use the default cipher type for keyfiles. * * XXX Should support explicitly setting the cipher type. */ if (pass[0] == '\0') ciphertype = SSH_CIPHER_NONE; else ciphertype = SSH_DEFAULT_KEYFILE_CIPHER; memset(&keyfile_cipher, 0, sizeof(struct ssh_cipher)); if (set_cipher_type(&keyfile_cipher, ciphertype)) { SSH_ERROR("encode_fressh_keyfile: bad cipher type %d\n", ciphertype); return (-1); } tmprand = malloc(cipher_block_size(&keyfile_cipher)); if (tmprand == NULL) { SSH_ERROR("encode_fressh_keyfile: memory allocation failure\n"); return (-1); } if (cipher_initialize_passphrase(&keyfile_cipher, pass, SSH_ROLE_NEITHER) == NULL) { SSH_ERROR("encode_fressh_keyfile: failed to initalize " "cipher\n"); free(tmprand); return (-1); } memset(&sbuf, 0, sizeof(struct ssh_buf)); memset(&privbuf, 0, sizeof(struct ssh_buf)); buf_alloc(&sbuf, 1024); buf_alloc(&privbuf, 1024); #define __IFOK(x, msg) \ do { \ if (x != 0) { \ SSH_ERROR("encode_fsecure_keyfile: put %s failed\n", msg); \ goto efsk_bad; \ } \ } while (0) __IFOK(buf_put_asciiz(&sbuf, FSECURE_PRIVKEY_IDSTR), "istring"); __IFOK(buf_put_int8(&sbuf, ciphertype), "cipher"); __IFOK(buf_put_int32(&sbuf, 0), "extra"); /* Future space */ __IFOK(buf_put_int32(&sbuf, bignum_num_bits(ssh_rsa_npart(key))), "nbits"); __IFOK(buf_put_bignum(&sbuf, ssh_rsa_npart(key)), "npart"); __IFOK(buf_put_bignum(&sbuf, ssh_rsa_epart(key)), "epart"); __IFOK(buf_put_binstr(&sbuf, comment, strlen(comment)), "comment"); ssh_rand_bytes(2, tmprand); __IFOK(buf_put_nbytes(&privbuf, 2, tmprand), "chk1"); /* Check bytes. */ __IFOK(buf_put_nbytes(&privbuf, 2, tmprand), "chk2"); __IFOK(buf_put_bignum(&privbuf, ssh_rsa_dpart(key)), "dpart"); __IFOK(buf_put_bignum(&privbuf, ssh_rsa_iqmppart(key)), "iqmp part"); __IFOK(buf_put_bignum(&privbuf, ssh_rsa_qpart(key)), "qpart"); __IFOK(buf_put_bignum(&privbuf, ssh_rsa_ppart(key)), "ppart"); /* * Add padding to make the size of the private key a multiple of the * cipher block size. */ if ((buf_alllen(&privbuf) % cipher_block_size(&keyfile_cipher)) != 0) { int padlen; padlen = cipher_block_size(&keyfile_cipher) - (buf_alllen(&privbuf) % cipher_block_size(&keyfile_cipher)); ssh_rand_bytes(padlen, tmprand); __IFOK(buf_put_nbytes(&privbuf, padlen, tmprand), "padding"); } memset(tmprand, 0, cipher_block_size(&keyfile_cipher)); __IFOK(buf_makeavail(&sbuf, buf_alllen(&privbuf)), "makeavail"); free(tmprand); cipher_encrypt(&keyfile_cipher, buf_alldata(&privbuf), buf_endofdata(&sbuf), buf_alllen(&privbuf)); buf_adjlen(&sbuf, buf_alllen(&privbuf)); buf_cleanup(&privbuf); cipher_destroy(&keyfile_cipher); *buf = buf_alldata(&sbuf); return (buf_alllen(&sbuf)); efsk_bad: free(tmprand); buf_cleanup(&privbuf); buf_cleanup(&sbuf); return (-1); } /* * Read the authorized keys file. * Compare each line against the given n. * Return a public key with the given n. */ int lookup_authorized_key(ssh_context_t *context, ssh_BIGNUM *n, ssh_RSA **key) { int ret; struct ssh_buf *ak_buf; char *ak_line; char *ak_file; int linelen; ak_buf = NULL; ak_line = NULL; ret = -1; /* Assume we fail. */ ak_file = authorized_keys_file(context); if (ssh_sys_readfile(ak_file, &ak_buf) < 0) { SSH_DLOG(4, ("lookup_authorized_key: unable to read file.\n")); free(ak_file); return (-1); } free(ak_file); while (buf_get_line(ak_buf, &ak_line, &linelen) == 0) { if (linelen < 1) { free(ak_line); ak_line = NULL; continue; } /* XXX grab comment and log it. */ if (decode_public_keyfile(context, ak_line, linelen, key, NULL) != 0) { free(ak_line); ssh_rsa_free(key); SSH_ERROR("lookup_authorized_key: Corrupted line in " "file.\n"); ret = -1; break; } free(ak_line); ak_line = NULL; if (bignum_compare(n, ssh_rsa_npart((*key))) == 0) { SSH_DLOG(4, ("lookup_auth_key: found rsa key.\n")); ret = 0; break; } ssh_rsa_free(key); *key = NULL; } buf_cleanup(ak_buf); free(ak_buf); return (ret); } /* * just looks for a match based on the hostname[,portno], since we * wanna be able to warn of a host key *CHANGING*. */ int lookup_known_host(ssh_context_t *context, const char *file, char *host, char *buf, int buflen) { FUNC_DECL(lookup_known_host); FILE *f; char *t, *c, *h, line[1024]; int rc = 0, l; if ((f = fopen(file, "r")) != NULL) { while (fgets(line, sizeof(line), f) != NULL) { line[sizeof(line) - 1] = 0; h = line; while (isspace(*h)) h++; if (*h == '#' || *h == 0) continue; if ((t = strpbrk(h, " \t")) == NULL) continue; *t = 0; while (h && *h) { if ((c = strchr(h, ',')) != NULL) *c = 0; if (strcasecmp(h, host) == 0) { *t = ' '; while (isspace(*t)) t++; l = strlen(t); strncpy(buf, t, MIN(l, buflen)); buf[MIN(l - 1, buflen - 1)] = 0; rc = 1; break; } if (c != NULL) *c = ','; h = c + !!c; } if (rc == 1) break; } fclose(f); } return rc; } int set_known_host(ssh_context_t *context, const char *file, char *host, char *keyname, char *buf, int buflen) { FILE *f; int ret; /* open, write, close. simple, eh? */ if ((f = fopen(file, "a")) != NULL && fprintf(f, "%s %s\n", keyname, buf) > 0 && fclose(f) == 0) { fprintf(stderr, "Host '%s' added to the list of known hosts.\r\n", host); ret = 0; } else { fprintf(stderr, "Failed to add the host to the list of known hosts (%s).\r\n", file); ret = 1; } return ret; } int verify_host_key(ssh_context_t *context, int keybits, ssh_RSA *key) { char kh[2][1024], *host; int l, save_key; char *e, *n; /* for printed representation of the host key */ e = bignum_bn2dec(ssh_rsa_epart(context->v1_ctx.hostkey)); n = bignum_bn2dec(ssh_rsa_npart(context->v1_ctx.hostkey)); /* * contruct the thing we expect to find, since we might need to * put it in there anyway. */ l = strlen(context->client->HostName) + 5 + 1; host = malloc(l); /* these are numeric strings, as set up in ssh_connect() */ if (strcmp(context->client->Port, context->client->DefaultPort)) snprintf(host, l, "%s@%s", context->client->HostName, context->client->Port); else snprintf(host, l, "%s", context->client->HostName); snprintf(kh[0], sizeof(kh[0]), "%d %s %s", keybits, e, n); free(e); free(n); /* * host key state: missing, match, mismatch * stricthostkeychecking: no, yes, ask * batchmode: no, yes * erg,. */ #ifdef FORCE_VALID_LOOPBACK_HOST_KEYS /* * f-secure think localhost and 127.0.0.1 are the same, and * makes it so. if you try to ssh to localhost, it replaces * it with 127.0.0.1 (to avoid dns attack) and then forces * acceptance of the key (nfs homedir problem). * we do it more "elegantly" here, and then leave it out. :) */ if (context->saddr.sin_addr.s_addr == inet_addr("127.0.0.1")) { if (context->client->Verbosity) { fprintf(stderr, "%s: Forcing accepting of host key for %s.\r\n", context->client->localhost, context->client->HostName); fprintf(stderr, "%s: Host '%s' is known and matches the host key.\r\n", context->client->localhost, context->client->HostName); } } else #endif /* FORCE_VALID_LOOPBACK_HOST_KEYS */ /* can we find it? */ if (lookup_known_host(context, context->client->UserKnownHostsFile, host, kh[1], sizeof(kh[1])) == 0 && lookup_known_host(context, context->client->GlobalKnownHostsFile, host, kh[1], sizeof(kh[1])) == 0) { /* * Nope, host not found. */ switch (context->client->StrictHostKeyChecking) { case NO: fprintf(stderr, "Host key not found from the list of known hosts.\r\n"); set_known_host(context, context->client->UserKnownHostsFile, host, host, kh[0], sizeof(kh[0])); break; case YES: fprintf(stderr, "No host key is known for %s and you have " "requested strict checking.\r\n", context->client->HostName); ssh_exit(context, 1, EXIT_NOW); break; case ASK: if (context->client->BatchMode) { fprintf(stderr, "No host key is known for %s and cannot " "confirm operation when running in batch " "mode.\r\n", context->client->HostName); ssh_exit(context, 1, EXIT_NOW); } else { save_key = YES; goto ask_user; } } } /* okay...do they match? */ else if (strcasecmp(kh[0], kh[1]) != 0) { /* no match...erg! */ fprintf(stderr, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\r\n" "@ WARNING: HOST IDENTIFICATION HAS CHANGED! @\r\n" "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\r\n" "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\r\n" "Someone could be eavesdropping on you right now (man-in-the-middle attack)!\r\n" "It is also possible that the host key has just been changed.\r\n" "Please contact your system administrator.\r\n" "Add correct host key for %s in %s to get rid of this message.\r\n", host, context->client->UserKnownHostsFile); if (context->client->ForwardAgent) { fprintf(stderr, "Agent forwarding is disabled to avoid " "attacks by corrupted servers.\r\n"); context->client->ForwardAgent = 0; } if (context->client->ForwardX11) { fprintf(stderr, "X11 forwarding is disabled to avoid " "attacks by corrupted servers.\r\n"); context->client->ForwardX11 = 0; } /* * XXX add correct code here to detect and disable port * forwardings if (context->client->) { fprintf(stderr, "Local * port forwarding is disabled to avoid " "attacks by * corrupted servers.\r\n"); context->client-> } if * (context->client->) { fprintf(stderr, "Remote port * forwarding is disabled to avoid " "attacks by corrupted * servers.\r\n"); context->client-> } */ switch (context->client->StrictHostKeyChecking) { case NO: /* well fooey! not much to do...we warned them. */ break; case YES: fprintf(stderr, "Host key for %s has changed and you have " "requested strict checking.\r\n", context->client->HostName); ssh_exit(context, 1, EXIT_NOW); case ASK: if (context->client->BatchMode) { fprintf(stderr, "Host key for %s has changed and cannot " "confirm operation when running in batch " "mode.\r\n", context->client->HostName); ssh_exit(context, 1, EXIT_NOW); } else { char *ans; int rc; save_key = NO; ask_user: rc = -1; do { if (ssh_ui_prompt(&ans, "Host key not found from the list of known hosts.\r\n" "Are you sure you want to continue connecting (yes/no/once)? ", 1)) { fprintf(stderr, "Unable to read " "response\r\n"); ssh_exit(context, 1, EXIT_NOW); } if (strcasecmp("no", ans) == 0) { fprintf(stderr, "Aborted by user\r\n"); ssh_exit(context, 1, EXIT_NOW); } else if (strcasecmp("yes", ans) == 0) rc = save_key; else if (strcasecmp("once", ans) == 0) rc = 0; free(ans); } while (rc == -1); if (rc == 1) set_known_host(context, context->client->UserKnownHostsFile, host, host, kh[0], sizeof(kh[0])); } } } /* key found, and it matches. phew! */ else { if (context->client->Verbosity) fprintf(stderr, "%s: Host '%s' is known and matches the host key.\r\n", context->client->localhost, context->client->HostName); } free(host); return 0; }